Check-in commons-math 3.6.1 am: 1354beaf45 am: 0018f64b87 am: b3715644fb am: 5484895ffd am: 029d049e49

Original change: https://android-review.googlesource.com/c/platform/external/apache-commons-math/+/2702413

Change-Id: I6451550459c6d42417e3214f1db820289d799bc7
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
index ac41b9c..3c2fb11 100644
--- a/Android.bp
+++ b/Android.bp
@@ -50,12 +50,15 @@
     ],
 }
 
+// NOTE: New users of this library should prefer the 
+// commons-math3 library over the commons-math library.
+
 java_library {
     name: "apache-commons-math",
     host_supported: true,
     java_version: "1.7",
 
-    srcs: ["src/main/java/**/*.java"],
+    srcs: ["src/main/java/org/apache/commons/math/**/*.java"],
     sdk_version: "current",
     errorprone: {
         javacflags: [
@@ -66,6 +69,28 @@
     },
 }
 
+java_library {
+    name: "apache-commons-math3",
+    host_supported: true,
+    java_version: "1.7",
+    srcs: ["src/main/java/org/apache/commons/math3/**/*.java"],
+    sdk_version: "current",
+    errorprone: {
+        javacflags: [
+            "-Xep:MissingOverride:OFF",
+            "-Xep:IdentityBinaryExpression:OFF",
+            "-Xep:BoxedPrimitiveEquality:OFF",
+        ],
+    },
+    target: {
+        android: {
+            // Exclude the geometry module from Android variant due to missing dependency
+            // on java.awt.geom.AffineTransform.
+            exclude_srcs: ["src/main/java/org/apache/commons/math3/geometry/**/*.java"]
+        }
+    }
+}
+
 java_library_host {
     name: "apache-commons-math-host",
     static_libs: ["apache-commons-math"],
diff --git a/src/main/java/org/apache/commons/math3/Field.java b/src/main/java/org/apache/commons/math3/Field.java
new file mode 100644
index 0000000..618536c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/Field.java
@@ -0,0 +1,58 @@
+/*
+ * 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.math3;
+
+/**
+ * Interface representing a <a href="http://mathworld.wolfram.com/Field.html">field</a>.
+ * <p>
+ * Classes implementing this interface will often be singletons.
+ * </p>
+ * @param <T> the type of the field elements
+ * @see FieldElement
+ * @since 2.0
+ */
+public interface Field<T> {
+
+    /** Get the additive identity of the field.
+     * <p>
+     * The additive identity is the element e<sub>0</sub> of the field such that
+     * for all elements a of the field, the equalities a + e<sub>0</sub> =
+     * e<sub>0</sub> + a = a hold.
+     * </p>
+     * @return additive identity of the field
+     */
+    T getZero();
+
+    /** Get the multiplicative identity of the field.
+     * <p>
+     * The multiplicative identity is the element e<sub>1</sub> of the field such that
+     * for all elements a of the field, the equalities a &times; e<sub>1</sub> =
+     * e<sub>1</sub> &times; a = a hold.
+     * </p>
+     * @return multiplicative identity of the field
+     */
+    T getOne();
+
+    /**
+     * Returns the runtime class of the FieldElement.
+     *
+     * @return The {@code Class} object that represents the runtime
+     *         class of this object.
+     */
+    Class<? extends FieldElement<T>> getRuntimeClass();
+
+}
diff --git a/src/main/java/org/apache/commons/math3/FieldElement.java b/src/main/java/org/apache/commons/math3/FieldElement.java
new file mode 100644
index 0000000..bd286ef
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/FieldElement.java
@@ -0,0 +1,87 @@
+/*
+ * 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.math3;
+
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.NullArgumentException;
+
+
+/**
+ * Interface representing <a href="http://mathworld.wolfram.com/Field.html">field</a> elements.
+ * @param <T> the type of the field elements
+ * @see Field
+ * @since 2.0
+ */
+public interface FieldElement<T> {
+
+    /** Compute this + a.
+     * @param a element to add
+     * @return a new element representing this + a
+     * @throws NullArgumentException if {@code a} is {@code null}.
+     */
+    T add(T a) throws NullArgumentException;
+
+    /** Compute this - a.
+     * @param a element to subtract
+     * @return a new element representing this - a
+     * @throws NullArgumentException if {@code a} is {@code null}.
+     */
+    T subtract(T a) throws NullArgumentException;
+
+    /**
+     * Returns the additive inverse of {@code this} element.
+     * @return the opposite of {@code this}.
+     */
+    T negate();
+
+    /** Compute n &times; this. Multiplication by an integer number is defined
+     * as the following sum
+     * <center>
+     * n &times; this = &sum;<sub>i=1</sub><sup>n</sup> this.
+     * </center>
+     * @param n Number of times {@code this} must be added to itself.
+     * @return A new element representing n &times; this.
+     */
+    T multiply(int n);
+
+    /** Compute this &times; a.
+     * @param a element to multiply
+     * @return a new element representing this &times; a
+     * @throws NullArgumentException if {@code a} is {@code null}.
+     */
+    T multiply(T a) throws NullArgumentException;
+
+    /** Compute this &divide; a.
+     * @param a element to divide by
+     * @return a new element representing this &divide; a
+     * @throws NullArgumentException if {@code a} is {@code null}.
+     * @throws MathArithmeticException if {@code a} is zero
+     */
+    T divide(T a) throws NullArgumentException, MathArithmeticException;
+
+    /**
+     * Returns the multiplicative inverse of {@code this} element.
+     * @return the inverse of {@code this}.
+     * @throws MathArithmeticException if {@code this} is zero
+     */
+    T reciprocal() throws MathArithmeticException;
+
+    /** Get the {@link Field} to which the instance belongs.
+     * @return {@link Field} to which the instance belongs
+     */
+    Field<T> getField();
+}
diff --git a/src/main/java/org/apache/commons/math3/RealFieldElement.java b/src/main/java/org/apache/commons/math3/RealFieldElement.java
new file mode 100644
index 0000000..e3656d4
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/RealFieldElement.java
@@ -0,0 +1,402 @@
+/*
+ * 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.math3;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+
+/**
+ * Interface representing a <a href="http://mathworld.wolfram.com/RealNumber.html">real</a>
+ * <a href="http://mathworld.wolfram.com/Field.html">field</a>.
+ * @param <T> the type of the field elements
+ * @see FieldElement
+ * @since 3.2
+ */
+public interface RealFieldElement<T> extends FieldElement<T> {
+
+    /** Get the real value of the number.
+     * @return real value
+     */
+    double getReal();
+
+    /** '+' operator.
+     * @param a right hand side parameter of the operator
+     * @return this+a
+     */
+    T add(double a);
+
+    /** '-' operator.
+     * @param a right hand side parameter of the operator
+     * @return this-a
+     */
+    T subtract(double a);
+
+    /** '&times;' operator.
+     * @param a right hand side parameter of the operator
+     * @return this&times;a
+     */
+    T multiply(double a);
+
+    /** '&divide;' operator.
+     * @param a right hand side parameter of the operator
+     * @return this&divide;a
+     */
+    T divide(double a);
+
+    /** IEEE remainder operator.
+     * @param a right hand side parameter of the operator
+     * @return this - n &times; a where n is the closest integer to this/a
+     * (the even integer is chosen for n if this/a is halfway between two integers)
+     */
+    T remainder(double a);
+
+    /** IEEE remainder operator.
+     * @param a right hand side parameter of the operator
+     * @return this - n &times; a where n is the closest integer to this/a
+     * (the even integer is chosen for n if this/a is halfway between two integers)
+     * @exception DimensionMismatchException if number of free parameters or orders are inconsistent
+     */
+    T remainder(T a)
+        throws DimensionMismatchException;
+
+    /** absolute value.
+     * @return abs(this)
+     */
+    T abs();
+
+    /** Get the smallest whole number larger than instance.
+     * @return ceil(this)
+     */
+    T ceil();
+
+    /** Get the largest whole number smaller than instance.
+     * @return floor(this)
+     */
+    T floor();
+
+    /** Get the whole number that is the nearest to the instance, or the even one if x is exactly half way between two integers.
+     * @return a double number r such that r is an integer r - 0.5 &le; this &le; r + 0.5
+     */
+    T rint();
+
+    /** Get the closest long to instance value.
+     * @return closest long to {@link #getReal()}
+     */
+    long round();
+
+    /** Compute the signum of the instance.
+     * The signum is -1 for negative numbers, +1 for positive numbers and 0 otherwise
+     * @return -1.0, -0.0, +0.0, +1.0 or NaN depending on sign of a
+     */
+    T signum();
+
+    /**
+     * Returns the instance with the sign of the argument.
+     * A NaN {@code sign} argument is treated as positive.
+     *
+     * @param sign the sign for the returned value
+     * @return the instance with the same sign as the {@code sign} argument
+     */
+    T copySign(T sign);
+
+    /**
+     * Returns the instance with the sign of the argument.
+     * A NaN {@code sign} argument is treated as positive.
+     *
+     * @param sign the sign for the returned value
+     * @return the instance with the same sign as the {@code sign} argument
+     */
+    T copySign(double sign);
+
+    /**
+     * Multiply the instance by a power of 2.
+     * @param n power of 2
+     * @return this &times; 2<sup>n</sup>
+     */
+    T scalb(int n);
+
+    /**
+     * Returns the hypotenuse of a triangle with sides {@code this} and {@code y}
+     * - sqrt(<i>this</i><sup>2</sup>&nbsp;+<i>y</i><sup>2</sup>)
+     * avoiding intermediate overflow or underflow.
+     *
+     * <ul>
+     * <li> If either argument is infinite, then the result is positive infinity.</li>
+     * <li> else, if either argument is NaN then the result is NaN.</li>
+     * </ul>
+     *
+     * @param y a value
+     * @return sqrt(<i>this</i><sup>2</sup>&nbsp;+<i>y</i><sup>2</sup>)
+     * @exception DimensionMismatchException if number of free parameters or orders are inconsistent
+     */
+    T hypot(T y)
+        throws DimensionMismatchException;
+
+    /** {@inheritDoc} */
+    T reciprocal();
+
+    /** Square root.
+     * @return square root of the instance
+     */
+    T sqrt();
+
+    /** Cubic root.
+     * @return cubic root of the instance
+     */
+    T cbrt();
+
+    /** N<sup>th</sup> root.
+     * @param n order of the root
+     * @return n<sup>th</sup> root of the instance
+     */
+    T rootN(int n);
+
+    /** Power operation.
+     * @param p power to apply
+     * @return this<sup>p</sup>
+     */
+    T pow(double p);
+
+    /** Integer power operation.
+     * @param n power to apply
+     * @return this<sup>n</sup>
+     */
+    T pow(int n);
+
+    /** Power operation.
+     * @param e exponent
+     * @return this<sup>e</sup>
+     * @exception DimensionMismatchException if number of free parameters or orders are inconsistent
+     */
+    T pow(T e)
+        throws DimensionMismatchException;
+
+    /** Exponential.
+     * @return exponential of the instance
+     */
+    T exp();
+
+    /** Exponential minus 1.
+     * @return exponential minus one of the instance
+     */
+    T expm1();
+
+    /** Natural logarithm.
+     * @return logarithm of the instance
+     */
+    T log();
+
+    /** Shifted natural logarithm.
+     * @return logarithm of one plus the instance
+     */
+    T log1p();
+
+//    TODO: add this method in 4.0, as it is not possible to do it in 3.2
+//          due to incompatibility of the return type in the Dfp class
+//    /** Base 10 logarithm.
+//     * @return base 10 logarithm of the instance
+//     */
+//    T log10();
+
+    /** Cosine operation.
+     * @return cos(this)
+     */
+    T cos();
+
+    /** Sine operation.
+     * @return sin(this)
+     */
+    T sin();
+
+    /** Tangent operation.
+     * @return tan(this)
+     */
+    T tan();
+
+    /** Arc cosine operation.
+     * @return acos(this)
+     */
+    T acos();
+
+    /** Arc sine operation.
+     * @return asin(this)
+     */
+    T asin();
+
+    /** Arc tangent operation.
+     * @return atan(this)
+     */
+    T atan();
+
+    /** Two arguments arc tangent operation.
+     * @param x second argument of the arc tangent
+     * @return atan2(this, x)
+     * @exception DimensionMismatchException if number of free parameters or orders are inconsistent
+     */
+    T atan2(T x)
+        throws DimensionMismatchException;
+
+    /** Hyperbolic cosine operation.
+     * @return cosh(this)
+     */
+    T cosh();
+
+    /** Hyperbolic sine operation.
+     * @return sinh(this)
+     */
+    T sinh();
+
+    /** Hyperbolic tangent operation.
+     * @return tanh(this)
+     */
+    T tanh();
+
+    /** Inverse hyperbolic cosine operation.
+     * @return acosh(this)
+     */
+    T acosh();
+
+    /** Inverse hyperbolic sine operation.
+     * @return asin(this)
+     */
+    T asinh();
+
+    /** Inverse hyperbolic  tangent operation.
+     * @return atanh(this)
+     */
+    T atanh();
+
+    /**
+     * Compute a linear combination.
+     * @param a Factors.
+     * @param b Factors.
+     * @return <code>&Sigma;<sub>i</sub> a<sub>i</sub> b<sub>i</sub></code>.
+     * @throws DimensionMismatchException if arrays dimensions don't match
+     * @since 3.2
+     */
+    T linearCombination(T[] a, T[] b)
+        throws DimensionMismatchException;
+
+    /**
+     * Compute a linear combination.
+     * @param a Factors.
+     * @param b Factors.
+     * @return <code>&Sigma;<sub>i</sub> a<sub>i</sub> b<sub>i</sub></code>.
+     * @throws DimensionMismatchException if arrays dimensions don't match
+     * @since 3.2
+     */
+    T linearCombination(double[] a, T[] b)
+        throws DimensionMismatchException;
+
+    /**
+     * Compute a linear combination.
+     * @param a1 first factor of the first term
+     * @param b1 second factor of the first term
+     * @param a2 first factor of the second term
+     * @param b2 second factor of the second term
+     * @return a<sub>1</sub>&times;b<sub>1</sub> +
+     * a<sub>2</sub>&times;b<sub>2</sub>
+     * @see #linearCombination(Object, Object, Object, Object, Object, Object)
+     * @see #linearCombination(Object, Object, Object, Object, Object, Object, Object, Object)
+     * @since 3.2
+     */
+    T linearCombination(T a1, T b1, T a2, T b2);
+
+    /**
+     * Compute a linear combination.
+     * @param a1 first factor of the first term
+     * @param b1 second factor of the first term
+     * @param a2 first factor of the second term
+     * @param b2 second factor of the second term
+     * @return a<sub>1</sub>&times;b<sub>1</sub> +
+     * a<sub>2</sub>&times;b<sub>2</sub>
+     * @see #linearCombination(double, Object, double, Object, double, Object)
+     * @see #linearCombination(double, Object, double, Object, double, Object, double, Object)
+     * @since 3.2
+     */
+    T linearCombination(double a1, T b1, double a2, T b2);
+
+    /**
+     * Compute a linear combination.
+     * @param a1 first factor of the first term
+     * @param b1 second factor of the first term
+     * @param a2 first factor of the second term
+     * @param b2 second factor of the second term
+     * @param a3 first factor of the third term
+     * @param b3 second factor of the third term
+     * @return a<sub>1</sub>&times;b<sub>1</sub> +
+     * a<sub>2</sub>&times;b<sub>2</sub> + a<sub>3</sub>&times;b<sub>3</sub>
+     * @see #linearCombination(Object, Object, Object, Object)
+     * @see #linearCombination(Object, Object, Object, Object, Object, Object, Object, Object)
+     * @since 3.2
+     */
+    T linearCombination(T a1, T b1, T a2, T b2, T a3, T b3);
+
+    /**
+     * Compute a linear combination.
+     * @param a1 first factor of the first term
+     * @param b1 second factor of the first term
+     * @param a2 first factor of the second term
+     * @param b2 second factor of the second term
+     * @param a3 first factor of the third term
+     * @param b3 second factor of the third term
+     * @return a<sub>1</sub>&times;b<sub>1</sub> +
+     * a<sub>2</sub>&times;b<sub>2</sub> + a<sub>3</sub>&times;b<sub>3</sub>
+     * @see #linearCombination(double, Object, double, Object)
+     * @see #linearCombination(double, Object, double, Object, double, Object, double, Object)
+     * @since 3.2
+     */
+    T linearCombination(double a1, T b1,  double a2, T b2, double a3, T b3);
+
+    /**
+     * Compute a linear combination.
+     * @param a1 first factor of the first term
+     * @param b1 second factor of the first term
+     * @param a2 first factor of the second term
+     * @param b2 second factor of the second term
+     * @param a3 first factor of the third term
+     * @param b3 second factor of the third term
+     * @param a4 first factor of the third term
+     * @param b4 second factor of the third term
+     * @return a<sub>1</sub>&times;b<sub>1</sub> +
+     * a<sub>2</sub>&times;b<sub>2</sub> + a<sub>3</sub>&times;b<sub>3</sub> +
+     * a<sub>4</sub>&times;b<sub>4</sub>
+     * @see #linearCombination(Object, Object, Object, Object)
+     * @see #linearCombination(Object, Object, Object, Object, Object, Object)
+     * @since 3.2
+     */
+    T linearCombination(T a1, T b1, T a2, T b2, T a3, T b3, T a4, T b4);
+
+    /**
+     * Compute a linear combination.
+     * @param a1 first factor of the first term
+     * @param b1 second factor of the first term
+     * @param a2 first factor of the second term
+     * @param b2 second factor of the second term
+     * @param a3 first factor of the third term
+     * @param b3 second factor of the third term
+     * @param a4 first factor of the third term
+     * @param b4 second factor of the third term
+     * @return a<sub>1</sub>&times;b<sub>1</sub> +
+     * a<sub>2</sub>&times;b<sub>2</sub> + a<sub>3</sub>&times;b<sub>3</sub> +
+     * a<sub>4</sub>&times;b<sub>4</sub>
+     * @see #linearCombination(double, Object, double, Object)
+     * @see #linearCombination(double, Object, double, Object, double, Object)
+     * @since 3.2
+     */
+    T linearCombination(double a1, T b1, double a2, T b2, double a3, T b3, double a4, T b4);
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/BivariateFunction.java b/src/main/java/org/apache/commons/math3/analysis/BivariateFunction.java
new file mode 100644
index 0000000..a595753
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/BivariateFunction.java
@@ -0,0 +1,34 @@
+/*
+ * 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.math3.analysis;
+
+/**
+ * An interface representing a bivariate real function.
+ *
+ * @since 2.1
+ */
+public interface BivariateFunction {
+    /**
+     * Compute the value for the function.
+     *
+     * @param x Abscissa for which the function value should be computed.
+     * @param y Ordinate for which the function value should be computed.
+     * @return the value.
+     */
+    double value(double x, double y);
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/DifferentiableMultivariateFunction.java b/src/main/java/org/apache/commons/math3/analysis/DifferentiableMultivariateFunction.java
new file mode 100644
index 0000000..c5561a5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/DifferentiableMultivariateFunction.java
@@ -0,0 +1,53 @@
+/*
+ * 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.math3.analysis;
+
+/**
+ * Extension of {@link MultivariateFunction} representing a differentiable multivariate real
+ * function.
+ *
+ * @since 2.0
+ * @deprecated as of 3.1 replaced by {@link
+ *     org.apache.commons.math3.analysis.differentiation.MultivariateDifferentiableFunction}
+ */
+@Deprecated
+public interface DifferentiableMultivariateFunction extends MultivariateFunction {
+
+    /**
+     * Returns the partial derivative of the function with respect to a point coordinate.
+     *
+     * <p>The partial derivative is defined with respect to point coordinate x<sub>k</sub>. If the
+     * partial derivatives with respect to all coordinates are needed, it may be more efficient to
+     * use the {@link #gradient()} method which will compute them all at once.
+     *
+     * @param k index of the coordinate with respect to which the partial derivative is computed
+     * @return the partial derivative function with respect to k<sup>th</sup> point coordinate
+     */
+    MultivariateFunction partialDerivative(int k);
+
+    /**
+     * Returns the gradient function.
+     *
+     * <p>If only one partial derivative with respect to a specific coordinate is needed, it may be
+     * more efficient to use the {@link #partialDerivative(int)} method which will compute only the
+     * specified component.
+     *
+     * @return the gradient function
+     */
+    MultivariateVectorFunction gradient();
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/DifferentiableMultivariateVectorFunction.java b/src/main/java/org/apache/commons/math3/analysis/DifferentiableMultivariateVectorFunction.java
new file mode 100644
index 0000000..a6a4907
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/DifferentiableMultivariateVectorFunction.java
@@ -0,0 +1,37 @@
+/*
+ * 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.math3.analysis;
+
+/**
+ * Extension of {@link MultivariateVectorFunction} representing a differentiable multivariate
+ * vectorial function.
+ *
+ * @since 2.0
+ * @deprecated as of 3.1 replaced by {@link
+ *     org.apache.commons.math3.analysis.differentiation.MultivariateDifferentiableVectorFunction}
+ */
+@Deprecated
+public interface DifferentiableMultivariateVectorFunction extends MultivariateVectorFunction {
+
+    /**
+     * Returns the jacobian function.
+     *
+     * @return the jacobian function
+     */
+    MultivariateMatrixFunction jacobian();
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/DifferentiableUnivariateFunction.java b/src/main/java/org/apache/commons/math3/analysis/DifferentiableUnivariateFunction.java
new file mode 100644
index 0000000..577e7d7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/DifferentiableUnivariateFunction.java
@@ -0,0 +1,34 @@
+/*
+ * 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.math3.analysis;
+
+/**
+ * Extension of {@link UnivariateFunction} representing a differentiable univariate real function.
+ *
+ * @deprecated as of 3.1 replaced by {@link
+ *     org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction}
+ */
+@Deprecated
+public interface DifferentiableUnivariateFunction extends UnivariateFunction {
+
+    /**
+     * Returns the derivative of the function
+     *
+     * @return the derivative function
+     */
+    UnivariateFunction derivative();
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/DifferentiableUnivariateMatrixFunction.java b/src/main/java/org/apache/commons/math3/analysis/DifferentiableUnivariateMatrixFunction.java
new file mode 100644
index 0000000..24ba488
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/DifferentiableUnivariateMatrixFunction.java
@@ -0,0 +1,36 @@
+/*
+ * 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.math3.analysis;
+
+/**
+ * Extension of {@link UnivariateMatrixFunction} representing a differentiable univariate matrix
+ * function.
+ *
+ * @since 2.0
+ * @deprecated as of 3.1 replaced by {@link
+ *     org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableMatrixFunction}
+ */
+@Deprecated
+public interface DifferentiableUnivariateMatrixFunction extends UnivariateMatrixFunction {
+
+    /**
+     * Returns the derivative of the function
+     *
+     * @return the derivative function
+     */
+    UnivariateMatrixFunction derivative();
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/DifferentiableUnivariateVectorFunction.java b/src/main/java/org/apache/commons/math3/analysis/DifferentiableUnivariateVectorFunction.java
new file mode 100644
index 0000000..6028bf6
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/DifferentiableUnivariateVectorFunction.java
@@ -0,0 +1,36 @@
+/*
+ * 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.math3.analysis;
+
+/**
+ * Extension of {@link UnivariateVectorFunction} representing a differentiable univariate vectorial
+ * function.
+ *
+ * @since 2.0
+ * @deprecated as of 3.1 replaced by {@link
+ *     org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableVectorFunction}
+ */
+@Deprecated
+public interface DifferentiableUnivariateVectorFunction extends UnivariateVectorFunction {
+
+    /**
+     * Returns the derivative of the function
+     *
+     * @return the derivative function
+     */
+    UnivariateVectorFunction derivative();
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/FunctionUtils.java b/src/main/java/org/apache/commons/math3/analysis/FunctionUtils.java
new file mode 100644
index 0000000..b3cdb9e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/FunctionUtils.java
@@ -0,0 +1,823 @@
+/*
+ * 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.math3.analysis;
+
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.MultivariateDifferentiableFunction;
+import org.apache.commons.math3.analysis.differentiation.MultivariateDifferentiableVectorFunction;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+import org.apache.commons.math3.analysis.function.Identity;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Utilities for manipulating function objects.
+ *
+ * @since 3.0
+ */
+public class FunctionUtils {
+    /** Class only contains static methods. */
+    private FunctionUtils() {}
+
+    /**
+     * Composes functions.
+     *
+     * <p>The functions in the argument list are composed sequentially, in the given order. For
+     * example, compose(f1,f2,f3) acts like f1(f2(f3(x))).
+     *
+     * @param f List of functions.
+     * @return the composite function.
+     */
+    public static UnivariateFunction compose(final UnivariateFunction... f) {
+        return new UnivariateFunction() {
+            /** {@inheritDoc} */
+            public double value(double x) {
+                double r = x;
+                for (int i = f.length - 1; i >= 0; i--) {
+                    r = f[i].value(r);
+                }
+                return r;
+            }
+        };
+    }
+
+    /**
+     * Composes functions.
+     *
+     * <p>The functions in the argument list are composed sequentially, in the given order. For
+     * example, compose(f1,f2,f3) acts like f1(f2(f3(x))).
+     *
+     * @param f List of functions.
+     * @return the composite function.
+     * @since 3.1
+     */
+    public static UnivariateDifferentiableFunction compose(
+            final UnivariateDifferentiableFunction... f) {
+        return new UnivariateDifferentiableFunction() {
+
+            /** {@inheritDoc} */
+            public double value(final double t) {
+                double r = t;
+                for (int i = f.length - 1; i >= 0; i--) {
+                    r = f[i].value(r);
+                }
+                return r;
+            }
+
+            /** {@inheritDoc} */
+            public DerivativeStructure value(final DerivativeStructure t) {
+                DerivativeStructure r = t;
+                for (int i = f.length - 1; i >= 0; i--) {
+                    r = f[i].value(r);
+                }
+                return r;
+            }
+        };
+    }
+
+    /**
+     * Composes functions.
+     *
+     * <p>The functions in the argument list are composed sequentially, in the given order. For
+     * example, compose(f1,f2,f3) acts like f1(f2(f3(x))).
+     *
+     * @param f List of functions.
+     * @return the composite function.
+     * @deprecated as of 3.1 replaced by {@link #compose(UnivariateDifferentiableFunction...)}
+     */
+    @Deprecated
+    public static DifferentiableUnivariateFunction compose(
+            final DifferentiableUnivariateFunction... f) {
+        return new DifferentiableUnivariateFunction() {
+            /** {@inheritDoc} */
+            public double value(double x) {
+                double r = x;
+                for (int i = f.length - 1; i >= 0; i--) {
+                    r = f[i].value(r);
+                }
+                return r;
+            }
+
+            /** {@inheritDoc} */
+            public UnivariateFunction derivative() {
+                return new UnivariateFunction() {
+                    /** {@inheritDoc} */
+                    public double value(double x) {
+                        double p = 1;
+                        double r = x;
+                        for (int i = f.length - 1; i >= 0; i--) {
+                            p *= f[i].derivative().value(r);
+                            r = f[i].value(r);
+                        }
+                        return p;
+                    }
+                };
+            }
+        };
+    }
+
+    /**
+     * Adds functions.
+     *
+     * @param f List of functions.
+     * @return a function that computes the sum of the functions.
+     */
+    public static UnivariateFunction add(final UnivariateFunction... f) {
+        return new UnivariateFunction() {
+            /** {@inheritDoc} */
+            public double value(double x) {
+                double r = f[0].value(x);
+                for (int i = 1; i < f.length; i++) {
+                    r += f[i].value(x);
+                }
+                return r;
+            }
+        };
+    }
+
+    /**
+     * Adds functions.
+     *
+     * @param f List of functions.
+     * @return a function that computes the sum of the functions.
+     * @since 3.1
+     */
+    public static UnivariateDifferentiableFunction add(
+            final UnivariateDifferentiableFunction... f) {
+        return new UnivariateDifferentiableFunction() {
+
+            /** {@inheritDoc} */
+            public double value(final double t) {
+                double r = f[0].value(t);
+                for (int i = 1; i < f.length; i++) {
+                    r += f[i].value(t);
+                }
+                return r;
+            }
+
+            /**
+             * {@inheritDoc}
+             *
+             * @throws DimensionMismatchException if functions are not consistent with each other
+             */
+            public DerivativeStructure value(final DerivativeStructure t)
+                    throws DimensionMismatchException {
+                DerivativeStructure r = f[0].value(t);
+                for (int i = 1; i < f.length; i++) {
+                    r = r.add(f[i].value(t));
+                }
+                return r;
+            }
+        };
+    }
+
+    /**
+     * Adds functions.
+     *
+     * @param f List of functions.
+     * @return a function that computes the sum of the functions.
+     * @deprecated as of 3.1 replaced by {@link #add(UnivariateDifferentiableFunction...)}
+     */
+    @Deprecated
+    public static DifferentiableUnivariateFunction add(
+            final DifferentiableUnivariateFunction... f) {
+        return new DifferentiableUnivariateFunction() {
+            /** {@inheritDoc} */
+            public double value(double x) {
+                double r = f[0].value(x);
+                for (int i = 1; i < f.length; i++) {
+                    r += f[i].value(x);
+                }
+                return r;
+            }
+
+            /** {@inheritDoc} */
+            public UnivariateFunction derivative() {
+                return new UnivariateFunction() {
+                    /** {@inheritDoc} */
+                    public double value(double x) {
+                        double r = f[0].derivative().value(x);
+                        for (int i = 1; i < f.length; i++) {
+                            r += f[i].derivative().value(x);
+                        }
+                        return r;
+                    }
+                };
+            }
+        };
+    }
+
+    /**
+     * Multiplies functions.
+     *
+     * @param f List of functions.
+     * @return a function that computes the product of the functions.
+     */
+    public static UnivariateFunction multiply(final UnivariateFunction... f) {
+        return new UnivariateFunction() {
+            /** {@inheritDoc} */
+            public double value(double x) {
+                double r = f[0].value(x);
+                for (int i = 1; i < f.length; i++) {
+                    r *= f[i].value(x);
+                }
+                return r;
+            }
+        };
+    }
+
+    /**
+     * Multiplies functions.
+     *
+     * @param f List of functions.
+     * @return a function that computes the product of the functions.
+     * @since 3.1
+     */
+    public static UnivariateDifferentiableFunction multiply(
+            final UnivariateDifferentiableFunction... f) {
+        return new UnivariateDifferentiableFunction() {
+
+            /** {@inheritDoc} */
+            public double value(final double t) {
+                double r = f[0].value(t);
+                for (int i = 1; i < f.length; i++) {
+                    r *= f[i].value(t);
+                }
+                return r;
+            }
+
+            /** {@inheritDoc} */
+            public DerivativeStructure value(final DerivativeStructure t) {
+                DerivativeStructure r = f[0].value(t);
+                for (int i = 1; i < f.length; i++) {
+                    r = r.multiply(f[i].value(t));
+                }
+                return r;
+            }
+        };
+    }
+
+    /**
+     * Multiplies functions.
+     *
+     * @param f List of functions.
+     * @return a function that computes the product of the functions.
+     * @deprecated as of 3.1 replaced by {@link #multiply(UnivariateDifferentiableFunction...)}
+     */
+    @Deprecated
+    public static DifferentiableUnivariateFunction multiply(
+            final DifferentiableUnivariateFunction... f) {
+        return new DifferentiableUnivariateFunction() {
+            /** {@inheritDoc} */
+            public double value(double x) {
+                double r = f[0].value(x);
+                for (int i = 1; i < f.length; i++) {
+                    r *= f[i].value(x);
+                }
+                return r;
+            }
+
+            /** {@inheritDoc} */
+            public UnivariateFunction derivative() {
+                return new UnivariateFunction() {
+                    /** {@inheritDoc} */
+                    public double value(double x) {
+                        double sum = 0;
+                        for (int i = 0; i < f.length; i++) {
+                            double prod = f[i].derivative().value(x);
+                            for (int j = 0; j < f.length; j++) {
+                                if (i != j) {
+                                    prod *= f[j].value(x);
+                                }
+                            }
+                            sum += prod;
+                        }
+                        return sum;
+                    }
+                };
+            }
+        };
+    }
+
+    /**
+     * Returns the univariate function {@code h(x) = combiner(f(x), g(x)).}
+     *
+     * @param combiner Combiner function.
+     * @param f Function.
+     * @param g Function.
+     * @return the composite function.
+     */
+    public static UnivariateFunction combine(
+            final BivariateFunction combiner,
+            final UnivariateFunction f,
+            final UnivariateFunction g) {
+        return new UnivariateFunction() {
+            /** {@inheritDoc} */
+            public double value(double x) {
+                return combiner.value(f.value(x), g.value(x));
+            }
+        };
+    }
+
+    /**
+     * Returns a MultivariateFunction h(x[]) defined by
+     *
+     * <pre> <code>
+     * h(x[]) = combiner(...combiner(combiner(initialValue,f(x[0])),f(x[1]))...),f(x[x.length-1]))
+     * </code></pre>
+     *
+     * @param combiner Combiner function.
+     * @param f Function.
+     * @param initialValue Initial value.
+     * @return a collector function.
+     */
+    public static MultivariateFunction collector(
+            final BivariateFunction combiner,
+            final UnivariateFunction f,
+            final double initialValue) {
+        return new MultivariateFunction() {
+            /** {@inheritDoc} */
+            public double value(double[] point) {
+                double result = combiner.value(initialValue, f.value(point[0]));
+                for (int i = 1; i < point.length; i++) {
+                    result = combiner.value(result, f.value(point[i]));
+                }
+                return result;
+            }
+        };
+    }
+
+    /**
+     * Returns a MultivariateFunction h(x[]) defined by
+     *
+     * <pre> <code>
+     * h(x[]) = combiner(...combiner(combiner(initialValue,x[0]),x[1])...),x[x.length-1])
+     * </code></pre>
+     *
+     * @param combiner Combiner function.
+     * @param initialValue Initial value.
+     * @return a collector function.
+     */
+    public static MultivariateFunction collector(
+            final BivariateFunction combiner, final double initialValue) {
+        return collector(combiner, new Identity(), initialValue);
+    }
+
+    /**
+     * Creates a unary function by fixing the first argument of a binary function.
+     *
+     * @param f Binary function.
+     * @param fixed value to which the first argument of {@code f} is set.
+     * @return the unary function h(x) = f(fixed, x)
+     */
+    public static UnivariateFunction fix1stArgument(final BivariateFunction f, final double fixed) {
+        return new UnivariateFunction() {
+            /** {@inheritDoc} */
+            public double value(double x) {
+                return f.value(fixed, x);
+            }
+        };
+    }
+
+    /**
+     * Creates a unary function by fixing the second argument of a binary function.
+     *
+     * @param f Binary function.
+     * @param fixed value to which the second argument of {@code f} is set.
+     * @return the unary function h(x) = f(x, fixed)
+     */
+    public static UnivariateFunction fix2ndArgument(final BivariateFunction f, final double fixed) {
+        return new UnivariateFunction() {
+            /** {@inheritDoc} */
+            public double value(double x) {
+                return f.value(x, fixed);
+            }
+        };
+    }
+
+    /**
+     * Samples the specified univariate real function on the specified interval.
+     *
+     * <p>The interval is divided equally into {@code n} sections and sample points are taken from
+     * {@code min} to {@code max - (max - min) / n}; therefore {@code f} is not sampled at the upper
+     * bound {@code max}.
+     *
+     * @param f Function to be sampled
+     * @param min Lower bound of the interval (included).
+     * @param max Upper bound of the interval (excluded).
+     * @param n Number of sample points.
+     * @return the array of samples.
+     * @throws NumberIsTooLargeException if the lower bound {@code min} is greater than, or equal to
+     *     the upper bound {@code max}.
+     * @throws NotStrictlyPositiveException if the number of sample points {@code n} is negative.
+     */
+    public static double[] sample(UnivariateFunction f, double min, double max, int n)
+            throws NumberIsTooLargeException, NotStrictlyPositiveException {
+
+        if (n <= 0) {
+            throw new NotStrictlyPositiveException(
+                    LocalizedFormats.NOT_POSITIVE_NUMBER_OF_SAMPLES, Integer.valueOf(n));
+        }
+        if (min >= max) {
+            throw new NumberIsTooLargeException(min, max, false);
+        }
+
+        final double[] s = new double[n];
+        final double h = (max - min) / n;
+        for (int i = 0; i < n; i++) {
+            s[i] = f.value(min + i * h);
+        }
+        return s;
+    }
+
+    /**
+     * Convert a {@link UnivariateDifferentiableFunction} into a {@link
+     * DifferentiableUnivariateFunction}.
+     *
+     * @param f function to convert
+     * @return converted function
+     * @deprecated this conversion method is temporary in version 3.1, as the {@link
+     *     DifferentiableUnivariateFunction} interface itself is deprecated
+     */
+    @Deprecated
+    public static DifferentiableUnivariateFunction toDifferentiableUnivariateFunction(
+            final UnivariateDifferentiableFunction f) {
+        return new DifferentiableUnivariateFunction() {
+
+            /** {@inheritDoc} */
+            public double value(final double x) {
+                return f.value(x);
+            }
+
+            /** {@inheritDoc} */
+            public UnivariateFunction derivative() {
+                return new UnivariateFunction() {
+                    /** {@inheritDoc} */
+                    public double value(final double x) {
+                        return f.value(new DerivativeStructure(1, 1, 0, x)).getPartialDerivative(1);
+                    }
+                };
+            }
+        };
+    }
+
+    /**
+     * Convert a {@link DifferentiableUnivariateFunction} into a {@link
+     * UnivariateDifferentiableFunction}.
+     *
+     * <p>Note that the converted function is able to handle {@link DerivativeStructure} up to order
+     * one. If the function is called with higher order, a {@link NumberIsTooLargeException} is
+     * thrown.
+     *
+     * @param f function to convert
+     * @return converted function
+     * @deprecated this conversion method is temporary in version 3.1, as the {@link
+     *     DifferentiableUnivariateFunction} interface itself is deprecated
+     */
+    @Deprecated
+    public static UnivariateDifferentiableFunction toUnivariateDifferential(
+            final DifferentiableUnivariateFunction f) {
+        return new UnivariateDifferentiableFunction() {
+
+            /** {@inheritDoc} */
+            public double value(final double x) {
+                return f.value(x);
+            }
+
+            /**
+             * {@inheritDoc}
+             *
+             * @exception NumberIsTooLargeException if derivation order is greater than 1
+             */
+            public DerivativeStructure value(final DerivativeStructure t)
+                    throws NumberIsTooLargeException {
+                switch (t.getOrder()) {
+                    case 0:
+                        return new DerivativeStructure(
+                                t.getFreeParameters(), 0, f.value(t.getValue()));
+                    case 1:
+                        {
+                            final int parameters = t.getFreeParameters();
+                            final double[] derivatives = new double[parameters + 1];
+                            derivatives[0] = f.value(t.getValue());
+                            final double fPrime = f.derivative().value(t.getValue());
+                            int[] orders = new int[parameters];
+                            for (int i = 0; i < parameters; ++i) {
+                                orders[i] = 1;
+                                derivatives[i + 1] = fPrime * t.getPartialDerivative(orders);
+                                orders[i] = 0;
+                            }
+                            return new DerivativeStructure(parameters, 1, derivatives);
+                        }
+                    default:
+                        throw new NumberIsTooLargeException(t.getOrder(), 1, true);
+                }
+            }
+        };
+    }
+
+    /**
+     * Convert a {@link MultivariateDifferentiableFunction} into a {@link
+     * DifferentiableMultivariateFunction}.
+     *
+     * @param f function to convert
+     * @return converted function
+     * @deprecated this conversion method is temporary in version 3.1, as the {@link
+     *     DifferentiableMultivariateFunction} interface itself is deprecated
+     */
+    @Deprecated
+    public static DifferentiableMultivariateFunction toDifferentiableMultivariateFunction(
+            final MultivariateDifferentiableFunction f) {
+        return new DifferentiableMultivariateFunction() {
+
+            /** {@inheritDoc} */
+            public double value(final double[] x) {
+                return f.value(x);
+            }
+
+            /** {@inheritDoc} */
+            public MultivariateFunction partialDerivative(final int k) {
+                return new MultivariateFunction() {
+                    /** {@inheritDoc} */
+                    public double value(final double[] x) {
+
+                        final int n = x.length;
+
+                        // delegate computation to underlying function
+                        final DerivativeStructure[] dsX = new DerivativeStructure[n];
+                        for (int i = 0; i < n; ++i) {
+                            if (i == k) {
+                                dsX[i] = new DerivativeStructure(1, 1, 0, x[i]);
+                            } else {
+                                dsX[i] = new DerivativeStructure(1, 1, x[i]);
+                            }
+                        }
+                        final DerivativeStructure y = f.value(dsX);
+
+                        // extract partial derivative
+                        return y.getPartialDerivative(1);
+                    }
+                };
+            }
+
+            /** {@inheritDoc} */
+            public MultivariateVectorFunction gradient() {
+                return new MultivariateVectorFunction() {
+                    /** {@inheritDoc} */
+                    public double[] value(final double[] x) {
+
+                        final int n = x.length;
+
+                        // delegate computation to underlying function
+                        final DerivativeStructure[] dsX = new DerivativeStructure[n];
+                        for (int i = 0; i < n; ++i) {
+                            dsX[i] = new DerivativeStructure(n, 1, i, x[i]);
+                        }
+                        final DerivativeStructure y = f.value(dsX);
+
+                        // extract gradient
+                        final double[] gradient = new double[n];
+                        final int[] orders = new int[n];
+                        for (int i = 0; i < n; ++i) {
+                            orders[i] = 1;
+                            gradient[i] = y.getPartialDerivative(orders);
+                            orders[i] = 0;
+                        }
+
+                        return gradient;
+                    }
+                };
+            }
+        };
+    }
+
+    /**
+     * Convert a {@link DifferentiableMultivariateFunction} into a {@link
+     * MultivariateDifferentiableFunction}.
+     *
+     * <p>Note that the converted function is able to handle {@link DerivativeStructure} elements
+     * that all have the same number of free parameters and order, and with order at most 1. If the
+     * function is called with inconsistent numbers of free parameters or higher order, a {@link
+     * DimensionMismatchException} or a {@link NumberIsTooLargeException} will be thrown.
+     *
+     * @param f function to convert
+     * @return converted function
+     * @deprecated this conversion method is temporary in version 3.1, as the {@link
+     *     DifferentiableMultivariateFunction} interface itself is deprecated
+     */
+    @Deprecated
+    public static MultivariateDifferentiableFunction toMultivariateDifferentiableFunction(
+            final DifferentiableMultivariateFunction f) {
+        return new MultivariateDifferentiableFunction() {
+
+            /** {@inheritDoc} */
+            public double value(final double[] x) {
+                return f.value(x);
+            }
+
+            /**
+             * {@inheritDoc}
+             *
+             * @exception NumberIsTooLargeException if derivation order is higher than 1
+             * @exception DimensionMismatchException if numbers of free parameters are inconsistent
+             */
+            public DerivativeStructure value(final DerivativeStructure[] t)
+                    throws DimensionMismatchException, NumberIsTooLargeException {
+
+                // check parameters and orders limits
+                final int parameters = t[0].getFreeParameters();
+                final int order = t[0].getOrder();
+                final int n = t.length;
+                if (order > 1) {
+                    throw new NumberIsTooLargeException(order, 1, true);
+                }
+
+                // check all elements in the array are consistent
+                for (int i = 0; i < n; ++i) {
+                    if (t[i].getFreeParameters() != parameters) {
+                        throw new DimensionMismatchException(t[i].getFreeParameters(), parameters);
+                    }
+
+                    if (t[i].getOrder() != order) {
+                        throw new DimensionMismatchException(t[i].getOrder(), order);
+                    }
+                }
+
+                // delegate computation to underlying function
+                final double[] point = new double[n];
+                for (int i = 0; i < n; ++i) {
+                    point[i] = t[i].getValue();
+                }
+                final double value = f.value(point);
+                final double[] gradient = f.gradient().value(point);
+
+                // merge value and gradient into one DerivativeStructure
+                final double[] derivatives = new double[parameters + 1];
+                derivatives[0] = value;
+                final int[] orders = new int[parameters];
+                for (int i = 0; i < parameters; ++i) {
+                    orders[i] = 1;
+                    for (int j = 0; j < n; ++j) {
+                        derivatives[i + 1] += gradient[j] * t[j].getPartialDerivative(orders);
+                    }
+                    orders[i] = 0;
+                }
+
+                return new DerivativeStructure(parameters, order, derivatives);
+            }
+        };
+    }
+
+    /**
+     * Convert a {@link MultivariateDifferentiableVectorFunction} into a {@link
+     * DifferentiableMultivariateVectorFunction}.
+     *
+     * @param f function to convert
+     * @return converted function
+     * @deprecated this conversion method is temporary in version 3.1, as the {@link
+     *     DifferentiableMultivariateVectorFunction} interface itself is deprecated
+     */
+    @Deprecated
+    public static DifferentiableMultivariateVectorFunction
+            toDifferentiableMultivariateVectorFunction(
+                    final MultivariateDifferentiableVectorFunction f) {
+        return new DifferentiableMultivariateVectorFunction() {
+
+            /** {@inheritDoc} */
+            public double[] value(final double[] x) {
+                return f.value(x);
+            }
+
+            /** {@inheritDoc} */
+            public MultivariateMatrixFunction jacobian() {
+                return new MultivariateMatrixFunction() {
+                    /** {@inheritDoc} */
+                    public double[][] value(final double[] x) {
+
+                        final int n = x.length;
+
+                        // delegate computation to underlying function
+                        final DerivativeStructure[] dsX = new DerivativeStructure[n];
+                        for (int i = 0; i < n; ++i) {
+                            dsX[i] = new DerivativeStructure(n, 1, i, x[i]);
+                        }
+                        final DerivativeStructure[] y = f.value(dsX);
+
+                        // extract Jacobian
+                        final double[][] jacobian = new double[y.length][n];
+                        final int[] orders = new int[n];
+                        for (int i = 0; i < y.length; ++i) {
+                            for (int j = 0; j < n; ++j) {
+                                orders[j] = 1;
+                                jacobian[i][j] = y[i].getPartialDerivative(orders);
+                                orders[j] = 0;
+                            }
+                        }
+
+                        return jacobian;
+                    }
+                };
+            }
+        };
+    }
+
+    /**
+     * Convert a {@link DifferentiableMultivariateVectorFunction} into a {@link
+     * MultivariateDifferentiableVectorFunction}.
+     *
+     * <p>Note that the converted function is able to handle {@link DerivativeStructure} elements
+     * that all have the same number of free parameters and order, and with order at most 1. If the
+     * function is called with inconsistent numbers of free parameters or higher order, a {@link
+     * DimensionMismatchException} or a {@link NumberIsTooLargeException} will be thrown.
+     *
+     * @param f function to convert
+     * @return converted function
+     * @deprecated this conversion method is temporary in version 3.1, as the {@link
+     *     DifferentiableMultivariateFunction} interface itself is deprecated
+     */
+    @Deprecated
+    public static MultivariateDifferentiableVectorFunction
+            toMultivariateDifferentiableVectorFunction(
+                    final DifferentiableMultivariateVectorFunction f) {
+        return new MultivariateDifferentiableVectorFunction() {
+
+            /** {@inheritDoc} */
+            public double[] value(final double[] x) {
+                return f.value(x);
+            }
+
+            /**
+             * {@inheritDoc}
+             *
+             * @exception NumberIsTooLargeException if derivation order is higher than 1
+             * @exception DimensionMismatchException if numbers of free parameters are inconsistent
+             */
+            public DerivativeStructure[] value(final DerivativeStructure[] t)
+                    throws DimensionMismatchException, NumberIsTooLargeException {
+
+                // check parameters and orders limits
+                final int parameters = t[0].getFreeParameters();
+                final int order = t[0].getOrder();
+                final int n = t.length;
+                if (order > 1) {
+                    throw new NumberIsTooLargeException(order, 1, true);
+                }
+
+                // check all elements in the array are consistent
+                for (int i = 0; i < n; ++i) {
+                    if (t[i].getFreeParameters() != parameters) {
+                        throw new DimensionMismatchException(t[i].getFreeParameters(), parameters);
+                    }
+
+                    if (t[i].getOrder() != order) {
+                        throw new DimensionMismatchException(t[i].getOrder(), order);
+                    }
+                }
+
+                // delegate computation to underlying function
+                final double[] point = new double[n];
+                for (int i = 0; i < n; ++i) {
+                    point[i] = t[i].getValue();
+                }
+                final double[] value = f.value(point);
+                final double[][] jacobian = f.jacobian().value(point);
+
+                // merge value and Jacobian into a DerivativeStructure array
+                final DerivativeStructure[] merged = new DerivativeStructure[value.length];
+                for (int k = 0; k < merged.length; ++k) {
+                    final double[] derivatives = new double[parameters + 1];
+                    derivatives[0] = value[k];
+                    final int[] orders = new int[parameters];
+                    for (int i = 0; i < parameters; ++i) {
+                        orders[i] = 1;
+                        for (int j = 0; j < n; ++j) {
+                            derivatives[i + 1] +=
+                                    jacobian[k][j] * t[j].getPartialDerivative(orders);
+                        }
+                        orders[i] = 0;
+                    }
+                    merged[k] = new DerivativeStructure(parameters, order, derivatives);
+                }
+
+                return merged;
+            }
+        };
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/MultivariateFunction.java b/src/main/java/org/apache/commons/math3/analysis/MultivariateFunction.java
new file mode 100644
index 0000000..cb6546f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/MultivariateFunction.java
@@ -0,0 +1,41 @@
+/*
+ * 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.math3.analysis;
+
+/**
+ * An interface representing a multivariate real function.
+ *
+ * @since 2.0
+ */
+public interface MultivariateFunction {
+
+    /**
+     * Compute the value for the function at the given point.
+     *
+     * @param point Point at which the function must be evaluated.
+     * @return the function value for the given point.
+     * @throws org.apache.commons.math3.exception.DimensionMismatchException if the parameter's
+     *     dimension is wrong for the function being evaluated.
+     * @throws org.apache.commons.math3.exception.MathIllegalArgumentException when the activated
+     *     method itself can ascertain that preconditions, specified in the API expressed at the
+     *     level of the activated method, have been violated. In the vast majority of cases where
+     *     Commons Math throws this exception, it is the result of argument checking of actual
+     *     parameters immediately passed to a method.
+     */
+    double value(double[] point);
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/MultivariateMatrixFunction.java b/src/main/java/org/apache/commons/math3/analysis/MultivariateMatrixFunction.java
new file mode 100644
index 0000000..8f7aaf0
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/MultivariateMatrixFunction.java
@@ -0,0 +1,35 @@
+/*
+ * 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.math3.analysis;
+
+/**
+ * An interface representing a multivariate matrix function.
+ *
+ * @since 2.0
+ */
+public interface MultivariateMatrixFunction {
+
+    /**
+     * Compute the value for the function at the given point.
+     *
+     * @param point point at which the function must be evaluated
+     * @return function value for the given point
+     * @exception IllegalArgumentException if point's dimension is wrong
+     */
+    double[][] value(double[] point) throws IllegalArgumentException;
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/MultivariateVectorFunction.java b/src/main/java/org/apache/commons/math3/analysis/MultivariateVectorFunction.java
new file mode 100644
index 0000000..e26d9a6
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/MultivariateVectorFunction.java
@@ -0,0 +1,35 @@
+/*
+ * 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.math3.analysis;
+
+/**
+ * An interface representing a multivariate vectorial function.
+ *
+ * @since 2.0
+ */
+public interface MultivariateVectorFunction {
+
+    /**
+     * Compute the value for the function at the given point.
+     *
+     * @param point point at which the function must be evaluated
+     * @return function value for the given point
+     * @exception IllegalArgumentException if point's dimension is wrong
+     */
+    double[] value(double[] point) throws IllegalArgumentException;
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/ParametricUnivariateFunction.java b/src/main/java/org/apache/commons/math3/analysis/ParametricUnivariateFunction.java
new file mode 100644
index 0000000..beee82f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/ParametricUnivariateFunction.java
@@ -0,0 +1,44 @@
+/*
+ * 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.math3.analysis;
+
+/**
+ * An interface representing a real function that depends on one independent variable plus some
+ * extra parameters.
+ *
+ * @since 3.0
+ */
+public interface ParametricUnivariateFunction {
+    /**
+     * Compute the value of the function.
+     *
+     * @param x Point for which the function value should be computed.
+     * @param parameters Function parameters.
+     * @return the value.
+     */
+    double value(double x, double... parameters);
+
+    /**
+     * Compute the gradient of the function with respect to its parameters.
+     *
+     * @param x Point for which the function value should be computed.
+     * @param parameters Function parameters.
+     * @return the value.
+     */
+    double[] gradient(double x, double... parameters);
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/RealFieldUnivariateFunction.java b/src/main/java/org/apache/commons/math3/analysis/RealFieldUnivariateFunction.java
new file mode 100644
index 0000000..431c867
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/RealFieldUnivariateFunction.java
@@ -0,0 +1,82 @@
+/*
+ * 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.math3.analysis;
+
+import org.apache.commons.math3.RealFieldElement;
+
+/**
+ * An interface representing a univariate real function.
+ *
+ * <p>When a <em>user-defined</em> function encounters an error during evaluation, the {@link
+ * #value(RealFieldElement) value} method should throw a <em>user-defined</em> unchecked exception.
+ *
+ * <p>The following code excerpt shows the recommended way to do that using a root solver as an
+ * example, but the same construct is applicable to ODE integrators or optimizers.
+ *
+ * <pre>
+ * private static class LocalException extends RuntimeException {
+ *     // The x value that caused the problem.
+ *     private final SomeFieldType x;
+ *
+ *     public LocalException(SomeFieldType x) {
+ *         this.x = x;
+ *     }
+ *
+ *     public double getX() {
+ *         return x;
+ *     }
+ * }
+ *
+ * private static class MyFunction implements FieldUnivariateFunction&lt;SomeFieldType&gt; {
+ *     public SomeFieldType value(SomeFieldType x) {
+ *         SomeFieldType y = hugeFormula(x);
+ *         if (somethingBadHappens) {
+ *           throw new LocalException(x);
+ *         }
+ *         return y;
+ *     }
+ * }
+ *
+ * public void compute() {
+ *     try {
+ *         solver.solve(maxEval, new MyFunction(a, b, c), min, max);
+ *     } catch (LocalException le) {
+ *         // Retrieve the x value.
+ *     }
+ * }
+ * </pre>
+ *
+ * As shown, the exception is local to the user's code and it is guaranteed that Apache Commons Math
+ * will not catch it.
+ *
+ * @param <T> the type of the field elements
+ * @since 3.6
+ * @see UnivariateFunction
+ */
+public interface RealFieldUnivariateFunction<T extends RealFieldElement<T>> {
+    /**
+     * Compute the value of the function.
+     *
+     * @param x Point at which the function value should be computed.
+     * @return the value of the function.
+     * @throws IllegalArgumentException when the activated method itself can ascertain that a
+     *     precondition, specified in the API expressed at the level of the activated method, has
+     *     been violated. When Commons Math throws an {@code IllegalArgumentException}, it is
+     *     usually the consequence of checking the actual parameters passed to the method.
+     */
+    T value(T x);
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/TrivariateFunction.java b/src/main/java/org/apache/commons/math3/analysis/TrivariateFunction.java
new file mode 100644
index 0000000..30ba767
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/TrivariateFunction.java
@@ -0,0 +1,35 @@
+/*
+ * 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.math3.analysis;
+
+/**
+ * An interface representing a trivariate real function.
+ *
+ * @since 2.2
+ */
+public interface TrivariateFunction {
+    /**
+     * Compute the value for the function.
+     *
+     * @param x x-coordinate for which the function value should be computed.
+     * @param y y-coordinate for which the function value should be computed.
+     * @param z z-coordinate for which the function value should be computed.
+     * @return the value.
+     */
+    double value(double x, double y, double z);
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/UnivariateFunction.java b/src/main/java/org/apache/commons/math3/analysis/UnivariateFunction.java
new file mode 100644
index 0000000..54b0d0f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/UnivariateFunction.java
@@ -0,0 +1,76 @@
+/*
+ * 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.math3.analysis;
+
+/**
+ * An interface representing a univariate real function.
+ *
+ * <p>When a <em>user-defined</em> function encounters an error during evaluation, the {@link
+ * #value(double) value} method should throw a <em>user-defined</em> unchecked exception.
+ *
+ * <p>The following code excerpt shows the recommended way to do that using a root solver as an
+ * example, but the same construct is applicable to ODE integrators or optimizers.
+ *
+ * <pre>
+ * private static class LocalException extends RuntimeException {
+ *     // The x value that caused the problem.
+ *     private final double x;
+ *
+ *     public LocalException(double x) {
+ *         this.x = x;
+ *     }
+ *
+ *     public double getX() {
+ *         return x;
+ *     }
+ * }
+ *
+ * private static class MyFunction implements UnivariateFunction {
+ *     public double value(double x) {
+ *         double y = hugeFormula(x);
+ *         if (somethingBadHappens) {
+ *           throw new LocalException(x);
+ *         }
+ *         return y;
+ *     }
+ * }
+ *
+ * public void compute() {
+ *     try {
+ *         solver.solve(maxEval, new MyFunction(a, b, c), min, max);
+ *     } catch (LocalException le) {
+ *         // Retrieve the x value.
+ *     }
+ * }
+ * </pre>
+ *
+ * As shown, the exception is local to the user's code and it is guaranteed that Apache Commons Math
+ * will not catch it.
+ */
+public interface UnivariateFunction {
+    /**
+     * Compute the value of the function.
+     *
+     * @param x Point at which the function value should be computed.
+     * @return the value of the function.
+     * @throws IllegalArgumentException when the activated method itself can ascertain that a
+     *     precondition, specified in the API expressed at the level of the activated method, has
+     *     been violated. When Commons Math throws an {@code IllegalArgumentException}, it is
+     *     usually the consequence of checking the actual parameters passed to the method.
+     */
+    double value(double x);
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/UnivariateMatrixFunction.java b/src/main/java/org/apache/commons/math3/analysis/UnivariateMatrixFunction.java
new file mode 100644
index 0000000..8348056
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/UnivariateMatrixFunction.java
@@ -0,0 +1,33 @@
+/*
+ * 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.math3.analysis;
+
+/**
+ * An interface representing a univariate matrix function.
+ *
+ * @since 2.0
+ */
+public interface UnivariateMatrixFunction {
+
+    /**
+     * Compute the value for the function.
+     *
+     * @param x the point for which the function value should be computed
+     * @return the value
+     */
+    double[][] value(double x);
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/UnivariateVectorFunction.java b/src/main/java/org/apache/commons/math3/analysis/UnivariateVectorFunction.java
new file mode 100644
index 0000000..2caeb36
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/UnivariateVectorFunction.java
@@ -0,0 +1,33 @@
+/*
+ * 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.math3.analysis;
+
+/**
+ * An interface representing a univariate vectorial function.
+ *
+ * @since 2.0
+ */
+public interface UnivariateVectorFunction {
+
+    /**
+     * Compute the value for the function.
+     *
+     * @param x the point for which the function value should be computed
+     * @return the value
+     */
+    double[] value(double x);
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/differentiation/DSCompiler.java b/src/main/java/org/apache/commons/math3/analysis/differentiation/DSCompiler.java
new file mode 100644
index 0000000..15fa499
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/differentiation/DSCompiler.java
@@ -0,0 +1,1820 @@
+/*
+ * 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.math3.analysis.differentiation;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.MathInternalError;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.util.CombinatoricsUtils;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathArrays;
+
+/** Class holding "compiled" computation rules for derivative structures.
+ * <p>This class implements the computation rules described in Dan Kalman's paper <a
+ * href="http://www1.american.edu/cas/mathstat/People/kalman/pdffiles/mmgautodiff.pdf">Doubly
+ * Recursive Multivariate Automatic Differentiation</a>, Mathematics Magazine, vol. 75,
+ * no. 3, June 2002. However, in order to avoid performances bottlenecks, the recursive
+ * rules are "compiled" once in an unfold form. This class does this recursion unrolling
+ * and stores the computation rules as simple loops with pre-computed indirection arrays.</p>
+ * <p>
+ * This class maps all derivative computation into single dimension arrays that hold the
+ * value and partial derivatives. The class does not hold these arrays, which remains under
+ * the responsibility of the caller. For each combination of number of free parameters and
+ * derivation order, only one compiler is necessary, and this compiler will be used to
+ * perform computations on all arrays provided to it, which can represent hundreds or
+ * thousands of different parameters kept together with all theur partial derivatives.
+ * </p>
+ * <p>
+ * The arrays on which compilers operate contain only the partial derivatives together
+ * with the 0<sup>th</sup> derivative, i.e. the value. The partial derivatives are stored in
+ * a compiler-specific order, which can be retrieved using methods {@link
+ * #getPartialDerivativeIndex(int...) getPartialDerivativeIndex} and {@link
+ * #getPartialDerivativeOrders(int)}. The value is guaranteed to be stored as the first element
+ * (i.e. the {@link #getPartialDerivativeIndex(int...) getPartialDerivativeIndex} method returns
+ * 0 when called with 0 for all derivation orders and {@link #getPartialDerivativeOrders(int)
+ * getPartialDerivativeOrders} returns an array filled with 0 when called with 0 as the index).
+ * </p>
+ * <p>
+ * Note that the ordering changes with number of parameters and derivation order. For example
+ * given 2 parameters x and y, df/dy is stored at index 2 when derivation order is set to 1 (in
+ * this case the array has three elements: f, df/dx and df/dy). If derivation order is set to
+ * 2, then df/dy will be stored at index 3 (in this case the array has six elements: f, df/dx,
+ * df/dxdx, df/dy, df/dxdy and df/dydy).
+ * </p>
+ * <p>
+ * Given this structure, users can perform some simple operations like adding, subtracting
+ * or multiplying constants and negating the elements by themselves, knowing if they want to
+ * mutate their array or create a new array. These simple operations are not provided by
+ * the compiler. The compiler provides only the more complex operations between several arrays.
+ * </p>
+ * <p>This class is mainly used as the engine for scalar variable {@link DerivativeStructure}.
+ * It can also be used directly to hold several variables in arrays for more complex data
+ * structures. User can for example store a vector of n variables depending on three x, y
+ * and z free parameters in one array as follows:</p> <pre>
+ *   // parameter 0 is x, parameter 1 is y, parameter 2 is z
+ *   int parameters = 3;
+ *   DSCompiler compiler = DSCompiler.getCompiler(parameters, order);
+ *   int size = compiler.getSize();
+ *
+ *   // pack all elements in a single array
+ *   double[] array = new double[n * size];
+ *   for (int i = 0; i &lt; n; ++i) {
+ *
+ *     // we know value is guaranteed to be the first element
+ *     array[i * size] = v[i];
+ *
+ *     // we don't know where first derivatives are stored, so we ask the compiler
+ *     array[i * size + compiler.getPartialDerivativeIndex(1, 0, 0) = dvOnDx[i][0];
+ *     array[i * size + compiler.getPartialDerivativeIndex(0, 1, 0) = dvOnDy[i][0];
+ *     array[i * size + compiler.getPartialDerivativeIndex(0, 0, 1) = dvOnDz[i][0];
+ *
+ *     // we let all higher order derivatives set to 0
+ *
+ *   }
+ * </pre>
+ * <p>Then in another function, user can perform some operations on all elements stored
+ * in the single array, such as a simple product of all variables:</p> <pre>
+ *   // compute the product of all elements
+ *   double[] product = new double[size];
+ *   prod[0] = 1.0;
+ *   for (int i = 0; i &lt; n; ++i) {
+ *     double[] tmp = product.clone();
+ *     compiler.multiply(tmp, 0, array, i * size, product, 0);
+ *   }
+ *
+ *   // value
+ *   double p = product[0];
+ *
+ *   // first derivatives
+ *   double dPdX = product[compiler.getPartialDerivativeIndex(1, 0, 0)];
+ *   double dPdY = product[compiler.getPartialDerivativeIndex(0, 1, 0)];
+ *   double dPdZ = product[compiler.getPartialDerivativeIndex(0, 0, 1)];
+ *
+ *   // cross derivatives (assuming order was at least 2)
+ *   double dPdXdX = product[compiler.getPartialDerivativeIndex(2, 0, 0)];
+ *   double dPdXdY = product[compiler.getPartialDerivativeIndex(1, 1, 0)];
+ *   double dPdXdZ = product[compiler.getPartialDerivativeIndex(1, 0, 1)];
+ *   double dPdYdY = product[compiler.getPartialDerivativeIndex(0, 2, 0)];
+ *   double dPdYdZ = product[compiler.getPartialDerivativeIndex(0, 1, 1)];
+ *   double dPdZdZ = product[compiler.getPartialDerivativeIndex(0, 0, 2)];
+ * </pre>
+ * @see DerivativeStructure
+ * @since 3.1
+ */
+public class DSCompiler {
+
+    /** Array of all compilers created so far. */
+    private static AtomicReference<DSCompiler[][]> compilers =
+            new AtomicReference<DSCompiler[][]>(null);
+
+    /** Number of free parameters. */
+    private final int parameters;
+
+    /** Derivation order. */
+    private final int order;
+
+    /** Number of partial derivatives (including the single 0 order derivative element). */
+    private final int[][] sizes;
+
+    /** Indirection array for partial derivatives. */
+    private final int[][] derivativesIndirection;
+
+    /** Indirection array of the lower derivative elements. */
+    private final int[] lowerIndirection;
+
+    /** Indirection arrays for multiplication. */
+    private final int[][][] multIndirection;
+
+    /** Indirection arrays for function composition. */
+    private final int[][][] compIndirection;
+
+    /** Private constructor, reserved for the factory method {@link #getCompiler(int, int)}.
+     * @param parameters number of free parameters
+     * @param order derivation order
+     * @param valueCompiler compiler for the value part
+     * @param derivativeCompiler compiler for the derivative part
+     * @throws NumberIsTooLargeException if order is too large
+     */
+    private DSCompiler(final int parameters, final int order,
+                       final DSCompiler valueCompiler, final DSCompiler derivativeCompiler)
+        throws NumberIsTooLargeException {
+
+        this.parameters = parameters;
+        this.order      = order;
+        this.sizes      = compileSizes(parameters, order, valueCompiler);
+        this.derivativesIndirection =
+                compileDerivativesIndirection(parameters, order,
+                                              valueCompiler, derivativeCompiler);
+        this.lowerIndirection =
+                compileLowerIndirection(parameters, order,
+                                        valueCompiler, derivativeCompiler);
+        this.multIndirection =
+                compileMultiplicationIndirection(parameters, order,
+                                                 valueCompiler, derivativeCompiler, lowerIndirection);
+        this.compIndirection =
+                compileCompositionIndirection(parameters, order,
+                                              valueCompiler, derivativeCompiler,
+                                              sizes, derivativesIndirection);
+
+    }
+
+    /** Get the compiler for number of free parameters and order.
+     * @param parameters number of free parameters
+     * @param order derivation order
+     * @return cached rules set
+     * @throws NumberIsTooLargeException if order is too large
+     */
+    public static DSCompiler getCompiler(int parameters, int order)
+        throws NumberIsTooLargeException {
+
+        // get the cached compilers
+        final DSCompiler[][] cache = compilers.get();
+        if (cache != null && cache.length > parameters &&
+            cache[parameters].length > order && cache[parameters][order] != null) {
+            // the compiler has already been created
+            return cache[parameters][order];
+        }
+
+        // we need to create more compilers
+        final int maxParameters = FastMath.max(parameters, cache == null ? 0 : cache.length);
+        final int maxOrder      = FastMath.max(order,     cache == null ? 0 : cache[0].length);
+        final DSCompiler[][] newCache = new DSCompiler[maxParameters + 1][maxOrder + 1];
+
+        if (cache != null) {
+            // preserve the already created compilers
+            for (int i = 0; i < cache.length; ++i) {
+                System.arraycopy(cache[i], 0, newCache[i], 0, cache[i].length);
+            }
+        }
+
+        // create the array in increasing diagonal order
+        for (int diag = 0; diag <= parameters + order; ++diag) {
+            for (int o = FastMath.max(0, diag - parameters); o <= FastMath.min(order, diag); ++o) {
+                final int p = diag - o;
+                if (newCache[p][o] == null) {
+                    final DSCompiler valueCompiler      = (p == 0) ? null : newCache[p - 1][o];
+                    final DSCompiler derivativeCompiler = (o == 0) ? null : newCache[p][o - 1];
+                    newCache[p][o] = new DSCompiler(p, o, valueCompiler, derivativeCompiler);
+                }
+            }
+        }
+
+        // atomically reset the cached compilers array
+        compilers.compareAndSet(cache, newCache);
+
+        return newCache[parameters][order];
+
+    }
+
+    /** Compile the sizes array.
+     * @param parameters number of free parameters
+     * @param order derivation order
+     * @param valueCompiler compiler for the value part
+     * @return sizes array
+     */
+    private static int[][] compileSizes(final int parameters, final int order,
+                                        final DSCompiler valueCompiler) {
+
+        final int[][] sizes = new int[parameters + 1][order + 1];
+        if (parameters == 0) {
+            Arrays.fill(sizes[0], 1);
+        } else {
+            System.arraycopy(valueCompiler.sizes, 0, sizes, 0, parameters);
+            sizes[parameters][0] = 1;
+            for (int i = 0; i < order; ++i) {
+                sizes[parameters][i + 1] = sizes[parameters][i] + sizes[parameters - 1][i + 1];
+            }
+        }
+
+        return sizes;
+
+    }
+
+    /** Compile the derivatives indirection array.
+     * @param parameters number of free parameters
+     * @param order derivation order
+     * @param valueCompiler compiler for the value part
+     * @param derivativeCompiler compiler for the derivative part
+     * @return derivatives indirection array
+     */
+    private static int[][] compileDerivativesIndirection(final int parameters, final int order,
+                                                      final DSCompiler valueCompiler,
+                                                      final DSCompiler derivativeCompiler) {
+
+        if (parameters == 0 || order == 0) {
+            return new int[1][parameters];
+        }
+
+        final int vSize = valueCompiler.derivativesIndirection.length;
+        final int dSize = derivativeCompiler.derivativesIndirection.length;
+        final int[][] derivativesIndirection = new int[vSize + dSize][parameters];
+
+        // set up the indices for the value part
+        for (int i = 0; i < vSize; ++i) {
+            // copy the first indices, the last one remaining set to 0
+            System.arraycopy(valueCompiler.derivativesIndirection[i], 0,
+                             derivativesIndirection[i], 0,
+                             parameters - 1);
+        }
+
+        // set up the indices for the derivative part
+        for (int i = 0; i < dSize; ++i) {
+
+            // copy the indices
+            System.arraycopy(derivativeCompiler.derivativesIndirection[i], 0,
+                             derivativesIndirection[vSize + i], 0,
+                             parameters);
+
+            // increment the derivation order for the last parameter
+            derivativesIndirection[vSize + i][parameters - 1]++;
+
+        }
+
+        return derivativesIndirection;
+
+    }
+
+    /** Compile the lower derivatives indirection array.
+     * <p>
+     * This indirection array contains the indices of all elements
+     * except derivatives for last derivation order.
+     * </p>
+     * @param parameters number of free parameters
+     * @param order derivation order
+     * @param valueCompiler compiler for the value part
+     * @param derivativeCompiler compiler for the derivative part
+     * @return lower derivatives indirection array
+     */
+    private static int[] compileLowerIndirection(final int parameters, final int order,
+                                              final DSCompiler valueCompiler,
+                                              final DSCompiler derivativeCompiler) {
+
+        if (parameters == 0 || order <= 1) {
+            return new int[] { 0 };
+        }
+
+        // this is an implementation of definition 6 in Dan Kalman's paper.
+        final int vSize = valueCompiler.lowerIndirection.length;
+        final int dSize = derivativeCompiler.lowerIndirection.length;
+        final int[] lowerIndirection = new int[vSize + dSize];
+        System.arraycopy(valueCompiler.lowerIndirection, 0, lowerIndirection, 0, vSize);
+        for (int i = 0; i < dSize; ++i) {
+            lowerIndirection[vSize + i] = valueCompiler.getSize() + derivativeCompiler.lowerIndirection[i];
+        }
+
+        return lowerIndirection;
+
+    }
+
+    /** Compile the multiplication indirection array.
+     * <p>
+     * This indirection array contains the indices of all pairs of elements
+     * involved when computing a multiplication. This allows a straightforward
+     * loop-based multiplication (see {@link #multiply(double[], int, double[], int, double[], int)}).
+     * </p>
+     * @param parameters number of free parameters
+     * @param order derivation order
+     * @param valueCompiler compiler for the value part
+     * @param derivativeCompiler compiler for the derivative part
+     * @param lowerIndirection lower derivatives indirection array
+     * @return multiplication indirection array
+     */
+    private static int[][][] compileMultiplicationIndirection(final int parameters, final int order,
+                                                           final DSCompiler valueCompiler,
+                                                           final DSCompiler derivativeCompiler,
+                                                           final int[] lowerIndirection) {
+
+        if ((parameters == 0) || (order == 0)) {
+            return new int[][][] { { { 1, 0, 0 } } };
+        }
+
+        // this is an implementation of definition 3 in Dan Kalman's paper.
+        final int vSize = valueCompiler.multIndirection.length;
+        final int dSize = derivativeCompiler.multIndirection.length;
+        final int[][][] multIndirection = new int[vSize + dSize][][];
+
+        System.arraycopy(valueCompiler.multIndirection, 0, multIndirection, 0, vSize);
+
+        for (int i = 0; i < dSize; ++i) {
+            final int[][] dRow = derivativeCompiler.multIndirection[i];
+            List<int[]> row = new ArrayList<int[]>(dRow.length * 2);
+            for (int j = 0; j < dRow.length; ++j) {
+                row.add(new int[] { dRow[j][0], lowerIndirection[dRow[j][1]], vSize + dRow[j][2] });
+                row.add(new int[] { dRow[j][0], vSize + dRow[j][1], lowerIndirection[dRow[j][2]] });
+            }
+
+            // combine terms with similar derivation orders
+            final List<int[]> combined = new ArrayList<int[]>(row.size());
+            for (int j = 0; j < row.size(); ++j) {
+                final int[] termJ = row.get(j);
+                if (termJ[0] > 0) {
+                    for (int k = j + 1; k < row.size(); ++k) {
+                        final int[] termK = row.get(k);
+                        if (termJ[1] == termK[1] && termJ[2] == termK[2]) {
+                            // combine termJ and termK
+                            termJ[0] += termK[0];
+                            // make sure we will skip termK later on in the outer loop
+                            termK[0] = 0;
+                        }
+                    }
+                    combined.add(termJ);
+                }
+            }
+
+            multIndirection[vSize + i] = combined.toArray(new int[combined.size()][]);
+
+        }
+
+        return multIndirection;
+
+    }
+
+    /** Compile the function composition indirection array.
+     * <p>
+     * This indirection array contains the indices of all sets of elements
+     * involved when computing a composition. This allows a straightforward
+     * loop-based composition (see {@link #compose(double[], int, double[], double[], int)}).
+     * </p>
+     * @param parameters number of free parameters
+     * @param order derivation order
+     * @param valueCompiler compiler for the value part
+     * @param derivativeCompiler compiler for the derivative part
+     * @param sizes sizes array
+     * @param derivativesIndirection derivatives indirection array
+     * @return multiplication indirection array
+     * @throws NumberIsTooLargeException if order is too large
+     */
+    private static int[][][] compileCompositionIndirection(final int parameters, final int order,
+                                                           final DSCompiler valueCompiler,
+                                                           final DSCompiler derivativeCompiler,
+                                                           final int[][] sizes,
+                                                           final int[][] derivativesIndirection)
+       throws NumberIsTooLargeException {
+
+        if ((parameters == 0) || (order == 0)) {
+            return new int[][][] { { { 1, 0 } } };
+        }
+
+        final int vSize = valueCompiler.compIndirection.length;
+        final int dSize = derivativeCompiler.compIndirection.length;
+        final int[][][] compIndirection = new int[vSize + dSize][][];
+
+        // the composition rules from the value part can be reused as is
+        System.arraycopy(valueCompiler.compIndirection, 0, compIndirection, 0, vSize);
+
+        // the composition rules for the derivative part are deduced by
+        // differentiation the rules from the underlying compiler once
+        // with respect to the parameter this compiler handles and the
+        // underlying one did not handle
+        for (int i = 0; i < dSize; ++i) {
+            List<int[]> row = new ArrayList<int[]>();
+            for (int[] term : derivativeCompiler.compIndirection[i]) {
+
+                // handle term p * f_k(g(x)) * g_l1(x) * g_l2(x) * ... * g_lp(x)
+
+                // derive the first factor in the term: f_k with respect to new parameter
+                int[] derivedTermF = new int[term.length + 1];
+                derivedTermF[0] = term[0];     // p
+                derivedTermF[1] = term[1] + 1; // f_(k+1)
+                int[] orders = new int[parameters];
+                orders[parameters - 1] = 1;
+                derivedTermF[term.length] = getPartialDerivativeIndex(parameters, order, sizes, orders);  // g_1
+                for (int j = 2; j < term.length; ++j) {
+                    // convert the indices as the mapping for the current order
+                    // is different from the mapping with one less order
+                    derivedTermF[j] = convertIndex(term[j], parameters,
+                                                   derivativeCompiler.derivativesIndirection,
+                                                   parameters, order, sizes);
+                }
+                Arrays.sort(derivedTermF, 2, derivedTermF.length);
+                row.add(derivedTermF);
+
+                // derive the various g_l
+                for (int l = 2; l < term.length; ++l) {
+                    int[] derivedTermG = new int[term.length];
+                    derivedTermG[0] = term[0];
+                    derivedTermG[1] = term[1];
+                    for (int j = 2; j < term.length; ++j) {
+                        // convert the indices as the mapping for the current order
+                        // is different from the mapping with one less order
+                        derivedTermG[j] = convertIndex(term[j], parameters,
+                                                       derivativeCompiler.derivativesIndirection,
+                                                       parameters, order, sizes);
+                        if (j == l) {
+                            // derive this term
+                            System.arraycopy(derivativesIndirection[derivedTermG[j]], 0, orders, 0, parameters);
+                            orders[parameters - 1]++;
+                            derivedTermG[j] = getPartialDerivativeIndex(parameters, order, sizes, orders);
+                        }
+                    }
+                    Arrays.sort(derivedTermG, 2, derivedTermG.length);
+                    row.add(derivedTermG);
+                }
+
+            }
+
+            // combine terms with similar derivation orders
+            final List<int[]> combined = new ArrayList<int[]>(row.size());
+            for (int j = 0; j < row.size(); ++j) {
+                final int[] termJ = row.get(j);
+                if (termJ[0] > 0) {
+                    for (int k = j + 1; k < row.size(); ++k) {
+                        final int[] termK = row.get(k);
+                        boolean equals = termJ.length == termK.length;
+                        for (int l = 1; equals && l < termJ.length; ++l) {
+                            equals &= termJ[l] == termK[l];
+                        }
+                        if (equals) {
+                            // combine termJ and termK
+                            termJ[0] += termK[0];
+                            // make sure we will skip termK later on in the outer loop
+                            termK[0] = 0;
+                        }
+                    }
+                    combined.add(termJ);
+                }
+            }
+
+            compIndirection[vSize + i] = combined.toArray(new int[combined.size()][]);
+
+        }
+
+        return compIndirection;
+
+    }
+
+    /** Get the index of a partial derivative in the array.
+     * <p>
+     * If all orders are set to 0, then the 0<sup>th</sup> order derivative
+     * is returned, which is the value of the function.
+     * </p>
+     * <p>The indices of derivatives are between 0 and {@link #getSize() getSize()} - 1.
+     * Their specific order is fixed for a given compiler, but otherwise not
+     * publicly specified. There are however some simple cases which have guaranteed
+     * indices:
+     * </p>
+     * <ul>
+     *   <li>the index of 0<sup>th</sup> order derivative is always 0</li>
+     *   <li>if there is only 1 {@link #getFreeParameters() free parameter}, then the
+     *   derivatives are sorted in increasing derivation order (i.e. f at index 0, df/dp
+     *   at index 1, d<sup>2</sup>f/dp<sup>2</sup> at index 2 ...
+     *   d<sup>k</sup>f/dp<sup>k</sup> at index k),</li>
+     *   <li>if the {@link #getOrder() derivation order} is 1, then the derivatives
+     *   are sorted in increasing free parameter order (i.e. f at index 0, df/dx<sub>1</sub>
+     *   at index 1, df/dx<sub>2</sub> at index 2 ... df/dx<sub>k</sub> at index k),</li>
+     *   <li>all other cases are not publicly specified</li>
+     * </ul>
+     * <p>
+     * This method is the inverse of method {@link #getPartialDerivativeOrders(int)}
+     * </p>
+     * @param orders derivation orders with respect to each parameter
+     * @return index of the partial derivative
+     * @exception DimensionMismatchException if the numbers of parameters does not
+     * match the instance
+     * @exception NumberIsTooLargeException if sum of derivation orders is larger
+     * than the instance limits
+     * @see #getPartialDerivativeOrders(int)
+     */
+    public int getPartialDerivativeIndex(final int ... orders)
+            throws DimensionMismatchException, NumberIsTooLargeException {
+
+        // safety check
+        if (orders.length != getFreeParameters()) {
+            throw new DimensionMismatchException(orders.length, getFreeParameters());
+        }
+
+        return getPartialDerivativeIndex(parameters, order, sizes, orders);
+
+    }
+
+    /** Get the index of a partial derivative in an array.
+     * @param parameters number of free parameters
+     * @param order derivation order
+     * @param sizes sizes array
+     * @param orders derivation orders with respect to each parameter
+     * (the lenght of this array must match the number of parameters)
+     * @return index of the partial derivative
+     * @exception NumberIsTooLargeException if sum of derivation orders is larger
+     * than the instance limits
+     */
+    private static int getPartialDerivativeIndex(final int parameters, final int order,
+                                                 final int[][] sizes, final int ... orders)
+        throws NumberIsTooLargeException {
+
+        // the value is obtained by diving into the recursive Dan Kalman's structure
+        // this is theorem 2 of his paper, with recursion replaced by iteration
+        int index     = 0;
+        int m         = order;
+        int ordersSum = 0;
+        for (int i = parameters - 1; i >= 0; --i) {
+
+            // derivative order for current free parameter
+            int derivativeOrder = orders[i];
+
+            // safety check
+            ordersSum += derivativeOrder;
+            if (ordersSum > order) {
+                throw new NumberIsTooLargeException(ordersSum, order, true);
+            }
+
+            while (derivativeOrder-- > 0) {
+                // as long as we differentiate according to current free parameter,
+                // we have to skip the value part and dive into the derivative part
+                // so we add the size of the value part to the base index
+                index += sizes[i][m--];
+            }
+
+        }
+
+        return index;
+
+    }
+
+    /** Convert an index from one (parameters, order) structure to another.
+     * @param index index of a partial derivative in source derivative structure
+     * @param srcP number of free parameters in source derivative structure
+     * @param srcDerivativesIndirection derivatives indirection array for the source
+     * derivative structure
+     * @param destP number of free parameters in destination derivative structure
+     * @param destO derivation order in destination derivative structure
+     * @param destSizes sizes array for the destination derivative structure
+     * @return index of the partial derivative with the <em>same</em> characteristics
+     * in destination derivative structure
+     * @throws NumberIsTooLargeException if order is too large
+     */
+    private static int convertIndex(final int index,
+                                    final int srcP, final int[][] srcDerivativesIndirection,
+                                    final int destP, final int destO, final int[][] destSizes)
+        throws NumberIsTooLargeException {
+        int[] orders = new int[destP];
+        System.arraycopy(srcDerivativesIndirection[index], 0, orders, 0, FastMath.min(srcP, destP));
+        return getPartialDerivativeIndex(destP, destO, destSizes, orders);
+    }
+
+    /** Get the derivation orders for a specific index in the array.
+     * <p>
+     * This method is the inverse of {@link #getPartialDerivativeIndex(int...)}.
+     * </p>
+     * @param index of the partial derivative
+     * @return orders derivation orders with respect to each parameter
+     * @see #getPartialDerivativeIndex(int...)
+     */
+    public int[] getPartialDerivativeOrders(final int index) {
+        return derivativesIndirection[index];
+    }
+
+    /** Get the number of free parameters.
+     * @return number of free parameters
+     */
+    public int getFreeParameters() {
+        return parameters;
+    }
+
+    /** Get the derivation order.
+     * @return derivation order
+     */
+    public int getOrder() {
+        return order;
+    }
+
+    /** Get the array size required for holding partial derivatives data.
+     * <p>
+     * This number includes the single 0 order derivative element, which is
+     * guaranteed to be stored in the first element of the array.
+     * </p>
+     * @return array size required for holding partial derivatives data
+     */
+    public int getSize() {
+        return sizes[parameters][order];
+    }
+
+    /** Compute linear combination.
+     * The derivative structure built will be a1 * ds1 + a2 * ds2
+     * @param a1 first scale factor
+     * @param c1 first base (unscaled) component
+     * @param offset1 offset of first operand in its array
+     * @param a2 second scale factor
+     * @param c2 second base (unscaled) component
+     * @param offset2 offset of second operand in its array
+     * @param result array where result must be stored (it may be
+     * one of the input arrays)
+     * @param resultOffset offset of the result in its array
+     */
+    public void linearCombination(final double a1, final double[] c1, final int offset1,
+                                  final double a2, final double[] c2, final int offset2,
+                                  final double[] result, final int resultOffset) {
+        for (int i = 0; i < getSize(); ++i) {
+            result[resultOffset + i] =
+                    MathArrays.linearCombination(a1, c1[offset1 + i], a2, c2[offset2 + i]);
+        }
+    }
+
+    /** Compute linear combination.
+     * The derivative structure built will be a1 * ds1 + a2 * ds2 + a3 * ds3 + a4 * ds4
+     * @param a1 first scale factor
+     * @param c1 first base (unscaled) component
+     * @param offset1 offset of first operand in its array
+     * @param a2 second scale factor
+     * @param c2 second base (unscaled) component
+     * @param offset2 offset of second operand in its array
+     * @param a3 third scale factor
+     * @param c3 third base (unscaled) component
+     * @param offset3 offset of third operand in its array
+     * @param result array where result must be stored (it may be
+     * one of the input arrays)
+     * @param resultOffset offset of the result in its array
+     */
+    public void linearCombination(final double a1, final double[] c1, final int offset1,
+                                  final double a2, final double[] c2, final int offset2,
+                                  final double a3, final double[] c3, final int offset3,
+                                  final double[] result, final int resultOffset) {
+        for (int i = 0; i < getSize(); ++i) {
+            result[resultOffset + i] =
+                    MathArrays.linearCombination(a1, c1[offset1 + i],
+                                                 a2, c2[offset2 + i],
+                                                 a3, c3[offset3 + i]);
+        }
+    }
+
+    /** Compute linear combination.
+     * The derivative structure built will be a1 * ds1 + a2 * ds2 + a3 * ds3 + a4 * ds4
+     * @param a1 first scale factor
+     * @param c1 first base (unscaled) component
+     * @param offset1 offset of first operand in its array
+     * @param a2 second scale factor
+     * @param c2 second base (unscaled) component
+     * @param offset2 offset of second operand in its array
+     * @param a3 third scale factor
+     * @param c3 third base (unscaled) component
+     * @param offset3 offset of third operand in its array
+     * @param a4 fourth scale factor
+     * @param c4 fourth base (unscaled) component
+     * @param offset4 offset of fourth operand in its array
+     * @param result array where result must be stored (it may be
+     * one of the input arrays)
+     * @param resultOffset offset of the result in its array
+     */
+    public void linearCombination(final double a1, final double[] c1, final int offset1,
+                                  final double a2, final double[] c2, final int offset2,
+                                  final double a3, final double[] c3, final int offset3,
+                                  final double a4, final double[] c4, final int offset4,
+                                  final double[] result, final int resultOffset) {
+        for (int i = 0; i < getSize(); ++i) {
+            result[resultOffset + i] =
+                    MathArrays.linearCombination(a1, c1[offset1 + i],
+                                                 a2, c2[offset2 + i],
+                                                 a3, c3[offset3 + i],
+                                                 a4, c4[offset4 + i]);
+        }
+    }
+
+    /** Perform addition of two derivative structures.
+     * @param lhs array holding left hand side of addition
+     * @param lhsOffset offset of the left hand side in its array
+     * @param rhs array right hand side of addition
+     * @param rhsOffset offset of the right hand side in its array
+     * @param result array where result must be stored (it may be
+     * one of the input arrays)
+     * @param resultOffset offset of the result in its array
+     */
+    public void add(final double[] lhs, final int lhsOffset,
+                    final double[] rhs, final int rhsOffset,
+                    final double[] result, final int resultOffset) {
+        for (int i = 0; i < getSize(); ++i) {
+            result[resultOffset + i] = lhs[lhsOffset + i] + rhs[rhsOffset + i];
+        }
+    }
+    /** Perform subtraction of two derivative structures.
+     * @param lhs array holding left hand side of subtraction
+     * @param lhsOffset offset of the left hand side in its array
+     * @param rhs array right hand side of subtraction
+     * @param rhsOffset offset of the right hand side in its array
+     * @param result array where result must be stored (it may be
+     * one of the input arrays)
+     * @param resultOffset offset of the result in its array
+     */
+    public void subtract(final double[] lhs, final int lhsOffset,
+                         final double[] rhs, final int rhsOffset,
+                         final double[] result, final int resultOffset) {
+        for (int i = 0; i < getSize(); ++i) {
+            result[resultOffset + i] = lhs[lhsOffset + i] - rhs[rhsOffset + i];
+        }
+    }
+
+    /** Perform multiplication of two derivative structures.
+     * @param lhs array holding left hand side of multiplication
+     * @param lhsOffset offset of the left hand side in its array
+     * @param rhs array right hand side of multiplication
+     * @param rhsOffset offset of the right hand side in its array
+     * @param result array where result must be stored (for
+     * multiplication the result array <em>cannot</em> be one of
+     * the input arrays)
+     * @param resultOffset offset of the result in its array
+     */
+    public void multiply(final double[] lhs, final int lhsOffset,
+                         final double[] rhs, final int rhsOffset,
+                         final double[] result, final int resultOffset) {
+        for (int i = 0; i < multIndirection.length; ++i) {
+            final int[][] mappingI = multIndirection[i];
+            double r = 0;
+            for (int j = 0; j < mappingI.length; ++j) {
+                r += mappingI[j][0] *
+                     lhs[lhsOffset + mappingI[j][1]] *
+                     rhs[rhsOffset + mappingI[j][2]];
+            }
+            result[resultOffset + i] = r;
+        }
+    }
+
+    /** Perform division of two derivative structures.
+     * @param lhs array holding left hand side of division
+     * @param lhsOffset offset of the left hand side in its array
+     * @param rhs array right hand side of division
+     * @param rhsOffset offset of the right hand side in its array
+     * @param result array where result must be stored (for
+     * division the result array <em>cannot</em> be one of
+     * the input arrays)
+     * @param resultOffset offset of the result in its array
+     */
+    public void divide(final double[] lhs, final int lhsOffset,
+                       final double[] rhs, final int rhsOffset,
+                       final double[] result, final int resultOffset) {
+        final double[] reciprocal = new double[getSize()];
+        pow(rhs, lhsOffset, -1, reciprocal, 0);
+        multiply(lhs, lhsOffset, reciprocal, 0, result, resultOffset);
+    }
+
+    /** Perform remainder of two derivative structures.
+     * @param lhs array holding left hand side of remainder
+     * @param lhsOffset offset of the left hand side in its array
+     * @param rhs array right hand side of remainder
+     * @param rhsOffset offset of the right hand side in its array
+     * @param result array where result must be stored (it may be
+     * one of the input arrays)
+     * @param resultOffset offset of the result in its array
+     */
+    public void remainder(final double[] lhs, final int lhsOffset,
+                          final double[] rhs, final int rhsOffset,
+                          final double[] result, final int resultOffset) {
+
+        // compute k such that lhs % rhs = lhs - k rhs
+        final double rem = FastMath.IEEEremainder(lhs[lhsOffset], rhs[rhsOffset]);
+        final double k   = FastMath.rint((lhs[lhsOffset] - rem) / rhs[rhsOffset]);
+
+        // set up value
+        result[resultOffset] = rem;
+
+        // set up partial derivatives
+        for (int i = 1; i < getSize(); ++i) {
+            result[resultOffset + i] = lhs[lhsOffset + i] - k * rhs[rhsOffset + i];
+        }
+
+    }
+
+    /** Compute power of a double to a derivative structure.
+     * @param a number to exponentiate
+     * @param operand array holding the power
+     * @param operandOffset offset of the power in its array
+     * @param result array where result must be stored (for
+     * power the result array <em>cannot</em> be the input
+     * array)
+     * @param resultOffset offset of the result in its array
+     * @since 3.3
+     */
+    public void pow(final double a,
+                    final double[] operand, final int operandOffset,
+                    final double[] result, final int resultOffset) {
+
+        // create the function value and derivatives
+        // [a^x, ln(a) a^x, ln(a)^2 a^x,, ln(a)^3 a^x, ... ]
+        final double[] function = new double[1 + order];
+        if (a == 0) {
+            if (operand[operandOffset] == 0) {
+                function[0] = 1;
+                double infinity = Double.POSITIVE_INFINITY;
+                for (int i = 1; i < function.length; ++i) {
+                    infinity = -infinity;
+                    function[i] = infinity;
+                }
+            } else if (operand[operandOffset] < 0) {
+                Arrays.fill(function, Double.NaN);
+            }
+        } else {
+            function[0] = FastMath.pow(a, operand[operandOffset]);
+            final double lnA = FastMath.log(a);
+            for (int i = 1; i < function.length; ++i) {
+                function[i] = lnA * function[i - 1];
+            }
+        }
+
+
+        // apply function composition
+        compose(operand, operandOffset, function, result, resultOffset);
+
+    }
+
+    /** Compute power of a derivative structure.
+     * @param operand array holding the operand
+     * @param operandOffset offset of the operand in its array
+     * @param p power to apply
+     * @param result array where result must be stored (for
+     * power the result array <em>cannot</em> be the input
+     * array)
+     * @param resultOffset offset of the result in its array
+     */
+    public void pow(final double[] operand, final int operandOffset, final double p,
+                    final double[] result, final int resultOffset) {
+
+        // create the function value and derivatives
+        // [x^p, px^(p-1), p(p-1)x^(p-2), ... ]
+        double[] function = new double[1 + order];
+        double xk = FastMath.pow(operand[operandOffset], p - order);
+        for (int i = order; i > 0; --i) {
+            function[i] = xk;
+            xk *= operand[operandOffset];
+        }
+        function[0] = xk;
+        double coefficient = p;
+        for (int i = 1; i <= order; ++i) {
+            function[i] *= coefficient;
+            coefficient *= p - i;
+        }
+
+        // apply function composition
+        compose(operand, operandOffset, function, result, resultOffset);
+
+    }
+
+    /** Compute integer power of a derivative structure.
+     * @param operand array holding the operand
+     * @param operandOffset offset of the operand in its array
+     * @param n power to apply
+     * @param result array where result must be stored (for
+     * power the result array <em>cannot</em> be the input
+     * array)
+     * @param resultOffset offset of the result in its array
+     */
+    public void pow(final double[] operand, final int operandOffset, final int n,
+                    final double[] result, final int resultOffset) {
+
+        if (n == 0) {
+            // special case, x^0 = 1 for all x
+            result[resultOffset] = 1.0;
+            Arrays.fill(result, resultOffset + 1, resultOffset + getSize(), 0);
+            return;
+        }
+
+        // create the power function value and derivatives
+        // [x^n, nx^(n-1), n(n-1)x^(n-2), ... ]
+        double[] function = new double[1 + order];
+
+        if (n > 0) {
+            // strictly positive power
+            final int maxOrder = FastMath.min(order, n);
+            double xk = FastMath.pow(operand[operandOffset], n - maxOrder);
+            for (int i = maxOrder; i > 0; --i) {
+                function[i] = xk;
+                xk *= operand[operandOffset];
+            }
+            function[0] = xk;
+        } else {
+            // strictly negative power
+            final double inv = 1.0 / operand[operandOffset];
+            double xk = FastMath.pow(inv, -n);
+            for (int i = 0; i <= order; ++i) {
+                function[i] = xk;
+                xk *= inv;
+            }
+        }
+
+        double coefficient = n;
+        for (int i = 1; i <= order; ++i) {
+            function[i] *= coefficient;
+            coefficient *= n - i;
+        }
+
+        // apply function composition
+        compose(operand, operandOffset, function, result, resultOffset);
+
+    }
+
+    /** Compute power of a derivative structure.
+     * @param x array holding the base
+     * @param xOffset offset of the base in its array
+     * @param y array holding the exponent
+     * @param yOffset offset of the exponent in its array
+     * @param result array where result must be stored (for
+     * power the result array <em>cannot</em> be the input
+     * array)
+     * @param resultOffset offset of the result in its array
+     */
+    public void pow(final double[] x, final int xOffset,
+                    final double[] y, final int yOffset,
+                    final double[] result, final int resultOffset) {
+        final double[] logX = new double[getSize()];
+        log(x, xOffset, logX, 0);
+        final double[] yLogX = new double[getSize()];
+        multiply(logX, 0, y, yOffset, yLogX, 0);
+        exp(yLogX, 0, result, resultOffset);
+    }
+
+    /** Compute n<sup>th</sup> root of a derivative structure.
+     * @param operand array holding the operand
+     * @param operandOffset offset of the operand in its array
+     * @param n order of the root
+     * @param result array where result must be stored (for
+     * n<sup>th</sup> root the result array <em>cannot</em> be the input
+     * array)
+     * @param resultOffset offset of the result in its array
+     */
+    public void rootN(final double[] operand, final int operandOffset, final int n,
+                      final double[] result, final int resultOffset) {
+
+        // create the function value and derivatives
+        // [x^(1/n), (1/n)x^((1/n)-1), (1-n)/n^2x^((1/n)-2), ... ]
+        double[] function = new double[1 + order];
+        double xk;
+        if (n == 2) {
+            function[0] = FastMath.sqrt(operand[operandOffset]);
+            xk          = 0.5 / function[0];
+        } else if (n == 3) {
+            function[0] = FastMath.cbrt(operand[operandOffset]);
+            xk          = 1.0 / (3.0 * function[0] * function[0]);
+        } else {
+            function[0] = FastMath.pow(operand[operandOffset], 1.0 / n);
+            xk          = 1.0 / (n * FastMath.pow(function[0], n - 1));
+        }
+        final double nReciprocal = 1.0 / n;
+        final double xReciprocal = 1.0 / operand[operandOffset];
+        for (int i = 1; i <= order; ++i) {
+            function[i] = xk;
+            xk *= xReciprocal * (nReciprocal - i);
+        }
+
+        // apply function composition
+        compose(operand, operandOffset, function, result, resultOffset);
+
+    }
+
+    /** Compute exponential of a derivative structure.
+     * @param operand array holding the operand
+     * @param operandOffset offset of the operand in its array
+     * @param result array where result must be stored (for
+     * exponential the result array <em>cannot</em> be the input
+     * array)
+     * @param resultOffset offset of the result in its array
+     */
+    public void exp(final double[] operand, final int operandOffset,
+                    final double[] result, final int resultOffset) {
+
+        // create the function value and derivatives
+        double[] function = new double[1 + order];
+        Arrays.fill(function, FastMath.exp(operand[operandOffset]));
+
+        // apply function composition
+        compose(operand, operandOffset, function, result, resultOffset);
+
+    }
+
+    /** Compute exp(x) - 1 of a derivative structure.
+     * @param operand array holding the operand
+     * @param operandOffset offset of the operand in its array
+     * @param result array where result must be stored (for
+     * exponential the result array <em>cannot</em> be the input
+     * array)
+     * @param resultOffset offset of the result in its array
+     */
+    public void expm1(final double[] operand, final int operandOffset,
+                      final double[] result, final int resultOffset) {
+
+        // create the function value and derivatives
+        double[] function = new double[1 + order];
+        function[0] = FastMath.expm1(operand[operandOffset]);
+        Arrays.fill(function, 1, 1 + order, FastMath.exp(operand[operandOffset]));
+
+        // apply function composition
+        compose(operand, operandOffset, function, result, resultOffset);
+
+    }
+
+    /** Compute natural logarithm of a derivative structure.
+     * @param operand array holding the operand
+     * @param operandOffset offset of the operand in its array
+     * @param result array where result must be stored (for
+     * logarithm the result array <em>cannot</em> be the input
+     * array)
+     * @param resultOffset offset of the result in its array
+     */
+    public void log(final double[] operand, final int operandOffset,
+                    final double[] result, final int resultOffset) {
+
+        // create the function value and derivatives
+        double[] function = new double[1 + order];
+        function[0] = FastMath.log(operand[operandOffset]);
+        if (order > 0) {
+            double inv = 1.0 / operand[operandOffset];
+            double xk  = inv;
+            for (int i = 1; i <= order; ++i) {
+                function[i] = xk;
+                xk *= -i * inv;
+            }
+        }
+
+        // apply function composition
+        compose(operand, operandOffset, function, result, resultOffset);
+
+    }
+
+    /** Computes shifted logarithm of a derivative structure.
+     * @param operand array holding the operand
+     * @param operandOffset offset of the operand in its array
+     * @param result array where result must be stored (for
+     * shifted logarithm the result array <em>cannot</em> be the input array)
+     * @param resultOffset offset of the result in its array
+     */
+    public void log1p(final double[] operand, final int operandOffset,
+                      final double[] result, final int resultOffset) {
+
+        // create the function value and derivatives
+        double[] function = new double[1 + order];
+        function[0] = FastMath.log1p(operand[operandOffset]);
+        if (order > 0) {
+            double inv = 1.0 / (1.0 + operand[operandOffset]);
+            double xk  = inv;
+            for (int i = 1; i <= order; ++i) {
+                function[i] = xk;
+                xk *= -i * inv;
+            }
+        }
+
+        // apply function composition
+        compose(operand, operandOffset, function, result, resultOffset);
+
+    }
+
+    /** Computes base 10 logarithm of a derivative structure.
+     * @param operand array holding the operand
+     * @param operandOffset offset of the operand in its array
+     * @param result array where result must be stored (for
+     * base 10 logarithm the result array <em>cannot</em> be the input array)
+     * @param resultOffset offset of the result in its array
+     */
+    public void log10(final double[] operand, final int operandOffset,
+                      final double[] result, final int resultOffset) {
+
+        // create the function value and derivatives
+        double[] function = new double[1 + order];
+        function[0] = FastMath.log10(operand[operandOffset]);
+        if (order > 0) {
+            double inv = 1.0 / operand[operandOffset];
+            double xk  = inv / FastMath.log(10.0);
+            for (int i = 1; i <= order; ++i) {
+                function[i] = xk;
+                xk *= -i * inv;
+            }
+        }
+
+        // apply function composition
+        compose(operand, operandOffset, function, result, resultOffset);
+
+    }
+
+    /** Compute cosine of a derivative structure.
+     * @param operand array holding the operand
+     * @param operandOffset offset of the operand in its array
+     * @param result array where result must be stored (for
+     * cosine the result array <em>cannot</em> be the input
+     * array)
+     * @param resultOffset offset of the result in its array
+     */
+    public void cos(final double[] operand, final int operandOffset,
+                    final double[] result, final int resultOffset) {
+
+        // create the function value and derivatives
+        double[] function = new double[1 + order];
+        function[0] = FastMath.cos(operand[operandOffset]);
+        if (order > 0) {
+            function[1] = -FastMath.sin(operand[operandOffset]);
+            for (int i = 2; i <= order; ++i) {
+                function[i] = -function[i - 2];
+            }
+        }
+
+        // apply function composition
+        compose(operand, operandOffset, function, result, resultOffset);
+
+    }
+
+    /** Compute sine of a derivative structure.
+     * @param operand array holding the operand
+     * @param operandOffset offset of the operand in its array
+     * @param result array where result must be stored (for
+     * sine the result array <em>cannot</em> be the input
+     * array)
+     * @param resultOffset offset of the result in its array
+     */
+    public void sin(final double[] operand, final int operandOffset,
+                    final double[] result, final int resultOffset) {
+
+        // create the function value and derivatives
+        double[] function = new double[1 + order];
+        function[0] = FastMath.sin(operand[operandOffset]);
+        if (order > 0) {
+            function[1] = FastMath.cos(operand[operandOffset]);
+            for (int i = 2; i <= order; ++i) {
+                function[i] = -function[i - 2];
+            }
+        }
+
+        // apply function composition
+        compose(operand, operandOffset, function, result, resultOffset);
+
+    }
+
+    /** Compute tangent of a derivative structure.
+     * @param operand array holding the operand
+     * @param operandOffset offset of the operand in its array
+     * @param result array where result must be stored (for
+     * tangent the result array <em>cannot</em> be the input
+     * array)
+     * @param resultOffset offset of the result in its array
+     */
+    public void tan(final double[] operand, final int operandOffset,
+                    final double[] result, final int resultOffset) {
+
+        // create the function value and derivatives
+        final double[] function = new double[1 + order];
+        final double t = FastMath.tan(operand[operandOffset]);
+        function[0] = t;
+
+        if (order > 0) {
+
+            // the nth order derivative of tan has the form:
+            // dn(tan(x)/dxn = P_n(tan(x))
+            // where P_n(t) is a degree n+1 polynomial with same parity as n+1
+            // P_0(t) = t, P_1(t) = 1 + t^2, P_2(t) = 2 t (1 + t^2) ...
+            // the general recurrence relation for P_n is:
+            // P_n(x) = (1+t^2) P_(n-1)'(t)
+            // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array
+            final double[] p = new double[order + 2];
+            p[1] = 1;
+            final double t2 = t * t;
+            for (int n = 1; n <= order; ++n) {
+
+                // update and evaluate polynomial P_n(t)
+                double v = 0;
+                p[n + 1] = n * p[n];
+                for (int k = n + 1; k >= 0; k -= 2) {
+                    v = v * t2 + p[k];
+                    if (k > 2) {
+                        p[k - 2] = (k - 1) * p[k - 1] + (k - 3) * p[k - 3];
+                    } else if (k == 2) {
+                        p[0] = p[1];
+                    }
+                }
+                if ((n & 0x1) == 0) {
+                    v *= t;
+                }
+
+                function[n] = v;
+
+            }
+        }
+
+        // apply function composition
+        compose(operand, operandOffset, function, result, resultOffset);
+
+    }
+
+    /** Compute arc cosine of a derivative structure.
+     * @param operand array holding the operand
+     * @param operandOffset offset of the operand in its array
+     * @param result array where result must be stored (for
+     * arc cosine the result array <em>cannot</em> be the input
+     * array)
+     * @param resultOffset offset of the result in its array
+     */
+    public void acos(final double[] operand, final int operandOffset,
+                    final double[] result, final int resultOffset) {
+
+        // create the function value and derivatives
+        double[] function = new double[1 + order];
+        final double x = operand[operandOffset];
+        function[0] = FastMath.acos(x);
+        if (order > 0) {
+            // the nth order derivative of acos has the form:
+            // dn(acos(x)/dxn = P_n(x) / [1 - x^2]^((2n-1)/2)
+            // where P_n(x) is a degree n-1 polynomial with same parity as n-1
+            // P_1(x) = -1, P_2(x) = -x, P_3(x) = -2x^2 - 1 ...
+            // the general recurrence relation for P_n is:
+            // P_n(x) = (1-x^2) P_(n-1)'(x) + (2n-3) x P_(n-1)(x)
+            // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array
+            final double[] p = new double[order];
+            p[0] = -1;
+            final double x2    = x * x;
+            final double f     = 1.0 / (1 - x2);
+            double coeff = FastMath.sqrt(f);
+            function[1] = coeff * p[0];
+            for (int n = 2; n <= order; ++n) {
+
+                // update and evaluate polynomial P_n(x)
+                double v = 0;
+                p[n - 1] = (n - 1) * p[n - 2];
+                for (int k = n - 1; k >= 0; k -= 2) {
+                    v = v * x2 + p[k];
+                    if (k > 2) {
+                        p[k - 2] = (k - 1) * p[k - 1] + (2 * n - k) * p[k - 3];
+                    } else if (k == 2) {
+                        p[0] = p[1];
+                    }
+                }
+                if ((n & 0x1) == 0) {
+                    v *= x;
+                }
+
+                coeff *= f;
+                function[n] = coeff * v;
+
+            }
+        }
+
+        // apply function composition
+        compose(operand, operandOffset, function, result, resultOffset);
+
+    }
+
+    /** Compute arc sine of a derivative structure.
+     * @param operand array holding the operand
+     * @param operandOffset offset of the operand in its array
+     * @param result array where result must be stored (for
+     * arc sine the result array <em>cannot</em> be the input
+     * array)
+     * @param resultOffset offset of the result in its array
+     */
+    public void asin(final double[] operand, final int operandOffset,
+                    final double[] result, final int resultOffset) {
+
+        // create the function value and derivatives
+        double[] function = new double[1 + order];
+        final double x = operand[operandOffset];
+        function[0] = FastMath.asin(x);
+        if (order > 0) {
+            // the nth order derivative of asin has the form:
+            // dn(asin(x)/dxn = P_n(x) / [1 - x^2]^((2n-1)/2)
+            // where P_n(x) is a degree n-1 polynomial with same parity as n-1
+            // P_1(x) = 1, P_2(x) = x, P_3(x) = 2x^2 + 1 ...
+            // the general recurrence relation for P_n is:
+            // P_n(x) = (1-x^2) P_(n-1)'(x) + (2n-3) x P_(n-1)(x)
+            // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array
+            final double[] p = new double[order];
+            p[0] = 1;
+            final double x2    = x * x;
+            final double f     = 1.0 / (1 - x2);
+            double coeff = FastMath.sqrt(f);
+            function[1] = coeff * p[0];
+            for (int n = 2; n <= order; ++n) {
+
+                // update and evaluate polynomial P_n(x)
+                double v = 0;
+                p[n - 1] = (n - 1) * p[n - 2];
+                for (int k = n - 1; k >= 0; k -= 2) {
+                    v = v * x2 + p[k];
+                    if (k > 2) {
+                        p[k - 2] = (k - 1) * p[k - 1] + (2 * n - k) * p[k - 3];
+                    } else if (k == 2) {
+                        p[0] = p[1];
+                    }
+                }
+                if ((n & 0x1) == 0) {
+                    v *= x;
+                }
+
+                coeff *= f;
+                function[n] = coeff * v;
+
+            }
+        }
+
+        // apply function composition
+        compose(operand, operandOffset, function, result, resultOffset);
+
+    }
+
+    /** Compute arc tangent of a derivative structure.
+     * @param operand array holding the operand
+     * @param operandOffset offset of the operand in its array
+     * @param result array where result must be stored (for
+     * arc tangent the result array <em>cannot</em> be the input
+     * array)
+     * @param resultOffset offset of the result in its array
+     */
+    public void atan(final double[] operand, final int operandOffset,
+                     final double[] result, final int resultOffset) {
+
+        // create the function value and derivatives
+        double[] function = new double[1 + order];
+        final double x = operand[operandOffset];
+        function[0] = FastMath.atan(x);
+        if (order > 0) {
+            // the nth order derivative of atan has the form:
+            // dn(atan(x)/dxn = Q_n(x) / (1 + x^2)^n
+            // where Q_n(x) is a degree n-1 polynomial with same parity as n-1
+            // Q_1(x) = 1, Q_2(x) = -2x, Q_3(x) = 6x^2 - 2 ...
+            // the general recurrence relation for Q_n is:
+            // Q_n(x) = (1+x^2) Q_(n-1)'(x) - 2(n-1) x Q_(n-1)(x)
+            // as per polynomial parity, we can store coefficients of both Q_(n-1) and Q_n in the same array
+            final double[] q = new double[order];
+            q[0] = 1;
+            final double x2    = x * x;
+            final double f     = 1.0 / (1 + x2);
+            double coeff = f;
+            function[1] = coeff * q[0];
+            for (int n = 2; n <= order; ++n) {
+
+                // update and evaluate polynomial Q_n(x)
+                double v = 0;
+                q[n - 1] = -n * q[n - 2];
+                for (int k = n - 1; k >= 0; k -= 2) {
+                    v = v * x2 + q[k];
+                    if (k > 2) {
+                        q[k - 2] = (k - 1) * q[k - 1] + (k - 1 - 2 * n) * q[k - 3];
+                    } else if (k == 2) {
+                        q[0] = q[1];
+                    }
+                }
+                if ((n & 0x1) == 0) {
+                    v *= x;
+                }
+
+                coeff *= f;
+                function[n] = coeff * v;
+
+            }
+        }
+
+        // apply function composition
+        compose(operand, operandOffset, function, result, resultOffset);
+
+    }
+
+    /** Compute two arguments arc tangent of a derivative structure.
+     * @param y array holding the first operand
+     * @param yOffset offset of the first operand in its array
+     * @param x array holding the second operand
+     * @param xOffset offset of the second operand in its array
+     * @param result array where result must be stored (for
+     * two arguments arc tangent the result array <em>cannot</em>
+     * be the input array)
+     * @param resultOffset offset of the result in its array
+     */
+    public void atan2(final double[] y, final int yOffset,
+                      final double[] x, final int xOffset,
+                      final double[] result, final int resultOffset) {
+
+        // compute r = sqrt(x^2+y^2)
+        double[] tmp1 = new double[getSize()];
+        multiply(x, xOffset, x, xOffset, tmp1, 0);      // x^2
+        double[] tmp2 = new double[getSize()];
+        multiply(y, yOffset, y, yOffset, tmp2, 0);      // y^2
+        add(tmp1, 0, tmp2, 0, tmp2, 0);                 // x^2 + y^2
+        rootN(tmp2, 0, 2, tmp1, 0);                     // r = sqrt(x^2 + y^2)
+
+        if (x[xOffset] >= 0) {
+
+            // compute atan2(y, x) = 2 atan(y / (r + x))
+            add(tmp1, 0, x, xOffset, tmp2, 0);          // r + x
+            divide(y, yOffset, tmp2, 0, tmp1, 0);       // y /(r + x)
+            atan(tmp1, 0, tmp2, 0);                     // atan(y / (r + x))
+            for (int i = 0; i < tmp2.length; ++i) {
+                result[resultOffset + i] = 2 * tmp2[i]; // 2 * atan(y / (r + x))
+            }
+
+        } else {
+
+            // compute atan2(y, x) = +/- pi - 2 atan(y / (r - x))
+            subtract(tmp1, 0, x, xOffset, tmp2, 0);     // r - x
+            divide(y, yOffset, tmp2, 0, tmp1, 0);       // y /(r - x)
+            atan(tmp1, 0, tmp2, 0);                     // atan(y / (r - x))
+            result[resultOffset] =
+                    ((tmp2[0] <= 0) ? -FastMath.PI : FastMath.PI) - 2 * tmp2[0]; // +/-pi - 2 * atan(y / (r - x))
+            for (int i = 1; i < tmp2.length; ++i) {
+                result[resultOffset + i] = -2 * tmp2[i]; // +/-pi - 2 * atan(y / (r - x))
+            }
+
+        }
+
+        // fix value to take special cases (+0/+0, +0/-0, -0/+0, -0/-0, +/-infinity) correctly
+        result[resultOffset] = FastMath.atan2(y[yOffset], x[xOffset]);
+
+    }
+
+    /** Compute hyperbolic cosine of a derivative structure.
+     * @param operand array holding the operand
+     * @param operandOffset offset of the operand in its array
+     * @param result array where result must be stored (for
+     * hyperbolic cosine the result array <em>cannot</em> be the input
+     * array)
+     * @param resultOffset offset of the result in its array
+     */
+    public void cosh(final double[] operand, final int operandOffset,
+                     final double[] result, final int resultOffset) {
+
+        // create the function value and derivatives
+        double[] function = new double[1 + order];
+        function[0] = FastMath.cosh(operand[operandOffset]);
+        if (order > 0) {
+            function[1] = FastMath.sinh(operand[operandOffset]);
+            for (int i = 2; i <= order; ++i) {
+                function[i] = function[i - 2];
+            }
+        }
+
+        // apply function composition
+        compose(operand, operandOffset, function, result, resultOffset);
+
+    }
+
+    /** Compute hyperbolic sine of a derivative structure.
+     * @param operand array holding the operand
+     * @param operandOffset offset of the operand in its array
+     * @param result array where result must be stored (for
+     * hyperbolic sine the result array <em>cannot</em> be the input
+     * array)
+     * @param resultOffset offset of the result in its array
+     */
+    public void sinh(final double[] operand, final int operandOffset,
+                     final double[] result, final int resultOffset) {
+
+        // create the function value and derivatives
+        double[] function = new double[1 + order];
+        function[0] = FastMath.sinh(operand[operandOffset]);
+        if (order > 0) {
+            function[1] = FastMath.cosh(operand[operandOffset]);
+            for (int i = 2; i <= order; ++i) {
+                function[i] = function[i - 2];
+            }
+        }
+
+        // apply function composition
+        compose(operand, operandOffset, function, result, resultOffset);
+
+    }
+
+    /** Compute hyperbolic tangent of a derivative structure.
+     * @param operand array holding the operand
+     * @param operandOffset offset of the operand in its array
+     * @param result array where result must be stored (for
+     * hyperbolic tangent the result array <em>cannot</em> be the input
+     * array)
+     * @param resultOffset offset of the result in its array
+     */
+    public void tanh(final double[] operand, final int operandOffset,
+                     final double[] result, final int resultOffset) {
+
+        // create the function value and derivatives
+        final double[] function = new double[1 + order];
+        final double t = FastMath.tanh(operand[operandOffset]);
+        function[0] = t;
+
+        if (order > 0) {
+
+            // the nth order derivative of tanh has the form:
+            // dn(tanh(x)/dxn = P_n(tanh(x))
+            // where P_n(t) is a degree n+1 polynomial with same parity as n+1
+            // P_0(t) = t, P_1(t) = 1 - t^2, P_2(t) = -2 t (1 - t^2) ...
+            // the general recurrence relation for P_n is:
+            // P_n(x) = (1-t^2) P_(n-1)'(t)
+            // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array
+            final double[] p = new double[order + 2];
+            p[1] = 1;
+            final double t2 = t * t;
+            for (int n = 1; n <= order; ++n) {
+
+                // update and evaluate polynomial P_n(t)
+                double v = 0;
+                p[n + 1] = -n * p[n];
+                for (int k = n + 1; k >= 0; k -= 2) {
+                    v = v * t2 + p[k];
+                    if (k > 2) {
+                        p[k - 2] = (k - 1) * p[k - 1] - (k - 3) * p[k - 3];
+                    } else if (k == 2) {
+                        p[0] = p[1];
+                    }
+                }
+                if ((n & 0x1) == 0) {
+                    v *= t;
+                }
+
+                function[n] = v;
+
+            }
+        }
+
+        // apply function composition
+        compose(operand, operandOffset, function, result, resultOffset);
+
+    }
+
+    /** Compute inverse hyperbolic cosine of a derivative structure.
+     * @param operand array holding the operand
+     * @param operandOffset offset of the operand in its array
+     * @param result array where result must be stored (for
+     * inverse hyperbolic cosine the result array <em>cannot</em> be the input
+     * array)
+     * @param resultOffset offset of the result in its array
+     */
+    public void acosh(final double[] operand, final int operandOffset,
+                     final double[] result, final int resultOffset) {
+
+        // create the function value and derivatives
+        double[] function = new double[1 + order];
+        final double x = operand[operandOffset];
+        function[0] = FastMath.acosh(x);
+        if (order > 0) {
+            // the nth order derivative of acosh has the form:
+            // dn(acosh(x)/dxn = P_n(x) / [x^2 - 1]^((2n-1)/2)
+            // where P_n(x) is a degree n-1 polynomial with same parity as n-1
+            // P_1(x) = 1, P_2(x) = -x, P_3(x) = 2x^2 + 1 ...
+            // the general recurrence relation for P_n is:
+            // P_n(x) = (x^2-1) P_(n-1)'(x) - (2n-3) x P_(n-1)(x)
+            // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array
+            final double[] p = new double[order];
+            p[0] = 1;
+            final double x2  = x * x;
+            final double f   = 1.0 / (x2 - 1);
+            double coeff = FastMath.sqrt(f);
+            function[1] = coeff * p[0];
+            for (int n = 2; n <= order; ++n) {
+
+                // update and evaluate polynomial P_n(x)
+                double v = 0;
+                p[n - 1] = (1 - n) * p[n - 2];
+                for (int k = n - 1; k >= 0; k -= 2) {
+                    v = v * x2 + p[k];
+                    if (k > 2) {
+                        p[k - 2] = (1 - k) * p[k - 1] + (k - 2 * n) * p[k - 3];
+                    } else if (k == 2) {
+                        p[0] = -p[1];
+                    }
+                }
+                if ((n & 0x1) == 0) {
+                    v *= x;
+                }
+
+                coeff *= f;
+                function[n] = coeff * v;
+
+            }
+        }
+
+        // apply function composition
+        compose(operand, operandOffset, function, result, resultOffset);
+
+    }
+
+    /** Compute inverse hyperbolic sine of a derivative structure.
+     * @param operand array holding the operand
+     * @param operandOffset offset of the operand in its array
+     * @param result array where result must be stored (for
+     * inverse hyperbolic sine the result array <em>cannot</em> be the input
+     * array)
+     * @param resultOffset offset of the result in its array
+     */
+    public void asinh(final double[] operand, final int operandOffset,
+                     final double[] result, final int resultOffset) {
+
+        // create the function value and derivatives
+        double[] function = new double[1 + order];
+        final double x = operand[operandOffset];
+        function[0] = FastMath.asinh(x);
+        if (order > 0) {
+            // the nth order derivative of asinh has the form:
+            // dn(asinh(x)/dxn = P_n(x) / [x^2 + 1]^((2n-1)/2)
+            // where P_n(x) is a degree n-1 polynomial with same parity as n-1
+            // P_1(x) = 1, P_2(x) = -x, P_3(x) = 2x^2 - 1 ...
+            // the general recurrence relation for P_n is:
+            // P_n(x) = (x^2+1) P_(n-1)'(x) - (2n-3) x P_(n-1)(x)
+            // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array
+            final double[] p = new double[order];
+            p[0] = 1;
+            final double x2    = x * x;
+            final double f     = 1.0 / (1 + x2);
+            double coeff = FastMath.sqrt(f);
+            function[1] = coeff * p[0];
+            for (int n = 2; n <= order; ++n) {
+
+                // update and evaluate polynomial P_n(x)
+                double v = 0;
+                p[n - 1] = (1 - n) * p[n - 2];
+                for (int k = n - 1; k >= 0; k -= 2) {
+                    v = v * x2 + p[k];
+                    if (k > 2) {
+                        p[k - 2] = (k - 1) * p[k - 1] + (k - 2 * n) * p[k - 3];
+                    } else if (k == 2) {
+                        p[0] = p[1];
+                    }
+                }
+                if ((n & 0x1) == 0) {
+                    v *= x;
+                }
+
+                coeff *= f;
+                function[n] = coeff * v;
+
+            }
+        }
+
+        // apply function composition
+        compose(operand, operandOffset, function, result, resultOffset);
+
+    }
+
+    /** Compute inverse hyperbolic tangent of a derivative structure.
+     * @param operand array holding the operand
+     * @param operandOffset offset of the operand in its array
+     * @param result array where result must be stored (for
+     * inverse hyperbolic tangent the result array <em>cannot</em> be the input
+     * array)
+     * @param resultOffset offset of the result in its array
+     */
+    public void atanh(final double[] operand, final int operandOffset,
+                      final double[] result, final int resultOffset) {
+
+        // create the function value and derivatives
+        double[] function = new double[1 + order];
+        final double x = operand[operandOffset];
+        function[0] = FastMath.atanh(x);
+        if (order > 0) {
+            // the nth order derivative of atanh has the form:
+            // dn(atanh(x)/dxn = Q_n(x) / (1 - x^2)^n
+            // where Q_n(x) is a degree n-1 polynomial with same parity as n-1
+            // Q_1(x) = 1, Q_2(x) = 2x, Q_3(x) = 6x^2 + 2 ...
+            // the general recurrence relation for Q_n is:
+            // Q_n(x) = (1-x^2) Q_(n-1)'(x) + 2(n-1) x Q_(n-1)(x)
+            // as per polynomial parity, we can store coefficients of both Q_(n-1) and Q_n in the same array
+            final double[] q = new double[order];
+            q[0] = 1;
+            final double x2 = x * x;
+            final double f  = 1.0 / (1 - x2);
+            double coeff = f;
+            function[1] = coeff * q[0];
+            for (int n = 2; n <= order; ++n) {
+
+                // update and evaluate polynomial Q_n(x)
+                double v = 0;
+                q[n - 1] = n * q[n - 2];
+                for (int k = n - 1; k >= 0; k -= 2) {
+                    v = v * x2 + q[k];
+                    if (k > 2) {
+                        q[k - 2] = (k - 1) * q[k - 1] + (2 * n - k + 1) * q[k - 3];
+                    } else if (k == 2) {
+                        q[0] = q[1];
+                    }
+                }
+                if ((n & 0x1) == 0) {
+                    v *= x;
+                }
+
+                coeff *= f;
+                function[n] = coeff * v;
+
+            }
+        }
+
+        // apply function composition
+        compose(operand, operandOffset, function, result, resultOffset);
+
+    }
+
+    /** Compute composition of a derivative structure by a function.
+     * @param operand array holding the operand
+     * @param operandOffset offset of the operand in its array
+     * @param f array of value and derivatives of the function at
+     * the current point (i.e. at {@code operand[operandOffset]}).
+     * @param result array where result must be stored (for
+     * composition the result array <em>cannot</em> be the input
+     * array)
+     * @param resultOffset offset of the result in its array
+     */
+    public void compose(final double[] operand, final int operandOffset, final double[] f,
+                        final double[] result, final int resultOffset) {
+        for (int i = 0; i < compIndirection.length; ++i) {
+            final int[][] mappingI = compIndirection[i];
+            double r = 0;
+            for (int j = 0; j < mappingI.length; ++j) {
+                final int[] mappingIJ = mappingI[j];
+                double product = mappingIJ[0] * f[mappingIJ[1]];
+                for (int k = 2; k < mappingIJ.length; ++k) {
+                    product *= operand[operandOffset + mappingIJ[k]];
+                }
+                r += product;
+            }
+            result[resultOffset + i] = r;
+        }
+    }
+
+    /** Evaluate Taylor expansion of a derivative structure.
+     * @param ds array holding the derivative structure
+     * @param dsOffset offset of the derivative structure in its array
+     * @param delta parameters offsets (&Delta;x, &Delta;y, ...)
+     * @return value of the Taylor expansion at x + &Delta;x, y + &Delta;y, ...
+     * @throws MathArithmeticException if factorials becomes too large
+     */
+    public double taylor(final double[] ds, final int dsOffset, final double ... delta)
+       throws MathArithmeticException {
+        double value = 0;
+        for (int i = getSize() - 1; i >= 0; --i) {
+            final int[] orders = getPartialDerivativeOrders(i);
+            double term = ds[dsOffset + i];
+            for (int k = 0; k < orders.length; ++k) {
+                if (orders[k] > 0) {
+                    try {
+                        term *= FastMath.pow(delta[k], orders[k]) /
+                        CombinatoricsUtils.factorial(orders[k]);
+                    } catch (NotPositiveException e) {
+                        // this cannot happen
+                        throw new MathInternalError(e);
+                    }
+                }
+            }
+            value += term;
+        }
+        return value;
+    }
+
+    /** Check rules set compatibility.
+     * @param compiler other compiler to check against instance
+     * @exception DimensionMismatchException if number of free parameters or orders are inconsistent
+     */
+    public void checkCompatibility(final DSCompiler compiler)
+            throws DimensionMismatchException {
+        if (parameters != compiler.parameters) {
+            throw new DimensionMismatchException(parameters, compiler.parameters);
+        }
+        if (order != compiler.order) {
+            throw new DimensionMismatchException(order, compiler.order);
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/differentiation/DerivativeStructure.java b/src/main/java/org/apache/commons/math3/analysis/differentiation/DerivativeStructure.java
new file mode 100644
index 0000000..da976fc
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/differentiation/DerivativeStructure.java
@@ -0,0 +1,1195 @@
+/*
+ * 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.math3.analysis.differentiation;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.FieldElement;
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.util.MathUtils;
+
+/** Class representing both the value and the differentials of a function.
+ * <p>This class is the workhorse of the differentiation package.</p>
+ * <p>This class is an implementation of the extension to Rall's
+ * numbers described in Dan Kalman's paper <a
+ * href="http://www1.american.edu/cas/mathstat/People/kalman/pdffiles/mmgautodiff.pdf">Doubly
+ * Recursive Multivariate Automatic Differentiation</a>, Mathematics Magazine, vol. 75,
+ * no. 3, June 2002. Rall's numbers are an extension to the real numbers used
+ * throughout mathematical expressions; they hold the derivative together with the
+ * value of a function. Dan Kalman's derivative structures hold all partial derivatives
+ * up to any specified order, with respect to any number of free parameters. Rall's
+ * numbers therefore can be seen as derivative structures for order one derivative and
+ * one free parameter, and real numbers can be seen as derivative structures with zero
+ * order derivative and no free parameters.</p>
+ * <p>{@link DerivativeStructure} instances can be used directly thanks to
+ * the arithmetic operators to the mathematical functions provided as
+ * methods by this class (+, -, *, /, %, sin, cos ...).</p>
+ * <p>Implementing complex expressions by hand using these classes is
+ * a tedious and error-prone task but has the advantage of having no limitation
+ * on the derivation order despite no requiring users to compute the derivatives by
+ * themselves. Implementing complex expression can also be done by developing computation
+ * code using standard primitive double values and to use {@link
+ * UnivariateFunctionDifferentiator differentiators} to create the {@link
+ * DerivativeStructure}-based instances. This method is simpler but may be limited in
+ * the accuracy and derivation orders and may be computationally intensive (this is
+ * typically the case for {@link FiniteDifferencesDifferentiator finite differences
+ * differentiator}.</p>
+ * <p>Instances of this class are guaranteed to be immutable.</p>
+ * @see DSCompiler
+ * @since 3.1
+ */
+public class DerivativeStructure implements RealFieldElement<DerivativeStructure>, Serializable {
+
+    /** Serializable UID. */
+    private static final long serialVersionUID = 20120730L;
+
+    /** Compiler for the current dimensions. */
+    private transient DSCompiler compiler;
+
+    /** Combined array holding all values. */
+    private final double[] data;
+
+    /** Build an instance with all values and derivatives set to 0.
+     * @param compiler compiler to use for computation
+     */
+    private DerivativeStructure(final DSCompiler compiler) {
+        this.compiler = compiler;
+        this.data     = new double[compiler.getSize()];
+    }
+
+    /** Build an instance with all values and derivatives set to 0.
+     * @param parameters number of free parameters
+     * @param order derivation order
+     * @throws NumberIsTooLargeException if order is too large
+     */
+    public DerivativeStructure(final int parameters, final int order)
+        throws NumberIsTooLargeException {
+        this(DSCompiler.getCompiler(parameters, order));
+    }
+
+    /** Build an instance representing a constant value.
+     * @param parameters number of free parameters
+     * @param order derivation order
+     * @param value value of the constant
+     * @throws NumberIsTooLargeException if order is too large
+     * @see #DerivativeStructure(int, int, int, double)
+     */
+    public DerivativeStructure(final int parameters, final int order, final double value)
+        throws NumberIsTooLargeException {
+        this(parameters, order);
+        this.data[0] = value;
+    }
+
+    /** Build an instance representing a variable.
+     * <p>Instances built using this constructor are considered
+     * to be the free variables with respect to which differentials
+     * are computed. As such, their differential with respect to
+     * themselves is +1.</p>
+     * @param parameters number of free parameters
+     * @param order derivation order
+     * @param index index of the variable (from 0 to {@code parameters - 1})
+     * @param value value of the variable
+     * @exception NumberIsTooLargeException if {@code index >= parameters}.
+     * @see #DerivativeStructure(int, int, double)
+     */
+    public DerivativeStructure(final int parameters, final int order,
+                               final int index, final double value)
+        throws NumberIsTooLargeException {
+        this(parameters, order, value);
+
+        if (index >= parameters) {
+            throw new NumberIsTooLargeException(index, parameters, false);
+        }
+
+        if (order > 0) {
+            // the derivative of the variable with respect to itself is 1.
+            data[DSCompiler.getCompiler(index, order).getSize()] = 1.0;
+        }
+
+    }
+
+    /** Linear combination constructor.
+     * The derivative structure built will be a1 * ds1 + a2 * ds2
+     * @param a1 first scale factor
+     * @param ds1 first base (unscaled) derivative structure
+     * @param a2 second scale factor
+     * @param ds2 second base (unscaled) derivative structure
+     * @exception DimensionMismatchException if number of free parameters or orders are inconsistent
+     */
+    public DerivativeStructure(final double a1, final DerivativeStructure ds1,
+                               final double a2, final DerivativeStructure ds2)
+        throws DimensionMismatchException {
+        this(ds1.compiler);
+        compiler.checkCompatibility(ds2.compiler);
+        compiler.linearCombination(a1, ds1.data, 0, a2, ds2.data, 0, data, 0);
+    }
+
+    /** Linear combination constructor.
+     * The derivative structure built will be a1 * ds1 + a2 * ds2 + a3 * ds3
+     * @param a1 first scale factor
+     * @param ds1 first base (unscaled) derivative structure
+     * @param a2 second scale factor
+     * @param ds2 second base (unscaled) derivative structure
+     * @param a3 third scale factor
+     * @param ds3 third base (unscaled) derivative structure
+     * @exception DimensionMismatchException if number of free parameters or orders are inconsistent
+     */
+    public DerivativeStructure(final double a1, final DerivativeStructure ds1,
+                               final double a2, final DerivativeStructure ds2,
+                               final double a3, final DerivativeStructure ds3)
+        throws DimensionMismatchException {
+        this(ds1.compiler);
+        compiler.checkCompatibility(ds2.compiler);
+        compiler.checkCompatibility(ds3.compiler);
+        compiler.linearCombination(a1, ds1.data, 0, a2, ds2.data, 0, a3, ds3.data, 0, data, 0);
+    }
+
+    /** Linear combination constructor.
+     * The derivative structure built will be a1 * ds1 + a2 * ds2 + a3 * ds3 + a4 * ds4
+     * @param a1 first scale factor
+     * @param ds1 first base (unscaled) derivative structure
+     * @param a2 second scale factor
+     * @param ds2 second base (unscaled) derivative structure
+     * @param a3 third scale factor
+     * @param ds3 third base (unscaled) derivative structure
+     * @param a4 fourth scale factor
+     * @param ds4 fourth base (unscaled) derivative structure
+     * @exception DimensionMismatchException if number of free parameters or orders are inconsistent
+     */
+    public DerivativeStructure(final double a1, final DerivativeStructure ds1,
+                               final double a2, final DerivativeStructure ds2,
+                               final double a3, final DerivativeStructure ds3,
+                               final double a4, final DerivativeStructure ds4)
+        throws DimensionMismatchException {
+        this(ds1.compiler);
+        compiler.checkCompatibility(ds2.compiler);
+        compiler.checkCompatibility(ds3.compiler);
+        compiler.checkCompatibility(ds4.compiler);
+        compiler.linearCombination(a1, ds1.data, 0, a2, ds2.data, 0,
+                                   a3, ds3.data, 0, a4, ds4.data, 0,
+                                   data, 0);
+    }
+
+    /** Build an instance from all its derivatives.
+     * @param parameters number of free parameters
+     * @param order derivation order
+     * @param derivatives derivatives sorted according to
+     * {@link DSCompiler#getPartialDerivativeIndex(int...)}
+     * @exception DimensionMismatchException if derivatives array does not match the
+     * {@link DSCompiler#getSize() size} expected by the compiler
+     * @throws NumberIsTooLargeException if order is too large
+     * @see #getAllDerivatives()
+     */
+    public DerivativeStructure(final int parameters, final int order, final double ... derivatives)
+        throws DimensionMismatchException, NumberIsTooLargeException {
+        this(parameters, order);
+        if (derivatives.length != data.length) {
+            throw new DimensionMismatchException(derivatives.length, data.length);
+        }
+        System.arraycopy(derivatives, 0, data, 0, data.length);
+    }
+
+    /** Copy constructor.
+     * @param ds instance to copy
+     */
+    private DerivativeStructure(final DerivativeStructure ds) {
+        this.compiler = ds.compiler;
+        this.data     = ds.data.clone();
+    }
+
+    /** Get the number of free parameters.
+     * @return number of free parameters
+     */
+    public int getFreeParameters() {
+        return compiler.getFreeParameters();
+    }
+
+    /** Get the derivation order.
+     * @return derivation order
+     */
+    public int getOrder() {
+        return compiler.getOrder();
+    }
+
+    /** Create a constant compatible with instance order and number of parameters.
+     * <p>
+     * This method is a convenience factory method, it simply calls
+     * {@code new DerivativeStructure(getFreeParameters(), getOrder(), c)}
+     * </p>
+     * @param c value of the constant
+     * @return a constant compatible with instance order and number of parameters
+     * @see #DerivativeStructure(int, int, double)
+     * @since 3.3
+     */
+    public DerivativeStructure createConstant(final double c) {
+        return new DerivativeStructure(getFreeParameters(), getOrder(), c);
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public double getReal() {
+        return data[0];
+    }
+
+    /** Get the value part of the derivative structure.
+     * @return value part of the derivative structure
+     * @see #getPartialDerivative(int...)
+     */
+    public double getValue() {
+        return data[0];
+    }
+
+    /** Get a partial derivative.
+     * @param orders derivation orders with respect to each variable (if all orders are 0,
+     * the value is returned)
+     * @return partial derivative
+     * @see #getValue()
+     * @exception DimensionMismatchException if the numbers of variables does not
+     * match the instance
+     * @exception NumberIsTooLargeException if sum of derivation orders is larger
+     * than the instance limits
+     */
+    public double getPartialDerivative(final int ... orders)
+        throws DimensionMismatchException, NumberIsTooLargeException {
+        return data[compiler.getPartialDerivativeIndex(orders)];
+    }
+
+    /** Get all partial derivatives.
+     * @return a fresh copy of partial derivatives, in an array sorted according to
+     * {@link DSCompiler#getPartialDerivativeIndex(int...)}
+     */
+    public double[] getAllDerivatives() {
+        return data.clone();
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure add(final double a) {
+        final DerivativeStructure ds = new DerivativeStructure(this);
+        ds.data[0] += a;
+        return ds;
+    }
+
+    /** {@inheritDoc}
+     * @exception DimensionMismatchException if number of free parameters
+     * or orders do not match
+     */
+    public DerivativeStructure add(final DerivativeStructure a)
+        throws DimensionMismatchException {
+        compiler.checkCompatibility(a.compiler);
+        final DerivativeStructure ds = new DerivativeStructure(this);
+        compiler.add(data, 0, a.data, 0, ds.data, 0);
+        return ds;
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure subtract(final double a) {
+        return add(-a);
+    }
+
+    /** {@inheritDoc}
+     * @exception DimensionMismatchException if number of free parameters
+     * or orders do not match
+     */
+    public DerivativeStructure subtract(final DerivativeStructure a)
+        throws DimensionMismatchException {
+        compiler.checkCompatibility(a.compiler);
+        final DerivativeStructure ds = new DerivativeStructure(this);
+        compiler.subtract(data, 0, a.data, 0, ds.data, 0);
+        return ds;
+    }
+
+    /** {@inheritDoc} */
+    public DerivativeStructure multiply(final int n) {
+        return multiply((double) n);
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure multiply(final double a) {
+        final DerivativeStructure ds = new DerivativeStructure(this);
+        for (int i = 0; i < ds.data.length; ++i) {
+            ds.data[i] *= a;
+        }
+        return ds;
+    }
+
+    /** {@inheritDoc}
+     * @exception DimensionMismatchException if number of free parameters
+     * or orders do not match
+     */
+    public DerivativeStructure multiply(final DerivativeStructure a)
+        throws DimensionMismatchException {
+        compiler.checkCompatibility(a.compiler);
+        final DerivativeStructure result = new DerivativeStructure(compiler);
+        compiler.multiply(data, 0, a.data, 0, result.data, 0);
+        return result;
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure divide(final double a) {
+        final DerivativeStructure ds = new DerivativeStructure(this);
+        for (int i = 0; i < ds.data.length; ++i) {
+            ds.data[i] /= a;
+        }
+        return ds;
+    }
+
+    /** {@inheritDoc}
+     * @exception DimensionMismatchException if number of free parameters
+     * or orders do not match
+     */
+    public DerivativeStructure divide(final DerivativeStructure a)
+        throws DimensionMismatchException {
+        compiler.checkCompatibility(a.compiler);
+        final DerivativeStructure result = new DerivativeStructure(compiler);
+        compiler.divide(data, 0, a.data, 0, result.data, 0);
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    public DerivativeStructure remainder(final double a) {
+        final DerivativeStructure ds = new DerivativeStructure(this);
+        ds.data[0] = FastMath.IEEEremainder(ds.data[0], a);
+        return ds;
+    }
+
+    /** {@inheritDoc}
+     * @exception DimensionMismatchException if number of free parameters
+     * or orders do not match
+     * @since 3.2
+     */
+    public DerivativeStructure remainder(final DerivativeStructure a)
+        throws DimensionMismatchException {
+        compiler.checkCompatibility(a.compiler);
+        final DerivativeStructure result = new DerivativeStructure(compiler);
+        compiler.remainder(data, 0, a.data, 0, result.data, 0);
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    public DerivativeStructure negate() {
+        final DerivativeStructure ds = new DerivativeStructure(compiler);
+        for (int i = 0; i < ds.data.length; ++i) {
+            ds.data[i] = -data[i];
+        }
+        return ds;
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure abs() {
+        if (Double.doubleToLongBits(data[0]) < 0) {
+            // we use the bits representation to also handle -0.0
+            return negate();
+        } else {
+            return this;
+        }
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure ceil() {
+        return new DerivativeStructure(compiler.getFreeParameters(),
+                                       compiler.getOrder(),
+                                       FastMath.ceil(data[0]));
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure floor() {
+        return new DerivativeStructure(compiler.getFreeParameters(),
+                                       compiler.getOrder(),
+                                       FastMath.floor(data[0]));
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure rint() {
+        return new DerivativeStructure(compiler.getFreeParameters(),
+                                       compiler.getOrder(),
+                                       FastMath.rint(data[0]));
+    }
+
+    /** {@inheritDoc} */
+    public long round() {
+        return FastMath.round(data[0]);
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure signum() {
+        return new DerivativeStructure(compiler.getFreeParameters(),
+                                       compiler.getOrder(),
+                                       FastMath.signum(data[0]));
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure copySign(final DerivativeStructure sign){
+        long m = Double.doubleToLongBits(data[0]);
+        long s = Double.doubleToLongBits(sign.data[0]);
+        if ((m >= 0 && s >= 0) || (m < 0 && s < 0)) { // Sign is currently OK
+            return this;
+        }
+        return negate(); // flip sign
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure copySign(final double sign) {
+        long m = Double.doubleToLongBits(data[0]);
+        long s = Double.doubleToLongBits(sign);
+        if ((m >= 0 && s >= 0) || (m < 0 && s < 0)) { // Sign is currently OK
+            return this;
+        }
+        return negate(); // flip sign
+    }
+
+    /**
+     * Return the exponent of the instance value, removing the bias.
+     * <p>
+     * For double numbers of the form 2<sup>x</sup>, the unbiased
+     * exponent is exactly x.
+     * </p>
+     * @return exponent for instance in IEEE754 representation, without bias
+     */
+    public int getExponent() {
+        return FastMath.getExponent(data[0]);
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure scalb(final int n) {
+        final DerivativeStructure ds = new DerivativeStructure(compiler);
+        for (int i = 0; i < ds.data.length; ++i) {
+            ds.data[i] = FastMath.scalb(data[i], n);
+        }
+        return ds;
+    }
+
+    /** {@inheritDoc}
+     * @exception DimensionMismatchException if number of free parameters
+     * or orders do not match
+     * @since 3.2
+     */
+    public DerivativeStructure hypot(final DerivativeStructure y)
+        throws DimensionMismatchException {
+
+        compiler.checkCompatibility(y.compiler);
+
+        if (Double.isInfinite(data[0]) || Double.isInfinite(y.data[0])) {
+            return new DerivativeStructure(compiler.getFreeParameters(),
+                                           compiler.getFreeParameters(),
+                                           Double.POSITIVE_INFINITY);
+        } else if (Double.isNaN(data[0]) || Double.isNaN(y.data[0])) {
+            return new DerivativeStructure(compiler.getFreeParameters(),
+                                           compiler.getFreeParameters(),
+                                           Double.NaN);
+        } else {
+
+            final int expX = getExponent();
+            final int expY = y.getExponent();
+            if (expX > expY + 27) {
+                // y is neglectible with respect to x
+                return abs();
+            } else if (expY > expX + 27) {
+                // x is neglectible with respect to y
+                return y.abs();
+            } else {
+
+                // find an intermediate scale to avoid both overflow and underflow
+                final int middleExp = (expX + expY) / 2;
+
+                // scale parameters without losing precision
+                final DerivativeStructure scaledX = scalb(-middleExp);
+                final DerivativeStructure scaledY = y.scalb(-middleExp);
+
+                // compute scaled hypotenuse
+                final DerivativeStructure scaledH =
+                        scaledX.multiply(scaledX).add(scaledY.multiply(scaledY)).sqrt();
+
+                // remove scaling
+                return scaledH.scalb(middleExp);
+
+            }
+
+        }
+    }
+
+    /**
+     * Returns the hypotenuse of a triangle with sides {@code x} and {@code y}
+     * - sqrt(<i>x</i><sup>2</sup>&nbsp;+<i>y</i><sup>2</sup>)
+     * avoiding intermediate overflow or underflow.
+     *
+     * <ul>
+     * <li> If either argument is infinite, then the result is positive infinity.</li>
+     * <li> else, if either argument is NaN then the result is NaN.</li>
+     * </ul>
+     *
+     * @param x a value
+     * @param y a value
+     * @return sqrt(<i>x</i><sup>2</sup>&nbsp;+<i>y</i><sup>2</sup>)
+     * @exception DimensionMismatchException if number of free parameters
+     * or orders do not match
+     * @since 3.2
+     */
+    public static DerivativeStructure hypot(final DerivativeStructure x, final DerivativeStructure y)
+        throws DimensionMismatchException {
+        return x.hypot(y);
+    }
+
+    /** Compute composition of the instance by a univariate function.
+     * @param f array of value and derivatives of the function at
+     * the current point (i.e. [f({@link #getValue()}),
+     * f'({@link #getValue()}), f''({@link #getValue()})...]).
+     * @return f(this)
+     * @exception DimensionMismatchException if the number of derivatives
+     * in the array is not equal to {@link #getOrder() order} + 1
+     */
+    public DerivativeStructure compose(final double ... f)
+        throws DimensionMismatchException {
+        if (f.length != getOrder() + 1) {
+            throw new DimensionMismatchException(f.length, getOrder() + 1);
+        }
+        final DerivativeStructure result = new DerivativeStructure(compiler);
+        compiler.compose(data, 0, f, result.data, 0);
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    public DerivativeStructure reciprocal() {
+        final DerivativeStructure result = new DerivativeStructure(compiler);
+        compiler.pow(data, 0, -1, result.data, 0);
+        return result;
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure sqrt() {
+        return rootN(2);
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure cbrt() {
+        return rootN(3);
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure rootN(final int n) {
+        final DerivativeStructure result = new DerivativeStructure(compiler);
+        compiler.rootN(data, 0, n, result.data, 0);
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    public Field<DerivativeStructure> getField() {
+        return new Field<DerivativeStructure>() {
+
+            /** {@inheritDoc} */
+            public DerivativeStructure getZero() {
+                return new DerivativeStructure(compiler.getFreeParameters(), compiler.getOrder(), 0.0);
+            }
+
+            /** {@inheritDoc} */
+            public DerivativeStructure getOne() {
+                return new DerivativeStructure(compiler.getFreeParameters(), compiler.getOrder(), 1.0);
+            }
+
+            /** {@inheritDoc} */
+            public Class<? extends FieldElement<DerivativeStructure>> getRuntimeClass() {
+                return DerivativeStructure.class;
+            }
+
+        };
+    }
+
+    /** Compute a<sup>x</sup> where a is a double and x a {@link DerivativeStructure}
+     * @param a number to exponentiate
+     * @param x power to apply
+     * @return a<sup>x</sup>
+     * @since 3.3
+     */
+    public static DerivativeStructure pow(final double a, final DerivativeStructure x) {
+        final DerivativeStructure result = new DerivativeStructure(x.compiler);
+        x.compiler.pow(a, x.data, 0, result.data, 0);
+        return result;
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure pow(final double p) {
+        final DerivativeStructure result = new DerivativeStructure(compiler);
+        compiler.pow(data, 0, p, result.data, 0);
+        return result;
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure pow(final int n) {
+        final DerivativeStructure result = new DerivativeStructure(compiler);
+        compiler.pow(data, 0, n, result.data, 0);
+        return result;
+    }
+
+    /** {@inheritDoc}
+     * @exception DimensionMismatchException if number of free parameters
+     * or orders do not match
+     * @since 3.2
+     */
+    public DerivativeStructure pow(final DerivativeStructure e)
+        throws DimensionMismatchException {
+        compiler.checkCompatibility(e.compiler);
+        final DerivativeStructure result = new DerivativeStructure(compiler);
+        compiler.pow(data, 0, e.data, 0, result.data, 0);
+        return result;
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure exp() {
+        final DerivativeStructure result = new DerivativeStructure(compiler);
+        compiler.exp(data, 0, result.data, 0);
+        return result;
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure expm1() {
+        final DerivativeStructure result = new DerivativeStructure(compiler);
+        compiler.expm1(data, 0, result.data, 0);
+        return result;
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure log() {
+        final DerivativeStructure result = new DerivativeStructure(compiler);
+        compiler.log(data, 0, result.data, 0);
+        return result;
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure log1p() {
+        final DerivativeStructure result = new DerivativeStructure(compiler);
+        compiler.log1p(data, 0, result.data, 0);
+        return result;
+    }
+
+    /** Base 10 logarithm.
+     * @return base 10 logarithm of the instance
+     */
+    public DerivativeStructure log10() {
+        final DerivativeStructure result = new DerivativeStructure(compiler);
+        compiler.log10(data, 0, result.data, 0);
+        return result;
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure cos() {
+        final DerivativeStructure result = new DerivativeStructure(compiler);
+        compiler.cos(data, 0, result.data, 0);
+        return result;
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure sin() {
+        final DerivativeStructure result = new DerivativeStructure(compiler);
+        compiler.sin(data, 0, result.data, 0);
+        return result;
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure tan() {
+        final DerivativeStructure result = new DerivativeStructure(compiler);
+        compiler.tan(data, 0, result.data, 0);
+        return result;
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure acos() {
+        final DerivativeStructure result = new DerivativeStructure(compiler);
+        compiler.acos(data, 0, result.data, 0);
+        return result;
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure asin() {
+        final DerivativeStructure result = new DerivativeStructure(compiler);
+        compiler.asin(data, 0, result.data, 0);
+        return result;
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure atan() {
+        final DerivativeStructure result = new DerivativeStructure(compiler);
+        compiler.atan(data, 0, result.data, 0);
+        return result;
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure atan2(final DerivativeStructure x)
+        throws DimensionMismatchException {
+        compiler.checkCompatibility(x.compiler);
+        final DerivativeStructure result = new DerivativeStructure(compiler);
+        compiler.atan2(data, 0, x.data, 0, result.data, 0);
+        return result;
+    }
+
+    /** Two arguments arc tangent operation.
+     * @param y first argument of the arc tangent
+     * @param x second argument of the arc tangent
+     * @return atan2(y, x)
+     * @exception DimensionMismatchException if number of free parameters
+     * or orders do not match
+     * @since 3.2
+     */
+    public static DerivativeStructure atan2(final DerivativeStructure y, final DerivativeStructure x)
+        throws DimensionMismatchException {
+        return y.atan2(x);
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure cosh() {
+        final DerivativeStructure result = new DerivativeStructure(compiler);
+        compiler.cosh(data, 0, result.data, 0);
+        return result;
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure sinh() {
+        final DerivativeStructure result = new DerivativeStructure(compiler);
+        compiler.sinh(data, 0, result.data, 0);
+        return result;
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure tanh() {
+        final DerivativeStructure result = new DerivativeStructure(compiler);
+        compiler.tanh(data, 0, result.data, 0);
+        return result;
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure acosh() {
+        final DerivativeStructure result = new DerivativeStructure(compiler);
+        compiler.acosh(data, 0, result.data, 0);
+        return result;
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure asinh() {
+        final DerivativeStructure result = new DerivativeStructure(compiler);
+        compiler.asinh(data, 0, result.data, 0);
+        return result;
+    }
+
+    /** {@inheritDoc}
+     * @since 3.2
+     */
+    public DerivativeStructure atanh() {
+        final DerivativeStructure result = new DerivativeStructure(compiler);
+        compiler.atanh(data, 0, result.data, 0);
+        return result;
+    }
+
+    /** Convert radians to degrees, with error of less than 0.5 ULP
+     *  @return instance converted into degrees
+     */
+    public DerivativeStructure toDegrees() {
+        final DerivativeStructure ds = new DerivativeStructure(compiler);
+        for (int i = 0; i < ds.data.length; ++i) {
+            ds.data[i] = FastMath.toDegrees(data[i]);
+        }
+        return ds;
+    }
+
+    /** Convert degrees to radians, with error of less than 0.5 ULP
+     *  @return instance converted into radians
+     */
+    public DerivativeStructure toRadians() {
+        final DerivativeStructure ds = new DerivativeStructure(compiler);
+        for (int i = 0; i < ds.data.length; ++i) {
+            ds.data[i] = FastMath.toRadians(data[i]);
+        }
+        return ds;
+    }
+
+    /** Evaluate Taylor expansion a derivative structure.
+     * @param delta parameters offsets (&Delta;x, &Delta;y, ...)
+     * @return value of the Taylor expansion at x + &Delta;x, y + &Delta;y, ...
+     * @throws MathArithmeticException if factorials becomes too large
+     */
+    public double taylor(final double ... delta) throws MathArithmeticException {
+        return compiler.taylor(data, 0, delta);
+    }
+
+    /** {@inheritDoc}
+     * @exception DimensionMismatchException if number of free parameters
+     * or orders do not match
+     * @since 3.2
+     */
+    public DerivativeStructure linearCombination(final DerivativeStructure[] a, final DerivativeStructure[] b)
+        throws DimensionMismatchException {
+
+        // compute an accurate value, taking care of cancellations
+        final double[] aDouble = new double[a.length];
+        for (int i = 0; i < a.length; ++i) {
+            aDouble[i] = a[i].getValue();
+        }
+        final double[] bDouble = new double[b.length];
+        for (int i = 0; i < b.length; ++i) {
+            bDouble[i] = b[i].getValue();
+        }
+        final double accurateValue = MathArrays.linearCombination(aDouble, bDouble);
+
+        // compute a simple value, with all partial derivatives
+        DerivativeStructure simpleValue = a[0].getField().getZero();
+        for (int i = 0; i < a.length; ++i) {
+            simpleValue = simpleValue.add(a[i].multiply(b[i]));
+        }
+
+        // create a result with accurate value and all derivatives (not necessarily as accurate as the value)
+        final double[] all = simpleValue.getAllDerivatives();
+        all[0] = accurateValue;
+        return new DerivativeStructure(simpleValue.getFreeParameters(), simpleValue.getOrder(), all);
+
+    }
+
+    /** {@inheritDoc}
+     * @exception DimensionMismatchException if number of free parameters
+     * or orders do not match
+     * @since 3.2
+     */
+    public DerivativeStructure linearCombination(final double[] a, final DerivativeStructure[] b)
+        throws DimensionMismatchException {
+
+        // compute an accurate value, taking care of cancellations
+        final double[] bDouble = new double[b.length];
+        for (int i = 0; i < b.length; ++i) {
+            bDouble[i] = b[i].getValue();
+        }
+        final double accurateValue = MathArrays.linearCombination(a, bDouble);
+
+        // compute a simple value, with all partial derivatives
+        DerivativeStructure simpleValue = b[0].getField().getZero();
+        for (int i = 0; i < a.length; ++i) {
+            simpleValue = simpleValue.add(b[i].multiply(a[i]));
+        }
+
+        // create a result with accurate value and all derivatives (not necessarily as accurate as the value)
+        final double[] all = simpleValue.getAllDerivatives();
+        all[0] = accurateValue;
+        return new DerivativeStructure(simpleValue.getFreeParameters(), simpleValue.getOrder(), all);
+
+    }
+
+    /** {@inheritDoc}
+     * @exception DimensionMismatchException if number of free parameters
+     * or orders do not match
+     * @since 3.2
+     */
+    public DerivativeStructure linearCombination(final DerivativeStructure a1, final DerivativeStructure b1,
+                                                 final DerivativeStructure a2, final DerivativeStructure b2)
+        throws DimensionMismatchException {
+
+        // compute an accurate value, taking care of cancellations
+        final double accurateValue = MathArrays.linearCombination(a1.getValue(), b1.getValue(),
+                                                                  a2.getValue(), b2.getValue());
+
+        // compute a simple value, with all partial derivatives
+        final DerivativeStructure simpleValue = a1.multiply(b1).add(a2.multiply(b2));
+
+        // create a result with accurate value and all derivatives (not necessarily as accurate as the value)
+        final double[] all = simpleValue.getAllDerivatives();
+        all[0] = accurateValue;
+        return new DerivativeStructure(getFreeParameters(), getOrder(), all);
+
+    }
+
+    /** {@inheritDoc}
+     * @exception DimensionMismatchException if number of free parameters
+     * or orders do not match
+     * @since 3.2
+     */
+    public DerivativeStructure linearCombination(final double a1, final DerivativeStructure b1,
+                                                 final double a2, final DerivativeStructure b2)
+        throws DimensionMismatchException {
+
+        // compute an accurate value, taking care of cancellations
+        final double accurateValue = MathArrays.linearCombination(a1, b1.getValue(),
+                                                                  a2, b2.getValue());
+
+        // compute a simple value, with all partial derivatives
+        final DerivativeStructure simpleValue = b1.multiply(a1).add(b2.multiply(a2));
+
+        // create a result with accurate value and all derivatives (not necessarily as accurate as the value)
+        final double[] all = simpleValue.getAllDerivatives();
+        all[0] = accurateValue;
+        return new DerivativeStructure(getFreeParameters(), getOrder(), all);
+
+    }
+
+    /** {@inheritDoc}
+     * @exception DimensionMismatchException if number of free parameters
+     * or orders do not match
+     * @since 3.2
+     */
+    public DerivativeStructure linearCombination(final DerivativeStructure a1, final DerivativeStructure b1,
+                                                 final DerivativeStructure a2, final DerivativeStructure b2,
+                                                 final DerivativeStructure a3, final DerivativeStructure b3)
+        throws DimensionMismatchException {
+
+        // compute an accurate value, taking care of cancellations
+        final double accurateValue = MathArrays.linearCombination(a1.getValue(), b1.getValue(),
+                                                                  a2.getValue(), b2.getValue(),
+                                                                  a3.getValue(), b3.getValue());
+
+        // compute a simple value, with all partial derivatives
+        final DerivativeStructure simpleValue = a1.multiply(b1).add(a2.multiply(b2)).add(a3.multiply(b3));
+
+        // create a result with accurate value and all derivatives (not necessarily as accurate as the value)
+        final double[] all = simpleValue.getAllDerivatives();
+        all[0] = accurateValue;
+        return new DerivativeStructure(getFreeParameters(), getOrder(), all);
+
+    }
+
+    /** {@inheritDoc}
+     * @exception DimensionMismatchException if number of free parameters
+     * or orders do not match
+     * @since 3.2
+     */
+    public DerivativeStructure linearCombination(final double a1, final DerivativeStructure b1,
+                                                 final double a2, final DerivativeStructure b2,
+                                                 final double a3, final DerivativeStructure b3)
+        throws DimensionMismatchException {
+
+        // compute an accurate value, taking care of cancellations
+        final double accurateValue = MathArrays.linearCombination(a1, b1.getValue(),
+                                                                  a2, b2.getValue(),
+                                                                  a3, b3.getValue());
+
+        // compute a simple value, with all partial derivatives
+        final DerivativeStructure simpleValue = b1.multiply(a1).add(b2.multiply(a2)).add(b3.multiply(a3));
+
+        // create a result with accurate value and all derivatives (not necessarily as accurate as the value)
+        final double[] all = simpleValue.getAllDerivatives();
+        all[0] = accurateValue;
+        return new DerivativeStructure(getFreeParameters(), getOrder(), all);
+
+    }
+
+    /** {@inheritDoc}
+     * @exception DimensionMismatchException if number of free parameters
+     * or orders do not match
+     * @since 3.2
+     */
+    public DerivativeStructure linearCombination(final DerivativeStructure a1, final DerivativeStructure b1,
+                                                 final DerivativeStructure a2, final DerivativeStructure b2,
+                                                 final DerivativeStructure a3, final DerivativeStructure b3,
+                                                 final DerivativeStructure a4, final DerivativeStructure b4)
+        throws DimensionMismatchException {
+
+        // compute an accurate value, taking care of cancellations
+        final double accurateValue = MathArrays.linearCombination(a1.getValue(), b1.getValue(),
+                                                                  a2.getValue(), b2.getValue(),
+                                                                  a3.getValue(), b3.getValue(),
+                                                                  a4.getValue(), b4.getValue());
+
+        // compute a simple value, with all partial derivatives
+        final DerivativeStructure simpleValue = a1.multiply(b1).add(a2.multiply(b2)).add(a3.multiply(b3)).add(a4.multiply(b4));
+
+        // create a result with accurate value and all derivatives (not necessarily as accurate as the value)
+        final double[] all = simpleValue.getAllDerivatives();
+        all[0] = accurateValue;
+        return new DerivativeStructure(getFreeParameters(), getOrder(), all);
+
+    }
+
+    /** {@inheritDoc}
+     * @exception DimensionMismatchException if number of free parameters
+     * or orders do not match
+     * @since 3.2
+     */
+    public DerivativeStructure linearCombination(final double a1, final DerivativeStructure b1,
+                                                 final double a2, final DerivativeStructure b2,
+                                                 final double a3, final DerivativeStructure b3,
+                                                 final double a4, final DerivativeStructure b4)
+        throws DimensionMismatchException {
+
+        // compute an accurate value, taking care of cancellations
+        final double accurateValue = MathArrays.linearCombination(a1, b1.getValue(),
+                                                                  a2, b2.getValue(),
+                                                                  a3, b3.getValue(),
+                                                                  a4, b4.getValue());
+
+        // compute a simple value, with all partial derivatives
+        final DerivativeStructure simpleValue = b1.multiply(a1).add(b2.multiply(a2)).add(b3.multiply(a3)).add(b4.multiply(a4));
+
+        // create a result with accurate value and all derivatives (not necessarily as accurate as the value)
+        final double[] all = simpleValue.getAllDerivatives();
+        all[0] = accurateValue;
+        return new DerivativeStructure(getFreeParameters(), getOrder(), all);
+
+    }
+
+    /**
+     * Test for the equality of two derivative structures.
+     * <p>
+     * Derivative structures are considered equal if they have the same number
+     * of free parameters, the same derivation order, and the same derivatives.
+     * </p>
+     * @param other Object to test for equality to this
+     * @return true if two derivative structures are equal
+     * @since 3.2
+     */
+    @Override
+    public boolean equals(Object other) {
+
+        if (this == other) {
+            return true;
+        }
+
+        if (other instanceof DerivativeStructure) {
+            final DerivativeStructure rhs = (DerivativeStructure)other;
+            return (getFreeParameters() == rhs.getFreeParameters()) &&
+                   (getOrder() == rhs.getOrder()) &&
+                   MathArrays.equals(data, rhs.data);
+        }
+
+        return false;
+
+    }
+
+    /**
+     * Get a hashCode for the derivative structure.
+     * @return a hash code value for this object
+     * @since 3.2
+     */
+    @Override
+    public int hashCode() {
+        return 227 + 229 * getFreeParameters() + 233 * getOrder() + 239 * MathUtils.hash(data);
+    }
+
+    /**
+     * Replace the instance with a data transfer object for serialization.
+     * @return data transfer object that will be serialized
+     */
+    private Object writeReplace() {
+        return new DataTransferObject(compiler.getFreeParameters(), compiler.getOrder(), data);
+    }
+
+    /** Internal class used only for serialization. */
+    private static class DataTransferObject implements Serializable {
+
+        /** Serializable UID. */
+        private static final long serialVersionUID = 20120730L;
+
+        /** Number of variables.
+         * @serial
+         */
+        private final int variables;
+
+        /** Derivation order.
+         * @serial
+         */
+        private final int order;
+
+        /** Partial derivatives.
+         * @serial
+         */
+        private final double[] data;
+
+        /** Simple constructor.
+         * @param variables number of variables
+         * @param order derivation order
+         * @param data partial derivatives
+         */
+        DataTransferObject(final int variables, final int order, final double[] data) {
+            this.variables = variables;
+            this.order     = order;
+            this.data      = data;
+        }
+
+        /** Replace the deserialized data transfer object with a {@link DerivativeStructure}.
+         * @return replacement {@link DerivativeStructure}
+         */
+        private Object readResolve() {
+            return new DerivativeStructure(variables, order, data);
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/differentiation/FiniteDifferencesDifferentiator.java b/src/main/java/org/apache/commons/math3/analysis/differentiation/FiniteDifferencesDifferentiator.java
new file mode 100644
index 0000000..c2f1002
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/differentiation/FiniteDifferencesDifferentiator.java
@@ -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.math3.analysis.differentiation;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.UnivariateMatrixFunction;
+import org.apache.commons.math3.analysis.UnivariateVectorFunction;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.util.FastMath;
+
+/** Univariate functions differentiator using finite differences.
+ * <p>
+ * This class creates some wrapper objects around regular
+ * {@link UnivariateFunction univariate functions} (or {@link
+ * UnivariateVectorFunction univariate vector functions} or {@link
+ * UnivariateMatrixFunction univariate matrix functions}). These
+ * wrapper objects compute derivatives in addition to function
+ * values.
+ * </p>
+ * <p>
+ * The wrapper objects work by calling the underlying function on
+ * a sampling grid around the current point and performing polynomial
+ * interpolation. A finite differences scheme with n points is
+ * theoretically able to compute derivatives up to order n-1, but
+ * it is generally better to have a slight margin. The step size must
+ * also be small enough in order for the polynomial approximation to
+ * be good in the current point neighborhood, but it should not be too
+ * small because numerical instability appears quickly (there are several
+ * differences of close points). Choosing the number of points and
+ * the step size is highly problem dependent.
+ * </p>
+ * <p>
+ * As an example of good and bad settings, lets consider the quintic
+ * polynomial function {@code f(x) = (x-1)*(x-0.5)*x*(x+0.5)*(x+1)}.
+ * Since it is a polynomial, finite differences with at least 6 points
+ * should theoretically recover the exact same polynomial and hence
+ * compute accurate derivatives for any order. However, due to numerical
+ * errors, we get the following results for a 7 points finite differences
+ * for abscissae in the [-10, 10] range:
+ * <ul>
+ *   <li>step size = 0.25, second order derivative error about 9.97e-10</li>
+ *   <li>step size = 0.25, fourth order derivative error about 5.43e-8</li>
+ *   <li>step size = 1.0e-6, second order derivative error about 148</li>
+ *   <li>step size = 1.0e-6, fourth order derivative error about 6.35e+14</li>
+ * </ul>
+ * <p>
+ * This example shows that the small step size is really bad, even simply
+ * for second order derivative!</p>
+ *
+ * @since 3.1
+ */
+public class FiniteDifferencesDifferentiator
+    implements UnivariateFunctionDifferentiator, UnivariateVectorFunctionDifferentiator,
+               UnivariateMatrixFunctionDifferentiator, Serializable {
+
+    /** Serializable UID. */
+    private static final long serialVersionUID = 20120917L;
+
+    /** Number of points to use. */
+    private final int nbPoints;
+
+    /** Step size. */
+    private final double stepSize;
+
+    /** Half sample span. */
+    private final double halfSampleSpan;
+
+    /** Lower bound for independent variable. */
+    private final double tMin;
+
+    /** Upper bound for independent variable. */
+    private final double tMax;
+
+    /**
+     * Build a differentiator with number of points and step size when independent variable is unbounded.
+     * <p>
+     * Beware that wrong settings for the finite differences differentiator
+     * can lead to highly unstable and inaccurate results, especially for
+     * high derivation orders. Using very small step sizes is often a
+     * <em>bad</em> idea.
+     * </p>
+     * @param nbPoints number of points to use
+     * @param stepSize step size (gap between each point)
+     * @exception NotPositiveException if {@code stepsize <= 0} (note that
+     * {@link NotPositiveException} extends {@link NumberIsTooSmallException})
+     * @exception NumberIsTooSmallException {@code nbPoint <= 1}
+     */
+    public FiniteDifferencesDifferentiator(final int nbPoints, final double stepSize)
+        throws NotPositiveException, NumberIsTooSmallException {
+        this(nbPoints, stepSize, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
+    }
+
+    /**
+     * Build a differentiator with number of points and step size when independent variable is bounded.
+     * <p>
+     * When the independent variable is bounded (tLower &lt; t &lt; tUpper), the sampling
+     * points used for differentiation will be adapted to ensure the constraint holds
+     * even near the boundaries. This means the sample will not be centered anymore in
+     * these cases. At an extreme case, computing derivatives exactly at the lower bound
+     * will lead the sample to be entirely on the right side of the derivation point.
+     * </p>
+     * <p>
+     * Note that the boundaries are considered to be excluded for function evaluation.
+     * </p>
+     * <p>
+     * Beware that wrong settings for the finite differences differentiator
+     * can lead to highly unstable and inaccurate results, especially for
+     * high derivation orders. Using very small step sizes is often a
+     * <em>bad</em> idea.
+     * </p>
+     * @param nbPoints number of points to use
+     * @param stepSize step size (gap between each point)
+     * @param tLower lower bound for independent variable (may be {@code Double.NEGATIVE_INFINITY}
+     * if there are no lower bounds)
+     * @param tUpper upper bound for independent variable (may be {@code Double.POSITIVE_INFINITY}
+     * if there are no upper bounds)
+     * @exception NotPositiveException if {@code stepsize <= 0} (note that
+     * {@link NotPositiveException} extends {@link NumberIsTooSmallException})
+     * @exception NumberIsTooSmallException {@code nbPoint <= 1}
+     * @exception NumberIsTooLargeException {@code stepSize * (nbPoints - 1) >= tUpper - tLower}
+     */
+    public FiniteDifferencesDifferentiator(final int nbPoints, final double stepSize,
+                                           final double tLower, final double tUpper)
+            throws NotPositiveException, NumberIsTooSmallException, NumberIsTooLargeException {
+
+        if (nbPoints <= 1) {
+            throw new NumberIsTooSmallException(stepSize, 1, false);
+        }
+        this.nbPoints = nbPoints;
+
+        if (stepSize <= 0) {
+            throw new NotPositiveException(stepSize);
+        }
+        this.stepSize = stepSize;
+
+        halfSampleSpan = 0.5 * stepSize * (nbPoints - 1);
+        if (2 * halfSampleSpan >= tUpper - tLower) {
+            throw new NumberIsTooLargeException(2 * halfSampleSpan, tUpper - tLower, false);
+        }
+        final double safety = FastMath.ulp(halfSampleSpan);
+        this.tMin = tLower + halfSampleSpan + safety;
+        this.tMax = tUpper - halfSampleSpan - safety;
+
+    }
+
+    /**
+     * Get the number of points to use.
+     * @return number of points to use
+     */
+    public int getNbPoints() {
+        return nbPoints;
+    }
+
+    /**
+     * Get the step size.
+     * @return step size
+     */
+    public double getStepSize() {
+        return stepSize;
+    }
+
+    /**
+     * Evaluate derivatives from a sample.
+     * <p>
+     * Evaluation is done using divided differences.
+     * </p>
+     * @param t evaluation abscissa value and derivatives
+     * @param t0 first sample point abscissa
+     * @param y function values sample {@code y[i] = f(t[i]) = f(t0 + i * stepSize)}
+     * @return value and derivatives at {@code t}
+     * @exception NumberIsTooLargeException if the requested derivation order
+     * is larger or equal to the number of points
+     */
+    private DerivativeStructure evaluate(final DerivativeStructure t, final double t0,
+                                         final double[] y)
+        throws NumberIsTooLargeException {
+
+        // create divided differences diagonal arrays
+        final double[] top    = new double[nbPoints];
+        final double[] bottom = new double[nbPoints];
+
+        for (int i = 0; i < nbPoints; ++i) {
+
+            // update the bottom diagonal of the divided differences array
+            bottom[i] = y[i];
+            for (int j = 1; j <= i; ++j) {
+                bottom[i - j] = (bottom[i - j + 1] - bottom[i - j]) / (j * stepSize);
+            }
+
+            // update the top diagonal of the divided differences array
+            top[i] = bottom[0];
+
+        }
+
+        // evaluate interpolation polynomial (represented by top diagonal) at t
+        final int order            = t.getOrder();
+        final int parameters       = t.getFreeParameters();
+        final double[] derivatives = t.getAllDerivatives();
+        final double dt0           = t.getValue() - t0;
+        DerivativeStructure interpolation = new DerivativeStructure(parameters, order, 0.0);
+        DerivativeStructure monomial = null;
+        for (int i = 0; i < nbPoints; ++i) {
+            if (i == 0) {
+                // start with monomial(t) = 1
+                monomial = new DerivativeStructure(parameters, order, 1.0);
+            } else {
+                // monomial(t) = (t - t0) * (t - t1) * ... * (t - t(i-1))
+                derivatives[0] = dt0 - (i - 1) * stepSize;
+                final DerivativeStructure deltaX = new DerivativeStructure(parameters, order, derivatives);
+                monomial = monomial.multiply(deltaX);
+            }
+            interpolation = interpolation.add(monomial.multiply(top[i]));
+        }
+
+        return interpolation;
+
+    }
+
+    /** {@inheritDoc}
+     * <p>The returned object cannot compute derivatives to arbitrary orders. The
+     * value function will throw a {@link NumberIsTooLargeException} if the requested
+     * derivation order is larger or equal to the number of points.
+     * </p>
+     */
+    public UnivariateDifferentiableFunction differentiate(final UnivariateFunction function) {
+        return new UnivariateDifferentiableFunction() {
+
+            /** {@inheritDoc} */
+            public double value(final double x) throws MathIllegalArgumentException {
+                return function.value(x);
+            }
+
+            /** {@inheritDoc} */
+            public DerivativeStructure value(final DerivativeStructure t)
+                throws MathIllegalArgumentException {
+
+                // check we can achieve the requested derivation order with the sample
+                if (t.getOrder() >= nbPoints) {
+                    throw new NumberIsTooLargeException(t.getOrder(), nbPoints, false);
+                }
+
+                // compute sample position, trying to be centered if possible
+                final double t0 = FastMath.max(FastMath.min(t.getValue(), tMax), tMin) - halfSampleSpan;
+
+                // compute sample points
+                final double[] y = new double[nbPoints];
+                for (int i = 0; i < nbPoints; ++i) {
+                    y[i] = function.value(t0 + i * stepSize);
+                }
+
+                // evaluate derivatives
+                return evaluate(t, t0, y);
+
+            }
+
+        };
+    }
+
+    /** {@inheritDoc}
+     * <p>The returned object cannot compute derivatives to arbitrary orders. The
+     * value function will throw a {@link NumberIsTooLargeException} if the requested
+     * derivation order is larger or equal to the number of points.
+     * </p>
+     */
+    public UnivariateDifferentiableVectorFunction differentiate(final UnivariateVectorFunction function) {
+        return new UnivariateDifferentiableVectorFunction() {
+
+            /** {@inheritDoc} */
+            public double[]value(final double x) throws MathIllegalArgumentException {
+                return function.value(x);
+            }
+
+            /** {@inheritDoc} */
+            public DerivativeStructure[] value(final DerivativeStructure t)
+                throws MathIllegalArgumentException {
+
+                // check we can achieve the requested derivation order with the sample
+                if (t.getOrder() >= nbPoints) {
+                    throw new NumberIsTooLargeException(t.getOrder(), nbPoints, false);
+                }
+
+                // compute sample position, trying to be centered if possible
+                final double t0 = FastMath.max(FastMath.min(t.getValue(), tMax), tMin) - halfSampleSpan;
+
+                // compute sample points
+                double[][] y = null;
+                for (int i = 0; i < nbPoints; ++i) {
+                    final double[] v = function.value(t0 + i * stepSize);
+                    if (i == 0) {
+                        y = new double[v.length][nbPoints];
+                    }
+                    for (int j = 0; j < v.length; ++j) {
+                        y[j][i] = v[j];
+                    }
+                }
+
+                // evaluate derivatives
+                final DerivativeStructure[] value = new DerivativeStructure[y.length];
+                for (int j = 0; j < value.length; ++j) {
+                    value[j] = evaluate(t, t0, y[j]);
+                }
+
+                return value;
+
+            }
+
+        };
+    }
+
+    /** {@inheritDoc}
+     * <p>The returned object cannot compute derivatives to arbitrary orders. The
+     * value function will throw a {@link NumberIsTooLargeException} if the requested
+     * derivation order is larger or equal to the number of points.
+     * </p>
+     */
+    public UnivariateDifferentiableMatrixFunction differentiate(final UnivariateMatrixFunction function) {
+        return new UnivariateDifferentiableMatrixFunction() {
+
+            /** {@inheritDoc} */
+            public double[][]  value(final double x) throws MathIllegalArgumentException {
+                return function.value(x);
+            }
+
+            /** {@inheritDoc} */
+            public DerivativeStructure[][]  value(final DerivativeStructure t)
+                throws MathIllegalArgumentException {
+
+                // check we can achieve the requested derivation order with the sample
+                if (t.getOrder() >= nbPoints) {
+                    throw new NumberIsTooLargeException(t.getOrder(), nbPoints, false);
+                }
+
+                // compute sample position, trying to be centered if possible
+                final double t0 = FastMath.max(FastMath.min(t.getValue(), tMax), tMin) - halfSampleSpan;
+
+                // compute sample points
+                double[][][] y = null;
+                for (int i = 0; i < nbPoints; ++i) {
+                    final double[][] v = function.value(t0 + i * stepSize);
+                    if (i == 0) {
+                        y = new double[v.length][v[0].length][nbPoints];
+                    }
+                    for (int j = 0; j < v.length; ++j) {
+                        for (int k = 0; k < v[j].length; ++k) {
+                            y[j][k][i] = v[j][k];
+                        }
+                    }
+                }
+
+                // evaluate derivatives
+                final DerivativeStructure[][] value = new DerivativeStructure[y.length][y[0].length];
+                for (int j = 0; j < value.length; ++j) {
+                    for (int k = 0; k < y[j].length; ++k) {
+                        value[j][k] = evaluate(t, t0, y[j][k]);
+                    }
+                }
+
+                return value;
+
+            }
+
+        };
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/differentiation/GradientFunction.java b/src/main/java/org/apache/commons/math3/analysis/differentiation/GradientFunction.java
new file mode 100644
index 0000000..25aa7c7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/differentiation/GradientFunction.java
@@ -0,0 +1,65 @@
+/*
+ * 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.math3.analysis.differentiation;
+
+import org.apache.commons.math3.analysis.MultivariateVectorFunction;
+
+/** Class representing the gradient of a multivariate function.
+ * <p>
+ * The vectorial components of the function represent the derivatives
+ * with respect to each function parameters.
+ * </p>
+ * @since 3.1
+ */
+public class GradientFunction implements MultivariateVectorFunction {
+
+    /** Underlying real-valued function. */
+    private final MultivariateDifferentiableFunction f;
+
+    /** Simple constructor.
+     * @param f underlying real-valued function
+     */
+    public GradientFunction(final MultivariateDifferentiableFunction f) {
+        this.f = f;
+    }
+
+    /** {@inheritDoc} */
+    public double[] value(double[] point) {
+
+        // set up parameters
+        final DerivativeStructure[] dsX = new DerivativeStructure[point.length];
+        for (int i = 0; i < point.length; ++i) {
+            dsX[i] = new DerivativeStructure(point.length, 1, i, point[i]);
+        }
+
+        // compute the derivatives
+        final DerivativeStructure dsY = f.value(dsX);
+
+        // extract the gradient
+        final double[] y = new double[point.length];
+        final int[] orders = new int[point.length];
+        for (int i = 0; i < point.length; ++i) {
+            orders[i] = 1;
+            y[i] = dsY.getPartialDerivative(orders);
+            orders[i] = 0;
+        }
+
+        return y;
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/differentiation/JacobianFunction.java b/src/main/java/org/apache/commons/math3/analysis/differentiation/JacobianFunction.java
new file mode 100644
index 0000000..0de47db
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/differentiation/JacobianFunction.java
@@ -0,0 +1,69 @@
+/*
+ * 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.math3.analysis.differentiation;
+
+import org.apache.commons.math3.analysis.MultivariateMatrixFunction;
+
+/** Class representing the Jacobian of a multivariate vector function.
+ * <p>
+ * The rows iterate on the model functions while the columns iterate on the parameters; thus,
+ * the numbers of rows is equal to the dimension of the underlying function vector
+ * value and the number of columns is equal to the number of free parameters of
+ * the underlying function.
+ * </p>
+ * @since 3.1
+ */
+public class JacobianFunction implements MultivariateMatrixFunction {
+
+    /** Underlying vector-valued function. */
+    private final MultivariateDifferentiableVectorFunction f;
+
+    /** Simple constructor.
+     * @param f underlying vector-valued function
+     */
+    public JacobianFunction(final MultivariateDifferentiableVectorFunction f) {
+        this.f = f;
+    }
+
+    /** {@inheritDoc} */
+    public double[][] value(double[] point) {
+
+        // set up parameters
+        final DerivativeStructure[] dsX = new DerivativeStructure[point.length];
+        for (int i = 0; i < point.length; ++i) {
+            dsX[i] = new DerivativeStructure(point.length, 1, i, point[i]);
+        }
+
+        // compute the derivatives
+        final DerivativeStructure[] dsY = f.value(dsX);
+
+        // extract the Jacobian
+        final double[][] y = new double[dsY.length][point.length];
+        final int[] orders = new int[point.length];
+        for (int i = 0; i < dsY.length; ++i) {
+            for (int j = 0; j < point.length; ++j) {
+                orders[j] = 1;
+                y[i][j] = dsY[i].getPartialDerivative(orders);
+                orders[j] = 0;
+            }
+        }
+
+        return y;
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/differentiation/MultivariateDifferentiableFunction.java b/src/main/java/org/apache/commons/math3/analysis/differentiation/MultivariateDifferentiableFunction.java
new file mode 100644
index 0000000..443671e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/differentiation/MultivariateDifferentiableFunction.java
@@ -0,0 +1,42 @@
+/*
+ * 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.math3.analysis.differentiation;
+
+import org.apache.commons.math3.analysis.MultivariateFunction;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+
+/**
+ * Extension of {@link MultivariateFunction} representing a
+ * multivariate differentiable real function.
+ * @since 3.1
+ */
+public interface MultivariateDifferentiableFunction extends MultivariateFunction {
+
+    /**
+     * Compute the value for the function at the given point.
+     *
+     * @param point Point at which the function must be evaluated.
+     * @return the function value for the given point.
+     * @exception MathIllegalArgumentException if {@code point} does not
+     * satisfy the function's constraints (wrong dimension, argument out of bound,
+     * or unsupported derivative order for example)
+     */
+    DerivativeStructure value(DerivativeStructure[] point)
+        throws MathIllegalArgumentException;
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/differentiation/MultivariateDifferentiableVectorFunction.java b/src/main/java/org/apache/commons/math3/analysis/differentiation/MultivariateDifferentiableVectorFunction.java
new file mode 100644
index 0000000..a5987ae
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/differentiation/MultivariateDifferentiableVectorFunction.java
@@ -0,0 +1,43 @@
+/*
+ * 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.math3.analysis.differentiation;
+
+import org.apache.commons.math3.analysis.MultivariateVectorFunction;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+
+
+/**
+ * Extension of {@link MultivariateVectorFunction} representing a
+ * multivariate differentiable vectorial function.
+ * @since 3.1
+ */
+public interface MultivariateDifferentiableVectorFunction
+    extends MultivariateVectorFunction {
+
+    /**
+     * Compute the value for the function at the given point.
+     * @param point point at which the function must be evaluated
+     * @return function value for the given point
+     * @exception MathIllegalArgumentException if {@code point} does not
+     * satisfy the function's constraints (wrong dimension, argument out of bound,
+     * or unsupported derivative order for example)
+     */
+    DerivativeStructure[] value(DerivativeStructure[] point)
+        throws MathIllegalArgumentException;
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/differentiation/SparseGradient.java b/src/main/java/org/apache/commons/math3/analysis/differentiation/SparseGradient.java
new file mode 100644
index 0000000..8a8d8ae
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/differentiation/SparseGradient.java
@@ -0,0 +1,877 @@
+/*
+ * 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.math3.analysis.differentiation;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.FieldElement;
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.util.MathUtils;
+import org.apache.commons.math3.util.Precision;
+
+/**
+ * First derivative computation with large number of variables.
+ * <p>
+ * This class plays a similar role to {@link DerivativeStructure}, with
+ * a focus on efficiency when dealing with large number of independent variables
+ * and most computation depend only on a few of them, and when only first derivative
+ * is desired. When these conditions are met, this class should be much faster than
+ * {@link DerivativeStructure} and use less memory.
+ * </p>
+ *
+ * @since 3.3
+ */
+public class SparseGradient implements RealFieldElement<SparseGradient>, Serializable {
+
+    /** Serializable UID. */
+    private static final long serialVersionUID = 20131025L;
+
+    /** Value of the calculation. */
+    private double value;
+
+    /** Stored derivative, each key representing a different independent variable. */
+    private final Map<Integer, Double> derivatives;
+
+    /** Internal constructor.
+     * @param value value of the function
+     * @param derivatives derivatives map, a deep copy will be performed,
+     * so the map given here will remain safe from changes in the new instance,
+     * may be null to create an empty derivatives map, i.e. a constant value
+     */
+    private SparseGradient(final double value, final Map<Integer, Double> derivatives) {
+        this.value = value;
+        this.derivatives = new HashMap<Integer, Double>();
+        if (derivatives != null) {
+            this.derivatives.putAll(derivatives);
+        }
+    }
+
+    /** Internal constructor.
+     * @param value value of the function
+     * @param scale scaling factor to apply to all derivatives
+     * @param derivatives derivatives map, a deep copy will be performed,
+     * so the map given here will remain safe from changes in the new instance,
+     * may be null to create an empty derivatives map, i.e. a constant value
+     */
+    private SparseGradient(final double value, final double scale,
+                             final Map<Integer, Double> derivatives) {
+        this.value = value;
+        this.derivatives = new HashMap<Integer, Double>();
+        if (derivatives != null) {
+            for (final Map.Entry<Integer, Double> entry : derivatives.entrySet()) {
+                this.derivatives.put(entry.getKey(), scale * entry.getValue());
+            }
+        }
+    }
+
+    /** Factory method creating a constant.
+     * @param value value of the constant
+     * @return a new instance
+     */
+    public static SparseGradient createConstant(final double value) {
+        return new SparseGradient(value, Collections.<Integer, Double> emptyMap());
+    }
+
+    /** Factory method creating an independent variable.
+     * @param idx index of the variable
+     * @param value value of the variable
+     * @return a new instance
+     */
+    public static SparseGradient createVariable(final int idx, final double value) {
+        return new SparseGradient(value, Collections.singletonMap(idx, 1.0));
+    }
+
+    /**
+     * Find the number of variables.
+     * @return number of variables
+     */
+    public int numVars() {
+        return derivatives.size();
+    }
+
+    /**
+     * Get the derivative with respect to a particular index variable.
+     *
+     * @param index index to differentiate with.
+     * @return derivative with respect to a particular index variable
+     */
+    public double getDerivative(final int index) {
+        final Double out = derivatives.get(index);
+        return (out == null) ? 0.0 : out;
+    }
+
+    /**
+     * Get the value of the function.
+     * @return value of the function.
+     */
+    public double getValue() {
+        return value;
+    }
+
+    /** {@inheritDoc} */
+    public double getReal() {
+        return value;
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient add(final SparseGradient a) {
+        final SparseGradient out = new SparseGradient(value + a.value, derivatives);
+        for (Map.Entry<Integer, Double> entry : a.derivatives.entrySet()) {
+            final int id = entry.getKey();
+            final Double old = out.derivatives.get(id);
+            if (old == null) {
+                out.derivatives.put(id, entry.getValue());
+            } else {
+                out.derivatives.put(id, old + entry.getValue());
+            }
+        }
+
+        return out;
+    }
+
+    /**
+     * Add in place.
+     * <p>
+     * This method is designed to be faster when used multiple times in a loop.
+     * </p>
+     * <p>
+     * The instance is changed here, in order to not change the
+     * instance the {@link #add(SparseGradient)} method should
+     * be used.
+     * </p>
+     * @param a instance to add
+     */
+    public void addInPlace(final SparseGradient a) {
+        value += a.value;
+        for (final Map.Entry<Integer, Double> entry : a.derivatives.entrySet()) {
+            final int id = entry.getKey();
+            final Double old = derivatives.get(id);
+            if (old == null) {
+                derivatives.put(id, entry.getValue());
+            } else {
+                derivatives.put(id, old + entry.getValue());
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient add(final double c) {
+        final SparseGradient out = new SparseGradient(value + c, derivatives);
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient subtract(final SparseGradient a) {
+        final SparseGradient out = new SparseGradient(value - a.value, derivatives);
+        for (Map.Entry<Integer, Double> entry : a.derivatives.entrySet()) {
+            final int id = entry.getKey();
+            final Double old = out.derivatives.get(id);
+            if (old == null) {
+                out.derivatives.put(id, -entry.getValue());
+            } else {
+                out.derivatives.put(id, old - entry.getValue());
+            }
+        }
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient subtract(double c) {
+        return new SparseGradient(value - c, derivatives);
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient multiply(final SparseGradient a) {
+        final SparseGradient out =
+            new SparseGradient(value * a.value, Collections.<Integer, Double> emptyMap());
+
+        // Derivatives.
+        for (Map.Entry<Integer, Double> entry : derivatives.entrySet()) {
+            out.derivatives.put(entry.getKey(), a.value * entry.getValue());
+        }
+        for (Map.Entry<Integer, Double> entry : a.derivatives.entrySet()) {
+            final int id = entry.getKey();
+            final Double old = out.derivatives.get(id);
+            if (old == null) {
+                out.derivatives.put(id, value * entry.getValue());
+            } else {
+                out.derivatives.put(id, old + value * entry.getValue());
+            }
+        }
+        return out;
+    }
+
+    /**
+     * Multiply in place.
+     * <p>
+     * This method is designed to be faster when used multiple times in a loop.
+     * </p>
+     * <p>
+     * The instance is changed here, in order to not change the
+     * instance the {@link #add(SparseGradient)} method should
+     * be used.
+     * </p>
+     * @param a instance to multiply
+     */
+    public void multiplyInPlace(final SparseGradient a) {
+        // Derivatives.
+        for (Map.Entry<Integer, Double> entry : derivatives.entrySet()) {
+            derivatives.put(entry.getKey(), a.value * entry.getValue());
+        }
+        for (Map.Entry<Integer, Double> entry : a.derivatives.entrySet()) {
+            final int id = entry.getKey();
+            final Double old = derivatives.get(id);
+            if (old == null) {
+                derivatives.put(id, value * entry.getValue());
+            } else {
+                derivatives.put(id, old + value * entry.getValue());
+            }
+        }
+        value *= a.value;
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient multiply(final double c) {
+        return new SparseGradient(value * c, c, derivatives);
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient multiply(final int n) {
+        return new SparseGradient(value * n, n, derivatives);
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient divide(final SparseGradient a) {
+        final SparseGradient out = new SparseGradient(value / a.value, Collections.<Integer, Double> emptyMap());
+
+        // Derivatives.
+        for (Map.Entry<Integer, Double> entry : derivatives.entrySet()) {
+            out.derivatives.put(entry.getKey(), entry.getValue() / a.value);
+        }
+        for (Map.Entry<Integer, Double> entry : a.derivatives.entrySet()) {
+            final int id = entry.getKey();
+            final Double old = out.derivatives.get(id);
+            if (old == null) {
+                out.derivatives.put(id, -out.value / a.value * entry.getValue());
+            } else {
+                out.derivatives.put(id, old - out.value / a.value * entry.getValue());
+            }
+        }
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient divide(final double c) {
+        return new SparseGradient(value / c, 1.0 / c, derivatives);
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient negate() {
+        return new SparseGradient(-value, -1.0, derivatives);
+    }
+
+    /** {@inheritDoc} */
+    public Field<SparseGradient> getField() {
+        return new Field<SparseGradient>() {
+
+            /** {@inheritDoc} */
+            public SparseGradient getZero() {
+                return createConstant(0);
+            }
+
+            /** {@inheritDoc} */
+            public SparseGradient getOne() {
+                return createConstant(1);
+            }
+
+            /** {@inheritDoc} */
+            public Class<? extends FieldElement<SparseGradient>> getRuntimeClass() {
+                return SparseGradient.class;
+            }
+
+        };
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient remainder(final double a) {
+        return new SparseGradient(FastMath.IEEEremainder(value, a), derivatives);
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient remainder(final SparseGradient a) {
+
+        // compute k such that lhs % rhs = lhs - k rhs
+        final double rem = FastMath.IEEEremainder(value, a.value);
+        final double k   = FastMath.rint((value - rem) / a.value);
+
+        return subtract(a.multiply(k));
+
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient abs() {
+        if (Double.doubleToLongBits(value) < 0) {
+            // we use the bits representation to also handle -0.0
+            return negate();
+        } else {
+            return this;
+        }
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient ceil() {
+        return createConstant(FastMath.ceil(value));
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient floor() {
+        return createConstant(FastMath.floor(value));
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient rint() {
+        return createConstant(FastMath.rint(value));
+    }
+
+    /** {@inheritDoc} */
+    public long round() {
+        return FastMath.round(value);
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient signum() {
+        return createConstant(FastMath.signum(value));
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient copySign(final SparseGradient sign) {
+        final long m = Double.doubleToLongBits(value);
+        final long s = Double.doubleToLongBits(sign.value);
+        if ((m >= 0 && s >= 0) || (m < 0 && s < 0)) { // Sign is currently OK
+            return this;
+        }
+        return negate(); // flip sign
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient copySign(final double sign) {
+        final long m = Double.doubleToLongBits(value);
+        final long s = Double.doubleToLongBits(sign);
+        if ((m >= 0 && s >= 0) || (m < 0 && s < 0)) { // Sign is currently OK
+            return this;
+        }
+        return negate(); // flip sign
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient scalb(final int n) {
+        final SparseGradient out = new SparseGradient(FastMath.scalb(value, n), Collections.<Integer, Double> emptyMap());
+        for (Map.Entry<Integer, Double> entry : derivatives.entrySet()) {
+            out.derivatives.put(entry.getKey(), FastMath.scalb(entry.getValue(), n));
+        }
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient hypot(final SparseGradient y) {
+        if (Double.isInfinite(value) || Double.isInfinite(y.value)) {
+            return createConstant(Double.POSITIVE_INFINITY);
+        } else if (Double.isNaN(value) || Double.isNaN(y.value)) {
+            return createConstant(Double.NaN);
+        } else {
+
+            final int expX = FastMath.getExponent(value);
+            final int expY = FastMath.getExponent(y.value);
+            if (expX > expY + 27) {
+                // y is negligible with respect to x
+                return abs();
+            } else if (expY > expX + 27) {
+                // x is negligible with respect to y
+                return y.abs();
+            } else {
+
+                // find an intermediate scale to avoid both overflow and underflow
+                final int middleExp = (expX + expY) / 2;
+
+                // scale parameters without losing precision
+                final SparseGradient scaledX = scalb(-middleExp);
+                final SparseGradient scaledY = y.scalb(-middleExp);
+
+                // compute scaled hypotenuse
+                final SparseGradient scaledH =
+                        scaledX.multiply(scaledX).add(scaledY.multiply(scaledY)).sqrt();
+
+                // remove scaling
+                return scaledH.scalb(middleExp);
+
+            }
+
+        }
+    }
+
+    /**
+     * Returns the hypotenuse of a triangle with sides {@code x} and {@code y}
+     * - sqrt(<i>x</i><sup>2</sup>&nbsp;+<i>y</i><sup>2</sup>)
+     * avoiding intermediate overflow or underflow.
+     *
+     * <ul>
+     * <li> If either argument is infinite, then the result is positive infinity.</li>
+     * <li> else, if either argument is NaN then the result is NaN.</li>
+     * </ul>
+     *
+     * @param x a value
+     * @param y a value
+     * @return sqrt(<i>x</i><sup>2</sup>&nbsp;+<i>y</i><sup>2</sup>)
+     */
+    public static SparseGradient hypot(final SparseGradient x, final SparseGradient y) {
+        return x.hypot(y);
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient reciprocal() {
+        return new SparseGradient(1.0 / value, -1.0 / (value * value), derivatives);
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient sqrt() {
+        final double sqrt = FastMath.sqrt(value);
+        return new SparseGradient(sqrt, 0.5 / sqrt, derivatives);
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient cbrt() {
+        final double cbrt = FastMath.cbrt(value);
+        return new SparseGradient(cbrt, 1.0 / (3 * cbrt * cbrt), derivatives);
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient rootN(final int n) {
+        if (n == 2) {
+            return sqrt();
+        } else if (n == 3) {
+            return cbrt();
+        } else {
+            final double root = FastMath.pow(value, 1.0 / n);
+            return new SparseGradient(root, 1.0 / (n * FastMath.pow(root, n - 1)), derivatives);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient pow(final double p) {
+        return new SparseGradient(FastMath.pow(value,  p), p * FastMath.pow(value,  p - 1), derivatives);
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient pow(final int n) {
+        if (n == 0) {
+            return getField().getOne();
+        } else {
+            final double valueNm1 = FastMath.pow(value,  n - 1);
+            return new SparseGradient(value * valueNm1, n * valueNm1, derivatives);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient pow(final SparseGradient e) {
+        return log().multiply(e).exp();
+    }
+
+    /** Compute a<sup>x</sup> where a is a double and x a {@link SparseGradient}
+     * @param a number to exponentiate
+     * @param x power to apply
+     * @return a<sup>x</sup>
+     */
+    public static SparseGradient pow(final double a, final SparseGradient x) {
+        if (a == 0) {
+            if (x.value == 0) {
+                return x.compose(1.0, Double.NEGATIVE_INFINITY);
+            } else if (x.value < 0) {
+                return x.compose(Double.NaN, Double.NaN);
+            } else {
+                return x.getField().getZero();
+            }
+        } else {
+            final double ax = FastMath.pow(a, x.value);
+            return new SparseGradient(ax, ax * FastMath.log(a), x.derivatives);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient exp() {
+        final double e = FastMath.exp(value);
+        return new SparseGradient(e, e, derivatives);
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient expm1() {
+        return new SparseGradient(FastMath.expm1(value), FastMath.exp(value), derivatives);
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient log() {
+        return new SparseGradient(FastMath.log(value), 1.0 / value, derivatives);
+    }
+
+    /** Base 10 logarithm.
+     * @return base 10 logarithm of the instance
+     */
+    public SparseGradient log10() {
+        return new SparseGradient(FastMath.log10(value), 1.0 / (FastMath.log(10.0) * value), derivatives);
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient log1p() {
+        return new SparseGradient(FastMath.log1p(value), 1.0 / (1.0 + value), derivatives);
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient cos() {
+        return new SparseGradient(FastMath.cos(value), -FastMath.sin(value), derivatives);
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient sin() {
+        return new SparseGradient(FastMath.sin(value), FastMath.cos(value), derivatives);
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient tan() {
+        final double t = FastMath.tan(value);
+        return new SparseGradient(t, 1 + t * t, derivatives);
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient acos() {
+        return new SparseGradient(FastMath.acos(value), -1.0 / FastMath.sqrt(1 - value * value), derivatives);
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient asin() {
+        return new SparseGradient(FastMath.asin(value), 1.0 / FastMath.sqrt(1 - value * value), derivatives);
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient atan() {
+        return new SparseGradient(FastMath.atan(value), 1.0 / (1 + value * value), derivatives);
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient atan2(final SparseGradient x) {
+
+        // compute r = sqrt(x^2+y^2)
+        final SparseGradient r = multiply(this).add(x.multiply(x)).sqrt();
+
+        final SparseGradient a;
+        if (x.value >= 0) {
+
+            // compute atan2(y, x) = 2 atan(y / (r + x))
+            a = divide(r.add(x)).atan().multiply(2);
+
+        } else {
+
+            // compute atan2(y, x) = +/- pi - 2 atan(y / (r - x))
+            final SparseGradient tmp = divide(r.subtract(x)).atan().multiply(-2);
+            a = tmp.add(tmp.value <= 0 ? -FastMath.PI : FastMath.PI);
+
+        }
+
+        // fix value to take special cases (+0/+0, +0/-0, -0/+0, -0/-0, +/-infinity) correctly
+        a.value = FastMath.atan2(value, x.value);
+
+        return a;
+
+    }
+
+    /** Two arguments arc tangent operation.
+     * @param y first argument of the arc tangent
+     * @param x second argument of the arc tangent
+     * @return atan2(y, x)
+     */
+    public static SparseGradient atan2(final SparseGradient y, final SparseGradient x) {
+        return y.atan2(x);
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient cosh() {
+        return new SparseGradient(FastMath.cosh(value), FastMath.sinh(value), derivatives);
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient sinh() {
+        return new SparseGradient(FastMath.sinh(value), FastMath.cosh(value), derivatives);
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient tanh() {
+        final double t = FastMath.tanh(value);
+        return new SparseGradient(t, 1 - t * t, derivatives);
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient acosh() {
+        return new SparseGradient(FastMath.acosh(value), 1.0 / FastMath.sqrt(value * value - 1.0), derivatives);
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient asinh() {
+        return new SparseGradient(FastMath.asinh(value), 1.0 / FastMath.sqrt(value * value + 1.0), derivatives);
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient atanh() {
+        return new SparseGradient(FastMath.atanh(value), 1.0 / (1.0 - value * value), derivatives);
+    }
+
+    /** Convert radians to degrees, with error of less than 0.5 ULP
+     *  @return instance converted into degrees
+     */
+    public SparseGradient toDegrees() {
+        return new SparseGradient(FastMath.toDegrees(value), FastMath.toDegrees(1.0), derivatives);
+    }
+
+    /** Convert degrees to radians, with error of less than 0.5 ULP
+     *  @return instance converted into radians
+     */
+    public SparseGradient toRadians() {
+        return new SparseGradient(FastMath.toRadians(value), FastMath.toRadians(1.0), derivatives);
+    }
+
+    /** Evaluate Taylor expansion of a sparse gradient.
+     * @param delta parameters offsets (&Delta;x, &Delta;y, ...)
+     * @return value of the Taylor expansion at x + &Delta;x, y + &Delta;y, ...
+     */
+    public double taylor(final double ... delta) {
+        double y = value;
+        for (int i = 0; i < delta.length; ++i) {
+            y += delta[i] * getDerivative(i);
+        }
+        return y;
+    }
+
+    /** Compute composition of the instance by a univariate function.
+     * @param f0 value of the function at (i.e. f({@link #getValue()}))
+     * @param f1 first derivative of the function at
+     * the current point (i.e. f'({@link #getValue()}))
+     * @return f(this)
+    */
+    public SparseGradient compose(final double f0, final double f1) {
+        return new SparseGradient(f0, f1, derivatives);
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient linearCombination(final SparseGradient[] a,
+                                              final SparseGradient[] b)
+        throws DimensionMismatchException {
+
+        // compute a simple value, with all partial derivatives
+        SparseGradient out = a[0].getField().getZero();
+        for (int i = 0; i < a.length; ++i) {
+            out = out.add(a[i].multiply(b[i]));
+        }
+
+        // recompute an accurate value, taking care of cancellations
+        final double[] aDouble = new double[a.length];
+        for (int i = 0; i < a.length; ++i) {
+            aDouble[i] = a[i].getValue();
+        }
+        final double[] bDouble = new double[b.length];
+        for (int i = 0; i < b.length; ++i) {
+            bDouble[i] = b[i].getValue();
+        }
+        out.value = MathArrays.linearCombination(aDouble, bDouble);
+
+        return out;
+
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient linearCombination(final double[] a, final SparseGradient[] b) {
+
+        // compute a simple value, with all partial derivatives
+        SparseGradient out = b[0].getField().getZero();
+        for (int i = 0; i < a.length; ++i) {
+            out = out.add(b[i].multiply(a[i]));
+        }
+
+        // recompute an accurate value, taking care of cancellations
+        final double[] bDouble = new double[b.length];
+        for (int i = 0; i < b.length; ++i) {
+            bDouble[i] = b[i].getValue();
+        }
+        out.value = MathArrays.linearCombination(a, bDouble);
+
+        return out;
+
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient linearCombination(final SparseGradient a1, final SparseGradient b1,
+                                              final SparseGradient a2, final SparseGradient b2) {
+
+        // compute a simple value, with all partial derivatives
+        SparseGradient out = a1.multiply(b1).add(a2.multiply(b2));
+
+        // recompute an accurate value, taking care of cancellations
+        out.value = MathArrays.linearCombination(a1.value, b1.value, a2.value, b2.value);
+
+        return out;
+
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient linearCombination(final double a1, final SparseGradient b1,
+                                              final double a2, final SparseGradient b2) {
+
+        // compute a simple value, with all partial derivatives
+        SparseGradient out = b1.multiply(a1).add(b2.multiply(a2));
+
+        // recompute an accurate value, taking care of cancellations
+        out.value = MathArrays.linearCombination(a1, b1.value, a2, b2.value);
+
+        return out;
+
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient linearCombination(final SparseGradient a1, final SparseGradient b1,
+                                              final SparseGradient a2, final SparseGradient b2,
+                                              final SparseGradient a3, final SparseGradient b3) {
+
+        // compute a simple value, with all partial derivatives
+        SparseGradient out = a1.multiply(b1).add(a2.multiply(b2)).add(a3.multiply(b3));
+
+        // recompute an accurate value, taking care of cancellations
+        out.value = MathArrays.linearCombination(a1.value, b1.value,
+                                                 a2.value, b2.value,
+                                                 a3.value, b3.value);
+
+        return out;
+
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient linearCombination(final double a1, final SparseGradient b1,
+                                              final double a2, final SparseGradient b2,
+                                              final double a3, final SparseGradient b3) {
+
+        // compute a simple value, with all partial derivatives
+        SparseGradient out = b1.multiply(a1).add(b2.multiply(a2)).add(b3.multiply(a3));
+
+        // recompute an accurate value, taking care of cancellations
+        out.value = MathArrays.linearCombination(a1, b1.value,
+                                                 a2, b2.value,
+                                                 a3, b3.value);
+
+        return out;
+
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient linearCombination(final SparseGradient a1, final SparseGradient b1,
+                                              final SparseGradient a2, final SparseGradient b2,
+                                              final SparseGradient a3, final SparseGradient b3,
+                                              final SparseGradient a4, final SparseGradient b4) {
+
+        // compute a simple value, with all partial derivatives
+        SparseGradient out = a1.multiply(b1).add(a2.multiply(b2)).add(a3.multiply(b3)).add(a4.multiply(b4));
+
+        // recompute an accurate value, taking care of cancellations
+        out.value = MathArrays.linearCombination(a1.value, b1.value,
+                                                 a2.value, b2.value,
+                                                 a3.value, b3.value,
+                                                 a4.value, b4.value);
+
+        return out;
+
+    }
+
+    /** {@inheritDoc} */
+    public SparseGradient linearCombination(final double a1, final SparseGradient b1,
+                                              final double a2, final SparseGradient b2,
+                                              final double a3, final SparseGradient b3,
+                                              final double a4, final SparseGradient b4) {
+
+        // compute a simple value, with all partial derivatives
+        SparseGradient out = b1.multiply(a1).add(b2.multiply(a2)).add(b3.multiply(a3)).add(b4.multiply(a4));
+
+        // recompute an accurate value, taking care of cancellations
+        out.value = MathArrays.linearCombination(a1, b1.value,
+                                                 a2, b2.value,
+                                                 a3, b3.value,
+                                                 a4, b4.value);
+
+        return out;
+
+    }
+
+    /**
+     * Test for the equality of two sparse gradients.
+     * <p>
+     * Sparse gradients are considered equal if they have the same value
+     * and the same derivatives.
+     * </p>
+     * @param other Object to test for equality to this
+     * @return true if two sparse gradients are equal
+     */
+    @Override
+    public boolean equals(Object other) {
+
+        if (this == other) {
+            return true;
+        }
+
+        if (other instanceof SparseGradient) {
+            final SparseGradient rhs = (SparseGradient)other;
+            if (!Precision.equals(value, rhs.value, 1)) {
+                return false;
+            }
+            if (derivatives.size() != rhs.derivatives.size()) {
+                return false;
+            }
+            for (final Map.Entry<Integer, Double> entry : derivatives.entrySet()) {
+                if (!rhs.derivatives.containsKey(entry.getKey())) {
+                    return false;
+                }
+                if (!Precision.equals(entry.getValue(), rhs.derivatives.get(entry.getKey()), 1)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        return false;
+
+    }
+
+    /**
+     * Get a hashCode for the derivative structure.
+     * @return a hash code value for this object
+     * @since 3.2
+     */
+    @Override
+    public int hashCode() {
+        return 743 + 809 * MathUtils.hash(value) + 167 * derivatives.hashCode();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/differentiation/UnivariateDifferentiableFunction.java b/src/main/java/org/apache/commons/math3/analysis/differentiation/UnivariateDifferentiableFunction.java
new file mode 100644
index 0000000..097b4e0
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/differentiation/UnivariateDifferentiableFunction.java
@@ -0,0 +1,43 @@
+/*
+ * 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.math3.analysis.differentiation;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+
+/** Interface for univariate functions derivatives.
+ * <p>This interface represents a simple function which computes
+ * both the value and the first derivative of a mathematical function.
+ * The derivative is computed with respect to the input variable.</p>
+ * @see UnivariateDifferentiableFunction
+ * @see UnivariateFunctionDifferentiator
+ * @since 3.1
+ */
+public interface UnivariateDifferentiableFunction extends UnivariateFunction {
+
+    /** Simple mathematical function.
+     * <p>{@link UnivariateDifferentiableFunction} classes compute both the
+     * value and the first derivative of the function.</p>
+     * @param t function input value
+     * @return function result
+     * @exception DimensionMismatchException if t is inconsistent with the
+     * function's free parameters or order
+     */
+    DerivativeStructure value(DerivativeStructure t)
+        throws DimensionMismatchException;
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/differentiation/UnivariateDifferentiableMatrixFunction.java b/src/main/java/org/apache/commons/math3/analysis/differentiation/UnivariateDifferentiableMatrixFunction.java
new file mode 100644
index 0000000..b31771b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/differentiation/UnivariateDifferentiableMatrixFunction.java
@@ -0,0 +1,40 @@
+/*
+ * 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.math3.analysis.differentiation;
+
+import org.apache.commons.math3.analysis.UnivariateMatrixFunction;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+
+/**
+ * Extension of {@link UnivariateMatrixFunction} representing a univariate differentiable matrix function.
+ *
+ * @since 3.1
+ */
+public interface UnivariateDifferentiableMatrixFunction
+    extends UnivariateMatrixFunction {
+
+    /**
+     * Compute the value for the function.
+     * @param x the point for which the function value should be computed
+     * @return the value
+     * @exception MathIllegalArgumentException if {@code x} does not
+     * satisfy the function's constraints (argument out of bound, or unsupported
+     * derivative order for example)
+     */
+    DerivativeStructure[][] value(DerivativeStructure x) throws MathIllegalArgumentException;
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/differentiation/UnivariateDifferentiableVectorFunction.java b/src/main/java/org/apache/commons/math3/analysis/differentiation/UnivariateDifferentiableVectorFunction.java
new file mode 100644
index 0000000..7e79eef
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/differentiation/UnivariateDifferentiableVectorFunction.java
@@ -0,0 +1,40 @@
+/*
+ * 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.math3.analysis.differentiation;
+
+import org.apache.commons.math3.analysis.UnivariateVectorFunction;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+
+/**
+ * Extension of {@link UnivariateVectorFunction} representing a univariate differentiable vectorial function.
+ *
+ * @since 3.1
+ */
+public interface UnivariateDifferentiableVectorFunction
+    extends UnivariateVectorFunction {
+
+    /**
+     * Compute the value for the function.
+     * @param x the point for which the function value should be computed
+     * @return the value
+     * @exception MathIllegalArgumentException if {@code x} does not
+     * satisfy the function's constraints (argument out of bound, or unsupported
+     * derivative order for example)
+     */
+    DerivativeStructure[] value(DerivativeStructure x) throws MathIllegalArgumentException;
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/differentiation/UnivariateFunctionDifferentiator.java b/src/main/java/org/apache/commons/math3/analysis/differentiation/UnivariateFunctionDifferentiator.java
new file mode 100644
index 0000000..f19ce20
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/differentiation/UnivariateFunctionDifferentiator.java
@@ -0,0 +1,33 @@
+/*
+ * 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.math3.analysis.differentiation;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+
+/** Interface defining the function differentiation operation.
+ * @since 3.1
+ */
+public interface UnivariateFunctionDifferentiator {
+
+    /** Create an implementation of a {@link UnivariateDifferentiableFunction
+     * differential} from a regular {@link UnivariateFunction function}.
+     * @param function function to differentiate
+     * @return differential function
+     */
+    UnivariateDifferentiableFunction differentiate(UnivariateFunction function);
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/differentiation/UnivariateMatrixFunctionDifferentiator.java b/src/main/java/org/apache/commons/math3/analysis/differentiation/UnivariateMatrixFunctionDifferentiator.java
new file mode 100644
index 0000000..bc0ccf3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/differentiation/UnivariateMatrixFunctionDifferentiator.java
@@ -0,0 +1,33 @@
+/*
+ * 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.math3.analysis.differentiation;
+
+import org.apache.commons.math3.analysis.UnivariateMatrixFunction;
+
+/** Interface defining the function differentiation operation.
+ * @since 3.1
+ */
+public interface UnivariateMatrixFunctionDifferentiator {
+
+    /** Create an implementation of a {@link UnivariateDifferentiableMatrixFunction
+     * differential} from a regular {@link UnivariateMatrixFunction matrix function}.
+     * @param function function to differentiate
+     * @return differential function
+     */
+    UnivariateDifferentiableMatrixFunction differentiate(UnivariateMatrixFunction function);
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/differentiation/UnivariateVectorFunctionDifferentiator.java b/src/main/java/org/apache/commons/math3/analysis/differentiation/UnivariateVectorFunctionDifferentiator.java
new file mode 100644
index 0000000..5500c50
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/differentiation/UnivariateVectorFunctionDifferentiator.java
@@ -0,0 +1,33 @@
+/*
+ * 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.math3.analysis.differentiation;
+
+import org.apache.commons.math3.analysis.UnivariateVectorFunction;
+
+/** Interface defining the function differentiation operation.
+ * @since 3.1
+ */
+public interface UnivariateVectorFunctionDifferentiator {
+
+    /** Create an implementation of a {@link UnivariateDifferentiableVectorFunction
+     * differential} from a regular {@link UnivariateVectorFunction vector function}.
+     * @param function function to differentiate
+     * @return differential function
+     */
+    UnivariateDifferentiableVectorFunction differentiate(UnivariateVectorFunction function);
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/differentiation/package-info.java b/src/main/java/org/apache/commons/math3/analysis/differentiation/package-info.java
new file mode 100644
index 0000000..b828981
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/differentiation/package-info.java
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * <p>
+ *   This package holds the main interfaces and basic building block classes
+ *   dealing with differentiation.
+ *   The core class is {@link org.apache.commons.math3.analysis.differentiation.DerivativeStructure
+ *   DerivativeStructure} which holds the value and the differentials of a function. This class
+ *   handles some arbitrary number of free parameters and arbitrary differentiation order. It is used
+ *   both as the input and the output type for the {@link
+ *   org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction
+ *   UnivariateDifferentiableFunction} interface. Any differentiable function should implement this
+ *   interface.
+ * </p>
+ * <p>
+ *   The {@link org.apache.commons.math3.analysis.differentiation.UnivariateFunctionDifferentiator
+ *   UnivariateFunctionDifferentiator} interface defines a way to differentiate a simple {@link
+ *   org.apache.commons.math3.analysis.UnivariateFunction UnivariateFunction} and get a {@link
+ *   org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction
+ *   UnivariateDifferentiableFunction}.
+ * </p>
+ * <p>
+ *   Similar interfaces also exist for multivariate functions and for vector or matrix valued functions.
+ * </p>
+ *
+ */
+package org.apache.commons.math3.analysis.differentiation;
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Abs.java b/src/main/java/org/apache/commons/math3/analysis/function/Abs.java
new file mode 100644
index 0000000..9db01fd
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Abs.java
@@ -0,0 +1,33 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Absolute value function.
+ *
+ * @since 3.0
+ */
+public class Abs implements UnivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return FastMath.abs(x);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Acos.java b/src/main/java/org/apache/commons/math3/analysis/function/Acos.java
new file mode 100644
index 0000000..a9df4b7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Acos.java
@@ -0,0 +1,53 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.FunctionUtils;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Arc-cosine function.
+ *
+ * @since 3.0
+ */
+public class Acos implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return FastMath.acos(x);
+    }
+
+    /** {@inheritDoc}
+     * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)}
+     */
+    @Deprecated
+    public UnivariateFunction derivative() {
+        return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative();
+    }
+
+    /** {@inheritDoc}
+     * @since 3.1
+     */
+    public DerivativeStructure value(final DerivativeStructure t) {
+        return t.acos();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Acosh.java b/src/main/java/org/apache/commons/math3/analysis/function/Acosh.java
new file mode 100644
index 0000000..58fb19f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Acosh.java
@@ -0,0 +1,53 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.FunctionUtils;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Hyperbolic arc-cosine function.
+ *
+ * @since 3.0
+ */
+public class Acosh implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return FastMath.acosh(x);
+    }
+
+    /** {@inheritDoc}
+     * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)}
+     */
+    @Deprecated
+    public UnivariateFunction derivative() {
+        return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative();
+    }
+
+    /** {@inheritDoc}
+     * @since 3.1
+     */
+    public DerivativeStructure value(final DerivativeStructure t) {
+        return t.acosh();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Add.java b/src/main/java/org/apache/commons/math3/analysis/function/Add.java
new file mode 100644
index 0000000..366a303
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Add.java
@@ -0,0 +1,32 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.BivariateFunction;
+
+/**
+ * Add the two operands.
+ *
+ * @since 3.0
+ */
+public class Add implements BivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x, double y) {
+        return x + y;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Asin.java b/src/main/java/org/apache/commons/math3/analysis/function/Asin.java
new file mode 100644
index 0000000..8fa9bdf
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Asin.java
@@ -0,0 +1,53 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.FunctionUtils;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Arc-sine function.
+ *
+ * @since 3.0
+ */
+public class Asin implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return FastMath.asin(x);
+    }
+
+    /** {@inheritDoc}
+     * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)}
+     */
+    @Deprecated
+    public UnivariateFunction derivative() {
+        return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative();
+    }
+
+    /** {@inheritDoc}
+     * @since 3.1
+     */
+    public DerivativeStructure value(final DerivativeStructure t) {
+        return t.asin();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Asinh.java b/src/main/java/org/apache/commons/math3/analysis/function/Asinh.java
new file mode 100644
index 0000000..b5b9fd2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Asinh.java
@@ -0,0 +1,53 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.FunctionUtils;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Hyperbolic arc-sine function.
+ *
+ * @since 3.0
+ */
+public class Asinh implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return FastMath.asinh(x);
+    }
+
+    /** {@inheritDoc}
+     * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)}
+     */
+    @Deprecated
+    public UnivariateFunction derivative() {
+        return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative();
+    }
+
+    /** {@inheritDoc}
+     * @since 3.1
+     */
+    public DerivativeStructure value(final DerivativeStructure t) {
+        return t.asinh();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Atan.java b/src/main/java/org/apache/commons/math3/analysis/function/Atan.java
new file mode 100644
index 0000000..36b1265
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Atan.java
@@ -0,0 +1,53 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.FunctionUtils;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Arc-tangent function.
+ *
+ * @since 3.0
+ */
+public class Atan implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return FastMath.atan(x);
+    }
+
+    /** {@inheritDoc}
+     * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)}
+     */
+    @Deprecated
+    public UnivariateFunction derivative() {
+        return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative();
+    }
+
+    /** {@inheritDoc}
+     * @since 3.1
+     */
+    public DerivativeStructure value(final DerivativeStructure t) {
+        return t.atan();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Atan2.java b/src/main/java/org/apache/commons/math3/analysis/function/Atan2.java
new file mode 100644
index 0000000..d5f385f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Atan2.java
@@ -0,0 +1,33 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.BivariateFunction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Arc-tangent function.
+ *
+ * @since 3.0
+ */
+public class Atan2 implements BivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x, double y) {
+        return FastMath.atan2(x, y);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Atanh.java b/src/main/java/org/apache/commons/math3/analysis/function/Atanh.java
new file mode 100644
index 0000000..5c04599
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Atanh.java
@@ -0,0 +1,53 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.FunctionUtils;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Hyperbolic arc-tangent function.
+ *
+ * @since 3.0
+ */
+public class Atanh implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return FastMath.atanh(x);
+    }
+
+    /** {@inheritDoc}
+     * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)}
+     */
+    @Deprecated
+    public UnivariateFunction derivative() {
+        return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative();
+    }
+
+    /** {@inheritDoc}
+     * @since 3.1
+     */
+    public DerivativeStructure value(final DerivativeStructure t) {
+        return t.atanh();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Cbrt.java b/src/main/java/org/apache/commons/math3/analysis/function/Cbrt.java
new file mode 100644
index 0000000..f26ef71
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Cbrt.java
@@ -0,0 +1,53 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.FunctionUtils;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Cube root function.
+ *
+ * @since 3.0
+ */
+public class Cbrt implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return FastMath.cbrt(x);
+    }
+
+    /** {@inheritDoc}
+     * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)}
+     */
+    @Deprecated
+    public UnivariateFunction derivative() {
+        return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative();
+    }
+
+    /** {@inheritDoc}
+     * @since 3.1
+     */
+    public DerivativeStructure value(final DerivativeStructure t) {
+        return t.cbrt();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Ceil.java b/src/main/java/org/apache/commons/math3/analysis/function/Ceil.java
new file mode 100644
index 0000000..2b9867e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Ceil.java
@@ -0,0 +1,33 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * {@code ceil} function.
+ *
+ * @since 3.0
+ */
+public class Ceil implements UnivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return FastMath.ceil(x);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Constant.java b/src/main/java/org/apache/commons/math3/analysis/function/Constant.java
new file mode 100644
index 0000000..4027e59
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Constant.java
@@ -0,0 +1,60 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+
+/**
+ * Constant function.
+ *
+ * @since 3.0
+ */
+public class Constant implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction {
+    /** Constant. */
+    private final double c;
+
+    /**
+     * @param c Constant.
+     */
+    public Constant(double c) {
+        this.c = c;
+    }
+
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return c;
+    }
+
+    /** {@inheritDoc}
+     * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)}
+     */
+    @Deprecated
+    public DifferentiableUnivariateFunction derivative() {
+        return new Constant(0);
+    }
+
+    /** {@inheritDoc}
+     * @since 3.1
+     */
+    public DerivativeStructure value(final DerivativeStructure t) {
+        return new DerivativeStructure(t.getFreeParameters(), t.getOrder(), c);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Cos.java b/src/main/java/org/apache/commons/math3/analysis/function/Cos.java
new file mode 100644
index 0000000..73a5e6e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Cos.java
@@ -0,0 +1,53 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.FunctionUtils;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Cosine function.
+ *
+ * @since 3.0
+ */
+public class Cos implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return FastMath.cos(x);
+    }
+
+    /** {@inheritDoc}
+     * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)}
+     */
+    @Deprecated
+    public UnivariateFunction derivative() {
+        return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative();
+    }
+
+    /** {@inheritDoc}
+     * @since 3.1
+     */
+    public DerivativeStructure value(final DerivativeStructure t) {
+        return t.cos();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Cosh.java b/src/main/java/org/apache/commons/math3/analysis/function/Cosh.java
new file mode 100644
index 0000000..185698b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Cosh.java
@@ -0,0 +1,51 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Hyperbolic cosine function.
+ *
+ * @since 3.0
+ */
+public class Cosh implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return FastMath.cosh(x);
+    }
+
+    /** {@inheritDoc}
+     * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)}
+     */
+    @Deprecated
+    public DifferentiableUnivariateFunction derivative() {
+        return new Sinh();
+    }
+
+    /** {@inheritDoc}
+     * @since 3.1
+     */
+    public DerivativeStructure value(final DerivativeStructure t) {
+        return t.cosh();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Divide.java b/src/main/java/org/apache/commons/math3/analysis/function/Divide.java
new file mode 100644
index 0000000..73413a2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Divide.java
@@ -0,0 +1,32 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.BivariateFunction;
+
+/**
+ * Divide the first operand by the second.
+ *
+ * @since 3.0
+ */
+public class Divide implements BivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x, double y) {
+        return x / y;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Exp.java b/src/main/java/org/apache/commons/math3/analysis/function/Exp.java
new file mode 100644
index 0000000..f656712
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Exp.java
@@ -0,0 +1,53 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.FunctionUtils;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Exponential function.
+ *
+ * @since 3.0
+ */
+public class Exp implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return FastMath.exp(x);
+    }
+
+    /** {@inheritDoc}
+     * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)}
+     */
+    @Deprecated
+    public UnivariateFunction derivative() {
+        return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative();
+    }
+
+    /** {@inheritDoc}
+     * @since 3.1
+     */
+    public DerivativeStructure value(final DerivativeStructure t) {
+        return t.exp();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Expm1.java b/src/main/java/org/apache/commons/math3/analysis/function/Expm1.java
new file mode 100644
index 0000000..46b0b2f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Expm1.java
@@ -0,0 +1,53 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.FunctionUtils;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * <code>e<sup>x</sup>-1</code> function.
+ *
+ * @since 3.0
+ */
+public class Expm1 implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return FastMath.expm1(x);
+    }
+
+    /** {@inheritDoc}
+     * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)}
+     */
+    @Deprecated
+    public UnivariateFunction derivative() {
+        return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative();
+    }
+
+    /** {@inheritDoc}
+     * @since 3.1
+     */
+    public DerivativeStructure value(final DerivativeStructure t) {
+        return t.expm1();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Floor.java b/src/main/java/org/apache/commons/math3/analysis/function/Floor.java
new file mode 100644
index 0000000..8d70627
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Floor.java
@@ -0,0 +1,33 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * {@code floor} function.
+ *
+ * @since 3.0
+ */
+public class Floor implements UnivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return FastMath.floor(x);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Gaussian.java b/src/main/java/org/apache/commons/math3/analysis/function/Gaussian.java
new file mode 100644
index 0000000..8c64c8b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Gaussian.java
@@ -0,0 +1,259 @@
+/*
+ * 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.math3.analysis.function;
+
+import java.util.Arrays;
+
+import org.apache.commons.math3.analysis.FunctionUtils;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.analysis.ParametricUnivariateFunction;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.Precision;
+
+/**
+ * <a href="http://en.wikipedia.org/wiki/Gaussian_function">
+ *  Gaussian</a> function.
+ *
+ * @since 3.0
+ */
+public class Gaussian implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction {
+    /** Mean. */
+    private final double mean;
+    /** Inverse of the standard deviation. */
+    private final double is;
+    /** Inverse of twice the square of the standard deviation. */
+    private final double i2s2;
+    /** Normalization factor. */
+    private final double norm;
+
+    /**
+     * Gaussian with given normalization factor, mean and standard deviation.
+     *
+     * @param norm Normalization factor.
+     * @param mean Mean.
+     * @param sigma Standard deviation.
+     * @throws NotStrictlyPositiveException if {@code sigma <= 0}.
+     */
+    public Gaussian(double norm,
+                    double mean,
+                    double sigma)
+        throws NotStrictlyPositiveException {
+        if (sigma <= 0) {
+            throw new NotStrictlyPositiveException(sigma);
+        }
+
+        this.norm = norm;
+        this.mean = mean;
+        this.is   = 1 / sigma;
+        this.i2s2 = 0.5 * is * is;
+    }
+
+    /**
+     * Normalized gaussian with given mean and standard deviation.
+     *
+     * @param mean Mean.
+     * @param sigma Standard deviation.
+     * @throws NotStrictlyPositiveException if {@code sigma <= 0}.
+     */
+    public Gaussian(double mean,
+                    double sigma)
+        throws NotStrictlyPositiveException {
+        this(1 / (sigma * FastMath.sqrt(2 * Math.PI)), mean, sigma);
+    }
+
+    /**
+     * Normalized gaussian with zero mean and unit standard deviation.
+     */
+    public Gaussian() {
+        this(0, 1);
+    }
+
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return value(x - mean, norm, i2s2);
+    }
+
+    /** {@inheritDoc}
+     * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)}
+     */
+    @Deprecated
+    public UnivariateFunction derivative() {
+        return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative();
+    }
+
+    /**
+     * Parametric function where the input array contains the parameters of
+     * the Gaussian, ordered as follows:
+     * <ul>
+     *  <li>Norm</li>
+     *  <li>Mean</li>
+     *  <li>Standard deviation</li>
+     * </ul>
+     */
+    public static class Parametric implements ParametricUnivariateFunction {
+        /**
+         * Computes the value of the Gaussian at {@code x}.
+         *
+         * @param x Value for which the function must be computed.
+         * @param param Values of norm, mean and standard deviation.
+         * @return the value of the function.
+         * @throws NullArgumentException if {@code param} is {@code null}.
+         * @throws DimensionMismatchException if the size of {@code param} is
+         * not 3.
+         * @throws NotStrictlyPositiveException if {@code param[2]} is negative.
+         */
+        public double value(double x, double ... param)
+            throws NullArgumentException,
+                   DimensionMismatchException,
+                   NotStrictlyPositiveException {
+            validateParameters(param);
+
+            final double diff = x - param[1];
+            final double i2s2 = 1 / (2 * param[2] * param[2]);
+            return Gaussian.value(diff, param[0], i2s2);
+        }
+
+        /**
+         * Computes the value of the gradient at {@code x}.
+         * The components of the gradient vector are the partial
+         * derivatives of the function with respect to each of the
+         * <em>parameters</em> (norm, mean and standard deviation).
+         *
+         * @param x Value at which the gradient must be computed.
+         * @param param Values of norm, mean and standard deviation.
+         * @return the gradient vector at {@code x}.
+         * @throws NullArgumentException if {@code param} is {@code null}.
+         * @throws DimensionMismatchException if the size of {@code param} is
+         * not 3.
+         * @throws NotStrictlyPositiveException if {@code param[2]} is negative.
+         */
+        public double[] gradient(double x, double ... param)
+            throws NullArgumentException,
+                   DimensionMismatchException,
+                   NotStrictlyPositiveException {
+            validateParameters(param);
+
+            final double norm = param[0];
+            final double diff = x - param[1];
+            final double sigma = param[2];
+            final double i2s2 = 1 / (2 * sigma * sigma);
+
+            final double n = Gaussian.value(diff, 1, i2s2);
+            final double m = norm * n * 2 * i2s2 * diff;
+            final double s = m * diff / sigma;
+
+            return new double[] { n, m, s };
+        }
+
+        /**
+         * Validates parameters to ensure they are appropriate for the evaluation of
+         * the {@link #value(double,double[])} and {@link #gradient(double,double[])}
+         * methods.
+         *
+         * @param param Values of norm, mean and standard deviation.
+         * @throws NullArgumentException if {@code param} is {@code null}.
+         * @throws DimensionMismatchException if the size of {@code param} is
+         * not 3.
+         * @throws NotStrictlyPositiveException if {@code param[2]} is negative.
+         */
+        private void validateParameters(double[] param)
+            throws NullArgumentException,
+                   DimensionMismatchException,
+                   NotStrictlyPositiveException {
+            if (param == null) {
+                throw new NullArgumentException();
+            }
+            if (param.length != 3) {
+                throw new DimensionMismatchException(param.length, 3);
+            }
+            if (param[2] <= 0) {
+                throw new NotStrictlyPositiveException(param[2]);
+            }
+        }
+    }
+
+    /**
+     * @param xMinusMean {@code x - mean}.
+     * @param norm Normalization factor.
+     * @param i2s2 Inverse of twice the square of the standard deviation.
+     * @return the value of the Gaussian at {@code x}.
+     */
+    private static double value(double xMinusMean,
+                                double norm,
+                                double i2s2) {
+        return norm * FastMath.exp(-xMinusMean * xMinusMean * i2s2);
+    }
+
+    /** {@inheritDoc}
+     * @since 3.1
+     */
+    public DerivativeStructure value(final DerivativeStructure t)
+        throws DimensionMismatchException {
+
+        final double u = is * (t.getValue() - mean);
+        double[] f = new double[t.getOrder() + 1];
+
+        // the nth order derivative of the Gaussian has the form:
+        // dn(g(x)/dxn = (norm / s^n) P_n(u) exp(-u^2/2) with u=(x-m)/s
+        // where P_n(u) is a degree n polynomial with same parity as n
+        // P_0(u) = 1, P_1(u) = -u, P_2(u) = u^2 - 1, P_3(u) = -u^3 + 3 u...
+        // the general recurrence relation for P_n is:
+        // P_n(u) = P_(n-1)'(u) - u P_(n-1)(u)
+        // as per polynomial parity, we can store coefficients of both P_(n-1) and P_n in the same array
+        final double[] p = new double[f.length];
+        p[0] = 1;
+        final double u2 = u * u;
+        double coeff = norm * FastMath.exp(-0.5 * u2);
+        if (coeff <= Precision.SAFE_MIN) {
+            Arrays.fill(f, 0.0);
+        } else {
+            f[0] = coeff;
+            for (int n = 1; n < f.length; ++n) {
+
+                // update and evaluate polynomial P_n(x)
+                double v = 0;
+                p[n] = -p[n - 1];
+                for (int k = n; k >= 0; k -= 2) {
+                    v = v * u2 + p[k];
+                    if (k > 2) {
+                        p[k - 2] = (k - 1) * p[k - 1] - p[k - 3];
+                    } else if (k == 2) {
+                        p[0] = p[1];
+                    }
+                }
+                if ((n & 0x1) == 1) {
+                    v *= u;
+                }
+
+                coeff *= is;
+                f[n] = coeff * v;
+
+            }
+        }
+
+        return t.compose(f);
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/HarmonicOscillator.java b/src/main/java/org/apache/commons/math3/analysis/function/HarmonicOscillator.java
new file mode 100644
index 0000000..0fbad9c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/HarmonicOscillator.java
@@ -0,0 +1,183 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.analysis.FunctionUtils;
+import org.apache.commons.math3.analysis.ParametricUnivariateFunction;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * <a href="http://en.wikipedia.org/wiki/Harmonic_oscillator">
+ *  simple harmonic oscillator</a> function.
+ *
+ * @since 3.0
+ */
+public class HarmonicOscillator implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction {
+    /** Amplitude. */
+    private final double amplitude;
+    /** Angular frequency. */
+    private final double omega;
+    /** Phase. */
+    private final double phase;
+
+    /**
+     * Harmonic oscillator function.
+     *
+     * @param amplitude Amplitude.
+     * @param omega Angular frequency.
+     * @param phase Phase.
+     */
+    public HarmonicOscillator(double amplitude,
+                              double omega,
+                              double phase) {
+        this.amplitude = amplitude;
+        this.omega = omega;
+        this.phase = phase;
+    }
+
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return value(omega * x + phase, amplitude);
+    }
+
+    /** {@inheritDoc}
+     * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)}
+     */
+    @Deprecated
+    public UnivariateFunction derivative() {
+        return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative();
+    }
+
+    /**
+     * Parametric function where the input array contains the parameters of
+     * the harmonic oscillator function, ordered as follows:
+     * <ul>
+     *  <li>Amplitude</li>
+     *  <li>Angular frequency</li>
+     *  <li>Phase</li>
+     * </ul>
+     */
+    public static class Parametric implements ParametricUnivariateFunction {
+        /**
+         * Computes the value of the harmonic oscillator at {@code x}.
+         *
+         * @param x Value for which the function must be computed.
+         * @param param Values of norm, mean and standard deviation.
+         * @return the value of the function.
+         * @throws NullArgumentException if {@code param} is {@code null}.
+         * @throws DimensionMismatchException if the size of {@code param} is
+         * not 3.
+         */
+        public double value(double x, double ... param)
+            throws NullArgumentException,
+                   DimensionMismatchException {
+            validateParameters(param);
+            return HarmonicOscillator.value(x * param[1] + param[2], param[0]);
+        }
+
+        /**
+         * Computes the value of the gradient at {@code x}.
+         * The components of the gradient vector are the partial
+         * derivatives of the function with respect to each of the
+         * <em>parameters</em> (amplitude, angular frequency and phase).
+         *
+         * @param x Value at which the gradient must be computed.
+         * @param param Values of amplitude, angular frequency and phase.
+         * @return the gradient vector at {@code x}.
+         * @throws NullArgumentException if {@code param} is {@code null}.
+         * @throws DimensionMismatchException if the size of {@code param} is
+         * not 3.
+         */
+        public double[] gradient(double x, double ... param)
+            throws NullArgumentException,
+                   DimensionMismatchException {
+            validateParameters(param);
+
+            final double amplitude = param[0];
+            final double omega = param[1];
+            final double phase = param[2];
+
+            final double xTimesOmegaPlusPhase = omega * x + phase;
+            final double a = HarmonicOscillator.value(xTimesOmegaPlusPhase, 1);
+            final double p = -amplitude * FastMath.sin(xTimesOmegaPlusPhase);
+            final double w = p * x;
+
+            return new double[] { a, w, p };
+        }
+
+        /**
+         * Validates parameters to ensure they are appropriate for the evaluation of
+         * the {@link #value(double,double[])} and {@link #gradient(double,double[])}
+         * methods.
+         *
+         * @param param Values of norm, mean and standard deviation.
+         * @throws NullArgumentException if {@code param} is {@code null}.
+         * @throws DimensionMismatchException if the size of {@code param} is
+         * not 3.
+         */
+        private void validateParameters(double[] param)
+            throws NullArgumentException,
+                   DimensionMismatchException {
+            if (param == null) {
+                throw new NullArgumentException();
+            }
+            if (param.length != 3) {
+                throw new DimensionMismatchException(param.length, 3);
+            }
+        }
+    }
+
+    /**
+     * @param xTimesOmegaPlusPhase {@code omega * x + phase}.
+     * @param amplitude Amplitude.
+     * @return the value of the harmonic oscillator function at {@code x}.
+     */
+    private static double value(double xTimesOmegaPlusPhase,
+                                double amplitude) {
+        return amplitude * FastMath.cos(xTimesOmegaPlusPhase);
+    }
+
+    /** {@inheritDoc}
+     * @since 3.1
+     */
+    public DerivativeStructure value(final DerivativeStructure t)
+        throws DimensionMismatchException {
+        final double x = t.getValue();
+        double[] f = new double[t.getOrder() + 1];
+
+        final double alpha = omega * x + phase;
+        f[0] = amplitude * FastMath.cos(alpha);
+        if (f.length > 1) {
+            f[1] = -amplitude * omega * FastMath.sin(alpha);
+            final double mo2 = - omega * omega;
+            for (int i = 2; i < f.length; ++i) {
+                f[i] = mo2 * f[i - 2];
+            }
+        }
+
+        return t.compose(f);
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Identity.java b/src/main/java/org/apache/commons/math3/analysis/function/Identity.java
new file mode 100644
index 0000000..d21f7e0
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Identity.java
@@ -0,0 +1,50 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+
+/**
+ * Identity function.
+ *
+ * @since 3.0
+ */
+public class Identity implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return x;
+    }
+
+    /** {@inheritDoc}
+     * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)}
+     */
+    @Deprecated
+    public DifferentiableUnivariateFunction derivative() {
+        return new Constant(1);
+    }
+
+    /** {@inheritDoc}
+     * @since 3.1
+     */
+    public DerivativeStructure value(final DerivativeStructure t) {
+        return t;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Inverse.java b/src/main/java/org/apache/commons/math3/analysis/function/Inverse.java
new file mode 100644
index 0000000..e38f689
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Inverse.java
@@ -0,0 +1,52 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.FunctionUtils;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+
+/**
+ * Inverse function.
+ *
+ * @since 3.0
+ */
+public class Inverse implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return 1 / x;
+    }
+
+    /** {@inheritDoc}
+     * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)}
+     */
+    @Deprecated
+    public UnivariateFunction derivative() {
+        return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative();
+    }
+
+    /** {@inheritDoc}
+     * @since 3.1
+     */
+    public DerivativeStructure value(final DerivativeStructure t) {
+        return t.reciprocal();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Log.java b/src/main/java/org/apache/commons/math3/analysis/function/Log.java
new file mode 100644
index 0000000..a1e12dc
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Log.java
@@ -0,0 +1,53 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.FunctionUtils;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Natural logarithm function.
+ *
+ * @since 3.0
+ */
+public class Log implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return FastMath.log(x);
+    }
+
+    /** {@inheritDoc}
+     * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)}
+     */
+    @Deprecated
+    public UnivariateFunction derivative() {
+        return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative();
+    }
+
+    /** {@inheritDoc}
+     * @since 3.1
+     */
+    public DerivativeStructure value(final DerivativeStructure t) {
+        return t.log();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Log10.java b/src/main/java/org/apache/commons/math3/analysis/function/Log10.java
new file mode 100644
index 0000000..66c03e1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Log10.java
@@ -0,0 +1,54 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.FunctionUtils;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Base 10 logarithm function.
+ *
+ * @since 3.0
+ */
+public class Log10 implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction {
+
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return FastMath.log10(x);
+    }
+
+    /** {@inheritDoc}
+     * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)}
+     */
+    @Deprecated
+    public UnivariateFunction derivative() {
+        return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative();
+    }
+
+    /** {@inheritDoc}
+     * @since 3.1
+     */
+    public DerivativeStructure value(final DerivativeStructure t) {
+        return t.log10();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Log1p.java b/src/main/java/org/apache/commons/math3/analysis/function/Log1p.java
new file mode 100644
index 0000000..4966318
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Log1p.java
@@ -0,0 +1,53 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.FunctionUtils;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * <code>log(1 + p)</code> function.
+ *
+ * @since 3.0
+ */
+public class Log1p implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return FastMath.log1p(x);
+    }
+
+    /** {@inheritDoc}
+     * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)}
+     */
+    @Deprecated
+    public UnivariateFunction derivative() {
+        return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative();
+    }
+
+    /** {@inheritDoc}
+     * @since 3.1
+     */
+    public DerivativeStructure value(final DerivativeStructure t) {
+        return t.log1p();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Logistic.java b/src/main/java/org/apache/commons/math3/analysis/function/Logistic.java
new file mode 100644
index 0000000..c90203c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Logistic.java
@@ -0,0 +1,228 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.FunctionUtils;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.analysis.ParametricUnivariateFunction;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * <a href="http://en.wikipedia.org/wiki/Generalised_logistic_function">
+ *  Generalised logistic</a> function.
+ *
+ * @since 3.0
+ */
+public class Logistic implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction {
+    /** Lower asymptote. */
+    private final double a;
+    /** Upper asymptote. */
+    private final double k;
+    /** Growth rate. */
+    private final double b;
+    /** Parameter that affects near which asymptote maximum growth occurs. */
+    private final double oneOverN;
+    /** Parameter that affects the position of the curve along the ordinate axis. */
+    private final double q;
+    /** Abscissa of maximum growth. */
+    private final double m;
+
+    /**
+     * @param k If {@code b > 0}, value of the function for x going towards +&infin;.
+     * If {@code b < 0}, value of the function for x going towards -&infin;.
+     * @param m Abscissa of maximum growth.
+     * @param b Growth rate.
+     * @param q Parameter that affects the position of the curve along the
+     * ordinate axis.
+     * @param a If {@code b > 0}, value of the function for x going towards -&infin;.
+     * If {@code b < 0}, value of the function for x going towards +&infin;.
+     * @param n Parameter that affects near which asymptote the maximum
+     * growth occurs.
+     * @throws NotStrictlyPositiveException if {@code n <= 0}.
+     */
+    public Logistic(double k,
+                    double m,
+                    double b,
+                    double q,
+                    double a,
+                    double n)
+        throws NotStrictlyPositiveException {
+        if (n <= 0) {
+            throw new NotStrictlyPositiveException(n);
+        }
+
+        this.k = k;
+        this.m = m;
+        this.b = b;
+        this.q = q;
+        this.a = a;
+        oneOverN = 1 / n;
+    }
+
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return value(m - x, k, b, q, a, oneOverN);
+    }
+
+    /** {@inheritDoc}
+     * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)}
+     */
+    @Deprecated
+    public UnivariateFunction derivative() {
+        return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative();
+    }
+
+    /**
+     * Parametric function where the input array contains the parameters of
+     * the {@link Logistic#Logistic(double,double,double,double,double,double)
+     * logistic function}, ordered as follows:
+     * <ul>
+     *  <li>k</li>
+     *  <li>m</li>
+     *  <li>b</li>
+     *  <li>q</li>
+     *  <li>a</li>
+     *  <li>n</li>
+     * </ul>
+     */
+    public static class Parametric implements ParametricUnivariateFunction {
+        /**
+         * Computes the value of the sigmoid at {@code x}.
+         *
+         * @param x Value for which the function must be computed.
+         * @param param Values for {@code k}, {@code m}, {@code b}, {@code q},
+         * {@code a} and  {@code n}.
+         * @return the value of the function.
+         * @throws NullArgumentException if {@code param} is {@code null}.
+         * @throws DimensionMismatchException if the size of {@code param} is
+         * not 6.
+         * @throws NotStrictlyPositiveException if {@code param[5] <= 0}.
+         */
+        public double value(double x, double ... param)
+            throws NullArgumentException,
+                   DimensionMismatchException,
+                   NotStrictlyPositiveException {
+            validateParameters(param);
+            return Logistic.value(param[1] - x, param[0],
+                                  param[2], param[3],
+                                  param[4], 1 / param[5]);
+        }
+
+        /**
+         * Computes the value of the gradient at {@code x}.
+         * The components of the gradient vector are the partial
+         * derivatives of the function with respect to each of the
+         * <em>parameters</em>.
+         *
+         * @param x Value at which the gradient must be computed.
+         * @param param Values for {@code k}, {@code m}, {@code b}, {@code q},
+         * {@code a} and  {@code n}.
+         * @return the gradient vector at {@code x}.
+         * @throws NullArgumentException if {@code param} is {@code null}.
+         * @throws DimensionMismatchException if the size of {@code param} is
+         * not 6.
+         * @throws NotStrictlyPositiveException if {@code param[5] <= 0}.
+         */
+        public double[] gradient(double x, double ... param)
+            throws NullArgumentException,
+                   DimensionMismatchException,
+                   NotStrictlyPositiveException {
+            validateParameters(param);
+
+            final double b = param[2];
+            final double q = param[3];
+
+            final double mMinusX = param[1] - x;
+            final double oneOverN = 1 / param[5];
+            final double exp = FastMath.exp(b * mMinusX);
+            final double qExp = q * exp;
+            final double qExp1 = qExp + 1;
+            final double factor1 = (param[0] - param[4]) * oneOverN / FastMath.pow(qExp1, oneOverN);
+            final double factor2 = -factor1 / qExp1;
+
+            // Components of the gradient.
+            final double gk = Logistic.value(mMinusX, 1, b, q, 0, oneOverN);
+            final double gm = factor2 * b * qExp;
+            final double gb = factor2 * mMinusX * qExp;
+            final double gq = factor2 * exp;
+            final double ga = Logistic.value(mMinusX, 0, b, q, 1, oneOverN);
+            final double gn = factor1 * FastMath.log(qExp1) * oneOverN;
+
+            return new double[] { gk, gm, gb, gq, ga, gn };
+        }
+
+        /**
+         * Validates parameters to ensure they are appropriate for the evaluation of
+         * the {@link #value(double,double[])} and {@link #gradient(double,double[])}
+         * methods.
+         *
+         * @param param Values for {@code k}, {@code m}, {@code b}, {@code q},
+         * {@code a} and {@code n}.
+         * @throws NullArgumentException if {@code param} is {@code null}.
+         * @throws DimensionMismatchException if the size of {@code param} is
+         * not 6.
+         * @throws NotStrictlyPositiveException if {@code param[5] <= 0}.
+         */
+        private void validateParameters(double[] param)
+            throws NullArgumentException,
+                   DimensionMismatchException,
+                   NotStrictlyPositiveException {
+            if (param == null) {
+                throw new NullArgumentException();
+            }
+            if (param.length != 6) {
+                throw new DimensionMismatchException(param.length, 6);
+            }
+            if (param[5] <= 0) {
+                throw new NotStrictlyPositiveException(param[5]);
+            }
+        }
+    }
+
+    /**
+     * @param mMinusX {@code m - x}.
+     * @param k {@code k}.
+     * @param b {@code b}.
+     * @param q {@code q}.
+     * @param a {@code a}.
+     * @param oneOverN {@code 1 / n}.
+     * @return the value of the function.
+     */
+    private static double value(double mMinusX,
+                                double k,
+                                double b,
+                                double q,
+                                double a,
+                                double oneOverN) {
+        return a + (k - a) / FastMath.pow(1 + q * FastMath.exp(b * mMinusX), oneOverN);
+    }
+
+    /** {@inheritDoc}
+     * @since 3.1
+     */
+    public DerivativeStructure value(final DerivativeStructure t) {
+        return t.negate().add(m).multiply(b).exp().multiply(q).add(1).pow(oneOverN).reciprocal().multiply(k - a).add(a);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Logit.java b/src/main/java/org/apache/commons/math3/analysis/function/Logit.java
new file mode 100644
index 0000000..39abe4d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Logit.java
@@ -0,0 +1,212 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.analysis.FunctionUtils;
+import org.apache.commons.math3.analysis.ParametricUnivariateFunction;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * <a href="http://en.wikipedia.org/wiki/Logit">
+ *  Logit</a> function.
+ * It is the inverse of the {@link Sigmoid sigmoid} function.
+ *
+ * @since 3.0
+ */
+public class Logit implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction {
+    /** Lower bound. */
+    private final double lo;
+    /** Higher bound. */
+    private final double hi;
+
+    /**
+     * Usual logit function, where the lower bound is 0 and the higher
+     * bound is 1.
+     */
+    public Logit() {
+        this(0, 1);
+    }
+
+    /**
+     * Logit function.
+     *
+     * @param lo Lower bound of the function domain.
+     * @param hi Higher bound of the function domain.
+     */
+    public Logit(double lo,
+                 double hi) {
+        this.lo = lo;
+        this.hi = hi;
+    }
+
+    /** {@inheritDoc} */
+    public double value(double x)
+        throws OutOfRangeException {
+        return value(x, lo, hi);
+    }
+
+    /** {@inheritDoc}
+     * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)}
+     */
+    @Deprecated
+    public UnivariateFunction derivative() {
+        return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative();
+    }
+
+    /**
+     * Parametric function where the input array contains the parameters of
+     * the logit function, ordered as follows:
+     * <ul>
+     *  <li>Lower bound</li>
+     *  <li>Higher bound</li>
+     * </ul>
+     */
+    public static class Parametric implements ParametricUnivariateFunction {
+        /**
+         * Computes the value of the logit at {@code x}.
+         *
+         * @param x Value for which the function must be computed.
+         * @param param Values of lower bound and higher bounds.
+         * @return the value of the function.
+         * @throws NullArgumentException if {@code param} is {@code null}.
+         * @throws DimensionMismatchException if the size of {@code param} is
+         * not 2.
+         */
+        public double value(double x, double ... param)
+            throws NullArgumentException,
+                   DimensionMismatchException {
+            validateParameters(param);
+            return Logit.value(x, param[0], param[1]);
+        }
+
+        /**
+         * Computes the value of the gradient at {@code x}.
+         * The components of the gradient vector are the partial
+         * derivatives of the function with respect to each of the
+         * <em>parameters</em> (lower bound and higher bound).
+         *
+         * @param x Value at which the gradient must be computed.
+         * @param param Values for lower and higher bounds.
+         * @return the gradient vector at {@code x}.
+         * @throws NullArgumentException if {@code param} is {@code null}.
+         * @throws DimensionMismatchException if the size of {@code param} is
+         * not 2.
+         */
+        public double[] gradient(double x, double ... param)
+            throws NullArgumentException,
+                   DimensionMismatchException {
+            validateParameters(param);
+
+            final double lo = param[0];
+            final double hi = param[1];
+
+            return new double[] { 1 / (lo - x), 1 / (hi - x) };
+        }
+
+        /**
+         * Validates parameters to ensure they are appropriate for the evaluation of
+         * the {@link #value(double,double[])} and {@link #gradient(double,double[])}
+         * methods.
+         *
+         * @param param Values for lower and higher bounds.
+         * @throws NullArgumentException if {@code param} is {@code null}.
+         * @throws DimensionMismatchException if the size of {@code param} is
+         * not 2.
+         */
+        private void validateParameters(double[] param)
+            throws NullArgumentException,
+                   DimensionMismatchException {
+            if (param == null) {
+                throw new NullArgumentException();
+            }
+            if (param.length != 2) {
+                throw new DimensionMismatchException(param.length, 2);
+            }
+        }
+    }
+
+    /**
+     * @param x Value at which to compute the logit.
+     * @param lo Lower bound.
+     * @param hi Higher bound.
+     * @return the value of the logit function at {@code x}.
+     * @throws OutOfRangeException if {@code x < lo} or {@code x > hi}.
+     */
+    private static double value(double x,
+                                double lo,
+                                double hi)
+        throws OutOfRangeException {
+        if (x < lo || x > hi) {
+            throw new OutOfRangeException(x, lo, hi);
+        }
+        return FastMath.log((x - lo) / (hi - x));
+    }
+
+    /** {@inheritDoc}
+     * @since 3.1
+     * @exception OutOfRangeException if parameter is outside of function domain
+     */
+    public DerivativeStructure value(final DerivativeStructure t)
+        throws OutOfRangeException {
+        final double x = t.getValue();
+        if (x < lo || x > hi) {
+            throw new OutOfRangeException(x, lo, hi);
+        }
+        double[] f = new double[t.getOrder() + 1];
+
+        // function value
+        f[0] = FastMath.log((x - lo) / (hi - x));
+
+        if (Double.isInfinite(f[0])) {
+
+            if (f.length > 1) {
+                f[1] = Double.POSITIVE_INFINITY;
+            }
+            // fill the array with infinities
+            // (for x close to lo the signs will flip between -inf and +inf,
+            //  for x close to hi the signs will always be +inf)
+            // this is probably overkill, since the call to compose at the end
+            // of the method will transform most infinities into NaN ...
+            for (int i = 2; i < f.length; ++i) {
+                f[i] = f[i - 2];
+            }
+
+        } else {
+
+            // function derivatives
+            final double invL = 1.0 / (x - lo);
+            double xL = invL;
+            final double invH = 1.0 / (hi - x);
+            double xH = invH;
+            for (int i = 1; i < f.length; ++i) {
+                f[i] = xL + xH;
+                xL  *= -i * invL;
+                xH  *=  i * invH;
+            }
+        }
+
+        return t.compose(f);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Max.java b/src/main/java/org/apache/commons/math3/analysis/function/Max.java
new file mode 100644
index 0000000..591ac55
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Max.java
@@ -0,0 +1,33 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.BivariateFunction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Maximum function.
+ *
+ * @since 3.0
+ */
+public class Max implements BivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x, double y) {
+        return FastMath.max(x, y);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Min.java b/src/main/java/org/apache/commons/math3/analysis/function/Min.java
new file mode 100644
index 0000000..a776b79
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Min.java
@@ -0,0 +1,33 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.BivariateFunction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Minimum function.
+ *
+ * @since 3.0
+ */
+public class Min implements BivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x, double y) {
+        return FastMath.min(x, y);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Minus.java b/src/main/java/org/apache/commons/math3/analysis/function/Minus.java
new file mode 100644
index 0000000..e532779
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Minus.java
@@ -0,0 +1,50 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+
+/**
+ * Minus function.
+ *
+ * @since 3.0
+ */
+public class Minus implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return -x;
+    }
+
+    /** {@inheritDoc}
+     * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)}
+     */
+    @Deprecated
+    public DifferentiableUnivariateFunction derivative() {
+        return new Constant(-1);
+    }
+
+    /** {@inheritDoc}
+     * @since 3.1
+     */
+    public DerivativeStructure value(final DerivativeStructure t) {
+        return t.negate();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Multiply.java b/src/main/java/org/apache/commons/math3/analysis/function/Multiply.java
new file mode 100644
index 0000000..b7e771b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Multiply.java
@@ -0,0 +1,32 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.BivariateFunction;
+
+/**
+ * Multiply the two operands.
+ *
+ * @since 3.0
+ */
+public class Multiply implements BivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x, double y) {
+        return x * y;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Pow.java b/src/main/java/org/apache/commons/math3/analysis/function/Pow.java
new file mode 100644
index 0000000..756dc42
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Pow.java
@@ -0,0 +1,33 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.BivariateFunction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Power function.
+ *
+ * @since 3.0
+ */
+public class Pow implements BivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x, double y) {
+        return FastMath.pow(x, y);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Power.java b/src/main/java/org/apache/commons/math3/analysis/function/Power.java
new file mode 100644
index 0000000..953bcab
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Power.java
@@ -0,0 +1,63 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.FunctionUtils;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Power function.
+ *
+ * @since 3.0
+ */
+public class Power implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction {
+    /** Power. */
+    private final double p;
+
+    /**
+     * @param p Power.
+     */
+    public Power(double p) {
+        this.p = p;
+    }
+
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return FastMath.pow(x, p);
+    }
+
+    /** {@inheritDoc}
+     * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)}
+     */
+    @Deprecated
+    public UnivariateFunction derivative() {
+        return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative();
+    }
+
+    /** {@inheritDoc}
+     * @since 3.1
+     */
+    public DerivativeStructure value(final DerivativeStructure t) {
+        return t.pow(p);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Rint.java b/src/main/java/org/apache/commons/math3/analysis/function/Rint.java
new file mode 100644
index 0000000..4edde58
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Rint.java
@@ -0,0 +1,33 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * {@code rint} function.
+ *
+ * @since 3.0
+ */
+public class Rint implements UnivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return FastMath.rint(x);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Sigmoid.java b/src/main/java/org/apache/commons/math3/analysis/function/Sigmoid.java
new file mode 100644
index 0000000..54639f9
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Sigmoid.java
@@ -0,0 +1,218 @@
+/*
+ * 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.math3.analysis.function;
+
+import java.util.Arrays;
+
+import org.apache.commons.math3.analysis.FunctionUtils;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.analysis.ParametricUnivariateFunction;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * <a href="http://en.wikipedia.org/wiki/Sigmoid_function">
+ *  Sigmoid</a> function.
+ * It is the inverse of the {@link Logit logit} function.
+ * A more flexible version, the generalised logistic, is implemented
+ * by the {@link Logistic} class.
+ *
+ * @since 3.0
+ */
+public class Sigmoid implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction {
+    /** Lower asymptote. */
+    private final double lo;
+    /** Higher asymptote. */
+    private final double hi;
+
+    /**
+     * Usual sigmoid function, where the lower asymptote is 0 and the higher
+     * asymptote is 1.
+     */
+    public Sigmoid() {
+        this(0, 1);
+    }
+
+    /**
+     * Sigmoid function.
+     *
+     * @param lo Lower asymptote.
+     * @param hi Higher asymptote.
+     */
+    public Sigmoid(double lo,
+                   double hi) {
+        this.lo = lo;
+        this.hi = hi;
+    }
+
+    /** {@inheritDoc}
+     * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)}
+     */
+    @Deprecated
+    public UnivariateFunction derivative() {
+        return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative();
+    }
+
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return value(x, lo, hi);
+    }
+
+    /**
+     * Parametric function where the input array contains the parameters of
+     * the {@link Sigmoid#Sigmoid(double,double) sigmoid function}, ordered
+     * as follows:
+     * <ul>
+     *  <li>Lower asymptote</li>
+     *  <li>Higher asymptote</li>
+     * </ul>
+     */
+    public static class Parametric implements ParametricUnivariateFunction {
+        /**
+         * Computes the value of the sigmoid at {@code x}.
+         *
+         * @param x Value for which the function must be computed.
+         * @param param Values of lower asymptote and higher asymptote.
+         * @return the value of the function.
+         * @throws NullArgumentException if {@code param} is {@code null}.
+         * @throws DimensionMismatchException if the size of {@code param} is
+         * not 2.
+         */
+        public double value(double x, double ... param)
+            throws NullArgumentException,
+                   DimensionMismatchException {
+            validateParameters(param);
+            return Sigmoid.value(x, param[0], param[1]);
+        }
+
+        /**
+         * Computes the value of the gradient at {@code x}.
+         * The components of the gradient vector are the partial
+         * derivatives of the function with respect to each of the
+         * <em>parameters</em> (lower asymptote and higher asymptote).
+         *
+         * @param x Value at which the gradient must be computed.
+         * @param param Values for lower asymptote and higher asymptote.
+         * @return the gradient vector at {@code x}.
+         * @throws NullArgumentException if {@code param} is {@code null}.
+         * @throws DimensionMismatchException if the size of {@code param} is
+         * not 2.
+         */
+        public double[] gradient(double x, double ... param)
+            throws NullArgumentException,
+                   DimensionMismatchException {
+            validateParameters(param);
+
+            final double invExp1 = 1 / (1 + FastMath.exp(-x));
+
+            return new double[] { 1 - invExp1, invExp1 };
+        }
+
+        /**
+         * Validates parameters to ensure they are appropriate for the evaluation of
+         * the {@link #value(double,double[])} and {@link #gradient(double,double[])}
+         * methods.
+         *
+         * @param param Values for lower and higher asymptotes.
+         * @throws NullArgumentException if {@code param} is {@code null}.
+         * @throws DimensionMismatchException if the size of {@code param} is
+         * not 2.
+         */
+        private void validateParameters(double[] param)
+            throws NullArgumentException,
+                   DimensionMismatchException {
+            if (param == null) {
+                throw new NullArgumentException();
+            }
+            if (param.length != 2) {
+                throw new DimensionMismatchException(param.length, 2);
+            }
+        }
+    }
+
+    /**
+     * @param x Value at which to compute the sigmoid.
+     * @param lo Lower asymptote.
+     * @param hi Higher asymptote.
+     * @return the value of the sigmoid function at {@code x}.
+     */
+    private static double value(double x,
+                                double lo,
+                                double hi) {
+        return lo + (hi - lo) / (1 + FastMath.exp(-x));
+    }
+
+    /** {@inheritDoc}
+     * @since 3.1
+     */
+    public DerivativeStructure value(final DerivativeStructure t)
+        throws DimensionMismatchException {
+
+        double[] f = new double[t.getOrder() + 1];
+        final double exp = FastMath.exp(-t.getValue());
+        if (Double.isInfinite(exp)) {
+
+            // special handling near lower boundary, to avoid NaN
+            f[0] = lo;
+            Arrays.fill(f, 1, f.length, 0.0);
+
+        } else {
+
+            // the nth order derivative of sigmoid has the form:
+            // dn(sigmoid(x)/dxn = P_n(exp(-x)) / (1+exp(-x))^(n+1)
+            // where P_n(t) is a degree n polynomial with normalized higher term
+            // P_0(t) = 1, P_1(t) = t, P_2(t) = t^2 - t, P_3(t) = t^3 - 4 t^2 + t...
+            // the general recurrence relation for P_n is:
+            // P_n(x) = n t P_(n-1)(t) - t (1 + t) P_(n-1)'(t)
+            final double[] p = new double[f.length];
+
+            final double inv   = 1 / (1 + exp);
+            double coeff = hi - lo;
+            for (int n = 0; n < f.length; ++n) {
+
+                // update and evaluate polynomial P_n(t)
+                double v = 0;
+                p[n] = 1;
+                for (int k = n; k >= 0; --k) {
+                    v = v * exp + p[k];
+                    if (k > 1) {
+                        p[k - 1] = (n - k + 2) * p[k - 2] - (k - 1) * p[k - 1];
+                    } else {
+                        p[0] = 0;
+                    }
+                }
+
+                coeff *= inv;
+                f[n]   = coeff * v;
+
+            }
+
+            // fix function value
+            f[0] += lo;
+
+        }
+
+        return t.compose(f);
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Signum.java b/src/main/java/org/apache/commons/math3/analysis/function/Signum.java
new file mode 100644
index 0000000..ddde66e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Signum.java
@@ -0,0 +1,33 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * {@code signum} function.
+ *
+ * @since 3.0
+ */
+public class Signum implements UnivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return FastMath.signum(x);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Sin.java b/src/main/java/org/apache/commons/math3/analysis/function/Sin.java
new file mode 100644
index 0000000..71c91e7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Sin.java
@@ -0,0 +1,51 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Sine function.
+ *
+ * @since 3.0
+ */
+public class Sin implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return FastMath.sin(x);
+    }
+
+    /** {@inheritDoc}
+     * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)}
+     */
+    @Deprecated
+    public DifferentiableUnivariateFunction derivative() {
+        return new Cos();
+    }
+
+    /** {@inheritDoc}
+     * @since 3.1
+     */
+    public DerivativeStructure value(final DerivativeStructure t) {
+        return t.sin();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Sinc.java b/src/main/java/org/apache/commons/math3/analysis/function/Sinc.java
new file mode 100644
index 0000000..553cfff
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Sinc.java
@@ -0,0 +1,205 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.analysis.FunctionUtils;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * <a href="http://en.wikipedia.org/wiki/Sinc_function">Sinc</a> function,
+ * defined by
+ * <pre><code>
+ *   sinc(x) = 1            if x = 0,
+ *             sin(x) / x   otherwise.
+ * </code></pre>
+ *
+ * @since 3.0
+ */
+public class Sinc implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction {
+    /**
+     * Value below which the computations are done using Taylor series.
+     * <p>
+     * The Taylor series for sinc even order derivatives are:
+     * <pre>
+     * d^(2n)sinc/dx^(2n)     = Sum_(k>=0) (-1)^(n+k) / ((2k)!(2n+2k+1)) x^(2k)
+     *                        = (-1)^n     [ 1/(2n+1) - x^2/(4n+6) + x^4/(48n+120) - x^6/(1440n+5040) + O(x^8) ]
+     * </pre>
+     * </p>
+     * <p>
+     * The Taylor series for sinc odd order derivatives are:
+     * <pre>
+     * d^(2n+1)sinc/dx^(2n+1) = Sum_(k>=0) (-1)^(n+k+1) / ((2k+1)!(2n+2k+3)) x^(2k+1)
+     *                        = (-1)^(n+1) [ x/(2n+3) - x^3/(12n+30) + x^5/(240n+840) - x^7/(10080n+45360) + O(x^9) ]
+     * </pre>
+     * </p>
+     * <p>
+     * So the ratio of the fourth term with respect to the first term
+     * is always smaller than x^6/720, for all derivative orders.
+     * This implies that neglecting this term and using only the first three terms induces
+     * a relative error bounded by x^6/720. The SHORTCUT value is chosen such that this
+     * relative error is below double precision accuracy when |x| <= SHORTCUT.
+     * </p>
+     */
+    private static final double SHORTCUT = 6.0e-3;
+    /** For normalized sinc function. */
+    private final boolean normalized;
+
+    /**
+     * The sinc function, {@code sin(x) / x}.
+     */
+    public Sinc() {
+        this(false);
+    }
+
+    /**
+     * Instantiates the sinc function.
+     *
+     * @param normalized If {@code true}, the function is
+     * <code> sin(&pi;x) / &pi;x</code>, otherwise {@code sin(x) / x}.
+     */
+    public Sinc(boolean normalized) {
+        this.normalized = normalized;
+    }
+
+    /** {@inheritDoc} */
+    public double value(final double x) {
+        final double scaledX = normalized ? FastMath.PI * x : x;
+        if (FastMath.abs(scaledX) <= SHORTCUT) {
+            // use Taylor series
+            final double scaledX2 = scaledX * scaledX;
+            return ((scaledX2 - 20) * scaledX2 + 120) / 120;
+        } else {
+            // use definition expression
+            return FastMath.sin(scaledX) / scaledX;
+        }
+    }
+
+    /** {@inheritDoc}
+     * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)}
+     */
+    @Deprecated
+    public UnivariateFunction derivative() {
+        return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative();
+    }
+
+    /** {@inheritDoc}
+     * @since 3.1
+     */
+    public DerivativeStructure value(final DerivativeStructure t)
+        throws DimensionMismatchException {
+
+        final double scaledX  = (normalized ? FastMath.PI : 1) * t.getValue();
+        final double scaledX2 = scaledX * scaledX;
+
+        double[] f = new double[t.getOrder() + 1];
+
+        if (FastMath.abs(scaledX) <= SHORTCUT) {
+
+            for (int i = 0; i < f.length; ++i) {
+                final int k = i / 2;
+                if ((i & 0x1) == 0) {
+                    // even derivation order
+                    f[i] = (((k & 0x1) == 0) ? 1 : -1) *
+                           (1.0 / (i + 1) - scaledX2 * (1.0 / (2 * i + 6) - scaledX2 / (24 * i + 120)));
+                } else {
+                    // odd derivation order
+                    f[i] = (((k & 0x1) == 0) ? -scaledX : scaledX) *
+                           (1.0 / (i + 2) - scaledX2 * (1.0 / (6 * i + 24) - scaledX2 / (120 * i + 720)));
+                }
+            }
+
+        } else {
+
+            final double inv = 1 / scaledX;
+            final double cos = FastMath.cos(scaledX);
+            final double sin = FastMath.sin(scaledX);
+
+            f[0] = inv * sin;
+
+            // the nth order derivative of sinc has the form:
+            // dn(sinc(x)/dxn = [S_n(x) sin(x) + C_n(x) cos(x)] / x^(n+1)
+            // where S_n(x) is an even polynomial with degree n-1 or n (depending on parity)
+            // and C_n(x) is an odd polynomial with degree n-1 or n (depending on parity)
+            // S_0(x) = 1, S_1(x) = -1, S_2(x) = -x^2 + 2, S_3(x) = 3x^2 - 6...
+            // C_0(x) = 0, C_1(x) = x, C_2(x) = -2x, C_3(x) = -x^3 + 6x...
+            // the general recurrence relations for S_n and C_n are:
+            // S_n(x) = x S_(n-1)'(x) - n S_(n-1)(x) - x C_(n-1)(x)
+            // C_n(x) = x C_(n-1)'(x) - n C_(n-1)(x) + x S_(n-1)(x)
+            // as per polynomials parity, we can store both S_n and C_n in the same array
+            final double[] sc = new double[f.length];
+            sc[0] = 1;
+
+            double coeff = inv;
+            for (int n = 1; n < f.length; ++n) {
+
+                double s = 0;
+                double c = 0;
+
+                // update and evaluate polynomials S_n(x) and C_n(x)
+                final int kStart;
+                if ((n & 0x1) == 0) {
+                    // even derivation order, S_n is degree n and C_n is degree n-1
+                    sc[n] = 0;
+                    kStart = n;
+                } else {
+                    // odd derivation order, S_n is degree n-1 and C_n is degree n
+                    sc[n] = sc[n - 1];
+                    c = sc[n];
+                    kStart = n - 1;
+                }
+
+                // in this loop, k is always even
+                for (int k = kStart; k > 1; k -= 2) {
+
+                    // sine part
+                    sc[k]     = (k - n) * sc[k] - sc[k - 1];
+                    s         = s * scaledX2 + sc[k];
+
+                    // cosine part
+                    sc[k - 1] = (k - 1 - n) * sc[k - 1] + sc[k -2];
+                    c         = c * scaledX2 + sc[k - 1];
+
+                }
+                sc[0] *= -n;
+                s      = s * scaledX2 + sc[0];
+
+                coeff *= inv;
+                f[n]   = coeff * (s * sin + c * scaledX * cos);
+
+            }
+
+        }
+
+        if (normalized) {
+            double scale = FastMath.PI;
+            for (int i = 1; i < f.length; ++i) {
+                f[i]  *= scale;
+                scale *= FastMath.PI;
+            }
+        }
+
+        return t.compose(f);
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Sinh.java b/src/main/java/org/apache/commons/math3/analysis/function/Sinh.java
new file mode 100644
index 0000000..1eac044
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Sinh.java
@@ -0,0 +1,51 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Hyperbolic sine function.
+ *
+ * @since 3.0
+ */
+public class Sinh implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return FastMath.sinh(x);
+    }
+
+    /** {@inheritDoc}
+     * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)}
+     */
+    @Deprecated
+    public DifferentiableUnivariateFunction derivative() {
+        return new Cosh();
+    }
+
+    /** {@inheritDoc}
+     * @since 3.1
+     */
+    public DerivativeStructure value(final DerivativeStructure t) {
+        return t.sinh();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Sqrt.java b/src/main/java/org/apache/commons/math3/analysis/function/Sqrt.java
new file mode 100644
index 0000000..720d44d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Sqrt.java
@@ -0,0 +1,53 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.FunctionUtils;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Square-root function.
+ *
+ * @since 3.0
+ */
+public class Sqrt implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return FastMath.sqrt(x);
+    }
+
+    /** {@inheritDoc}
+     * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)}
+     */
+    @Deprecated
+    public UnivariateFunction derivative() {
+        return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative();
+    }
+
+    /** {@inheritDoc}
+     * @since 3.1
+     */
+    public DerivativeStructure value(final DerivativeStructure t) {
+        return t.sqrt();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/StepFunction.java b/src/main/java/org/apache/commons/math3/analysis/function/StepFunction.java
new file mode 100644
index 0000000..e3d16be
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/StepFunction.java
@@ -0,0 +1,101 @@
+/*
+ * 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.math3.analysis.function;
+
+import java.util.Arrays;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NonMonotonicSequenceException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * <a href="http://en.wikipedia.org/wiki/Step_function">
+ *  Step function</a>.
+ *
+ * @since 3.0
+ */
+public class StepFunction implements UnivariateFunction {
+    /** Abscissae. */
+    private final double[] abscissa;
+    /** Ordinates. */
+    private final double[] ordinate;
+
+    /**
+     * Builds a step function from a list of arguments and the corresponding
+     * values. Specifically, returns the function h(x) defined by <pre><code>
+     * h(x) = y[0] for all x &lt; x[1]
+     *        y[1] for x[1] &le; x &lt; x[2]
+     *        ...
+     *        y[y.length - 1] for x &ge; x[x.length - 1]
+     * </code></pre>
+     * The value of {@code x[0]} is ignored, but it must be strictly less than
+     * {@code x[1]}.
+     *
+     * @param x Domain values where the function changes value.
+     * @param y Values of the function.
+     * @throws NonMonotonicSequenceException
+     * if the {@code x} array is not sorted in strictly increasing order.
+     * @throws NullArgumentException if {@code x} or {@code y} are {@code null}.
+     * @throws NoDataException if {@code x} or {@code y} are zero-length.
+     * @throws DimensionMismatchException if {@code x} and {@code y} do not
+     * have the same length.
+     */
+    public StepFunction(double[] x,
+                        double[] y)
+        throws NullArgumentException, NoDataException,
+               DimensionMismatchException, NonMonotonicSequenceException {
+        if (x == null ||
+            y == null) {
+            throw new NullArgumentException();
+        }
+        if (x.length == 0 ||
+            y.length == 0) {
+            throw new NoDataException();
+        }
+        if (y.length != x.length) {
+            throw new DimensionMismatchException(y.length, x.length);
+        }
+        MathArrays.checkOrder(x);
+
+        abscissa = MathArrays.copyOf(x);
+        ordinate = MathArrays.copyOf(y);
+    }
+
+    /** {@inheritDoc} */
+    public double value(double x) {
+        int index = Arrays.binarySearch(abscissa, x);
+        double fx = 0;
+
+        if (index < -1) {
+            // "x" is between "abscissa[-index-2]" and "abscissa[-index-1]".
+            fx = ordinate[-index-2];
+        } else if (index >= 0) {
+            // "x" is exactly "abscissa[index]".
+            fx = ordinate[index];
+        } else {
+            // Otherwise, "x" is smaller than the first value in "abscissa"
+            // (hence the returned value should be "ordinate[0]").
+            fx = ordinate[0];
+        }
+
+        return fx;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Subtract.java b/src/main/java/org/apache/commons/math3/analysis/function/Subtract.java
new file mode 100644
index 0000000..7b87dd6
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Subtract.java
@@ -0,0 +1,32 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.BivariateFunction;
+
+/**
+ * Subtract the second operand from the first.
+ *
+ * @since 3.0
+ */
+public class Subtract implements BivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x, double y) {
+        return x - y;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Tan.java b/src/main/java/org/apache/commons/math3/analysis/function/Tan.java
new file mode 100644
index 0000000..03304b4
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Tan.java
@@ -0,0 +1,53 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.FunctionUtils;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Tangent function.
+ *
+ * @since 3.0
+ */
+public class Tan implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return FastMath.tan(x);
+    }
+
+    /** {@inheritDoc}
+     * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)}
+     */
+    @Deprecated
+    public UnivariateFunction derivative() {
+        return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative();
+    }
+
+    /** {@inheritDoc}
+     * @since 3.1
+     */
+    public DerivativeStructure value(final DerivativeStructure t) {
+        return t.tan();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Tanh.java b/src/main/java/org/apache/commons/math3/analysis/function/Tanh.java
new file mode 100644
index 0000000..6c7ef0d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Tanh.java
@@ -0,0 +1,53 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.FunctionUtils;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Hyperbolic tangent function.
+ *
+ * @since 3.0
+ */
+public class Tanh implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return FastMath.tanh(x);
+    }
+
+    /** {@inheritDoc}
+     * @deprecated as of 3.1, replaced by {@link #value(DerivativeStructure)}
+     */
+    @Deprecated
+    public UnivariateFunction derivative() {
+        return FunctionUtils.toDifferentiableUnivariateFunction(this).derivative();
+    }
+
+    /** {@inheritDoc}
+     * @since 3.1
+     */
+    public DerivativeStructure value(final DerivativeStructure t) {
+        return t.tanh();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/Ulp.java b/src/main/java/org/apache/commons/math3/analysis/function/Ulp.java
new file mode 100644
index 0000000..d075a73
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/Ulp.java
@@ -0,0 +1,33 @@
+/*
+ * 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.math3.analysis.function;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * {@code ulp} function.
+ *
+ * @since 3.0
+ */
+public class Ulp implements UnivariateFunction {
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return FastMath.ulp(x);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/function/package-info.java b/src/main/java/org/apache/commons/math3/analysis/function/package-info.java
new file mode 100644
index 0000000..cb24544
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/function/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+/**
+ *
+ *    <p>
+ *      The {@code function} package contains function objects that wrap the
+ *      methods contained in {@link java.lang.Math}, as well as common
+ *      mathematical functions such as the gaussian and sinc functions.
+ *    </p>
+ *
+ */
+package org.apache.commons.math3.analysis.function;
diff --git a/src/main/java/org/apache/commons/math3/analysis/integration/BaseAbstractUnivariateIntegrator.java b/src/main/java/org/apache/commons/math3/analysis/integration/BaseAbstractUnivariateIntegrator.java
new file mode 100644
index 0000000..74b959b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/integration/BaseAbstractUnivariateIntegrator.java
@@ -0,0 +1,297 @@
+/*
+ * 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.math3.analysis.integration;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.solvers.UnivariateSolverUtils;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+import org.apache.commons.math3.util.IntegerSequence;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * Provide a default implementation for several generic functions.
+ *
+ * @since 1.2
+ */
+public abstract class BaseAbstractUnivariateIntegrator implements UnivariateIntegrator {
+
+    /** Default absolute accuracy. */
+    public static final double DEFAULT_ABSOLUTE_ACCURACY = 1.0e-15;
+
+    /** Default relative accuracy. */
+    public static final double DEFAULT_RELATIVE_ACCURACY = 1.0e-6;
+
+    /** Default minimal iteration count. */
+    public static final int DEFAULT_MIN_ITERATIONS_COUNT = 3;
+
+    /** Default maximal iteration count. */
+    public static final int DEFAULT_MAX_ITERATIONS_COUNT = Integer.MAX_VALUE;
+
+    /** The iteration count.
+     * @deprecated as of 3.6, this field has been replaced with {@link #incrementCount()}
+     */
+    @Deprecated
+    protected org.apache.commons.math3.util.Incrementor iterations;
+
+    /** The iteration count. */
+    private IntegerSequence.Incrementor count;
+
+    /** Maximum absolute error. */
+    private final double absoluteAccuracy;
+
+    /** Maximum relative error. */
+    private final double relativeAccuracy;
+
+    /** minimum number of iterations */
+    private final int minimalIterationCount;
+
+    /** The functions evaluation count. */
+    private IntegerSequence.Incrementor evaluations;
+
+    /** Function to integrate. */
+    private UnivariateFunction function;
+
+    /** Lower bound for the interval. */
+    private double min;
+
+    /** Upper bound for the interval. */
+    private double max;
+
+    /**
+     * Construct an integrator with given accuracies and iteration counts.
+     * <p>
+     * The meanings of the various parameters are:
+     * <ul>
+     *   <li>relative accuracy:
+     *       this is used to stop iterations if the absolute accuracy can't be
+     *       achieved due to large values or short mantissa length. If this
+     *       should be the primary criterion for convergence rather then a
+     *       safety measure, set the absolute accuracy to a ridiculously small value,
+     *       like {@link org.apache.commons.math3.util.Precision#SAFE_MIN Precision.SAFE_MIN}.</li>
+     *   <li>absolute accuracy:
+     *       The default is usually chosen so that results in the interval
+     *       -10..-0.1 and +0.1..+10 can be found with a reasonable accuracy. If the
+     *       expected absolute value of your results is of much smaller magnitude, set
+     *       this to a smaller value.</li>
+     *   <li>minimum number of iterations:
+     *       minimal iteration is needed to avoid false early convergence, e.g.
+     *       the sample points happen to be zeroes of the function. Users can
+     *       use the default value or choose one that they see as appropriate.</li>
+     *   <li>maximum number of iterations:
+     *       usually a high iteration count indicates convergence problems. However,
+     *       the "reasonable value" varies widely for different algorithms. Users are
+     *       advised to use the default value supplied by the algorithm.</li>
+     * </ul>
+     *
+     * @param relativeAccuracy relative accuracy of the result
+     * @param absoluteAccuracy absolute accuracy of the result
+     * @param minimalIterationCount minimum number of iterations
+     * @param maximalIterationCount maximum number of iterations
+     * @exception NotStrictlyPositiveException if minimal number of iterations
+     * is not strictly positive
+     * @exception NumberIsTooSmallException if maximal number of iterations
+     * is lesser than or equal to the minimal number of iterations
+     */
+    protected BaseAbstractUnivariateIntegrator(final double relativeAccuracy,
+                                               final double absoluteAccuracy,
+                                               final int minimalIterationCount,
+                                               final int maximalIterationCount)
+        throws NotStrictlyPositiveException, NumberIsTooSmallException {
+
+        // accuracy settings
+        this.relativeAccuracy      = relativeAccuracy;
+        this.absoluteAccuracy      = absoluteAccuracy;
+
+        // iterations count settings
+        if (minimalIterationCount <= 0) {
+            throw new NotStrictlyPositiveException(minimalIterationCount);
+        }
+        if (maximalIterationCount <= minimalIterationCount) {
+            throw new NumberIsTooSmallException(maximalIterationCount, minimalIterationCount, false);
+        }
+        this.minimalIterationCount = minimalIterationCount;
+        this.count                 = IntegerSequence.Incrementor.create().withMaximalCount(maximalIterationCount);
+
+        @SuppressWarnings("deprecation")
+        org.apache.commons.math3.util.Incrementor wrapped =
+                        org.apache.commons.math3.util.Incrementor.wrap(count);
+        this.iterations = wrapped;
+
+        // prepare evaluations counter, but do not set it yet
+        evaluations = IntegerSequence.Incrementor.create();
+
+    }
+
+    /**
+     * Construct an integrator with given accuracies.
+     * @param relativeAccuracy relative accuracy of the result
+     * @param absoluteAccuracy absolute accuracy of the result
+     */
+    protected BaseAbstractUnivariateIntegrator(final double relativeAccuracy,
+                                           final double absoluteAccuracy) {
+        this(relativeAccuracy, absoluteAccuracy,
+             DEFAULT_MIN_ITERATIONS_COUNT, DEFAULT_MAX_ITERATIONS_COUNT);
+    }
+
+    /**
+     * Construct an integrator with given iteration counts.
+     * @param minimalIterationCount minimum number of iterations
+     * @param maximalIterationCount maximum number of iterations
+     * @exception NotStrictlyPositiveException if minimal number of iterations
+     * is not strictly positive
+     * @exception NumberIsTooSmallException if maximal number of iterations
+     * is lesser than or equal to the minimal number of iterations
+     */
+    protected BaseAbstractUnivariateIntegrator(final int minimalIterationCount,
+                                           final int maximalIterationCount)
+        throws NotStrictlyPositiveException, NumberIsTooSmallException {
+        this(DEFAULT_RELATIVE_ACCURACY, DEFAULT_ABSOLUTE_ACCURACY,
+             minimalIterationCount, maximalIterationCount);
+    }
+
+    /** {@inheritDoc} */
+    public double getRelativeAccuracy() {
+        return relativeAccuracy;
+    }
+
+    /** {@inheritDoc} */
+    public double getAbsoluteAccuracy() {
+        return absoluteAccuracy;
+    }
+
+    /** {@inheritDoc} */
+    public int getMinimalIterationCount() {
+        return minimalIterationCount;
+    }
+
+    /** {@inheritDoc} */
+    public int getMaximalIterationCount() {
+        return count.getMaximalCount();
+    }
+
+    /** {@inheritDoc} */
+    public int getEvaluations() {
+        return evaluations.getCount();
+    }
+
+    /** {@inheritDoc} */
+    public int getIterations() {
+        return count.getCount();
+    }
+
+    /** Increment the number of iterations.
+     * @exception MaxCountExceededException if the number of iterations
+     * exceeds the allowed maximum number
+     */
+    protected void incrementCount() throws MaxCountExceededException {
+        count.increment();
+    }
+
+    /**
+     * @return the lower bound.
+     */
+    protected double getMin() {
+        return min;
+    }
+    /**
+     * @return the upper bound.
+     */
+    protected double getMax() {
+        return max;
+    }
+
+    /**
+     * Compute the objective function value.
+     *
+     * @param point Point at which the objective function must be evaluated.
+     * @return the objective function value at specified point.
+     * @throws TooManyEvaluationsException if the maximal number of function
+     * evaluations is exceeded.
+     */
+    protected double computeObjectiveValue(final double point)
+        throws TooManyEvaluationsException {
+        try {
+            evaluations.increment();
+        } catch (MaxCountExceededException e) {
+            throw new TooManyEvaluationsException(e.getMax());
+        }
+        return function.value(point);
+    }
+
+    /**
+     * Prepare for computation.
+     * Subclasses must call this method if they override any of the
+     * {@code solve} methods.
+     *
+     * @param maxEval Maximum number of evaluations.
+     * @param f the integrand function
+     * @param lower the min bound for the interval
+     * @param upper the upper bound for the interval
+     * @throws NullArgumentException if {@code f} is {@code null}.
+     * @throws MathIllegalArgumentException if {@code min >= max}.
+     */
+    protected void setup(final int maxEval,
+                         final UnivariateFunction f,
+                         final double lower, final double upper)
+        throws NullArgumentException, MathIllegalArgumentException {
+
+        // Checks.
+        MathUtils.checkNotNull(f);
+        UnivariateSolverUtils.verifyInterval(lower, upper);
+
+        // Reset.
+        min = lower;
+        max = upper;
+        function = f;
+        evaluations = evaluations.withMaximalCount(maxEval).withStart(0);
+        count       = count.withStart(0);
+
+    }
+
+    /** {@inheritDoc} */
+    public double integrate(final int maxEval, final UnivariateFunction f,
+                            final double lower, final double upper)
+        throws TooManyEvaluationsException, MaxCountExceededException,
+               MathIllegalArgumentException, NullArgumentException {
+
+        // Initialization.
+        setup(maxEval, f, lower, upper);
+
+        // Perform computation.
+        return doIntegrate();
+
+    }
+
+    /**
+     * Method for implementing actual integration algorithms in derived
+     * classes.
+     *
+     * @return the root.
+     * @throws TooManyEvaluationsException if the maximal number of evaluations
+     * is exceeded.
+     * @throws MaxCountExceededException if the maximum iteration count is exceeded
+     * or the integrator detects convergence problems otherwise
+     */
+    protected abstract double doIntegrate()
+        throws TooManyEvaluationsException, MaxCountExceededException;
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/integration/IterativeLegendreGaussIntegrator.java b/src/main/java/org/apache/commons/math3/analysis/integration/IterativeLegendreGaussIntegrator.java
new file mode 100644
index 0000000..20700dd
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/integration/IterativeLegendreGaussIntegrator.java
@@ -0,0 +1,183 @@
+/*
+ * 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.math3.analysis.integration;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.integration.gauss.GaussIntegratorFactory;
+import org.apache.commons.math3.analysis.integration.gauss.GaussIntegrator;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * This algorithm divides the integration interval into equally-sized
+ * sub-interval and on each of them performs a
+ * <a href="http://mathworld.wolfram.com/Legendre-GaussQuadrature.html">
+ * Legendre-Gauss</a> quadrature.
+ * Because of its <em>non-adaptive</em> nature, this algorithm can
+ * converge to a wrong value for the integral (for example, if the
+ * function is significantly different from zero toward the ends of the
+ * integration interval).
+ * In particular, a change of variables aimed at estimating integrals
+ * over infinite intervals as proposed
+ * <a href="http://en.wikipedia.org/w/index.php?title=Numerical_integration#Integrals_over_infinite_intervals">
+ *  here</a> should be avoided when using this class.
+ *
+ * @since 3.1
+ */
+
+public class IterativeLegendreGaussIntegrator
+    extends BaseAbstractUnivariateIntegrator {
+    /** Factory that computes the points and weights. */
+    private static final GaussIntegratorFactory FACTORY
+        = new GaussIntegratorFactory();
+    /** Number of integration points (per interval). */
+    private final int numberOfPoints;
+
+    /**
+     * Builds an integrator with given accuracies and iterations counts.
+     *
+     * @param n Number of integration points.
+     * @param relativeAccuracy Relative accuracy of the result.
+     * @param absoluteAccuracy Absolute accuracy of the result.
+     * @param minimalIterationCount Minimum number of iterations.
+     * @param maximalIterationCount Maximum number of iterations.
+     * @throws NotStrictlyPositiveException if minimal number of iterations
+     * or number of points are not strictly positive.
+     * @throws NumberIsTooSmallException if maximal number of iterations
+     * is smaller than or equal to the minimal number of iterations.
+     */
+    public IterativeLegendreGaussIntegrator(final int n,
+                                            final double relativeAccuracy,
+                                            final double absoluteAccuracy,
+                                            final int minimalIterationCount,
+                                            final int maximalIterationCount)
+        throws NotStrictlyPositiveException, NumberIsTooSmallException {
+        super(relativeAccuracy, absoluteAccuracy, minimalIterationCount, maximalIterationCount);
+        if (n <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.NUMBER_OF_POINTS, n);
+        }
+       numberOfPoints = n;
+    }
+
+    /**
+     * Builds an integrator with given accuracies.
+     *
+     * @param n Number of integration points.
+     * @param relativeAccuracy Relative accuracy of the result.
+     * @param absoluteAccuracy Absolute accuracy of the result.
+     * @throws NotStrictlyPositiveException if {@code n < 1}.
+     */
+    public IterativeLegendreGaussIntegrator(final int n,
+                                            final double relativeAccuracy,
+                                            final double absoluteAccuracy)
+        throws NotStrictlyPositiveException {
+        this(n, relativeAccuracy, absoluteAccuracy,
+             DEFAULT_MIN_ITERATIONS_COUNT, DEFAULT_MAX_ITERATIONS_COUNT);
+    }
+
+    /**
+     * Builds an integrator with given iteration counts.
+     *
+     * @param n Number of integration points.
+     * @param minimalIterationCount Minimum number of iterations.
+     * @param maximalIterationCount Maximum number of iterations.
+     * @throws NotStrictlyPositiveException if minimal number of iterations
+     * is not strictly positive.
+     * @throws NumberIsTooSmallException if maximal number of iterations
+     * is smaller than or equal to the minimal number of iterations.
+     * @throws NotStrictlyPositiveException if {@code n < 1}.
+     */
+    public IterativeLegendreGaussIntegrator(final int n,
+                                            final int minimalIterationCount,
+                                            final int maximalIterationCount)
+        throws NotStrictlyPositiveException, NumberIsTooSmallException {
+        this(n, DEFAULT_RELATIVE_ACCURACY, DEFAULT_ABSOLUTE_ACCURACY,
+             minimalIterationCount, maximalIterationCount);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected double doIntegrate()
+        throws MathIllegalArgumentException, TooManyEvaluationsException, MaxCountExceededException {
+        // Compute first estimate with a single step.
+        double oldt = stage(1);
+
+        int n = 2;
+        while (true) {
+            // Improve integral with a larger number of steps.
+            final double t = stage(n);
+
+            // Estimate the error.
+            final double delta = FastMath.abs(t - oldt);
+            final double limit =
+                FastMath.max(getAbsoluteAccuracy(),
+                             getRelativeAccuracy() * (FastMath.abs(oldt) + FastMath.abs(t)) * 0.5);
+
+            // check convergence
+            if (getIterations() + 1 >= getMinimalIterationCount() &&
+                delta <= limit) {
+                return t;
+            }
+
+            // Prepare next iteration.
+            final double ratio = FastMath.min(4, FastMath.pow(delta / limit, 0.5 / numberOfPoints));
+            n = FastMath.max((int) (ratio * n), n + 1);
+            oldt = t;
+            incrementCount();
+        }
+    }
+
+    /**
+     * Compute the n-th stage integral.
+     *
+     * @param n Number of steps.
+     * @return the value of n-th stage integral.
+     * @throws TooManyEvaluationsException if the maximum number of evaluations
+     * is exceeded.
+     */
+    private double stage(final int n)
+        throws TooManyEvaluationsException {
+        // Function to be integrated is stored in the base class.
+        final UnivariateFunction f = new UnivariateFunction() {
+                /** {@inheritDoc} */
+                public double value(double x)
+                    throws MathIllegalArgumentException, TooManyEvaluationsException {
+                    return computeObjectiveValue(x);
+                }
+            };
+
+        final double min = getMin();
+        final double max = getMax();
+        final double step = (max - min) / n;
+
+        double sum = 0;
+        for (int i = 0; i < n; i++) {
+            // Integrate over each sub-interval [a, b].
+            final double a = min + i * step;
+            final double b = a + step;
+            final GaussIntegrator g = FACTORY.legendreHighPrecision(numberOfPoints, a, b);
+            sum += g.integrate(f);
+        }
+
+        return sum;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/integration/LegendreGaussIntegrator.java b/src/main/java/org/apache/commons/math3/analysis/integration/LegendreGaussIntegrator.java
new file mode 100644
index 0000000..bfb24a1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/integration/LegendreGaussIntegrator.java
@@ -0,0 +1,265 @@
+/*
+ * 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.math3.analysis.integration;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Implements the <a href="http://mathworld.wolfram.com/Legendre-GaussQuadrature.html">
+ * Legendre-Gauss</a> quadrature formula.
+ * <p>
+ * Legendre-Gauss integrators are efficient integrators that can
+ * accurately integrate functions with few function evaluations. A
+ * Legendre-Gauss integrator using an n-points quadrature formula can
+ * integrate 2n-1 degree polynomials exactly.
+ * </p>
+ * <p>
+ * These integrators evaluate the function on n carefully chosen
+ * abscissas in each step interval (mapped to the canonical [-1,1] interval).
+ * The evaluation abscissas are not evenly spaced and none of them are
+ * at the interval endpoints. This implies the function integrated can be
+ * undefined at integration interval endpoints.
+ * </p>
+ * <p>
+ * The evaluation abscissas x<sub>i</sub> are the roots of the degree n
+ * Legendre polynomial. The weights a<sub>i</sub> of the quadrature formula
+ * integrals from -1 to +1 &int; Li<sup>2</sup> where Li (x) =
+ * &prod; (x-x<sub>k</sub>)/(x<sub>i</sub>-x<sub>k</sub>) for k != i.
+ * </p>
+ * <p>
+ * @since 1.2
+ * @deprecated As of 3.1 (to be removed in 4.0). Please use
+ * {@link IterativeLegendreGaussIntegrator} instead.
+ */
+@Deprecated
+public class LegendreGaussIntegrator extends BaseAbstractUnivariateIntegrator {
+
+    /** Abscissas for the 2 points method. */
+    private static final double[] ABSCISSAS_2 = {
+        -1.0 / FastMath.sqrt(3.0),
+         1.0 / FastMath.sqrt(3.0)
+    };
+
+    /** Weights for the 2 points method. */
+    private static final double[] WEIGHTS_2 = {
+        1.0,
+        1.0
+    };
+
+    /** Abscissas for the 3 points method. */
+    private static final double[] ABSCISSAS_3 = {
+        -FastMath.sqrt(0.6),
+         0.0,
+         FastMath.sqrt(0.6)
+    };
+
+    /** Weights for the 3 points method. */
+    private static final double[] WEIGHTS_3 = {
+        5.0 / 9.0,
+        8.0 / 9.0,
+        5.0 / 9.0
+    };
+
+    /** Abscissas for the 4 points method. */
+    private static final double[] ABSCISSAS_4 = {
+        -FastMath.sqrt((15.0 + 2.0 * FastMath.sqrt(30.0)) / 35.0),
+        -FastMath.sqrt((15.0 - 2.0 * FastMath.sqrt(30.0)) / 35.0),
+         FastMath.sqrt((15.0 - 2.0 * FastMath.sqrt(30.0)) / 35.0),
+         FastMath.sqrt((15.0 + 2.0 * FastMath.sqrt(30.0)) / 35.0)
+    };
+
+    /** Weights for the 4 points method. */
+    private static final double[] WEIGHTS_4 = {
+        (90.0 - 5.0 * FastMath.sqrt(30.0)) / 180.0,
+        (90.0 + 5.0 * FastMath.sqrt(30.0)) / 180.0,
+        (90.0 + 5.0 * FastMath.sqrt(30.0)) / 180.0,
+        (90.0 - 5.0 * FastMath.sqrt(30.0)) / 180.0
+    };
+
+    /** Abscissas for the 5 points method. */
+    private static final double[] ABSCISSAS_5 = {
+        -FastMath.sqrt((35.0 + 2.0 * FastMath.sqrt(70.0)) / 63.0),
+        -FastMath.sqrt((35.0 - 2.0 * FastMath.sqrt(70.0)) / 63.0),
+         0.0,
+         FastMath.sqrt((35.0 - 2.0 * FastMath.sqrt(70.0)) / 63.0),
+         FastMath.sqrt((35.0 + 2.0 * FastMath.sqrt(70.0)) / 63.0)
+    };
+
+    /** Weights for the 5 points method. */
+    private static final double[] WEIGHTS_5 = {
+        (322.0 - 13.0 * FastMath.sqrt(70.0)) / 900.0,
+        (322.0 + 13.0 * FastMath.sqrt(70.0)) / 900.0,
+        128.0 / 225.0,
+        (322.0 + 13.0 * FastMath.sqrt(70.0)) / 900.0,
+        (322.0 - 13.0 * FastMath.sqrt(70.0)) / 900.0
+    };
+
+    /** Abscissas for the current method. */
+    private final double[] abscissas;
+
+    /** Weights for the current method. */
+    private final double[] weights;
+
+    /**
+     * Build a Legendre-Gauss integrator with given accuracies and iterations counts.
+     * @param n number of points desired (must be between 2 and 5 inclusive)
+     * @param relativeAccuracy relative accuracy of the result
+     * @param absoluteAccuracy absolute accuracy of the result
+     * @param minimalIterationCount minimum number of iterations
+     * @param maximalIterationCount maximum number of iterations
+     * @exception MathIllegalArgumentException if number of points is out of [2; 5]
+     * @exception NotStrictlyPositiveException if minimal number of iterations
+     * is not strictly positive
+     * @exception NumberIsTooSmallException if maximal number of iterations
+     * is lesser than or equal to the minimal number of iterations
+     */
+    public LegendreGaussIntegrator(final int n,
+                                   final double relativeAccuracy,
+                                   final double absoluteAccuracy,
+                                   final int minimalIterationCount,
+                                   final int maximalIterationCount)
+        throws MathIllegalArgumentException, NotStrictlyPositiveException, NumberIsTooSmallException {
+        super(relativeAccuracy, absoluteAccuracy, minimalIterationCount, maximalIterationCount);
+        switch(n) {
+        case 2 :
+            abscissas = ABSCISSAS_2;
+            weights   = WEIGHTS_2;
+            break;
+        case 3 :
+            abscissas = ABSCISSAS_3;
+            weights   = WEIGHTS_3;
+            break;
+        case 4 :
+            abscissas = ABSCISSAS_4;
+            weights   = WEIGHTS_4;
+            break;
+        case 5 :
+            abscissas = ABSCISSAS_5;
+            weights   = WEIGHTS_5;
+            break;
+        default :
+            throw new MathIllegalArgumentException(
+                    LocalizedFormats.N_POINTS_GAUSS_LEGENDRE_INTEGRATOR_NOT_SUPPORTED,
+                    n, 2, 5);
+        }
+
+    }
+
+    /**
+     * Build a Legendre-Gauss integrator with given accuracies.
+     * @param n number of points desired (must be between 2 and 5 inclusive)
+     * @param relativeAccuracy relative accuracy of the result
+     * @param absoluteAccuracy absolute accuracy of the result
+     * @exception MathIllegalArgumentException if number of points is out of [2; 5]
+     */
+    public LegendreGaussIntegrator(final int n,
+                                   final double relativeAccuracy,
+                                   final double absoluteAccuracy)
+        throws MathIllegalArgumentException {
+        this(n, relativeAccuracy, absoluteAccuracy,
+             DEFAULT_MIN_ITERATIONS_COUNT, DEFAULT_MAX_ITERATIONS_COUNT);
+    }
+
+    /**
+     * Build a Legendre-Gauss integrator with given iteration counts.
+     * @param n number of points desired (must be between 2 and 5 inclusive)
+     * @param minimalIterationCount minimum number of iterations
+     * @param maximalIterationCount maximum number of iterations
+     * @exception MathIllegalArgumentException if number of points is out of [2; 5]
+     * @exception NotStrictlyPositiveException if minimal number of iterations
+     * is not strictly positive
+     * @exception NumberIsTooSmallException if maximal number of iterations
+     * is lesser than or equal to the minimal number of iterations
+     */
+    public LegendreGaussIntegrator(final int n,
+                                   final int minimalIterationCount,
+                                   final int maximalIterationCount)
+        throws MathIllegalArgumentException {
+        this(n, DEFAULT_RELATIVE_ACCURACY, DEFAULT_ABSOLUTE_ACCURACY,
+             minimalIterationCount, maximalIterationCount);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected double doIntegrate()
+        throws MathIllegalArgumentException, TooManyEvaluationsException, MaxCountExceededException {
+
+        // compute first estimate with a single step
+        double oldt = stage(1);
+
+        int n = 2;
+        while (true) {
+
+            // improve integral with a larger number of steps
+            final double t = stage(n);
+
+            // estimate error
+            final double delta = FastMath.abs(t - oldt);
+            final double limit =
+                FastMath.max(getAbsoluteAccuracy(),
+                             getRelativeAccuracy() * (FastMath.abs(oldt) + FastMath.abs(t)) * 0.5);
+
+            // check convergence
+            if ((getIterations() + 1 >= getMinimalIterationCount()) && (delta <= limit)) {
+                return t;
+            }
+
+            // prepare next iteration
+            double ratio = FastMath.min(4, FastMath.pow(delta / limit, 0.5 / abscissas.length));
+            n = FastMath.max((int) (ratio * n), n + 1);
+            oldt = t;
+            incrementCount();
+
+        }
+
+    }
+
+    /**
+     * Compute the n-th stage integral.
+     * @param n number of steps
+     * @return the value of n-th stage integral
+     * @throws TooManyEvaluationsException if the maximum number of evaluations
+     * is exceeded.
+     */
+    private double stage(final int n)
+        throws TooManyEvaluationsException {
+
+        // set up the step for the current stage
+        final double step     = (getMax() - getMin()) / n;
+        final double halfStep = step / 2.0;
+
+        // integrate over all elementary steps
+        double midPoint = getMin() + halfStep;
+        double sum = 0.0;
+        for (int i = 0; i < n; ++i) {
+            for (int j = 0; j < abscissas.length; ++j) {
+                sum += weights[j] * computeObjectiveValue(midPoint + halfStep * abscissas[j]);
+            }
+            midPoint += step;
+        }
+
+        return halfStep * sum;
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/integration/MidPointIntegrator.java b/src/main/java/org/apache/commons/math3/analysis/integration/MidPointIntegrator.java
new file mode 100644
index 0000000..766a917
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/integration/MidPointIntegrator.java
@@ -0,0 +1,169 @@
+/*
+ * 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.math3.analysis.integration;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Implements the <a href="http://en.wikipedia.org/wiki/Midpoint_method">
+ * Midpoint Rule</a> for integration of real univariate functions. For
+ * reference, see <b>Numerical Mathematics</b>, ISBN 0387989595,
+ * chapter 9.2.
+ * <p>
+ * The function should be integrable.</p>
+ *
+ * @since 3.3
+ */
+public class MidPointIntegrator extends BaseAbstractUnivariateIntegrator {
+
+    /** Maximum number of iterations for midpoint. */
+    public static final int MIDPOINT_MAX_ITERATIONS_COUNT = 64;
+
+    /**
+     * Build a midpoint integrator with given accuracies and iterations counts.
+     * @param relativeAccuracy relative accuracy of the result
+     * @param absoluteAccuracy absolute accuracy of the result
+     * @param minimalIterationCount minimum number of iterations
+     * @param maximalIterationCount maximum number of iterations
+     * (must be less than or equal to {@link #MIDPOINT_MAX_ITERATIONS_COUNT}
+     * @exception NotStrictlyPositiveException if minimal number of iterations
+     * is not strictly positive
+     * @exception NumberIsTooSmallException if maximal number of iterations
+     * is lesser than or equal to the minimal number of iterations
+     * @exception NumberIsTooLargeException if maximal number of iterations
+     * is greater than {@link #MIDPOINT_MAX_ITERATIONS_COUNT}
+     */
+    public MidPointIntegrator(final double relativeAccuracy,
+                              final double absoluteAccuracy,
+                              final int minimalIterationCount,
+                              final int maximalIterationCount)
+        throws NotStrictlyPositiveException, NumberIsTooSmallException, NumberIsTooLargeException {
+        super(relativeAccuracy, absoluteAccuracy, minimalIterationCount, maximalIterationCount);
+        if (maximalIterationCount > MIDPOINT_MAX_ITERATIONS_COUNT) {
+            throw new NumberIsTooLargeException(maximalIterationCount,
+                                                MIDPOINT_MAX_ITERATIONS_COUNT, false);
+        }
+    }
+
+    /**
+     * Build a midpoint integrator with given iteration counts.
+     * @param minimalIterationCount minimum number of iterations
+     * @param maximalIterationCount maximum number of iterations
+     * (must be less than or equal to {@link #MIDPOINT_MAX_ITERATIONS_COUNT}
+     * @exception NotStrictlyPositiveException if minimal number of iterations
+     * is not strictly positive
+     * @exception NumberIsTooSmallException if maximal number of iterations
+     * is lesser than or equal to the minimal number of iterations
+     * @exception NumberIsTooLargeException if maximal number of iterations
+     * is greater than {@link #MIDPOINT_MAX_ITERATIONS_COUNT}
+     */
+    public MidPointIntegrator(final int minimalIterationCount,
+                              final int maximalIterationCount)
+        throws NotStrictlyPositiveException, NumberIsTooSmallException, NumberIsTooLargeException {
+        super(minimalIterationCount, maximalIterationCount);
+        if (maximalIterationCount > MIDPOINT_MAX_ITERATIONS_COUNT) {
+            throw new NumberIsTooLargeException(maximalIterationCount,
+                                                MIDPOINT_MAX_ITERATIONS_COUNT, false);
+        }
+    }
+
+    /**
+     * Construct a midpoint integrator with default settings.
+     * (max iteration count set to {@link #MIDPOINT_MAX_ITERATIONS_COUNT})
+     */
+    public MidPointIntegrator() {
+        super(DEFAULT_MIN_ITERATIONS_COUNT, MIDPOINT_MAX_ITERATIONS_COUNT);
+    }
+
+    /**
+     * Compute the n-th stage integral of midpoint rule.
+     * This function should only be called by API <code>integrate()</code> in the package.
+     * To save time it does not verify arguments - caller does.
+     * <p>
+     * The interval is divided equally into 2^n sections rather than an
+     * arbitrary m sections because this configuration can best utilize the
+     * already computed values.</p>
+     *
+     * @param n the stage of 1/2 refinement. Must be larger than 0.
+     * @param previousStageResult Result from the previous call to the
+     * {@code stage} method.
+     * @param min Lower bound of the integration interval.
+     * @param diffMaxMin Difference between the lower bound and upper bound
+     * of the integration interval.
+     * @return the value of n-th stage integral
+     * @throws TooManyEvaluationsException if the maximal number of evaluations
+     * is exceeded.
+     */
+    private double stage(final int n,
+                         double previousStageResult,
+                         double min,
+                         double diffMaxMin)
+        throws TooManyEvaluationsException {
+
+        // number of new points in this stage
+        final long np = 1L << (n - 1);
+        double sum = 0;
+
+        // spacing between adjacent new points
+        final double spacing = diffMaxMin / np;
+
+        // the first new point
+        double x = min + 0.5 * spacing;
+        for (long i = 0; i < np; i++) {
+            sum += computeObjectiveValue(x);
+            x += spacing;
+        }
+        // add the new sum to previously calculated result
+        return 0.5 * (previousStageResult + sum * spacing);
+    }
+
+
+    /** {@inheritDoc} */
+    @Override
+    protected double doIntegrate()
+        throws MathIllegalArgumentException, TooManyEvaluationsException, MaxCountExceededException {
+
+        final double min = getMin();
+        final double diff = getMax() - min;
+        final double midPoint = min + 0.5 * diff;
+
+        double oldt = diff * computeObjectiveValue(midPoint);
+
+        while (true) {
+            incrementCount();
+            final int i = getIterations();
+            final double t = stage(i, oldt, min, diff);
+            if (i >= getMinimalIterationCount()) {
+                final double delta = FastMath.abs(t - oldt);
+                final double rLimit =
+                        getRelativeAccuracy() * (FastMath.abs(oldt) + FastMath.abs(t)) * 0.5;
+                if ((delta <= rLimit) || (delta <= getAbsoluteAccuracy())) {
+                    return t;
+                }
+            }
+            oldt = t;
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/integration/RombergIntegrator.java b/src/main/java/org/apache/commons/math3/analysis/integration/RombergIntegrator.java
new file mode 100644
index 0000000..125d251
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/integration/RombergIntegrator.java
@@ -0,0 +1,142 @@
+/*
+ * 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.math3.analysis.integration;
+
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Implements the <a href="http://mathworld.wolfram.com/RombergIntegration.html">
+ * Romberg Algorithm</a> for integration of real univariate functions. For
+ * reference, see <b>Introduction to Numerical Analysis</b>, ISBN 038795452X,
+ * chapter 3.
+ * <p>
+ * Romberg integration employs k successive refinements of the trapezoid
+ * rule to remove error terms less than order O(N^(-2k)). Simpson's rule
+ * is a special case of k = 2.</p>
+ *
+ * @since 1.2
+ */
+public class RombergIntegrator extends BaseAbstractUnivariateIntegrator {
+
+    /** Maximal number of iterations for Romberg. */
+    public static final int ROMBERG_MAX_ITERATIONS_COUNT = 32;
+
+    /**
+     * Build a Romberg integrator with given accuracies and iterations counts.
+     * @param relativeAccuracy relative accuracy of the result
+     * @param absoluteAccuracy absolute accuracy of the result
+     * @param minimalIterationCount minimum number of iterations
+     * @param maximalIterationCount maximum number of iterations
+     * (must be less than or equal to {@link #ROMBERG_MAX_ITERATIONS_COUNT})
+     * @exception NotStrictlyPositiveException if minimal number of iterations
+     * is not strictly positive
+     * @exception NumberIsTooSmallException if maximal number of iterations
+     * is lesser than or equal to the minimal number of iterations
+     * @exception NumberIsTooLargeException if maximal number of iterations
+     * is greater than {@link #ROMBERG_MAX_ITERATIONS_COUNT}
+     */
+    public RombergIntegrator(final double relativeAccuracy,
+                             final double absoluteAccuracy,
+                             final int minimalIterationCount,
+                             final int maximalIterationCount)
+        throws NotStrictlyPositiveException, NumberIsTooSmallException, NumberIsTooLargeException {
+        super(relativeAccuracy, absoluteAccuracy, minimalIterationCount, maximalIterationCount);
+        if (maximalIterationCount > ROMBERG_MAX_ITERATIONS_COUNT) {
+            throw new NumberIsTooLargeException(maximalIterationCount,
+                                                ROMBERG_MAX_ITERATIONS_COUNT, false);
+        }
+    }
+
+    /**
+     * Build a Romberg integrator with given iteration counts.
+     * @param minimalIterationCount minimum number of iterations
+     * @param maximalIterationCount maximum number of iterations
+     * (must be less than or equal to {@link #ROMBERG_MAX_ITERATIONS_COUNT})
+     * @exception NotStrictlyPositiveException if minimal number of iterations
+     * is not strictly positive
+     * @exception NumberIsTooSmallException if maximal number of iterations
+     * is lesser than or equal to the minimal number of iterations
+     * @exception NumberIsTooLargeException if maximal number of iterations
+     * is greater than {@link #ROMBERG_MAX_ITERATIONS_COUNT}
+     */
+    public RombergIntegrator(final int minimalIterationCount,
+                             final int maximalIterationCount)
+        throws NotStrictlyPositiveException, NumberIsTooSmallException, NumberIsTooLargeException {
+        super(minimalIterationCount, maximalIterationCount);
+        if (maximalIterationCount > ROMBERG_MAX_ITERATIONS_COUNT) {
+            throw new NumberIsTooLargeException(maximalIterationCount,
+                                                ROMBERG_MAX_ITERATIONS_COUNT, false);
+        }
+    }
+
+    /**
+     * Construct a Romberg integrator with default settings
+     * (max iteration count set to {@link #ROMBERG_MAX_ITERATIONS_COUNT})
+     */
+    public RombergIntegrator() {
+        super(DEFAULT_MIN_ITERATIONS_COUNT, ROMBERG_MAX_ITERATIONS_COUNT);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected double doIntegrate()
+        throws TooManyEvaluationsException, MaxCountExceededException {
+
+        final int m = getMaximalIterationCount() + 1;
+        double previousRow[] = new double[m];
+        double currentRow[]  = new double[m];
+
+        TrapezoidIntegrator qtrap = new TrapezoidIntegrator();
+        currentRow[0] = qtrap.stage(this, 0);
+        incrementCount();
+        double olds = currentRow[0];
+        while (true) {
+
+            final int i = getIterations();
+
+            // switch rows
+            final double[] tmpRow = previousRow;
+            previousRow = currentRow;
+            currentRow = tmpRow;
+
+            currentRow[0] = qtrap.stage(this, i);
+            incrementCount();
+            for (int j = 1; j <= i; j++) {
+                // Richardson extrapolation coefficient
+                final double r = (1L << (2 * j)) - 1;
+                final double tIJm1 = currentRow[j - 1];
+                currentRow[j] = tIJm1 + (tIJm1 - previousRow[j - 1]) / r;
+            }
+            final double s = currentRow[i];
+            if (i >= getMinimalIterationCount()) {
+                final double delta  = FastMath.abs(s - olds);
+                final double rLimit = getRelativeAccuracy() * (FastMath.abs(olds) + FastMath.abs(s)) * 0.5;
+                if ((delta <= rLimit) || (delta <= getAbsoluteAccuracy())) {
+                    return s;
+                }
+            }
+            olds = s;
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/integration/SimpsonIntegrator.java b/src/main/java/org/apache/commons/math3/analysis/integration/SimpsonIntegrator.java
new file mode 100644
index 0000000..527bb82
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/integration/SimpsonIntegrator.java
@@ -0,0 +1,129 @@
+/*
+ * 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.math3.analysis.integration;
+
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Implements <a href="http://mathworld.wolfram.com/SimpsonsRule.html">
+ * Simpson's Rule</a> for integration of real univariate functions. For
+ * reference, see <b>Introduction to Numerical Analysis</b>, ISBN 038795452X,
+ * chapter 3.
+ * <p>
+ * This implementation employs the basic trapezoid rule to calculate Simpson's
+ * rule.</p>
+ *
+ * @since 1.2
+ */
+public class SimpsonIntegrator extends BaseAbstractUnivariateIntegrator {
+
+    /** Maximal number of iterations for Simpson. */
+    public static final int SIMPSON_MAX_ITERATIONS_COUNT = 64;
+
+    /**
+     * Build a Simpson integrator with given accuracies and iterations counts.
+     * @param relativeAccuracy relative accuracy of the result
+     * @param absoluteAccuracy absolute accuracy of the result
+     * @param minimalIterationCount minimum number of iterations
+     * @param maximalIterationCount maximum number of iterations
+     * (must be less than or equal to {@link #SIMPSON_MAX_ITERATIONS_COUNT})
+     * @exception NotStrictlyPositiveException if minimal number of iterations
+     * is not strictly positive
+     * @exception NumberIsTooSmallException if maximal number of iterations
+     * is lesser than or equal to the minimal number of iterations
+     * @exception NumberIsTooLargeException if maximal number of iterations
+     * is greater than {@link #SIMPSON_MAX_ITERATIONS_COUNT}
+     */
+    public SimpsonIntegrator(final double relativeAccuracy,
+                             final double absoluteAccuracy,
+                             final int minimalIterationCount,
+                             final int maximalIterationCount)
+        throws NotStrictlyPositiveException, NumberIsTooSmallException, NumberIsTooLargeException {
+        super(relativeAccuracy, absoluteAccuracy, minimalIterationCount, maximalIterationCount);
+        if (maximalIterationCount > SIMPSON_MAX_ITERATIONS_COUNT) {
+            throw new NumberIsTooLargeException(maximalIterationCount,
+                                                SIMPSON_MAX_ITERATIONS_COUNT, false);
+        }
+    }
+
+    /**
+     * Build a Simpson integrator with given iteration counts.
+     * @param minimalIterationCount minimum number of iterations
+     * @param maximalIterationCount maximum number of iterations
+     * (must be less than or equal to {@link #SIMPSON_MAX_ITERATIONS_COUNT})
+     * @exception NotStrictlyPositiveException if minimal number of iterations
+     * is not strictly positive
+     * @exception NumberIsTooSmallException if maximal number of iterations
+     * is lesser than or equal to the minimal number of iterations
+     * @exception NumberIsTooLargeException if maximal number of iterations
+     * is greater than {@link #SIMPSON_MAX_ITERATIONS_COUNT}
+     */
+    public SimpsonIntegrator(final int minimalIterationCount,
+                             final int maximalIterationCount)
+        throws NotStrictlyPositiveException, NumberIsTooSmallException, NumberIsTooLargeException {
+        super(minimalIterationCount, maximalIterationCount);
+        if (maximalIterationCount > SIMPSON_MAX_ITERATIONS_COUNT) {
+            throw new NumberIsTooLargeException(maximalIterationCount,
+                                                SIMPSON_MAX_ITERATIONS_COUNT, false);
+        }
+    }
+
+    /**
+     * Construct an integrator with default settings.
+     * (max iteration count set to {@link #SIMPSON_MAX_ITERATIONS_COUNT})
+     */
+    public SimpsonIntegrator() {
+        super(DEFAULT_MIN_ITERATIONS_COUNT, SIMPSON_MAX_ITERATIONS_COUNT);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected double doIntegrate()
+        throws TooManyEvaluationsException, MaxCountExceededException {
+
+        TrapezoidIntegrator qtrap = new TrapezoidIntegrator();
+        if (getMinimalIterationCount() == 1) {
+            return (4 * qtrap.stage(this, 1) - qtrap.stage(this, 0)) / 3.0;
+        }
+
+        // Simpson's rule requires at least two trapezoid stages.
+        double olds = 0;
+        double oldt = qtrap.stage(this, 0);
+        while (true) {
+            final double t = qtrap.stage(this, getIterations());
+            incrementCount();
+            final double s = (4 * t - oldt) / 3.0;
+            if (getIterations() >= getMinimalIterationCount()) {
+                final double delta = FastMath.abs(s - olds);
+                final double rLimit =
+                    getRelativeAccuracy() * (FastMath.abs(olds) + FastMath.abs(s)) * 0.5;
+                if ((delta <= rLimit) || (delta <= getAbsoluteAccuracy())) {
+                    return s;
+                }
+            }
+            olds = s;
+            oldt = t;
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/integration/TrapezoidIntegrator.java b/src/main/java/org/apache/commons/math3/analysis/integration/TrapezoidIntegrator.java
new file mode 100644
index 0000000..8a737f1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/integration/TrapezoidIntegrator.java
@@ -0,0 +1,168 @@
+/*
+ * 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.math3.analysis.integration;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Implements the <a href="http://mathworld.wolfram.com/TrapezoidalRule.html">
+ * Trapezoid Rule</a> for integration of real univariate functions. For
+ * reference, see <b>Introduction to Numerical Analysis</b>, ISBN 038795452X,
+ * chapter 3.
+ * <p>
+ * The function should be integrable.</p>
+ *
+ * @since 1.2
+ */
+public class TrapezoidIntegrator extends BaseAbstractUnivariateIntegrator {
+
+    /** Maximum number of iterations for trapezoid. */
+    public static final int TRAPEZOID_MAX_ITERATIONS_COUNT = 64;
+
+    /** Intermediate result. */
+    private double s;
+
+    /**
+     * Build a trapezoid integrator with given accuracies and iterations counts.
+     * @param relativeAccuracy relative accuracy of the result
+     * @param absoluteAccuracy absolute accuracy of the result
+     * @param minimalIterationCount minimum number of iterations
+     * @param maximalIterationCount maximum number of iterations
+     * (must be less than or equal to {@link #TRAPEZOID_MAX_ITERATIONS_COUNT}
+     * @exception NotStrictlyPositiveException if minimal number of iterations
+     * is not strictly positive
+     * @exception NumberIsTooSmallException if maximal number of iterations
+     * is lesser than or equal to the minimal number of iterations
+     * @exception NumberIsTooLargeException if maximal number of iterations
+     * is greater than {@link #TRAPEZOID_MAX_ITERATIONS_COUNT}
+     */
+    public TrapezoidIntegrator(final double relativeAccuracy,
+                               final double absoluteAccuracy,
+                               final int minimalIterationCount,
+                               final int maximalIterationCount)
+        throws NotStrictlyPositiveException, NumberIsTooSmallException, NumberIsTooLargeException {
+        super(relativeAccuracy, absoluteAccuracy, minimalIterationCount, maximalIterationCount);
+        if (maximalIterationCount > TRAPEZOID_MAX_ITERATIONS_COUNT) {
+            throw new NumberIsTooLargeException(maximalIterationCount,
+                                                TRAPEZOID_MAX_ITERATIONS_COUNT, false);
+        }
+    }
+
+    /**
+     * Build a trapezoid integrator with given iteration counts.
+     * @param minimalIterationCount minimum number of iterations
+     * @param maximalIterationCount maximum number of iterations
+     * (must be less than or equal to {@link #TRAPEZOID_MAX_ITERATIONS_COUNT}
+     * @exception NotStrictlyPositiveException if minimal number of iterations
+     * is not strictly positive
+     * @exception NumberIsTooSmallException if maximal number of iterations
+     * is lesser than or equal to the minimal number of iterations
+     * @exception NumberIsTooLargeException if maximal number of iterations
+     * is greater than {@link #TRAPEZOID_MAX_ITERATIONS_COUNT}
+     */
+    public TrapezoidIntegrator(final int minimalIterationCount,
+                               final int maximalIterationCount)
+        throws NotStrictlyPositiveException, NumberIsTooSmallException, NumberIsTooLargeException {
+        super(minimalIterationCount, maximalIterationCount);
+        if (maximalIterationCount > TRAPEZOID_MAX_ITERATIONS_COUNT) {
+            throw new NumberIsTooLargeException(maximalIterationCount,
+                                                TRAPEZOID_MAX_ITERATIONS_COUNT, false);
+        }
+    }
+
+    /**
+     * Construct a trapezoid integrator with default settings.
+     * (max iteration count set to {@link #TRAPEZOID_MAX_ITERATIONS_COUNT})
+     */
+    public TrapezoidIntegrator() {
+        super(DEFAULT_MIN_ITERATIONS_COUNT, TRAPEZOID_MAX_ITERATIONS_COUNT);
+    }
+
+    /**
+     * Compute the n-th stage integral of trapezoid rule. This function
+     * should only be called by API <code>integrate()</code> in the package.
+     * To save time it does not verify arguments - caller does.
+     * <p>
+     * The interval is divided equally into 2^n sections rather than an
+     * arbitrary m sections because this configuration can best utilize the
+     * already computed values.</p>
+     *
+     * @param baseIntegrator integrator holding integration parameters
+     * @param n the stage of 1/2 refinement, n = 0 is no refinement
+     * @return the value of n-th stage integral
+     * @throws TooManyEvaluationsException if the maximal number of evaluations
+     * is exceeded.
+     */
+    double stage(final BaseAbstractUnivariateIntegrator baseIntegrator, final int n)
+        throws TooManyEvaluationsException {
+
+        if (n == 0) {
+            final double max = baseIntegrator.getMax();
+            final double min = baseIntegrator.getMin();
+            s = 0.5 * (max - min) *
+                      (baseIntegrator.computeObjectiveValue(min) +
+                       baseIntegrator.computeObjectiveValue(max));
+            return s;
+        } else {
+            final long np = 1L << (n-1);           // number of new points in this stage
+            double sum = 0;
+            final double max = baseIntegrator.getMax();
+            final double min = baseIntegrator.getMin();
+            // spacing between adjacent new points
+            final double spacing = (max - min) / np;
+            double x = min + 0.5 * spacing;    // the first new point
+            for (long i = 0; i < np; i++) {
+                sum += baseIntegrator.computeObjectiveValue(x);
+                x += spacing;
+            }
+            // add the new sum to previously calculated result
+            s = 0.5 * (s + sum * spacing);
+            return s;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected double doIntegrate()
+        throws MathIllegalArgumentException, TooManyEvaluationsException, MaxCountExceededException {
+
+        double oldt = stage(this, 0);
+        incrementCount();
+        while (true) {
+            final int i = getIterations();
+            final double t = stage(this, i);
+            if (i >= getMinimalIterationCount()) {
+                final double delta = FastMath.abs(t - oldt);
+                final double rLimit =
+                    getRelativeAccuracy() * (FastMath.abs(oldt) + FastMath.abs(t)) * 0.5;
+                if ((delta <= rLimit) || (delta <= getAbsoluteAccuracy())) {
+                    return t;
+                }
+            }
+            oldt = t;
+            incrementCount();
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/integration/UnivariateIntegrator.java b/src/main/java/org/apache/commons/math3/analysis/integration/UnivariateIntegrator.java
new file mode 100644
index 0000000..6453eba
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/integration/UnivariateIntegrator.java
@@ -0,0 +1,95 @@
+/*
+ * 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.math3.analysis.integration;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+
+/**
+ * Interface for univariate real integration algorithms.
+ *
+ * @since 1.2
+ */
+public interface UnivariateIntegrator {
+
+    /**
+     * Get the relative accuracy.
+     *
+     * @return the accuracy
+     */
+    double getRelativeAccuracy();
+
+    /**
+     * Get the absolute accuracy.
+     *
+     * @return the accuracy
+     */
+    double getAbsoluteAccuracy();
+
+    /**
+     * Get the min limit for the number of iterations.
+     *
+     * @return the actual min limit
+     */
+    int getMinimalIterationCount();
+
+    /**
+     * Get the upper limit for the number of iterations.
+     *
+     * @return the actual upper limit
+     */
+    int getMaximalIterationCount();
+
+    /**
+     * Integrate the function in the given interval.
+     *
+     * @param maxEval Maximum number of evaluations.
+     * @param f the integrand function
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @return the value of integral
+     * @throws TooManyEvaluationsException if the maximum number of function
+     * evaluations is exceeded
+     * @throws MaxCountExceededException if the maximum iteration count is exceeded
+     * or the integrator detects convergence problems otherwise
+     * @throws MathIllegalArgumentException if {@code min > max} or the endpoints do not
+     * satisfy the requirements specified by the integrator
+     * @throws NullArgumentException if {@code f} is {@code null}.
+     */
+    double integrate(int maxEval, UnivariateFunction f, double min,
+                     double max)
+        throws TooManyEvaluationsException, MaxCountExceededException,
+               MathIllegalArgumentException, NullArgumentException;
+
+    /**
+     * Get the number of function evaluations of the last run of the integrator.
+     *
+     * @return number of function evaluations
+     */
+    int getEvaluations();
+
+    /**
+     * Get the number of iterations of the last run of the integrator.
+     *
+     * @return number of iterations
+     */
+    int getIterations();
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/integration/gauss/BaseRuleFactory.java b/src/main/java/org/apache/commons/math3/analysis/integration/gauss/BaseRuleFactory.java
new file mode 100644
index 0000000..556fa0c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/integration/gauss/BaseRuleFactory.java
@@ -0,0 +1,153 @@
+/*
+ * 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.math3.analysis.integration.gauss;
+
+import java.util.Map;
+import java.util.TreeMap;
+import org.apache.commons.math3.util.Pair;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Base class for rules that determines the integration nodes and their
+ * weights.
+ * Subclasses must implement the {@link #computeRule(int) computeRule} method.
+ *
+ * @param <T> Type of the number used to represent the points and weights of
+ * the quadrature rules.
+ *
+ * @since 3.1
+ */
+public abstract class BaseRuleFactory<T extends Number> {
+    /** List of points and weights, indexed by the order of the rule. */
+    private final Map<Integer, Pair<T[], T[]>> pointsAndWeights
+        = new TreeMap<Integer, Pair<T[], T[]>>();
+    /** Cache for double-precision rules. */
+    private final Map<Integer, Pair<double[], double[]>> pointsAndWeightsDouble
+        = new TreeMap<Integer, Pair<double[], double[]>>();
+
+    /**
+     * Gets a copy of the quadrature rule with the given number of integration
+     * points.
+     *
+     * @param numberOfPoints Number of integration points.
+     * @return a copy of the integration rule.
+     * @throws NotStrictlyPositiveException if {@code numberOfPoints < 1}.
+     * @throws DimensionMismatchException if the elements of the rule pair do not
+     * have the same length.
+     */
+    public Pair<double[], double[]> getRule(int numberOfPoints)
+        throws NotStrictlyPositiveException, DimensionMismatchException {
+
+        if (numberOfPoints <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.NUMBER_OF_POINTS,
+                                                   numberOfPoints);
+        }
+
+        // Try to obtain the rule from the cache.
+        Pair<double[], double[]> cached = pointsAndWeightsDouble.get(numberOfPoints);
+
+        if (cached == null) {
+            // Rule not computed yet.
+
+            // Compute the rule.
+            final Pair<T[], T[]> rule = getRuleInternal(numberOfPoints);
+            cached = convertToDouble(rule);
+
+            // Cache it.
+            pointsAndWeightsDouble.put(numberOfPoints, cached);
+        }
+
+        // Return a copy.
+        return new Pair<double[], double[]>(cached.getFirst().clone(),
+                                            cached.getSecond().clone());
+    }
+
+    /**
+     * Gets a rule.
+     * Synchronization ensures that rules will be computed and added to the
+     * cache at most once.
+     * The returned rule is a reference into the cache.
+     *
+     * @param numberOfPoints Order of the rule to be retrieved.
+     * @return the points and weights corresponding to the given order.
+     * @throws DimensionMismatchException if the elements of the rule pair do not
+     * have the same length.
+     */
+    protected synchronized Pair<T[], T[]> getRuleInternal(int numberOfPoints)
+        throws DimensionMismatchException {
+        final Pair<T[], T[]> rule = pointsAndWeights.get(numberOfPoints);
+        if (rule == null) {
+            addRule(computeRule(numberOfPoints));
+            // The rule should be available now.
+            return getRuleInternal(numberOfPoints);
+        }
+        return rule;
+    }
+
+    /**
+     * Stores a rule.
+     *
+     * @param rule Rule to be stored.
+     * @throws DimensionMismatchException if the elements of the pair do not
+     * have the same length.
+     */
+    protected void addRule(Pair<T[], T[]> rule) throws DimensionMismatchException {
+        if (rule.getFirst().length != rule.getSecond().length) {
+            throw new DimensionMismatchException(rule.getFirst().length,
+                                                 rule.getSecond().length);
+        }
+
+        pointsAndWeights.put(rule.getFirst().length, rule);
+    }
+
+    /**
+     * Computes the rule for the given order.
+     *
+     * @param numberOfPoints Order of the rule to be computed.
+     * @return the computed rule.
+     * @throws DimensionMismatchException if the elements of the pair do not
+     * have the same length.
+     */
+    protected abstract Pair<T[], T[]> computeRule(int numberOfPoints)
+        throws DimensionMismatchException;
+
+    /**
+     * Converts the from the actual {@code Number} type to {@code double}
+     *
+     * @param <T> Type of the number used to represent the points and
+     * weights of the quadrature rules.
+     * @param rule Points and weights.
+     * @return points and weights as {@code double}s.
+     */
+    private static <T extends Number> Pair<double[], double[]> convertToDouble(Pair<T[], T[]> rule) {
+        final T[] pT = rule.getFirst();
+        final T[] wT = rule.getSecond();
+
+        final int len = pT.length;
+        final double[] pD = new double[len];
+        final double[] wD = new double[len];
+
+        for (int i = 0; i < len; i++) {
+            pD[i] = pT[i].doubleValue();
+            wD[i] = wT[i].doubleValue();
+        }
+
+        return new Pair<double[], double[]>(pD, wD);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/integration/gauss/GaussIntegrator.java b/src/main/java/org/apache/commons/math3/analysis/integration/gauss/GaussIntegrator.java
new file mode 100644
index 0000000..5c7b37f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/integration/gauss/GaussIntegrator.java
@@ -0,0 +1,129 @@
+/*
+ * 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.math3.analysis.integration.gauss;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NonMonotonicSequenceException;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.util.Pair;
+
+/**
+ * Class that implements the Gaussian rule for
+ * {@link #integrate(UnivariateFunction) integrating} a weighted
+ * function.
+ *
+ * @since 3.1
+ */
+public class GaussIntegrator {
+    /** Nodes. */
+    private final double[] points;
+    /** Nodes weights. */
+    private final double[] weights;
+
+    /**
+     * Creates an integrator from the given {@code points} and {@code weights}.
+     * The integration interval is defined by the first and last value of
+     * {@code points} which must be sorted in increasing order.
+     *
+     * @param points Integration points.
+     * @param weights Weights of the corresponding integration nodes.
+     * @throws NonMonotonicSequenceException if the {@code points} are not
+     * sorted in increasing order.
+     * @throws DimensionMismatchException if points and weights don't have the same length
+     */
+    public GaussIntegrator(double[] points,
+                           double[] weights)
+        throws NonMonotonicSequenceException, DimensionMismatchException {
+        if (points.length != weights.length) {
+            throw new DimensionMismatchException(points.length,
+                                                 weights.length);
+        }
+
+        MathArrays.checkOrder(points, MathArrays.OrderDirection.INCREASING, true, true);
+
+        this.points = points.clone();
+        this.weights = weights.clone();
+    }
+
+    /**
+     * Creates an integrator from the given pair of points (first element of
+     * the pair) and weights (second element of the pair.
+     *
+     * @param pointsAndWeights Integration points and corresponding weights.
+     * @throws NonMonotonicSequenceException if the {@code points} are not
+     * sorted in increasing order.
+     *
+     * @see #GaussIntegrator(double[], double[])
+     */
+    public GaussIntegrator(Pair<double[], double[]> pointsAndWeights)
+        throws NonMonotonicSequenceException {
+        this(pointsAndWeights.getFirst(), pointsAndWeights.getSecond());
+    }
+
+    /**
+     * Returns an estimate of the integral of {@code f(x) * w(x)},
+     * where {@code w} is a weight function that depends on the actual
+     * flavor of the Gauss integration scheme.
+     * The algorithm uses the points and associated weights, as passed
+     * to the {@link #GaussIntegrator(double[],double[]) constructor}.
+     *
+     * @param f Function to integrate.
+     * @return the integral of the weighted function.
+     */
+    public double integrate(UnivariateFunction f) {
+        double s = 0;
+        double c = 0;
+        for (int i = 0; i < points.length; i++) {
+            final double x = points[i];
+            final double w = weights[i];
+            final double y = w * f.value(x) - c;
+            final double t = s + y;
+            c = (t - s) - y;
+            s = t;
+        }
+        return s;
+    }
+
+    /**
+     * @return the order of the integration rule (the number of integration
+     * points).
+     */
+    public int getNumberOfPoints() {
+        return points.length;
+    }
+
+    /**
+     * Gets the integration point at the given index.
+     * The index must be in the valid range but no check is performed.
+     * @param index index of the integration point
+     * @return the integration point.
+     */
+    public double getPoint(int index) {
+        return points[index];
+    }
+
+    /**
+     * Gets the weight of the integration point at the given index.
+     * The index must be in the valid range but no check is performed.
+     * @param index index of the integration point
+     * @return the weight.
+     */
+    public double getWeight(int index) {
+        return weights[index];
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/integration/gauss/GaussIntegratorFactory.java b/src/main/java/org/apache/commons/math3/analysis/integration/gauss/GaussIntegratorFactory.java
new file mode 100644
index 0000000..570a7ba
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/integration/gauss/GaussIntegratorFactory.java
@@ -0,0 +1,167 @@
+/*
+ * 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.math3.analysis.integration.gauss;
+
+import java.math.BigDecimal;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.util.Pair;
+
+/**
+ * Class that provides different ways to compute the nodes and weights to be
+ * used by the {@link GaussIntegrator Gaussian integration rule}.
+ *
+ * @since 3.1
+ */
+public class GaussIntegratorFactory {
+    /** Generator of Gauss-Legendre integrators. */
+    private final BaseRuleFactory<Double> legendre = new LegendreRuleFactory();
+    /** Generator of Gauss-Legendre integrators. */
+    private final BaseRuleFactory<BigDecimal> legendreHighPrecision = new LegendreHighPrecisionRuleFactory();
+    /** Generator of Gauss-Hermite integrators. */
+    private final BaseRuleFactory<Double> hermite = new HermiteRuleFactory();
+
+    /**
+     * Creates a Gauss-Legendre integrator of the given order.
+     * The call to the
+     * {@link GaussIntegrator#integrate(org.apache.commons.math3.analysis.UnivariateFunction)
+     * integrate} method will perform an integration on the natural interval
+     * {@code [-1 , 1]}.
+     *
+     * @param numberOfPoints Order of the integration rule.
+     * @return a Gauss-Legendre integrator.
+     */
+    public GaussIntegrator legendre(int numberOfPoints) {
+        return new GaussIntegrator(getRule(legendre, numberOfPoints));
+    }
+
+    /**
+     * Creates a Gauss-Legendre integrator of the given order.
+     * The call to the
+     * {@link GaussIntegrator#integrate(org.apache.commons.math3.analysis.UnivariateFunction)
+     * integrate} method will perform an integration on the given interval.
+     *
+     * @param numberOfPoints Order of the integration rule.
+     * @param lowerBound Lower bound of the integration interval.
+     * @param upperBound Upper bound of the integration interval.
+     * @return a Gauss-Legendre integrator.
+     * @throws NotStrictlyPositiveException if number of points is not positive
+     */
+    public GaussIntegrator legendre(int numberOfPoints,
+                                    double lowerBound,
+                                    double upperBound)
+        throws NotStrictlyPositiveException {
+        return new GaussIntegrator(transform(getRule(legendre, numberOfPoints),
+                                             lowerBound, upperBound));
+    }
+
+    /**
+     * Creates a Gauss-Legendre integrator of the given order.
+     * The call to the
+     * {@link GaussIntegrator#integrate(org.apache.commons.math3.analysis.UnivariateFunction)
+     * integrate} method will perform an integration on the natural interval
+     * {@code [-1 , 1]}.
+     *
+     * @param numberOfPoints Order of the integration rule.
+     * @return a Gauss-Legendre integrator.
+     * @throws NotStrictlyPositiveException if number of points is not positive
+     */
+    public GaussIntegrator legendreHighPrecision(int numberOfPoints)
+        throws NotStrictlyPositiveException {
+        return new GaussIntegrator(getRule(legendreHighPrecision, numberOfPoints));
+    }
+
+    /**
+     * Creates an integrator of the given order, and whose call to the
+     * {@link GaussIntegrator#integrate(org.apache.commons.math3.analysis.UnivariateFunction)
+     * integrate} method will perform an integration on the given interval.
+     *
+     * @param numberOfPoints Order of the integration rule.
+     * @param lowerBound Lower bound of the integration interval.
+     * @param upperBound Upper bound of the integration interval.
+     * @return a Gauss-Legendre integrator.
+     * @throws NotStrictlyPositiveException if number of points is not positive
+     */
+    public GaussIntegrator legendreHighPrecision(int numberOfPoints,
+                                                 double lowerBound,
+                                                 double upperBound)
+        throws NotStrictlyPositiveException {
+        return new GaussIntegrator(transform(getRule(legendreHighPrecision, numberOfPoints),
+                                             lowerBound, upperBound));
+    }
+
+    /**
+     * Creates a Gauss-Hermite integrator of the given order.
+     * The call to the
+     * {@link SymmetricGaussIntegrator#integrate(org.apache.commons.math3.analysis.UnivariateFunction)
+     * integrate} method will perform a weighted integration on the interval
+     * \([-\infty, +\infty]\): the computed value is the improper integral of
+     * \(e^{-x^2}f(x)\)
+     * where \(f(x)\) is the function passed to the
+     * {@link SymmetricGaussIntegrator#integrate(org.apache.commons.math3.analysis.UnivariateFunction)
+     * integrate} method.
+     *
+     * @param numberOfPoints Order of the integration rule.
+     * @return a Gauss-Hermite integrator.
+     */
+    public SymmetricGaussIntegrator hermite(int numberOfPoints) {
+        return new SymmetricGaussIntegrator(getRule(hermite, numberOfPoints));
+    }
+
+    /**
+     * @param factory Integration rule factory.
+     * @param numberOfPoints Order of the integration rule.
+     * @return the integration nodes and weights.
+     * @throws NotStrictlyPositiveException if number of points is not positive
+     * @throws DimensionMismatchException if the elements of the rule pair do not
+     * have the same length.
+     */
+    private static Pair<double[], double[]> getRule(BaseRuleFactory<? extends Number> factory,
+                                                    int numberOfPoints)
+        throws NotStrictlyPositiveException, DimensionMismatchException {
+        return factory.getRule(numberOfPoints);
+    }
+
+    /**
+     * Performs a change of variable so that the integration can be performed
+     * on an arbitrary interval {@code [a, b]}.
+     * It is assumed that the natural interval is {@code [-1, 1]}.
+     *
+     * @param rule Original points and weights.
+     * @param a Lower bound of the integration interval.
+     * @param b Lower bound of the integration interval.
+     * @return the points and weights adapted to the new interval.
+     */
+    private static Pair<double[], double[]> transform(Pair<double[], double[]> rule,
+                                                      double a,
+                                                      double b) {
+        final double[] points = rule.getFirst();
+        final double[] weights = rule.getSecond();
+
+        // Scaling
+        final double scale = (b - a) / 2;
+        final double shift = a + scale;
+
+        for (int i = 0; i < points.length; i++) {
+            points[i] = points[i] * scale + shift;
+            weights[i] *= scale;
+        }
+
+        return new Pair<double[], double[]>(points, weights);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/integration/gauss/HermiteRuleFactory.java b/src/main/java/org/apache/commons/math3/analysis/integration/gauss/HermiteRuleFactory.java
new file mode 100644
index 0000000..abe3fbe
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/integration/gauss/HermiteRuleFactory.java
@@ -0,0 +1,177 @@
+/*
+ * 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.math3.analysis.integration.gauss;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.util.Pair;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Factory that creates a
+ * <a href="http://en.wikipedia.org/wiki/Gauss-Hermite_quadrature">
+ * Gauss-type quadrature rule using Hermite polynomials</a>
+ * of the first kind.
+ * Such a quadrature rule allows the calculation of improper integrals
+ * of a function
+ * <p>
+ *  \(f(x) e^{-x^2}\)
+ * </p><p>
+ * Recurrence relation and weights computation follow
+ * <a href="http://en.wikipedia.org/wiki/Abramowitz_and_Stegun">
+ * Abramowitz and Stegun, 1964</a>.
+ * </p><p>
+ * The coefficients of the standard Hermite polynomials grow very rapidly.
+ * In order to avoid overflows, each Hermite polynomial is normalized with
+ * respect to the underlying scalar product.
+ * The initial interval for the application of the bisection method is
+ * based on the roots of the previous Hermite polynomial (interlacing).
+ * Upper and lower bounds of these roots are provided by </p>
+ * <blockquote>
+ *  I. Krasikov,
+ *  <em>Nonnegative quadratic forms and bounds on orthogonal polynomials</em>,
+ *  Journal of Approximation theory <b>111</b>, 31-49
+ * </blockquote>
+ *
+ * @since 3.3
+ */
+public class HermiteRuleFactory extends BaseRuleFactory<Double> {
+    /** &pi;<sup>1/2</sup> */
+    private static final double SQRT_PI = 1.77245385090551602729;
+    /** &pi;<sup>-1/4</sup> */
+    private static final double H0 = 7.5112554446494248286e-1;
+    /** &pi;<sup>-1/4</sup> &radic;2 */
+    private static final double H1 = 1.0622519320271969145;
+
+    /** {@inheritDoc} */
+    @Override
+    protected Pair<Double[], Double[]> computeRule(int numberOfPoints)
+        throws DimensionMismatchException {
+
+        if (numberOfPoints == 1) {
+            // Break recursion.
+            return new Pair<Double[], Double[]>(new Double[] { 0d },
+                                                new Double[] { SQRT_PI });
+        }
+
+        // Get previous rule.
+        // If it has not been computed yet it will trigger a recursive call
+        // to this method.
+        final int lastNumPoints = numberOfPoints - 1;
+        final Double[] previousPoints = getRuleInternal(lastNumPoints).getFirst();
+
+        // Compute next rule.
+        final Double[] points = new Double[numberOfPoints];
+        final Double[] weights = new Double[numberOfPoints];
+
+        final double sqrtTwoTimesLastNumPoints = FastMath.sqrt(2 * lastNumPoints);
+        final double sqrtTwoTimesNumPoints = FastMath.sqrt(2 * numberOfPoints);
+
+        // Find i-th root of H[n+1] by bracketing.
+        final int iMax = numberOfPoints / 2;
+        for (int i = 0; i < iMax; i++) {
+            // Lower-bound of the interval.
+            double a = (i == 0) ? -sqrtTwoTimesLastNumPoints : previousPoints[i - 1].doubleValue();
+            // Upper-bound of the interval.
+            double b = (iMax == 1) ? -0.5 : previousPoints[i].doubleValue();
+
+            // H[j-1](a)
+            double hma = H0;
+            // H[j](a)
+            double ha = H1 * a;
+            // H[j-1](b)
+            double hmb = H0;
+            // H[j](b)
+            double hb = H1 * b;
+            for (int j = 1; j < numberOfPoints; j++) {
+                // Compute H[j+1](a) and H[j+1](b)
+                final double jp1 = j + 1;
+                final double s = FastMath.sqrt(2 / jp1);
+                final double sm = FastMath.sqrt(j / jp1);
+                final double hpa = s * a * ha - sm * hma;
+                final double hpb = s * b * hb - sm * hmb;
+                hma = ha;
+                ha = hpa;
+                hmb = hb;
+                hb = hpb;
+            }
+
+            // Now ha = H[n+1](a), and hma = H[n](a) (same holds for b).
+            // Middle of the interval.
+            double c = 0.5 * (a + b);
+            // P[j-1](c)
+            double hmc = H0;
+            // P[j](c)
+            double hc = H1 * c;
+            boolean done = false;
+            while (!done) {
+                done = b - a <= Math.ulp(c);
+                hmc = H0;
+                hc = H1 * c;
+                for (int j = 1; j < numberOfPoints; j++) {
+                    // Compute H[j+1](c)
+                    final double jp1 = j + 1;
+                    final double s = FastMath.sqrt(2 / jp1);
+                    final double sm = FastMath.sqrt(j / jp1);
+                    final double hpc = s * c * hc - sm * hmc;
+                    hmc = hc;
+                    hc = hpc;
+                }
+                // Now h = H[n+1](c) and hm = H[n](c).
+                if (!done) {
+                    if (ha * hc < 0) {
+                        b = c;
+                        hmb = hmc;
+                        hb = hc;
+                    } else {
+                        a = c;
+                        hma = hmc;
+                        ha = hc;
+                    }
+                    c = 0.5 * (a + b);
+                }
+            }
+            final double d = sqrtTwoTimesNumPoints * hmc;
+            final double w = 2 / (d * d);
+
+            points[i] = c;
+            weights[i] = w;
+
+            final int idx = lastNumPoints - i;
+            points[idx] = -c;
+            weights[idx] = w;
+        }
+
+        // If "numberOfPoints" is odd, 0 is a root.
+        // Note: as written, the test for oddness will work for negative
+        // integers too (although it is not necessary here), preventing
+        // a FindBugs warning.
+        if (numberOfPoints % 2 != 0) {
+            double hm = H0;
+            for (int j = 1; j < numberOfPoints; j += 2) {
+                final double jp1 = j + 1;
+                hm = -FastMath.sqrt(j / jp1) * hm;
+            }
+            final double d = sqrtTwoTimesNumPoints * hm;
+            final double w = 2 / (d * d);
+
+            points[iMax] = 0d;
+            weights[iMax] = w;
+        }
+
+        return new Pair<Double[], Double[]>(points, weights);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/integration/gauss/LegendreHighPrecisionRuleFactory.java b/src/main/java/org/apache/commons/math3/analysis/integration/gauss/LegendreHighPrecisionRuleFactory.java
new file mode 100644
index 0000000..e07cb16
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/integration/gauss/LegendreHighPrecisionRuleFactory.java
@@ -0,0 +1,215 @@
+/*
+ * 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.math3.analysis.integration.gauss;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.util.Pair;
+
+/**
+ * Factory that creates Gauss-type quadrature rule using Legendre polynomials.
+ * In this implementation, the lower and upper bounds of the natural interval
+ * of integration are -1 and 1, respectively.
+ * The Legendre polynomials are evaluated using the recurrence relation
+ * presented in <a href="http://en.wikipedia.org/wiki/Abramowitz_and_Stegun">
+ * Abramowitz and Stegun, 1964</a>.
+ *
+ * @since 3.1
+ */
+public class LegendreHighPrecisionRuleFactory extends BaseRuleFactory<BigDecimal> {
+    /** Settings for enhanced precision computations. */
+    private final MathContext mContext;
+    /** The number {@code 2}. */
+    private final BigDecimal two;
+    /** The number {@code -1}. */
+    private final BigDecimal minusOne;
+    /** The number {@code 0.5}. */
+    private final BigDecimal oneHalf;
+
+    /**
+     * Default precision is {@link MathContext#DECIMAL128 DECIMAL128}.
+     */
+    public LegendreHighPrecisionRuleFactory() {
+        this(MathContext.DECIMAL128);
+    }
+
+    /**
+     * @param mContext Precision setting for computing the quadrature rules.
+     */
+    public LegendreHighPrecisionRuleFactory(MathContext mContext) {
+        this.mContext = mContext;
+        two = new BigDecimal("2", mContext);
+        minusOne = new BigDecimal("-1", mContext);
+        oneHalf = new BigDecimal("0.5", mContext);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected Pair<BigDecimal[], BigDecimal[]> computeRule(int numberOfPoints)
+        throws DimensionMismatchException {
+
+        if (numberOfPoints == 1) {
+            // Break recursion.
+            return new Pair<BigDecimal[], BigDecimal[]>(new BigDecimal[] { BigDecimal.ZERO },
+                                                        new BigDecimal[] { two });
+        }
+
+        // Get previous rule.
+        // If it has not been computed yet it will trigger a recursive call
+        // to this method.
+        final BigDecimal[] previousPoints = getRuleInternal(numberOfPoints - 1).getFirst();
+
+        // Compute next rule.
+        final BigDecimal[] points = new BigDecimal[numberOfPoints];
+        final BigDecimal[] weights = new BigDecimal[numberOfPoints];
+
+        // Find i-th root of P[n+1] by bracketing.
+        final int iMax = numberOfPoints / 2;
+        for (int i = 0; i < iMax; i++) {
+            // Lower-bound of the interval.
+            BigDecimal a = (i == 0) ? minusOne : previousPoints[i - 1];
+            // Upper-bound of the interval.
+            BigDecimal b = (iMax == 1) ? BigDecimal.ONE : previousPoints[i];
+            // P[j-1](a)
+            BigDecimal pma = BigDecimal.ONE;
+            // P[j](a)
+            BigDecimal pa = a;
+            // P[j-1](b)
+            BigDecimal pmb = BigDecimal.ONE;
+            // P[j](b)
+            BigDecimal pb = b;
+            for (int j = 1; j < numberOfPoints; j++) {
+                final BigDecimal b_two_j_p_1 = new BigDecimal(2 * j + 1, mContext);
+                final BigDecimal b_j = new BigDecimal(j, mContext);
+                final BigDecimal b_j_p_1 = new BigDecimal(j + 1, mContext);
+
+                // Compute P[j+1](a)
+                // ppa = ((2 * j + 1) * a * pa - j * pma) / (j + 1);
+
+                BigDecimal tmp1 = a.multiply(b_two_j_p_1, mContext);
+                tmp1 = pa.multiply(tmp1, mContext);
+                BigDecimal tmp2 = pma.multiply(b_j, mContext);
+                // P[j+1](a)
+                BigDecimal ppa = tmp1.subtract(tmp2, mContext);
+                ppa = ppa.divide(b_j_p_1, mContext);
+
+                // Compute P[j+1](b)
+                // ppb = ((2 * j + 1) * b * pb - j * pmb) / (j + 1);
+
+                tmp1 = b.multiply(b_two_j_p_1, mContext);
+                tmp1 = pb.multiply(tmp1, mContext);
+                tmp2 = pmb.multiply(b_j, mContext);
+                // P[j+1](b)
+                BigDecimal ppb = tmp1.subtract(tmp2, mContext);
+                ppb = ppb.divide(b_j_p_1, mContext);
+
+                pma = pa;
+                pa = ppa;
+                pmb = pb;
+                pb = ppb;
+            }
+            // Now pa = P[n+1](a), and pma = P[n](a). Same holds for b.
+            // Middle of the interval.
+            BigDecimal c = a.add(b, mContext).multiply(oneHalf, mContext);
+            // P[j-1](c)
+            BigDecimal pmc = BigDecimal.ONE;
+            // P[j](c)
+            BigDecimal pc = c;
+            boolean done = false;
+            while (!done) {
+                BigDecimal tmp1 = b.subtract(a, mContext);
+                BigDecimal tmp2 = c.ulp().multiply(BigDecimal.TEN, mContext);
+                done = tmp1.compareTo(tmp2) <= 0;
+                pmc = BigDecimal.ONE;
+                pc = c;
+                for (int j = 1; j < numberOfPoints; j++) {
+                    final BigDecimal b_two_j_p_1 = new BigDecimal(2 * j + 1, mContext);
+                    final BigDecimal b_j = new BigDecimal(j, mContext);
+                    final BigDecimal b_j_p_1 = new BigDecimal(j + 1, mContext);
+
+                    // Compute P[j+1](c)
+                    tmp1 = c.multiply(b_two_j_p_1, mContext);
+                    tmp1 = pc.multiply(tmp1, mContext);
+                    tmp2 = pmc.multiply(b_j, mContext);
+                    // P[j+1](c)
+                    BigDecimal ppc = tmp1.subtract(tmp2, mContext);
+                    ppc = ppc.divide(b_j_p_1, mContext);
+
+                    pmc = pc;
+                    pc = ppc;
+                }
+                // Now pc = P[n+1](c) and pmc = P[n](c).
+                if (!done) {
+                    if (pa.signum() * pc.signum() <= 0) {
+                        b = c;
+                        pmb = pmc;
+                        pb = pc;
+                    } else {
+                        a = c;
+                        pma = pmc;
+                        pa = pc;
+                    }
+                    c = a.add(b, mContext).multiply(oneHalf, mContext);
+                }
+            }
+            final BigDecimal nP = new BigDecimal(numberOfPoints, mContext);
+            BigDecimal tmp1 = pmc.subtract(c.multiply(pc, mContext), mContext);
+            tmp1 = tmp1.multiply(nP);
+            tmp1 = tmp1.pow(2, mContext);
+            BigDecimal tmp2 = c.pow(2, mContext);
+            tmp2 = BigDecimal.ONE.subtract(tmp2, mContext);
+            tmp2 = tmp2.multiply(two, mContext);
+            tmp2 = tmp2.divide(tmp1, mContext);
+
+            points[i] = c;
+            weights[i] = tmp2;
+
+            final int idx = numberOfPoints - i - 1;
+            points[idx] = c.negate(mContext);
+            weights[idx] = tmp2;
+        }
+        // If "numberOfPoints" is odd, 0 is a root.
+        // Note: as written, the test for oddness will work for negative
+        // integers too (although it is not necessary here), preventing
+        // a FindBugs warning.
+        if (numberOfPoints % 2 != 0) {
+            BigDecimal pmc = BigDecimal.ONE;
+            for (int j = 1; j < numberOfPoints; j += 2) {
+                final BigDecimal b_j = new BigDecimal(j, mContext);
+                final BigDecimal b_j_p_1 = new BigDecimal(j + 1, mContext);
+
+                // pmc = -j * pmc / (j + 1);
+                pmc = pmc.multiply(b_j, mContext);
+                pmc = pmc.divide(b_j_p_1, mContext);
+                pmc = pmc.negate(mContext);
+            }
+
+            // 2 / pow(numberOfPoints * pmc, 2);
+            final BigDecimal nP = new BigDecimal(numberOfPoints, mContext);
+            BigDecimal tmp1 = pmc.multiply(nP, mContext);
+            tmp1 = tmp1.pow(2, mContext);
+            BigDecimal tmp2 = two.divide(tmp1, mContext);
+
+            points[iMax] = BigDecimal.ZERO;
+            weights[iMax] = tmp2;
+        }
+
+        return new Pair<BigDecimal[], BigDecimal[]>(points, weights);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/integration/gauss/LegendreRuleFactory.java b/src/main/java/org/apache/commons/math3/analysis/integration/gauss/LegendreRuleFactory.java
new file mode 100644
index 0000000..cb0bfec
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/integration/gauss/LegendreRuleFactory.java
@@ -0,0 +1,140 @@
+/*
+ * 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.math3.analysis.integration.gauss;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.util.Pair;
+
+/**
+ * Factory that creates Gauss-type quadrature rule using Legendre polynomials.
+ * In this implementation, the lower and upper bounds of the natural interval
+ * of integration are -1 and 1, respectively.
+ * The Legendre polynomials are evaluated using the recurrence relation
+ * presented in <a href="http://en.wikipedia.org/wiki/Abramowitz_and_Stegun">
+ * Abramowitz and Stegun, 1964</a>.
+ *
+ * @since 3.1
+ */
+public class LegendreRuleFactory extends BaseRuleFactory<Double> {
+    /** {@inheritDoc} */
+    @Override
+    protected Pair<Double[], Double[]> computeRule(int numberOfPoints)
+        throws DimensionMismatchException {
+
+        if (numberOfPoints == 1) {
+            // Break recursion.
+            return new Pair<Double[], Double[]>(new Double[] { 0d },
+                                                new Double[] { 2d });
+        }
+
+        // Get previous rule.
+        // If it has not been computed yet it will trigger a recursive call
+        // to this method.
+        final Double[] previousPoints = getRuleInternal(numberOfPoints - 1).getFirst();
+
+        // Compute next rule.
+        final Double[] points = new Double[numberOfPoints];
+        final Double[] weights = new Double[numberOfPoints];
+
+        // Find i-th root of P[n+1] by bracketing.
+        final int iMax = numberOfPoints / 2;
+        for (int i = 0; i < iMax; i++) {
+            // Lower-bound of the interval.
+            double a = (i == 0) ? -1 : previousPoints[i - 1].doubleValue();
+            // Upper-bound of the interval.
+            double b = (iMax == 1) ? 1 : previousPoints[i].doubleValue();
+            // P[j-1](a)
+            double pma = 1;
+            // P[j](a)
+            double pa = a;
+            // P[j-1](b)
+            double pmb = 1;
+            // P[j](b)
+            double pb = b;
+            for (int j = 1; j < numberOfPoints; j++) {
+                final int two_j_p_1 = 2 * j + 1;
+                final int j_p_1 = j + 1;
+                // P[j+1](a)
+                final double ppa = (two_j_p_1 * a * pa - j * pma) / j_p_1;
+                // P[j+1](b)
+                final double ppb = (two_j_p_1 * b * pb - j * pmb) / j_p_1;
+                pma = pa;
+                pa = ppa;
+                pmb = pb;
+                pb = ppb;
+            }
+            // Now pa = P[n+1](a), and pma = P[n](a) (same holds for b).
+            // Middle of the interval.
+            double c = 0.5 * (a + b);
+            // P[j-1](c)
+            double pmc = 1;
+            // P[j](c)
+            double pc = c;
+            boolean done = false;
+            while (!done) {
+                done = b - a <= Math.ulp(c);
+                pmc = 1;
+                pc = c;
+                for (int j = 1; j < numberOfPoints; j++) {
+                    // P[j+1](c)
+                    final double ppc = ((2 * j + 1) * c * pc - j * pmc) / (j + 1);
+                    pmc = pc;
+                    pc = ppc;
+                }
+                // Now pc = P[n+1](c) and pmc = P[n](c).
+                if (!done) {
+                    if (pa * pc <= 0) {
+                        b = c;
+                        pmb = pmc;
+                        pb = pc;
+                    } else {
+                        a = c;
+                        pma = pmc;
+                        pa = pc;
+                    }
+                    c = 0.5 * (a + b);
+                }
+            }
+            final double d = numberOfPoints * (pmc - c * pc);
+            final double w = 2 * (1 - c * c) / (d * d);
+
+            points[i] = c;
+            weights[i] = w;
+
+            final int idx = numberOfPoints - i - 1;
+            points[idx] = -c;
+            weights[idx] = w;
+        }
+        // If "numberOfPoints" is odd, 0 is a root.
+        // Note: as written, the test for oddness will work for negative
+        // integers too (although it is not necessary here), preventing
+        // a FindBugs warning.
+        if (numberOfPoints % 2 != 0) {
+            double pmc = 1;
+            for (int j = 1; j < numberOfPoints; j += 2) {
+                pmc = -j * pmc / (j + 1);
+            }
+            final double d = numberOfPoints * pmc;
+            final double w = 2 / (d * d);
+
+            points[iMax] = 0d;
+            weights[iMax] = w;
+        }
+
+        return new Pair<Double[], Double[]>(points, weights);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/integration/gauss/SymmetricGaussIntegrator.java b/src/main/java/org/apache/commons/math3/analysis/integration/gauss/SymmetricGaussIntegrator.java
new file mode 100644
index 0000000..7fa4884
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/integration/gauss/SymmetricGaussIntegrator.java
@@ -0,0 +1,103 @@
+/*
+ * 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.math3.analysis.integration.gauss;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NonMonotonicSequenceException;
+import org.apache.commons.math3.util.Pair;
+
+/**
+ * This class's implements {@link #integrate(UnivariateFunction) integrate}
+ * method assuming that the integral is symmetric about 0.
+ * This allows to reduce numerical errors.
+ *
+ * @since 3.3
+ */
+public class SymmetricGaussIntegrator extends GaussIntegrator {
+    /**
+     * Creates an integrator from the given {@code points} and {@code weights}.
+     * The integration interval is defined by the first and last value of
+     * {@code points} which must be sorted in increasing order.
+     *
+     * @param points Integration points.
+     * @param weights Weights of the corresponding integration nodes.
+     * @throws NonMonotonicSequenceException if the {@code points} are not
+     * sorted in increasing order.
+     * @throws DimensionMismatchException if points and weights don't have the same length
+     */
+    public SymmetricGaussIntegrator(double[] points,
+                                    double[] weights)
+        throws NonMonotonicSequenceException, DimensionMismatchException {
+        super(points, weights);
+    }
+
+    /**
+     * Creates an integrator from the given pair of points (first element of
+     * the pair) and weights (second element of the pair.
+     *
+     * @param pointsAndWeights Integration points and corresponding weights.
+     * @throws NonMonotonicSequenceException if the {@code points} are not
+     * sorted in increasing order.
+     *
+     * @see #SymmetricGaussIntegrator(double[], double[])
+     */
+    public SymmetricGaussIntegrator(Pair<double[], double[]> pointsAndWeights)
+        throws NonMonotonicSequenceException {
+        this(pointsAndWeights.getFirst(), pointsAndWeights.getSecond());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double integrate(UnivariateFunction f) {
+        final int ruleLength = getNumberOfPoints();
+
+        if (ruleLength == 1) {
+            return getWeight(0) * f.value(0d);
+        }
+
+        final int iMax = ruleLength / 2;
+        double s = 0;
+        double c = 0;
+        for (int i = 0; i < iMax; i++) {
+            final double p = getPoint(i);
+            final double w = getWeight(i);
+
+            final double f1 = f.value(p);
+            final double f2 = f.value(-p);
+
+            final double y = w * (f1 + f2) - c;
+            final double t = s + y;
+
+            c = (t - s) - y;
+            s = t;
+        }
+
+        if (ruleLength % 2 != 0) {
+            final double w = getWeight(iMax);
+
+            final double y = w * f.value(0d) - c;
+            final double t = s + y;
+
+            s = t;
+        }
+
+        return s;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/integration/gauss/package-info.java b/src/main/java/org/apache/commons/math3/analysis/integration/gauss/package-info.java
new file mode 100644
index 0000000..066da30
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/integration/gauss/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * Gauss family of quadrature schemes.
+ *
+ */
+package org.apache.commons.math3.analysis.integration.gauss;
diff --git a/src/main/java/org/apache/commons/math3/analysis/integration/package-info.java b/src/main/java/org/apache/commons/math3/analysis/integration/package-info.java
new file mode 100644
index 0000000..f4df3ba
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/integration/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+/**
+ *
+ *     Numerical integration (quadrature) algorithms for univariate real functions.
+ *
+ */
+package org.apache.commons.math3.analysis.integration;
diff --git a/src/main/java/org/apache/commons/math3/analysis/interpolation/AkimaSplineInterpolator.java b/src/main/java/org/apache/commons/math3/analysis/interpolation/AkimaSplineInterpolator.java
new file mode 100644
index 0000000..5b89dfe
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/interpolation/AkimaSplineInterpolator.java
@@ -0,0 +1,215 @@
+/*
+ * 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.math3.analysis.interpolation;
+
+import org.apache.commons.math3.analysis.polynomials.PolynomialFunction;
+import org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NonMonotonicSequenceException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.util.Precision;
+
+/**
+ * Computes a cubic spline interpolation for the data set using the Akima
+ * algorithm, as originally formulated by Hiroshi Akima in his 1970 paper
+ * "A New Method of Interpolation and Smooth Curve Fitting Based on Local Procedures."
+ * J. ACM 17, 4 (October 1970), 589-602. DOI=10.1145/321607.321609
+ * http://doi.acm.org/10.1145/321607.321609
+ * <p>
+ * This implementation is based on the Akima implementation in the CubicSpline
+ * class in the Math.NET Numerics library. The method referenced is
+ * CubicSpline.InterpolateAkimaSorted
+ * </p>
+ * <p>
+ * The {@link #interpolate(double[], double[]) interpolate} method returns a
+ * {@link PolynomialSplineFunction} consisting of n cubic polynomials, defined
+ * over the subintervals determined by the x values, {@code x[0] < x[i] ... < x[n]}.
+ * The Akima algorithm requires that {@code n >= 5}.
+ * </p>
+ */
+public class AkimaSplineInterpolator
+    implements UnivariateInterpolator {
+    /** The minimum number of points that are needed to compute the function. */
+    private static final int MINIMUM_NUMBER_POINTS = 5;
+
+    /**
+     * Computes an interpolating function for the data set.
+     *
+     * @param xvals the arguments for the interpolation points
+     * @param yvals the values for the interpolation points
+     * @return a function which interpolates the data set
+     * @throws DimensionMismatchException if {@code xvals} and {@code yvals} have
+     *         different sizes.
+     * @throws NonMonotonicSequenceException if {@code xvals} is not sorted in
+     *         strict increasing order.
+     * @throws NumberIsTooSmallException if the size of {@code xvals} is smaller
+     *         than 5.
+     */
+    public PolynomialSplineFunction interpolate(double[] xvals,
+                                                double[] yvals)
+        throws DimensionMismatchException,
+               NumberIsTooSmallException,
+               NonMonotonicSequenceException {
+        if (xvals == null ||
+            yvals == null) {
+            throw new NullArgumentException();
+        }
+
+        if (xvals.length != yvals.length) {
+            throw new DimensionMismatchException(xvals.length, yvals.length);
+        }
+
+        if (xvals.length < MINIMUM_NUMBER_POINTS) {
+            throw new NumberIsTooSmallException(LocalizedFormats.NUMBER_OF_POINTS,
+                                                xvals.length,
+                                                MINIMUM_NUMBER_POINTS, true);
+        }
+
+        MathArrays.checkOrder(xvals);
+
+        final int numberOfDiffAndWeightElements = xvals.length - 1;
+
+        final double[] differences = new double[numberOfDiffAndWeightElements];
+        final double[] weights = new double[numberOfDiffAndWeightElements];
+
+        for (int i = 0; i < differences.length; i++) {
+            differences[i] = (yvals[i + 1] - yvals[i]) / (xvals[i + 1] - xvals[i]);
+        }
+
+        for (int i = 1; i < weights.length; i++) {
+            weights[i] = FastMath.abs(differences[i] - differences[i - 1]);
+        }
+
+        // Prepare Hermite interpolation scheme.
+        final double[] firstDerivatives = new double[xvals.length];
+
+        for (int i = 2; i < firstDerivatives.length - 2; i++) {
+            final double wP = weights[i + 1];
+            final double wM = weights[i - 1];
+            if (Precision.equals(wP, 0.0) &&
+                Precision.equals(wM, 0.0)) {
+                final double xv = xvals[i];
+                final double xvP = xvals[i + 1];
+                final double xvM = xvals[i - 1];
+                firstDerivatives[i] = (((xvP - xv) * differences[i - 1]) + ((xv - xvM) * differences[i])) / (xvP - xvM);
+            } else {
+                firstDerivatives[i] = ((wP * differences[i - 1]) + (wM * differences[i])) / (wP + wM);
+            }
+        }
+
+        firstDerivatives[0] = differentiateThreePoint(xvals, yvals, 0, 0, 1, 2);
+        firstDerivatives[1] = differentiateThreePoint(xvals, yvals, 1, 0, 1, 2);
+        firstDerivatives[xvals.length - 2] = differentiateThreePoint(xvals, yvals, xvals.length - 2,
+                                                                     xvals.length - 3, xvals.length - 2,
+                                                                     xvals.length - 1);
+        firstDerivatives[xvals.length - 1] = differentiateThreePoint(xvals, yvals, xvals.length - 1,
+                                                                     xvals.length - 3, xvals.length - 2,
+                                                                     xvals.length - 1);
+
+        return interpolateHermiteSorted(xvals, yvals, firstDerivatives);
+    }
+
+    /**
+     * Three point differentiation helper, modeled off of the same method in the
+     * Math.NET CubicSpline class. This is used by both the Apache Math and the
+     * Math.NET Akima Cubic Spline algorithms
+     *
+     * @param xvals x values to calculate the numerical derivative with
+     * @param yvals y values to calculate the numerical derivative with
+     * @param indexOfDifferentiation index of the elemnt we are calculating the derivative around
+     * @param indexOfFirstSample index of the first element to sample for the three point method
+     * @param indexOfSecondsample index of the second element to sample for the three point method
+     * @param indexOfThirdSample index of the third element to sample for the three point method
+     * @return the derivative
+     */
+    private double differentiateThreePoint(double[] xvals, double[] yvals,
+                                           int indexOfDifferentiation,
+                                           int indexOfFirstSample,
+                                           int indexOfSecondsample,
+                                           int indexOfThirdSample) {
+        final double x0 = yvals[indexOfFirstSample];
+        final double x1 = yvals[indexOfSecondsample];
+        final double x2 = yvals[indexOfThirdSample];
+
+        final double t = xvals[indexOfDifferentiation] - xvals[indexOfFirstSample];
+        final double t1 = xvals[indexOfSecondsample] - xvals[indexOfFirstSample];
+        final double t2 = xvals[indexOfThirdSample] - xvals[indexOfFirstSample];
+
+        final double a = (x2 - x0 - (t2 / t1 * (x1 - x0))) / (t2 * t2 - t1 * t2);
+        final double b = (x1 - x0 - a * t1 * t1) / t1;
+
+        return (2 * a * t) + b;
+    }
+
+    /**
+     * Creates a Hermite cubic spline interpolation from the set of (x,y) value
+     * pairs and their derivatives. This is modeled off of the
+     * InterpolateHermiteSorted method in the Math.NET CubicSpline class.
+     *
+     * @param xvals x values for interpolation
+     * @param yvals y values for interpolation
+     * @param firstDerivatives first derivative values of the function
+     * @return polynomial that fits the function
+     */
+    private PolynomialSplineFunction interpolateHermiteSorted(double[] xvals,
+                                                              double[] yvals,
+                                                              double[] firstDerivatives) {
+        if (xvals.length != yvals.length) {
+            throw new DimensionMismatchException(xvals.length, yvals.length);
+        }
+
+        if (xvals.length != firstDerivatives.length) {
+            throw new DimensionMismatchException(xvals.length,
+                                                 firstDerivatives.length);
+        }
+
+        final int minimumLength = 2;
+        if (xvals.length < minimumLength) {
+            throw new NumberIsTooSmallException(LocalizedFormats.NUMBER_OF_POINTS,
+                                                xvals.length, minimumLength,
+                                                true);
+        }
+
+        final int size = xvals.length - 1;
+        final PolynomialFunction[] polynomials = new PolynomialFunction[size];
+        final double[] coefficients = new double[4];
+
+        for (int i = 0; i < polynomials.length; i++) {
+            final double w = xvals[i + 1] - xvals[i];
+            final double w2 = w * w;
+
+            final double yv = yvals[i];
+            final double yvP = yvals[i + 1];
+
+            final double fd = firstDerivatives[i];
+            final double fdP = firstDerivatives[i + 1];
+
+            coefficients[0] = yv;
+            coefficients[1] = firstDerivatives[i];
+            coefficients[2] = (3 * (yvP - yv) / w - 2 * fd - fdP) / w;
+            coefficients[3] = (2 * (yv - yvP) / w + fd + fdP) / w2;
+            polynomials[i] = new PolynomialFunction(coefficients);
+        }
+
+        return new PolynomialSplineFunction(xvals, polynomials);
+
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/interpolation/BicubicInterpolatingFunction.java b/src/main/java/org/apache/commons/math3/analysis/interpolation/BicubicInterpolatingFunction.java
new file mode 100644
index 0000000..454d8f8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/interpolation/BicubicInterpolatingFunction.java
@@ -0,0 +1,325 @@
+/*
+ * 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.math3.analysis.interpolation;
+
+import java.util.Arrays;
+import org.apache.commons.math3.analysis.BivariateFunction;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.NonMonotonicSequenceException;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * Function that implements the
+ * <a href="http://en.wikipedia.org/wiki/Bicubic_interpolation">
+ * bicubic spline interpolation</a>.
+ *
+ * @since 3.4
+ */
+public class BicubicInterpolatingFunction
+    implements BivariateFunction {
+    /** Number of coefficients. */
+    private static final int NUM_COEFF = 16;
+    /**
+     * Matrix to compute the spline coefficients from the function values
+     * and function derivatives values
+     */
+    private static final double[][] AINV = {
+        { 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0 },
+        { -3,3,0,0,-2,-1,0,0,0,0,0,0,0,0,0,0 },
+        { 2,-2,0,0,1,1,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0 },
+        { 0,0,0,0,0,0,0,0,-3,3,0,0,-2,-1,0,0 },
+        { 0,0,0,0,0,0,0,0,2,-2,0,0,1,1,0,0 },
+        { -3,0,3,0,0,0,0,0,-2,0,-1,0,0,0,0,0 },
+        { 0,0,0,0,-3,0,3,0,0,0,0,0,-2,0,-1,0 },
+        { 9,-9,-9,9,6,3,-6,-3,6,-6,3,-3,4,2,2,1 },
+        { -6,6,6,-6,-3,-3,3,3,-4,4,-2,2,-2,-2,-1,-1 },
+        { 2,0,-2,0,0,0,0,0,1,0,1,0,0,0,0,0 },
+        { 0,0,0,0,2,0,-2,0,0,0,0,0,1,0,1,0 },
+        { -6,6,6,-6,-4,-2,4,2,-3,3,-3,3,-2,-1,-2,-1 },
+        { 4,-4,-4,4,2,2,-2,-2,2,-2,2,-2,1,1,1,1 }
+    };
+
+    /** Samples x-coordinates */
+    private final double[] xval;
+    /** Samples y-coordinates */
+    private final double[] yval;
+    /** Set of cubic splines patching the whole data grid */
+    private final BicubicFunction[][] splines;
+
+    /**
+     * @param x Sample values of the x-coordinate, in increasing order.
+     * @param y Sample values of the y-coordinate, in increasing order.
+     * @param f Values of the function on every grid point.
+     * @param dFdX Values of the partial derivative of function with respect
+     * to x on every grid point.
+     * @param dFdY Values of the partial derivative of function with respect
+     * to y on every grid point.
+     * @param d2FdXdY Values of the cross partial derivative of function on
+     * every grid point.
+     * @throws DimensionMismatchException if the various arrays do not contain
+     * the expected number of elements.
+     * @throws NonMonotonicSequenceException if {@code x} or {@code y} are
+     * not strictly increasing.
+     * @throws NoDataException if any of the arrays has zero length.
+     */
+    public BicubicInterpolatingFunction(double[] x,
+                                        double[] y,
+                                        double[][] f,
+                                        double[][] dFdX,
+                                        double[][] dFdY,
+                                        double[][] d2FdXdY)
+        throws DimensionMismatchException,
+               NoDataException,
+               NonMonotonicSequenceException {
+        final int xLen = x.length;
+        final int yLen = y.length;
+
+        if (xLen == 0 || yLen == 0 || f.length == 0 || f[0].length == 0) {
+            throw new NoDataException();
+        }
+        if (xLen != f.length) {
+            throw new DimensionMismatchException(xLen, f.length);
+        }
+        if (xLen != dFdX.length) {
+            throw new DimensionMismatchException(xLen, dFdX.length);
+        }
+        if (xLen != dFdY.length) {
+            throw new DimensionMismatchException(xLen, dFdY.length);
+        }
+        if (xLen != d2FdXdY.length) {
+            throw new DimensionMismatchException(xLen, d2FdXdY.length);
+        }
+
+        MathArrays.checkOrder(x);
+        MathArrays.checkOrder(y);
+
+        xval = x.clone();
+        yval = y.clone();
+
+        final int lastI = xLen - 1;
+        final int lastJ = yLen - 1;
+        splines = new BicubicFunction[lastI][lastJ];
+
+        for (int i = 0; i < lastI; i++) {
+            if (f[i].length != yLen) {
+                throw new DimensionMismatchException(f[i].length, yLen);
+            }
+            if (dFdX[i].length != yLen) {
+                throw new DimensionMismatchException(dFdX[i].length, yLen);
+            }
+            if (dFdY[i].length != yLen) {
+                throw new DimensionMismatchException(dFdY[i].length, yLen);
+            }
+            if (d2FdXdY[i].length != yLen) {
+                throw new DimensionMismatchException(d2FdXdY[i].length, yLen);
+            }
+            final int ip1 = i + 1;
+            final double xR = xval[ip1] - xval[i];
+            for (int j = 0; j < lastJ; j++) {
+                final int jp1 = j + 1;
+                final double yR = yval[jp1] - yval[j];
+                final double xRyR = xR * yR;
+                final double[] beta = new double[] {
+                    f[i][j], f[ip1][j], f[i][jp1], f[ip1][jp1],
+                    dFdX[i][j] * xR, dFdX[ip1][j] * xR, dFdX[i][jp1] * xR, dFdX[ip1][jp1] * xR,
+                    dFdY[i][j] * yR, dFdY[ip1][j] * yR, dFdY[i][jp1] * yR, dFdY[ip1][jp1] * yR,
+                    d2FdXdY[i][j] * xRyR, d2FdXdY[ip1][j] * xRyR, d2FdXdY[i][jp1] * xRyR, d2FdXdY[ip1][jp1] * xRyR
+                };
+
+                splines[i][j] = new BicubicFunction(computeSplineCoefficients(beta));
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public double value(double x, double y)
+        throws OutOfRangeException {
+        final int i = searchIndex(x, xval);
+        final int j = searchIndex(y, yval);
+
+        final double xN = (x - xval[i]) / (xval[i + 1] - xval[i]);
+        final double yN = (y - yval[j]) / (yval[j + 1] - yval[j]);
+
+        return splines[i][j].value(xN, yN);
+    }
+
+    /**
+     * Indicates whether a point is within the interpolation range.
+     *
+     * @param x First coordinate.
+     * @param y Second coordinate.
+     * @return {@code true} if (x, y) is a valid point.
+     */
+    public boolean isValidPoint(double x, double y) {
+        if (x < xval[0] ||
+            x > xval[xval.length - 1] ||
+            y < yval[0] ||
+            y > yval[yval.length - 1]) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * @param c Coordinate.
+     * @param val Coordinate samples.
+     * @return the index in {@code val} corresponding to the interval
+     * containing {@code c}.
+     * @throws OutOfRangeException if {@code c} is out of the
+     * range defined by the boundary values of {@code val}.
+     */
+    private int searchIndex(double c, double[] val) {
+        final int r = Arrays.binarySearch(val, c);
+
+        if (r == -1 ||
+            r == -val.length - 1) {
+            throw new OutOfRangeException(c, val[0], val[val.length - 1]);
+        }
+
+        if (r < 0) {
+            // "c" in within an interpolation sub-interval: Return the
+            // index of the sample at the lower end of the sub-interval.
+            return -r - 2;
+        }
+        final int last = val.length - 1;
+        if (r == last) {
+            // "c" is the last sample of the range: Return the index
+            // of the sample at the lower end of the last sub-interval.
+            return last - 1;
+        }
+
+        // "c" is another sample point.
+        return r;
+    }
+
+    /**
+     * Compute the spline coefficients from the list of function values and
+     * function partial derivatives values at the four corners of a grid
+     * element. They must be specified in the following order:
+     * <ul>
+     *  <li>f(0,0)</li>
+     *  <li>f(1,0)</li>
+     *  <li>f(0,1)</li>
+     *  <li>f(1,1)</li>
+     *  <li>f<sub>x</sub>(0,0)</li>
+     *  <li>f<sub>x</sub>(1,0)</li>
+     *  <li>f<sub>x</sub>(0,1)</li>
+     *  <li>f<sub>x</sub>(1,1)</li>
+     *  <li>f<sub>y</sub>(0,0)</li>
+     *  <li>f<sub>y</sub>(1,0)</li>
+     *  <li>f<sub>y</sub>(0,1)</li>
+     *  <li>f<sub>y</sub>(1,1)</li>
+     *  <li>f<sub>xy</sub>(0,0)</li>
+     *  <li>f<sub>xy</sub>(1,0)</li>
+     *  <li>f<sub>xy</sub>(0,1)</li>
+     *  <li>f<sub>xy</sub>(1,1)</li>
+     * </ul>
+     * where the subscripts indicate the partial derivative with respect to
+     * the corresponding variable(s).
+     *
+     * @param beta List of function values and function partial derivatives
+     * values.
+     * @return the spline coefficients.
+     */
+    private double[] computeSplineCoefficients(double[] beta) {
+        final double[] a = new double[NUM_COEFF];
+
+        for (int i = 0; i < NUM_COEFF; i++) {
+            double result = 0;
+            final double[] row = AINV[i];
+            for (int j = 0; j < NUM_COEFF; j++) {
+                result += row[j] * beta[j];
+            }
+            a[i] = result;
+        }
+
+        return a;
+    }
+}
+
+/**
+ * Bicubic function.
+ */
+class BicubicFunction implements BivariateFunction {
+    /** Number of points. */
+    private static final short N = 4;
+    /** Coefficients */
+    private final double[][] a;
+
+    /**
+     * Simple constructor.
+     *
+     * @param coeff Spline coefficients.
+     */
+    BicubicFunction(double[] coeff) {
+        a = new double[N][N];
+        for (int j = 0; j < N; j++) {
+            final double[] aJ = a[j];
+            for (int i = 0; i < N; i++) {
+                aJ[i] = coeff[i * N + j];
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public double value(double x, double y) {
+        if (x < 0 || x > 1) {
+            throw new OutOfRangeException(x, 0, 1);
+        }
+        if (y < 0 || y > 1) {
+            throw new OutOfRangeException(y, 0, 1);
+        }
+
+        final double x2 = x * x;
+        final double x3 = x2 * x;
+        final double[] pX = {1, x, x2, x3};
+
+        final double y2 = y * y;
+        final double y3 = y2 * y;
+        final double[] pY = {1, y, y2, y3};
+
+        return apply(pX, pY, a);
+    }
+
+    /**
+     * Compute the value of the bicubic polynomial.
+     *
+     * @param pX Powers of the x-coordinate.
+     * @param pY Powers of the y-coordinate.
+     * @param coeff Spline coefficients.
+     * @return the interpolated value.
+     */
+    private double apply(double[] pX, double[] pY, double[][] coeff) {
+        double result = 0;
+        for (int i = 0; i < N; i++) {
+            final double r = MathArrays.linearCombination(coeff[i], pY);
+            result += r * pX[i];
+        }
+
+        return result;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/interpolation/BicubicInterpolator.java b/src/main/java/org/apache/commons/math3/analysis/interpolation/BicubicInterpolator.java
new file mode 100644
index 0000000..4aebdff
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/interpolation/BicubicInterpolator.java
@@ -0,0 +1,113 @@
+/*
+ * 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.math3.analysis.interpolation;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NonMonotonicSequenceException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * Generates a {@link BicubicInterpolatingFunction bicubic interpolating
+ * function}.
+ * <p>
+ *  Caveat: Because the interpolation scheme requires that derivatives be
+ *  specified at the sample points, those are approximated with finite
+ *  differences (using the 2-points symmetric formulae).
+ *  Since their values are undefined at the borders of the provided
+ *  interpolation ranges, the interpolated values will be wrong at the
+ *  edges of the patch.
+ *  The {@code interpolate} method will return a function that overrides
+ *  {@link BicubicInterpolatingFunction#isValidPoint(double,double)} to
+ *  indicate points where the interpolation will be inaccurate.
+ * </p>
+ *
+ * @since 3.4
+ */
+public class BicubicInterpolator
+    implements BivariateGridInterpolator {
+    /**
+     * {@inheritDoc}
+     */
+    public BicubicInterpolatingFunction interpolate(final double[] xval,
+                                                    final double[] yval,
+                                                    final double[][] fval)
+        throws NoDataException, DimensionMismatchException,
+               NonMonotonicSequenceException, NumberIsTooSmallException {
+        if (xval.length == 0 || yval.length == 0 || fval.length == 0) {
+            throw new NoDataException();
+        }
+        if (xval.length != fval.length) {
+            throw new DimensionMismatchException(xval.length, fval.length);
+        }
+
+        MathArrays.checkOrder(xval);
+        MathArrays.checkOrder(yval);
+
+        final int xLen = xval.length;
+        final int yLen = yval.length;
+
+        // Approximation to the partial derivatives using finite differences.
+        final double[][] dFdX = new double[xLen][yLen];
+        final double[][] dFdY = new double[xLen][yLen];
+        final double[][] d2FdXdY = new double[xLen][yLen];
+        for (int i = 1; i < xLen - 1; i++) {
+            final int nI = i + 1;
+            final int pI = i - 1;
+
+            final double nX = xval[nI];
+            final double pX = xval[pI];
+
+            final double deltaX = nX - pX;
+
+            for (int j = 1; j < yLen - 1; j++) {
+                final int nJ = j + 1;
+                final int pJ = j - 1;
+
+                final double nY = yval[nJ];
+                final double pY = yval[pJ];
+
+                final double deltaY = nY - pY;
+
+                dFdX[i][j] = (fval[nI][j] - fval[pI][j]) / deltaX;
+                dFdY[i][j] = (fval[i][nJ] - fval[i][pJ]) / deltaY;
+
+                final double deltaXY = deltaX * deltaY;
+
+                d2FdXdY[i][j] = (fval[nI][nJ] - fval[nI][pJ] - fval[pI][nJ] + fval[pI][pJ]) / deltaXY;
+            }
+        }
+
+        // Create the interpolating function.
+        return new BicubicInterpolatingFunction(xval, yval, fval,
+                                                dFdX, dFdY, d2FdXdY) {
+            /** {@inheritDoc} */
+            @Override
+            public boolean isValidPoint(double x, double y) {
+                if (x < xval[1] ||
+                    x > xval[xval.length - 2] ||
+                    y < yval[1] ||
+                    y > yval[yval.length - 2]) {
+                    return false;
+                } else {
+                    return true;
+                }
+            }
+        };
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/interpolation/BicubicSplineInterpolatingFunction.java b/src/main/java/org/apache/commons/math3/analysis/interpolation/BicubicSplineInterpolatingFunction.java
new file mode 100644
index 0000000..8f1771a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/interpolation/BicubicSplineInterpolatingFunction.java
@@ -0,0 +1,641 @@
+/*
+ * 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.math3.analysis.interpolation;
+
+import java.util.Arrays;
+import org.apache.commons.math3.analysis.BivariateFunction;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.NonMonotonicSequenceException;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * Function that implements the
+ * <a href="http://en.wikipedia.org/wiki/Bicubic_interpolation">
+ * bicubic spline interpolation</a>. Due to numerical accuracy issues this should not
+ * be used.
+ *
+ * @since 2.1
+ * @deprecated as of 3.4 replaced by
+ * {@link org.apache.commons.math3.analysis.interpolation.PiecewiseBicubicSplineInterpolatingFunction}
+ */
+@Deprecated
+public class BicubicSplineInterpolatingFunction
+    implements BivariateFunction {
+    /** Number of coefficients. */
+    private static final int NUM_COEFF = 16;
+    /**
+     * Matrix to compute the spline coefficients from the function values
+     * and function derivatives values
+     */
+    private static final double[][] AINV = {
+        { 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0 },
+        { -3,3,0,0,-2,-1,0,0,0,0,0,0,0,0,0,0 },
+        { 2,-2,0,0,1,1,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0 },
+        { 0,0,0,0,0,0,0,0,-3,3,0,0,-2,-1,0,0 },
+        { 0,0,0,0,0,0,0,0,2,-2,0,0,1,1,0,0 },
+        { -3,0,3,0,0,0,0,0,-2,0,-1,0,0,0,0,0 },
+        { 0,0,0,0,-3,0,3,0,0,0,0,0,-2,0,-1,0 },
+        { 9,-9,-9,9,6,3,-6,-3,6,-6,3,-3,4,2,2,1 },
+        { -6,6,6,-6,-3,-3,3,3,-4,4,-2,2,-2,-2,-1,-1 },
+        { 2,0,-2,0,0,0,0,0,1,0,1,0,0,0,0,0 },
+        { 0,0,0,0,2,0,-2,0,0,0,0,0,1,0,1,0 },
+        { -6,6,6,-6,-4,-2,4,2,-3,3,-3,3,-2,-1,-2,-1 },
+        { 4,-4,-4,4,2,2,-2,-2,2,-2,2,-2,1,1,1,1 }
+    };
+
+    /** Samples x-coordinates */
+    private final double[] xval;
+    /** Samples y-coordinates */
+    private final double[] yval;
+    /** Set of cubic splines patching the whole data grid */
+    private final BicubicSplineFunction[][] splines;
+    /**
+     * Partial derivatives.
+     * The value of the first index determines the kind of derivatives:
+     * 0 = first partial derivatives wrt x
+     * 1 = first partial derivatives wrt y
+     * 2 = second partial derivatives wrt x
+     * 3 = second partial derivatives wrt y
+     * 4 = cross partial derivatives
+     */
+    private final BivariateFunction[][][] partialDerivatives;
+
+    /**
+     * @param x Sample values of the x-coordinate, in increasing order.
+     * @param y Sample values of the y-coordinate, in increasing order.
+     * @param f Values of the function on every grid point.
+     * @param dFdX Values of the partial derivative of function with respect
+     * to x on every grid point.
+     * @param dFdY Values of the partial derivative of function with respect
+     * to y on every grid point.
+     * @param d2FdXdY Values of the cross partial derivative of function on
+     * every grid point.
+     * @throws DimensionMismatchException if the various arrays do not contain
+     * the expected number of elements.
+     * @throws NonMonotonicSequenceException if {@code x} or {@code y} are
+     * not strictly increasing.
+     * @throws NoDataException if any of the arrays has zero length.
+     */
+    public BicubicSplineInterpolatingFunction(double[] x,
+                                              double[] y,
+                                              double[][] f,
+                                              double[][] dFdX,
+                                              double[][] dFdY,
+                                              double[][] d2FdXdY)
+        throws DimensionMismatchException,
+               NoDataException,
+               NonMonotonicSequenceException {
+        this(x, y, f, dFdX, dFdY, d2FdXdY, false);
+    }
+
+    /**
+     * @param x Sample values of the x-coordinate, in increasing order.
+     * @param y Sample values of the y-coordinate, in increasing order.
+     * @param f Values of the function on every grid point.
+     * @param dFdX Values of the partial derivative of function with respect
+     * to x on every grid point.
+     * @param dFdY Values of the partial derivative of function with respect
+     * to y on every grid point.
+     * @param d2FdXdY Values of the cross partial derivative of function on
+     * every grid point.
+     * @param initializeDerivatives Whether to initialize the internal data
+     * needed for calling any of the methods that compute the partial derivatives
+     * this function.
+     * @throws DimensionMismatchException if the various arrays do not contain
+     * the expected number of elements.
+     * @throws NonMonotonicSequenceException if {@code x} or {@code y} are
+     * not strictly increasing.
+     * @throws NoDataException if any of the arrays has zero length.
+     *
+     * @see #partialDerivativeX(double,double)
+     * @see #partialDerivativeY(double,double)
+     * @see #partialDerivativeXX(double,double)
+     * @see #partialDerivativeYY(double,double)
+     * @see #partialDerivativeXY(double,double)
+     */
+    public BicubicSplineInterpolatingFunction(double[] x,
+                                              double[] y,
+                                              double[][] f,
+                                              double[][] dFdX,
+                                              double[][] dFdY,
+                                              double[][] d2FdXdY,
+                                              boolean initializeDerivatives)
+        throws DimensionMismatchException,
+               NoDataException,
+               NonMonotonicSequenceException {
+        final int xLen = x.length;
+        final int yLen = y.length;
+
+        if (xLen == 0 || yLen == 0 || f.length == 0 || f[0].length == 0) {
+            throw new NoDataException();
+        }
+        if (xLen != f.length) {
+            throw new DimensionMismatchException(xLen, f.length);
+        }
+        if (xLen != dFdX.length) {
+            throw new DimensionMismatchException(xLen, dFdX.length);
+        }
+        if (xLen != dFdY.length) {
+            throw new DimensionMismatchException(xLen, dFdY.length);
+        }
+        if (xLen != d2FdXdY.length) {
+            throw new DimensionMismatchException(xLen, d2FdXdY.length);
+        }
+
+        MathArrays.checkOrder(x);
+        MathArrays.checkOrder(y);
+
+        xval = x.clone();
+        yval = y.clone();
+
+        final int lastI = xLen - 1;
+        final int lastJ = yLen - 1;
+        splines = new BicubicSplineFunction[lastI][lastJ];
+
+        for (int i = 0; i < lastI; i++) {
+            if (f[i].length != yLen) {
+                throw new DimensionMismatchException(f[i].length, yLen);
+            }
+            if (dFdX[i].length != yLen) {
+                throw new DimensionMismatchException(dFdX[i].length, yLen);
+            }
+            if (dFdY[i].length != yLen) {
+                throw new DimensionMismatchException(dFdY[i].length, yLen);
+            }
+            if (d2FdXdY[i].length != yLen) {
+                throw new DimensionMismatchException(d2FdXdY[i].length, yLen);
+            }
+            final int ip1 = i + 1;
+            for (int j = 0; j < lastJ; j++) {
+                final int jp1 = j + 1;
+                final double[] beta = new double[] {
+                    f[i][j], f[ip1][j], f[i][jp1], f[ip1][jp1],
+                    dFdX[i][j], dFdX[ip1][j], dFdX[i][jp1], dFdX[ip1][jp1],
+                    dFdY[i][j], dFdY[ip1][j], dFdY[i][jp1], dFdY[ip1][jp1],
+                    d2FdXdY[i][j], d2FdXdY[ip1][j], d2FdXdY[i][jp1], d2FdXdY[ip1][jp1]
+                };
+
+                splines[i][j] = new BicubicSplineFunction(computeSplineCoefficients(beta),
+                                                          initializeDerivatives);
+            }
+        }
+
+        if (initializeDerivatives) {
+            // Compute all partial derivatives.
+            partialDerivatives = new BivariateFunction[5][lastI][lastJ];
+
+            for (int i = 0; i < lastI; i++) {
+                for (int j = 0; j < lastJ; j++) {
+                    final BicubicSplineFunction bcs = splines[i][j];
+                    partialDerivatives[0][i][j] = bcs.partialDerivativeX();
+                    partialDerivatives[1][i][j] = bcs.partialDerivativeY();
+                    partialDerivatives[2][i][j] = bcs.partialDerivativeXX();
+                    partialDerivatives[3][i][j] = bcs.partialDerivativeYY();
+                    partialDerivatives[4][i][j] = bcs.partialDerivativeXY();
+                }
+            }
+        } else {
+            // Partial derivative methods cannot be used.
+            partialDerivatives = null;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public double value(double x, double y)
+        throws OutOfRangeException {
+        final int i = searchIndex(x, xval);
+        final int j = searchIndex(y, yval);
+
+        final double xN = (x - xval[i]) / (xval[i + 1] - xval[i]);
+        final double yN = (y - yval[j]) / (yval[j + 1] - yval[j]);
+
+        return splines[i][j].value(xN, yN);
+    }
+
+    /**
+     * Indicates whether a point is within the interpolation range.
+     *
+     * @param x First coordinate.
+     * @param y Second coordinate.
+     * @return {@code true} if (x, y) is a valid point.
+     * @since 3.3
+     */
+    public boolean isValidPoint(double x, double y) {
+        if (x < xval[0] ||
+            x > xval[xval.length - 1] ||
+            y < yval[0] ||
+            y > yval[yval.length - 1]) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * @param x x-coordinate.
+     * @param y y-coordinate.
+     * @return the value at point (x, y) of the first partial derivative with
+     * respect to x.
+     * @throws OutOfRangeException if {@code x} (resp. {@code y}) is outside
+     * the range defined by the boundary values of {@code xval} (resp.
+     * {@code yval}).
+     * @throws NullPointerException if the internal data were not initialized
+     * (cf. {@link #BicubicSplineInterpolatingFunction(double[],double[],double[][],
+     *             double[][],double[][],double[][],boolean) constructor}).
+     */
+    public double partialDerivativeX(double x, double y)
+        throws OutOfRangeException {
+        return partialDerivative(0, x, y);
+    }
+    /**
+     * @param x x-coordinate.
+     * @param y y-coordinate.
+     * @return the value at point (x, y) of the first partial derivative with
+     * respect to y.
+     * @throws OutOfRangeException if {@code x} (resp. {@code y}) is outside
+     * the range defined by the boundary values of {@code xval} (resp.
+     * {@code yval}).
+     * @throws NullPointerException if the internal data were not initialized
+     * (cf. {@link #BicubicSplineInterpolatingFunction(double[],double[],double[][],
+     *             double[][],double[][],double[][],boolean) constructor}).
+     */
+    public double partialDerivativeY(double x, double y)
+        throws OutOfRangeException {
+        return partialDerivative(1, x, y);
+    }
+    /**
+     * @param x x-coordinate.
+     * @param y y-coordinate.
+     * @return the value at point (x, y) of the second partial derivative with
+     * respect to x.
+     * @throws OutOfRangeException if {@code x} (resp. {@code y}) is outside
+     * the range defined by the boundary values of {@code xval} (resp.
+     * {@code yval}).
+     * @throws NullPointerException if the internal data were not initialized
+     * (cf. {@link #BicubicSplineInterpolatingFunction(double[],double[],double[][],
+     *             double[][],double[][],double[][],boolean) constructor}).
+     */
+    public double partialDerivativeXX(double x, double y)
+        throws OutOfRangeException {
+        return partialDerivative(2, x, y);
+    }
+    /**
+     * @param x x-coordinate.
+     * @param y y-coordinate.
+     * @return the value at point (x, y) of the second partial derivative with
+     * respect to y.
+     * @throws OutOfRangeException if {@code x} (resp. {@code y}) is outside
+     * the range defined by the boundary values of {@code xval} (resp.
+     * {@code yval}).
+     * @throws NullPointerException if the internal data were not initialized
+     * (cf. {@link #BicubicSplineInterpolatingFunction(double[],double[],double[][],
+     *             double[][],double[][],double[][],boolean) constructor}).
+     */
+    public double partialDerivativeYY(double x, double y)
+        throws OutOfRangeException {
+        return partialDerivative(3, x, y);
+    }
+    /**
+     * @param x x-coordinate.
+     * @param y y-coordinate.
+     * @return the value at point (x, y) of the second partial cross-derivative.
+     * @throws OutOfRangeException if {@code x} (resp. {@code y}) is outside
+     * the range defined by the boundary values of {@code xval} (resp.
+     * {@code yval}).
+     * @throws NullPointerException if the internal data were not initialized
+     * (cf. {@link #BicubicSplineInterpolatingFunction(double[],double[],double[][],
+     *             double[][],double[][],double[][],boolean) constructor}).
+     */
+    public double partialDerivativeXY(double x, double y)
+        throws OutOfRangeException {
+        return partialDerivative(4, x, y);
+    }
+
+    /**
+     * @param which First index in {@link #partialDerivatives}.
+     * @param x x-coordinate.
+     * @param y y-coordinate.
+     * @return the value at point (x, y) of the selected partial derivative.
+     * @throws OutOfRangeException if {@code x} (resp. {@code y}) is outside
+     * the range defined by the boundary values of {@code xval} (resp.
+     * {@code yval}).
+     * @throws NullPointerException if the internal data were not initialized
+     * (cf. {@link #BicubicSplineInterpolatingFunction(double[],double[],double[][],
+     *             double[][],double[][],double[][],boolean) constructor}).
+     */
+    private double partialDerivative(int which, double x, double y)
+        throws OutOfRangeException {
+        final int i = searchIndex(x, xval);
+        final int j = searchIndex(y, yval);
+
+        final double xN = (x - xval[i]) / (xval[i + 1] - xval[i]);
+        final double yN = (y - yval[j]) / (yval[j + 1] - yval[j]);
+
+        return partialDerivatives[which][i][j].value(xN, yN);
+    }
+
+    /**
+     * @param c Coordinate.
+     * @param val Coordinate samples.
+     * @return the index in {@code val} corresponding to the interval
+     * containing {@code c}.
+     * @throws OutOfRangeException if {@code c} is out of the
+     * range defined by the boundary values of {@code val}.
+     */
+    private int searchIndex(double c, double[] val) {
+        final int r = Arrays.binarySearch(val, c);
+
+        if (r == -1 ||
+            r == -val.length - 1) {
+            throw new OutOfRangeException(c, val[0], val[val.length - 1]);
+        }
+
+        if (r < 0) {
+            // "c" in within an interpolation sub-interval: Return the
+            // index of the sample at the lower end of the sub-interval.
+            return -r - 2;
+        }
+        final int last = val.length - 1;
+        if (r == last) {
+            // "c" is the last sample of the range: Return the index
+            // of the sample at the lower end of the last sub-interval.
+            return last - 1;
+        }
+
+        // "c" is another sample point.
+        return r;
+    }
+
+    /**
+     * Compute the spline coefficients from the list of function values and
+     * function partial derivatives values at the four corners of a grid
+     * element. They must be specified in the following order:
+     * <ul>
+     *  <li>f(0,0)</li>
+     *  <li>f(1,0)</li>
+     *  <li>f(0,1)</li>
+     *  <li>f(1,1)</li>
+     *  <li>f<sub>x</sub>(0,0)</li>
+     *  <li>f<sub>x</sub>(1,0)</li>
+     *  <li>f<sub>x</sub>(0,1)</li>
+     *  <li>f<sub>x</sub>(1,1)</li>
+     *  <li>f<sub>y</sub>(0,0)</li>
+     *  <li>f<sub>y</sub>(1,0)</li>
+     *  <li>f<sub>y</sub>(0,1)</li>
+     *  <li>f<sub>y</sub>(1,1)</li>
+     *  <li>f<sub>xy</sub>(0,0)</li>
+     *  <li>f<sub>xy</sub>(1,0)</li>
+     *  <li>f<sub>xy</sub>(0,1)</li>
+     *  <li>f<sub>xy</sub>(1,1)</li>
+     * </ul>
+     * where the subscripts indicate the partial derivative with respect to
+     * the corresponding variable(s).
+     *
+     * @param beta List of function values and function partial derivatives
+     * values.
+     * @return the spline coefficients.
+     */
+    private double[] computeSplineCoefficients(double[] beta) {
+        final double[] a = new double[NUM_COEFF];
+
+        for (int i = 0; i < NUM_COEFF; i++) {
+            double result = 0;
+            final double[] row = AINV[i];
+            for (int j = 0; j < NUM_COEFF; j++) {
+                result += row[j] * beta[j];
+            }
+            a[i] = result;
+        }
+
+        return a;
+    }
+}
+
+/**
+ * 2D-spline function.
+ *
+ */
+class BicubicSplineFunction implements BivariateFunction {
+    /** Number of points. */
+    private static final short N = 4;
+    /** Coefficients */
+    private final double[][] a;
+    /** First partial derivative along x. */
+    private final BivariateFunction partialDerivativeX;
+    /** First partial derivative along y. */
+    private final BivariateFunction partialDerivativeY;
+    /** Second partial derivative along x. */
+    private final BivariateFunction partialDerivativeXX;
+    /** Second partial derivative along y. */
+    private final BivariateFunction partialDerivativeYY;
+    /** Second crossed partial derivative. */
+    private final BivariateFunction partialDerivativeXY;
+
+    /**
+     * Simple constructor.
+     *
+     * @param coeff Spline coefficients.
+     */
+    BicubicSplineFunction(double[] coeff) {
+        this(coeff, false);
+    }
+
+    /**
+     * Simple constructor.
+     *
+     * @param coeff Spline coefficients.
+     * @param initializeDerivatives Whether to initialize the internal data
+     * needed for calling any of the methods that compute the partial derivatives
+     * this function.
+     */
+    BicubicSplineFunction(double[] coeff, boolean initializeDerivatives) {
+        a = new double[N][N];
+        for (int i = 0; i < N; i++) {
+            for (int j = 0; j < N; j++) {
+                a[i][j] = coeff[i * N + j];
+            }
+        }
+
+        if (initializeDerivatives) {
+            // Compute all partial derivatives functions.
+            final double[][] aX = new double[N][N];
+            final double[][] aY = new double[N][N];
+            final double[][] aXX = new double[N][N];
+            final double[][] aYY = new double[N][N];
+            final double[][] aXY = new double[N][N];
+
+            for (int i = 0; i < N; i++) {
+                for (int j = 0; j < N; j++) {
+                    final double c = a[i][j];
+                    aX[i][j] = i * c;
+                    aY[i][j] = j * c;
+                    aXX[i][j] = (i - 1) * aX[i][j];
+                    aYY[i][j] = (j - 1) * aY[i][j];
+                    aXY[i][j] = j * aX[i][j];
+                }
+            }
+
+            partialDerivativeX = new BivariateFunction() {
+                    /** {@inheritDoc} */
+                    public double value(double x, double y)  {
+                        final double x2 = x * x;
+                        final double[] pX = {0, 1, x, x2};
+
+                        final double y2 = y * y;
+                        final double y3 = y2 * y;
+                        final double[] pY = {1, y, y2, y3};
+
+                        return apply(pX, pY, aX);
+                    }
+                };
+            partialDerivativeY = new BivariateFunction() {
+                    /** {@inheritDoc} */
+                    public double value(double x, double y)  {
+                        final double x2 = x * x;
+                        final double x3 = x2 * x;
+                        final double[] pX = {1, x, x2, x3};
+
+                        final double y2 = y * y;
+                        final double[] pY = {0, 1, y, y2};
+
+                        return apply(pX, pY, aY);
+                    }
+                };
+            partialDerivativeXX = new BivariateFunction() {
+                    /** {@inheritDoc} */
+                    public double value(double x, double y)  {
+                        final double[] pX = {0, 0, 1, x};
+
+                        final double y2 = y * y;
+                        final double y3 = y2 * y;
+                        final double[] pY = {1, y, y2, y3};
+
+                        return apply(pX, pY, aXX);
+                    }
+                };
+            partialDerivativeYY = new BivariateFunction() {
+                    /** {@inheritDoc} */
+                    public double value(double x, double y)  {
+                        final double x2 = x * x;
+                        final double x3 = x2 * x;
+                        final double[] pX = {1, x, x2, x3};
+
+                        final double[] pY = {0, 0, 1, y};
+
+                        return apply(pX, pY, aYY);
+                    }
+                };
+            partialDerivativeXY = new BivariateFunction() {
+                    /** {@inheritDoc} */
+                    public double value(double x, double y)  {
+                        final double x2 = x * x;
+                        final double[] pX = {0, 1, x, x2};
+
+                        final double y2 = y * y;
+                        final double[] pY = {0, 1, y, y2};
+
+                        return apply(pX, pY, aXY);
+                    }
+                };
+        } else {
+            partialDerivativeX = null;
+            partialDerivativeY = null;
+            partialDerivativeXX = null;
+            partialDerivativeYY = null;
+            partialDerivativeXY = null;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public double value(double x, double y) {
+        if (x < 0 || x > 1) {
+            throw new OutOfRangeException(x, 0, 1);
+        }
+        if (y < 0 || y > 1) {
+            throw new OutOfRangeException(y, 0, 1);
+        }
+
+        final double x2 = x * x;
+        final double x3 = x2 * x;
+        final double[] pX = {1, x, x2, x3};
+
+        final double y2 = y * y;
+        final double y3 = y2 * y;
+        final double[] pY = {1, y, y2, y3};
+
+        return apply(pX, pY, a);
+    }
+
+    /**
+     * Compute the value of the bicubic polynomial.
+     *
+     * @param pX Powers of the x-coordinate.
+     * @param pY Powers of the y-coordinate.
+     * @param coeff Spline coefficients.
+     * @return the interpolated value.
+     */
+    private double apply(double[] pX, double[] pY, double[][] coeff) {
+        double result = 0;
+        for (int i = 0; i < N; i++) {
+            for (int j = 0; j < N; j++) {
+                result += coeff[i][j] * pX[i] * pY[j];
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * @return the partial derivative wrt {@code x}.
+     */
+    public BivariateFunction partialDerivativeX() {
+        return partialDerivativeX;
+    }
+    /**
+     * @return the partial derivative wrt {@code y}.
+     */
+    public BivariateFunction partialDerivativeY() {
+        return partialDerivativeY;
+    }
+    /**
+     * @return the second partial derivative wrt {@code x}.
+     */
+    public BivariateFunction partialDerivativeXX() {
+        return partialDerivativeXX;
+    }
+    /**
+     * @return the second partial derivative wrt {@code y}.
+     */
+    public BivariateFunction partialDerivativeYY() {
+        return partialDerivativeYY;
+    }
+    /**
+     * @return the second partial cross-derivative.
+     */
+    public BivariateFunction partialDerivativeXY() {
+        return partialDerivativeXY;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/interpolation/BicubicSplineInterpolator.java b/src/main/java/org/apache/commons/math3/analysis/interpolation/BicubicSplineInterpolator.java
new file mode 100644
index 0000000..09acd07
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/interpolation/BicubicSplineInterpolator.java
@@ -0,0 +1,176 @@
+/*
+ * 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.math3.analysis.interpolation;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NonMonotonicSequenceException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * Generates a bicubic interpolating function. Due to numerical accuracy issues this should not
+ * be used.
+ *
+ * @since 2.2
+ * @deprecated as of 3.4 replaced by {@link org.apache.commons.math3.analysis.interpolation.PiecewiseBicubicSplineInterpolator}
+ */
+@Deprecated
+public class BicubicSplineInterpolator
+    implements BivariateGridInterpolator {
+    /** Whether to initialize internal data used to compute the analytical
+        derivatives of the splines. */
+    private final boolean initializeDerivatives;
+
+    /**
+     * Default constructor.
+     * The argument {@link #BicubicSplineInterpolator(boolean) initializeDerivatives}
+     * is set to {@code false}.
+     */
+    public BicubicSplineInterpolator() {
+        this(false);
+    }
+
+    /**
+     * Creates an interpolator.
+     *
+     * @param initializeDerivatives Whether to initialize the internal data
+     * needed for calling any of the methods that compute the partial derivatives
+     * of the {@link BicubicSplineInterpolatingFunction function} returned from
+     * the call to {@link #interpolate(double[],double[],double[][]) interpolate}.
+     */
+    public BicubicSplineInterpolator(boolean initializeDerivatives) {
+        this.initializeDerivatives = initializeDerivatives;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public BicubicSplineInterpolatingFunction interpolate(final double[] xval,
+                                                          final double[] yval,
+                                                          final double[][] fval)
+        throws NoDataException, DimensionMismatchException,
+               NonMonotonicSequenceException, NumberIsTooSmallException {
+        if (xval.length == 0 || yval.length == 0 || fval.length == 0) {
+            throw new NoDataException();
+        }
+        if (xval.length != fval.length) {
+            throw new DimensionMismatchException(xval.length, fval.length);
+        }
+
+        MathArrays.checkOrder(xval);
+        MathArrays.checkOrder(yval);
+
+        final int xLen = xval.length;
+        final int yLen = yval.length;
+
+        // Samples (first index is y-coordinate, i.e. subarray variable is x)
+        // 0 <= i < xval.length
+        // 0 <= j < yval.length
+        // fX[j][i] = f(xval[i], yval[j])
+        final double[][] fX = new double[yLen][xLen];
+        for (int i = 0; i < xLen; i++) {
+            if (fval[i].length != yLen) {
+                throw new DimensionMismatchException(fval[i].length, yLen);
+            }
+
+            for (int j = 0; j < yLen; j++) {
+                fX[j][i] = fval[i][j];
+            }
+        }
+
+        final SplineInterpolator spInterpolator = new SplineInterpolator();
+
+        // For each line y[j] (0 <= j < yLen), construct a 1D spline with
+        // respect to variable x
+        final PolynomialSplineFunction[] ySplineX = new PolynomialSplineFunction[yLen];
+        for (int j = 0; j < yLen; j++) {
+            ySplineX[j] = spInterpolator.interpolate(xval, fX[j]);
+        }
+
+        // For each line x[i] (0 <= i < xLen), construct a 1D spline with
+        // respect to variable y generated by array fY_1[i]
+        final PolynomialSplineFunction[] xSplineY = new PolynomialSplineFunction[xLen];
+        for (int i = 0; i < xLen; i++) {
+            xSplineY[i] = spInterpolator.interpolate(yval, fval[i]);
+        }
+
+        // Partial derivatives with respect to x at the grid knots
+        final double[][] dFdX = new double[xLen][yLen];
+        for (int j = 0; j < yLen; j++) {
+            final UnivariateFunction f = ySplineX[j].derivative();
+            for (int i = 0; i < xLen; i++) {
+                dFdX[i][j] = f.value(xval[i]);
+            }
+        }
+
+        // Partial derivatives with respect to y at the grid knots
+        final double[][] dFdY = new double[xLen][yLen];
+        for (int i = 0; i < xLen; i++) {
+            final UnivariateFunction f = xSplineY[i].derivative();
+            for (int j = 0; j < yLen; j++) {
+                dFdY[i][j] = f.value(yval[j]);
+            }
+        }
+
+        // Cross partial derivatives
+        final double[][] d2FdXdY = new double[xLen][yLen];
+        for (int i = 0; i < xLen ; i++) {
+            final int nI = nextIndex(i, xLen);
+            final int pI = previousIndex(i);
+            for (int j = 0; j < yLen; j++) {
+                final int nJ = nextIndex(j, yLen);
+                final int pJ = previousIndex(j);
+                d2FdXdY[i][j] = (fval[nI][nJ] - fval[nI][pJ] -
+                                 fval[pI][nJ] + fval[pI][pJ]) /
+                    ((xval[nI] - xval[pI]) * (yval[nJ] - yval[pJ]));
+            }
+        }
+
+        // Create the interpolating splines
+        return new BicubicSplineInterpolatingFunction(xval, yval, fval,
+                                                      dFdX, dFdY, d2FdXdY,
+                                                      initializeDerivatives);
+    }
+
+    /**
+     * Computes the next index of an array, clipping if necessary.
+     * It is assumed (but not checked) that {@code i >= 0}.
+     *
+     * @param i Index.
+     * @param max Upper limit of the array.
+     * @return the next index.
+     */
+    private int nextIndex(int i, int max) {
+        final int index = i + 1;
+        return index < max ? index : index - 1;
+    }
+    /**
+     * Computes the previous index of an array, clipping if necessary.
+     * It is assumed (but not checked) that {@code i} is smaller than the size
+     * of the array.
+     *
+     * @param i Index.
+     * @return the previous index.
+     */
+    private int previousIndex(int i) {
+        final int index = i - 1;
+        return index >= 0 ? index : 0;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/interpolation/BivariateGridInterpolator.java b/src/main/java/org/apache/commons/math3/analysis/interpolation/BivariateGridInterpolator.java
new file mode 100644
index 0000000..94d75ad
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/interpolation/BivariateGridInterpolator.java
@@ -0,0 +1,51 @@
+/*
+ * 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.math3.analysis.interpolation;
+
+import org.apache.commons.math3.analysis.BivariateFunction;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NonMonotonicSequenceException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+
+/**
+ * Interface representing a bivariate real interpolating function where the
+ * sample points must be specified on a regular grid.
+ *
+ */
+public interface BivariateGridInterpolator {
+    /**
+     * Compute an interpolating function for the dataset.
+     *
+     * @param xval All the x-coordinates of the interpolation points, sorted
+     * in increasing order.
+     * @param yval All the y-coordinates of the interpolation points, sorted
+     * in increasing order.
+     * @param fval The values of the interpolation points on all the grid knots:
+     * {@code fval[i][j] = f(xval[i], yval[j])}.
+     * @return a function which interpolates the dataset.
+     * @throws NoDataException if any of the arrays has zero length.
+     * @throws DimensionMismatchException if the array lengths are inconsistent.
+     * @throws NonMonotonicSequenceException if the array is not sorted.
+     * @throws NumberIsTooSmallException if the number of points is too small for
+     * the order of the interpolation
+     */
+    BivariateFunction interpolate(double[] xval, double[] yval,
+                                  double[][] fval)
+        throws NoDataException, DimensionMismatchException,
+               NonMonotonicSequenceException, NumberIsTooSmallException;
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/interpolation/DividedDifferenceInterpolator.java b/src/main/java/org/apache/commons/math3/analysis/interpolation/DividedDifferenceInterpolator.java
new file mode 100644
index 0000000..86b988c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/interpolation/DividedDifferenceInterpolator.java
@@ -0,0 +1,120 @@
+/*
+ * 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.math3.analysis.interpolation;
+
+import java.io.Serializable;
+import org.apache.commons.math3.analysis.polynomials.PolynomialFunctionLagrangeForm;
+import org.apache.commons.math3.analysis.polynomials.PolynomialFunctionNewtonForm;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.NonMonotonicSequenceException;
+
+/**
+ * Implements the <a href=
+ * "http://mathworld.wolfram.com/NewtonsDividedDifferenceInterpolationFormula.html">
+ * Divided Difference Algorithm</a> for interpolation of real univariate
+ * functions. For reference, see <b>Introduction to Numerical Analysis</b>,
+ * ISBN 038795452X, chapter 2.
+ * <p>
+ * The actual code of Neville's evaluation is in PolynomialFunctionLagrangeForm,
+ * this class provides an easy-to-use interface to it.</p>
+ *
+ * @since 1.2
+ */
+public class DividedDifferenceInterpolator
+    implements UnivariateInterpolator, Serializable {
+    /** serializable version identifier */
+    private static final long serialVersionUID = 107049519551235069L;
+
+    /**
+     * Compute an interpolating function for the dataset.
+     *
+     * @param x Interpolating points array.
+     * @param y Interpolating values array.
+     * @return a function which interpolates the dataset.
+     * @throws DimensionMismatchException if the array lengths are different.
+     * @throws NumberIsTooSmallException if the number of points is less than 2.
+     * @throws NonMonotonicSequenceException if {@code x} is not sorted in
+     * strictly increasing order.
+     */
+    public PolynomialFunctionNewtonForm interpolate(double x[], double y[])
+        throws DimensionMismatchException,
+               NumberIsTooSmallException,
+               NonMonotonicSequenceException {
+        /**
+         * a[] and c[] are defined in the general formula of Newton form:
+         * p(x) = a[0] + a[1](x-c[0]) + a[2](x-c[0])(x-c[1]) + ... +
+         *        a[n](x-c[0])(x-c[1])...(x-c[n-1])
+         */
+        PolynomialFunctionLagrangeForm.verifyInterpolationArray(x, y, true);
+
+        /**
+         * When used for interpolation, the Newton form formula becomes
+         * p(x) = f[x0] + f[x0,x1](x-x0) + f[x0,x1,x2](x-x0)(x-x1) + ... +
+         *        f[x0,x1,...,x[n-1]](x-x0)(x-x1)...(x-x[n-2])
+         * Therefore, a[k] = f[x0,x1,...,xk], c[k] = x[k].
+         * <p>
+         * Note x[], y[], a[] have the same length but c[]'s size is one less.</p>
+         */
+        final double[] c = new double[x.length-1];
+        System.arraycopy(x, 0, c, 0, c.length);
+
+        final double[] a = computeDividedDifference(x, y);
+        return new PolynomialFunctionNewtonForm(a, c);
+    }
+
+    /**
+     * Return a copy of the divided difference array.
+     * <p>
+     * The divided difference array is defined recursively by <pre>
+     * f[x0] = f(x0)
+     * f[x0,x1,...,xk] = (f[x1,...,xk] - f[x0,...,x[k-1]]) / (xk - x0)
+     * </pre>
+     * <p>
+     * The computational complexity is \(O(n^2)\) where \(n\) is the common
+     * length of {@code x} and {@code y}.</p>
+     *
+     * @param x Interpolating points array.
+     * @param y Interpolating values array.
+     * @return a fresh copy of the divided difference array.
+     * @throws DimensionMismatchException if the array lengths are different.
+     * @throws NumberIsTooSmallException if the number of points is less than 2.
+     * @throws NonMonotonicSequenceException
+     * if {@code x} is not sorted in strictly increasing order.
+     */
+    protected static double[] computeDividedDifference(final double x[], final double y[])
+        throws DimensionMismatchException,
+               NumberIsTooSmallException,
+               NonMonotonicSequenceException {
+        PolynomialFunctionLagrangeForm.verifyInterpolationArray(x, y, true);
+
+        final double[] divdiff = y.clone(); // initialization
+
+        final int n = x.length;
+        final double[] a = new double [n];
+        a[0] = divdiff[0];
+        for (int i = 1; i < n; i++) {
+            for (int j = 0; j < n-i; j++) {
+                final double denominator = x[j+i] - x[j];
+                divdiff[j] = (divdiff[j+1] - divdiff[j]) / denominator;
+            }
+            a[i] = divdiff[0];
+        }
+
+        return a;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/interpolation/FieldHermiteInterpolator.java b/src/main/java/org/apache/commons/math3/analysis/interpolation/FieldHermiteInterpolator.java
new file mode 100644
index 0000000..9125b83
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/interpolation/FieldHermiteInterpolator.java
@@ -0,0 +1,209 @@
+/*
+ * 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.math3.analysis.interpolation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.math3.FieldElement;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.ZeroException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.util.MathUtils;
+
+/** Polynomial interpolator using both sample values and sample derivatives.
+ * <p>
+ * The interpolation polynomials match all sample points, including both values
+ * and provided derivatives. There is one polynomial for each component of
+ * the values vector. All polynomials have the same degree. The degree of the
+ * polynomials depends on the number of points and number of derivatives at each
+ * point. For example the interpolation polynomials for n sample points without
+ * any derivatives all have degree n-1. The interpolation polynomials for n
+ * sample points with the two extreme points having value and first derivative
+ * and the remaining points having value only all have degree n+1. The
+ * interpolation polynomial for n sample points with value, first and second
+ * derivative for all points all have degree 3n-1.
+ * </p>
+ *
+ * @param <T> Type of the field elements.
+ *
+ * @since 3.2
+ */
+public class FieldHermiteInterpolator<T extends FieldElement<T>> {
+
+    /** Sample abscissae. */
+    private final List<T> abscissae;
+
+    /** Top diagonal of the divided differences array. */
+    private final List<T[]> topDiagonal;
+
+    /** Bottom diagonal of the divided differences array. */
+    private final List<T[]> bottomDiagonal;
+
+    /** Create an empty interpolator.
+     */
+    public FieldHermiteInterpolator() {
+        this.abscissae      = new ArrayList<T>();
+        this.topDiagonal    = new ArrayList<T[]>();
+        this.bottomDiagonal = new ArrayList<T[]>();
+    }
+
+    /** Add a sample point.
+     * <p>
+     * This method must be called once for each sample point. It is allowed to
+     * mix some calls with values only with calls with values and first
+     * derivatives.
+     * </p>
+     * <p>
+     * The point abscissae for all calls <em>must</em> be different.
+     * </p>
+     * @param x abscissa of the sample point
+     * @param value value and derivatives of the sample point
+     * (if only one row is passed, it is the value, if two rows are
+     * passed the first one is the value and the second the derivative
+     * and so on)
+     * @exception ZeroException if the abscissa difference between added point
+     * and a previous point is zero (i.e. the two points are at same abscissa)
+     * @exception MathArithmeticException if the number of derivatives is larger
+     * than 20, which prevents computation of a factorial
+     * @throws DimensionMismatchException if derivative structures are inconsistent
+     * @throws NullArgumentException if x is null
+     */
+    public void addSamplePoint(final T x, final T[] ... value)
+        throws ZeroException, MathArithmeticException,
+               DimensionMismatchException, NullArgumentException {
+
+        MathUtils.checkNotNull(x);
+        T factorial = x.getField().getOne();
+        for (int i = 0; i < value.length; ++i) {
+
+            final T[] y = value[i].clone();
+            if (i > 1) {
+                factorial = factorial.multiply(i);
+                final T inv = factorial.reciprocal();
+                for (int j = 0; j < y.length; ++j) {
+                    y[j] = y[j].multiply(inv);
+                }
+            }
+
+            // update the bottom diagonal of the divided differences array
+            final int n = abscissae.size();
+            bottomDiagonal.add(n - i, y);
+            T[] bottom0 = y;
+            for (int j = i; j < n; ++j) {
+                final T[] bottom1 = bottomDiagonal.get(n - (j + 1));
+                if (x.equals(abscissae.get(n - (j + 1)))) {
+                    throw new ZeroException(LocalizedFormats.DUPLICATED_ABSCISSA_DIVISION_BY_ZERO, x);
+                }
+                final T inv = x.subtract(abscissae.get(n - (j + 1))).reciprocal();
+                for (int k = 0; k < y.length; ++k) {
+                    bottom1[k] = inv.multiply(bottom0[k].subtract(bottom1[k]));
+                }
+                bottom0 = bottom1;
+            }
+
+            // update the top diagonal of the divided differences array
+            topDiagonal.add(bottom0.clone());
+
+            // update the abscissae array
+            abscissae.add(x);
+
+        }
+
+    }
+
+    /** Interpolate value at a specified abscissa.
+     * @param x interpolation abscissa
+     * @return interpolated value
+     * @exception NoDataException if sample is empty
+     * @throws NullArgumentException if x is null
+     */
+    public T[] value(T x) throws NoDataException, NullArgumentException {
+
+        // safety check
+        MathUtils.checkNotNull(x);
+        if (abscissae.isEmpty()) {
+            throw new NoDataException(LocalizedFormats.EMPTY_INTERPOLATION_SAMPLE);
+        }
+
+        final T[] value = MathArrays.buildArray(x.getField(), topDiagonal.get(0).length);
+        T valueCoeff = x.getField().getOne();
+        for (int i = 0; i < topDiagonal.size(); ++i) {
+            T[] dividedDifference = topDiagonal.get(i);
+            for (int k = 0; k < value.length; ++k) {
+                value[k] = value[k].add(dividedDifference[k].multiply(valueCoeff));
+            }
+            final T deltaX = x.subtract(abscissae.get(i));
+            valueCoeff = valueCoeff.multiply(deltaX);
+        }
+
+        return value;
+
+    }
+
+    /** Interpolate value and first derivatives at a specified abscissa.
+     * @param x interpolation abscissa
+     * @param order maximum derivation order
+     * @return interpolated value and derivatives (value in row 0,
+     * 1<sup>st</sup> derivative in row 1, ... n<sup>th</sup> derivative in row n)
+     * @exception NoDataException if sample is empty
+     * @throws NullArgumentException if x is null
+     */
+    public T[][] derivatives(T x, int order) throws NoDataException, NullArgumentException {
+
+        // safety check
+        MathUtils.checkNotNull(x);
+        if (abscissae.isEmpty()) {
+            throw new NoDataException(LocalizedFormats.EMPTY_INTERPOLATION_SAMPLE);
+        }
+
+        final T zero = x.getField().getZero();
+        final T one  = x.getField().getOne();
+        final T[] tj = MathArrays.buildArray(x.getField(), order + 1);
+        tj[0] = zero;
+        for (int i = 0; i < order; ++i) {
+            tj[i + 1] = tj[i].add(one);
+        }
+
+        final T[][] derivatives =
+                MathArrays.buildArray(x.getField(), order + 1, topDiagonal.get(0).length);
+        final T[] valueCoeff = MathArrays.buildArray(x.getField(), order + 1);
+        valueCoeff[0] = x.getField().getOne();
+        for (int i = 0; i < topDiagonal.size(); ++i) {
+            T[] dividedDifference = topDiagonal.get(i);
+            final T deltaX = x.subtract(abscissae.get(i));
+            for (int j = order; j >= 0; --j) {
+                for (int k = 0; k < derivatives[j].length; ++k) {
+                    derivatives[j][k] =
+                            derivatives[j][k].add(dividedDifference[k].multiply(valueCoeff[j]));
+                }
+                valueCoeff[j] = valueCoeff[j].multiply(deltaX);
+                if (j > 0) {
+                    valueCoeff[j] = valueCoeff[j].add(tj[j].multiply(valueCoeff[j - 1]));
+                }
+            }
+        }
+
+        return derivatives;
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/interpolation/HermiteInterpolator.java b/src/main/java/org/apache/commons/math3/analysis/interpolation/HermiteInterpolator.java
new file mode 100644
index 0000000..15ed322
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/interpolation/HermiteInterpolator.java
@@ -0,0 +1,239 @@
+/*
+ * 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.math3.analysis.interpolation;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableVectorFunction;
+import org.apache.commons.math3.analysis.polynomials.PolynomialFunction;
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.ZeroException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.CombinatoricsUtils;
+
+/** Polynomial interpolator using both sample values and sample derivatives.
+ * <p>
+ * The interpolation polynomials match all sample points, including both values
+ * and provided derivatives. There is one polynomial for each component of
+ * the values vector. All polynomials have the same degree. The degree of the
+ * polynomials depends on the number of points and number of derivatives at each
+ * point. For example the interpolation polynomials for n sample points without
+ * any derivatives all have degree n-1. The interpolation polynomials for n
+ * sample points with the two extreme points having value and first derivative
+ * and the remaining points having value only all have degree n+1. The
+ * interpolation polynomial for n sample points with value, first and second
+ * derivative for all points all have degree 3n-1.
+ * </p>
+ *
+ * @since 3.1
+ */
+public class HermiteInterpolator implements UnivariateDifferentiableVectorFunction {
+
+    /** Sample abscissae. */
+    private final List<Double> abscissae;
+
+    /** Top diagonal of the divided differences array. */
+    private final List<double[]> topDiagonal;
+
+    /** Bottom diagonal of the divided differences array. */
+    private final List<double[]> bottomDiagonal;
+
+    /** Create an empty interpolator.
+     */
+    public HermiteInterpolator() {
+        this.abscissae      = new ArrayList<Double>();
+        this.topDiagonal    = new ArrayList<double[]>();
+        this.bottomDiagonal = new ArrayList<double[]>();
+    }
+
+    /** Add a sample point.
+     * <p>
+     * This method must be called once for each sample point. It is allowed to
+     * mix some calls with values only with calls with values and first
+     * derivatives.
+     * </p>
+     * <p>
+     * The point abscissae for all calls <em>must</em> be different.
+     * </p>
+     * @param x abscissa of the sample point
+     * @param value value and derivatives of the sample point
+     * (if only one row is passed, it is the value, if two rows are
+     * passed the first one is the value and the second the derivative
+     * and so on)
+     * @exception ZeroException if the abscissa difference between added point
+     * and a previous point is zero (i.e. the two points are at same abscissa)
+     * @exception MathArithmeticException if the number of derivatives is larger
+     * than 20, which prevents computation of a factorial
+     */
+    public void addSamplePoint(final double x, final double[] ... value)
+        throws ZeroException, MathArithmeticException {
+
+        for (int i = 0; i < value.length; ++i) {
+
+            final double[] y = value[i].clone();
+            if (i > 1) {
+                double inv = 1.0 / CombinatoricsUtils.factorial(i);
+                for (int j = 0; j < y.length; ++j) {
+                    y[j] *= inv;
+                }
+            }
+
+            // update the bottom diagonal of the divided differences array
+            final int n = abscissae.size();
+            bottomDiagonal.add(n - i, y);
+            double[] bottom0 = y;
+            for (int j = i; j < n; ++j) {
+                final double[] bottom1 = bottomDiagonal.get(n - (j + 1));
+                final double inv = 1.0 / (x - abscissae.get(n - (j + 1)));
+                if (Double.isInfinite(inv)) {
+                    throw new ZeroException(LocalizedFormats.DUPLICATED_ABSCISSA_DIVISION_BY_ZERO, x);
+                }
+                for (int k = 0; k < y.length; ++k) {
+                    bottom1[k] = inv * (bottom0[k] - bottom1[k]);
+                }
+                bottom0 = bottom1;
+            }
+
+            // update the top diagonal of the divided differences array
+            topDiagonal.add(bottom0.clone());
+
+            // update the abscissae array
+            abscissae.add(x);
+
+        }
+
+    }
+
+    /** Compute the interpolation polynomials.
+     * @return interpolation polynomials array
+     * @exception NoDataException if sample is empty
+     */
+    public PolynomialFunction[] getPolynomials()
+        throws NoDataException {
+
+        // safety check
+        checkInterpolation();
+
+        // iteration initialization
+        final PolynomialFunction zero = polynomial(0);
+        PolynomialFunction[] polynomials = new PolynomialFunction[topDiagonal.get(0).length];
+        for (int i = 0; i < polynomials.length; ++i) {
+            polynomials[i] = zero;
+        }
+        PolynomialFunction coeff = polynomial(1);
+
+        // build the polynomials by iterating on the top diagonal of the divided differences array
+        for (int i = 0; i < topDiagonal.size(); ++i) {
+            double[] tdi = topDiagonal.get(i);
+            for (int k = 0; k < polynomials.length; ++k) {
+                polynomials[k] = polynomials[k].add(coeff.multiply(polynomial(tdi[k])));
+            }
+            coeff = coeff.multiply(polynomial(-abscissae.get(i), 1.0));
+        }
+
+        return polynomials;
+
+    }
+
+    /** Interpolate value at a specified abscissa.
+     * <p>
+     * Calling this method is equivalent to call the {@link PolynomialFunction#value(double)
+     * value} methods of all polynomials returned by {@link #getPolynomials() getPolynomials},
+     * except it does not build the intermediate polynomials, so this method is faster and
+     * numerically more stable.
+     * </p>
+     * @param x interpolation abscissa
+     * @return interpolated value
+     * @exception NoDataException if sample is empty
+     */
+    public double[] value(double x)
+        throws NoDataException {
+
+        // safety check
+        checkInterpolation();
+
+        final double[] value = new double[topDiagonal.get(0).length];
+        double valueCoeff = 1;
+        for (int i = 0; i < topDiagonal.size(); ++i) {
+            double[] dividedDifference = topDiagonal.get(i);
+            for (int k = 0; k < value.length; ++k) {
+                value[k] += dividedDifference[k] * valueCoeff;
+            }
+            final double deltaX = x - abscissae.get(i);
+            valueCoeff *= deltaX;
+        }
+
+        return value;
+
+    }
+
+    /** Interpolate value at a specified abscissa.
+     * <p>
+     * Calling this method is equivalent to call the {@link
+     * PolynomialFunction#value(DerivativeStructure) value} methods of all polynomials
+     * returned by {@link #getPolynomials() getPolynomials}, except it does not build the
+     * intermediate polynomials, so this method is faster and numerically more stable.
+     * </p>
+     * @param x interpolation abscissa
+     * @return interpolated value
+     * @exception NoDataException if sample is empty
+     */
+    public DerivativeStructure[] value(final DerivativeStructure x)
+        throws NoDataException {
+
+        // safety check
+        checkInterpolation();
+
+        final DerivativeStructure[] value = new DerivativeStructure[topDiagonal.get(0).length];
+        Arrays.fill(value, x.getField().getZero());
+        DerivativeStructure valueCoeff = x.getField().getOne();
+        for (int i = 0; i < topDiagonal.size(); ++i) {
+            double[] dividedDifference = topDiagonal.get(i);
+            for (int k = 0; k < value.length; ++k) {
+                value[k] = value[k].add(valueCoeff.multiply(dividedDifference[k]));
+            }
+            final DerivativeStructure deltaX = x.subtract(abscissae.get(i));
+            valueCoeff = valueCoeff.multiply(deltaX);
+        }
+
+        return value;
+
+    }
+
+    /** Check interpolation can be performed.
+     * @exception NoDataException if interpolation cannot be performed
+     * because sample is empty
+     */
+    private void checkInterpolation() throws NoDataException {
+        if (abscissae.isEmpty()) {
+            throw new NoDataException(LocalizedFormats.EMPTY_INTERPOLATION_SAMPLE);
+        }
+    }
+
+    /** Create a polynomial from its coefficients.
+     * @param c polynomials coefficients
+     * @return polynomial
+     */
+    private PolynomialFunction polynomial(double ... c) {
+        return new PolynomialFunction(c);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/interpolation/InterpolatingMicrosphere.java b/src/main/java/org/apache/commons/math3/analysis/interpolation/InterpolatingMicrosphere.java
new file mode 100644
index 0000000..dc600bd
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/interpolation/InterpolatingMicrosphere.java
@@ -0,0 +1,385 @@
+/*
+ * 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.math3.analysis.interpolation;
+
+import java.util.List;
+import java.util.ArrayList;
+import org.apache.commons.math3.random.UnitSphereRandomVectorGenerator;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * Utility class for the {@link MicrosphereProjectionInterpolator} algorithm.
+ *
+ * @since 3.6
+ */
+public class InterpolatingMicrosphere {
+    /** Microsphere. */
+    private final List<Facet> microsphere;
+    /** Microsphere data. */
+    private final List<FacetData> microsphereData;
+    /** Space dimension. */
+    private final int dimension;
+    /** Number of surface elements. */
+    private final int size;
+    /** Maximum fraction of the facets that can be dark. */
+    private final double maxDarkFraction;
+    /** Lowest non-zero illumination. */
+    private final double darkThreshold;
+    /** Background value. */
+    private final double background;
+
+    /**
+     * Create an unitialiazed sphere.
+     * Sub-classes are responsible for calling the {@code add(double[]) add}
+     * method in order to initialize all the sphere's facets.
+     *
+     * @param dimension Dimension of the data space.
+     * @param size Number of surface elements of the sphere.
+     * @param maxDarkFraction Maximum fraction of the facets that can be dark.
+     * If the fraction of "non-illuminated" facets is larger, no estimation
+     * of the value will be performed, and the {@code background} value will
+     * be returned instead.
+     * @param darkThreshold Value of the illumination below which a facet is
+     * considered dark.
+     * @param background Value returned when the {@code maxDarkFraction}
+     * threshold is exceeded.
+     * @throws NotStrictlyPositiveException if {@code dimension <= 0}
+     * or {@code size <= 0}.
+     * @throws NotPositiveException if {@code darkThreshold < 0}.
+     * @throws OutOfRangeException if {@code maxDarkFraction} does not
+     * belong to the interval {@code [0, 1]}.
+     */
+    protected InterpolatingMicrosphere(int dimension,
+                                       int size,
+                                       double maxDarkFraction,
+                                       double darkThreshold,
+                                       double background) {
+        if (dimension <= 0) {
+            throw new NotStrictlyPositiveException(dimension);
+        }
+        if (size <= 0) {
+            throw new NotStrictlyPositiveException(size);
+        }
+        if (maxDarkFraction < 0 ||
+            maxDarkFraction > 1) {
+            throw new OutOfRangeException(maxDarkFraction, 0, 1);
+        }
+        if (darkThreshold < 0) {
+            throw new NotPositiveException(darkThreshold);
+        }
+
+        this.dimension = dimension;
+        this.size = size;
+        this.maxDarkFraction = maxDarkFraction;
+        this.darkThreshold = darkThreshold;
+        this.background = background;
+        microsphere = new ArrayList<Facet>(size);
+        microsphereData = new ArrayList<FacetData>(size);
+    }
+
+    /**
+     * Create a sphere from randomly sampled vectors.
+     *
+     * @param dimension Dimension of the data space.
+     * @param size Number of surface elements of the sphere.
+     * @param rand Unit vector generator for creating the microsphere.
+     * @param maxDarkFraction Maximum fraction of the facets that can be dark.
+     * If the fraction of "non-illuminated" facets is larger, no estimation
+     * of the value will be performed, and the {@code background} value will
+     * be returned instead.
+     * @param darkThreshold Value of the illumination below which a facet
+     * is considered dark.
+     * @param background Value returned when the {@code maxDarkFraction}
+     * threshold is exceeded.
+     * @throws DimensionMismatchException if the size of the generated
+     * vectors does not match the dimension set in the constructor.
+     * @throws NotStrictlyPositiveException if {@code dimension <= 0}
+     * or {@code size <= 0}.
+     * @throws NotPositiveException if {@code darkThreshold < 0}.
+     * @throws OutOfRangeException if {@code maxDarkFraction} does not
+     * belong to the interval {@code [0, 1]}.
+     */
+    public InterpolatingMicrosphere(int dimension,
+                                    int size,
+                                    double maxDarkFraction,
+                                    double darkThreshold,
+                                    double background,
+                                    UnitSphereRandomVectorGenerator rand) {
+        this(dimension, size, maxDarkFraction, darkThreshold, background);
+
+        // Generate the microsphere normals, assuming that a number of
+        // randomly generated normals will represent a sphere.
+        for (int i = 0; i < size; i++) {
+            add(rand.nextVector(), false);
+        }
+    }
+
+    /**
+     * Copy constructor.
+     *
+     * @param other Instance to copy.
+     */
+    protected InterpolatingMicrosphere(InterpolatingMicrosphere other) {
+        dimension = other.dimension;
+        size = other.size;
+        maxDarkFraction = other.maxDarkFraction;
+        darkThreshold = other.darkThreshold;
+        background = other.background;
+
+        // Field can be shared.
+        microsphere = other.microsphere;
+
+        // Field must be copied.
+        microsphereData = new ArrayList<FacetData>(size);
+        for (FacetData fd : other.microsphereData) {
+            microsphereData.add(new FacetData(fd.illumination(), fd.sample()));
+        }
+    }
+
+    /**
+     * Perform a copy.
+     *
+     * @return a copy of this instance.
+     */
+    public InterpolatingMicrosphere copy() {
+        return new InterpolatingMicrosphere(this);
+    }
+
+    /**
+     * Get the space dimensionality.
+     *
+     * @return the number of space dimensions.
+     */
+    public int getDimension() {
+        return dimension;
+    }
+
+    /**
+     * Get the size of the sphere.
+     *
+     * @return the number of surface elements of the microspshere.
+     */
+    public int getSize() {
+        return size;
+    }
+
+    /**
+     * Estimate the value at the requested location.
+     * This microsphere is placed at the given {@code point}, contribution
+     * of the given {@code samplePoints} to each sphere facet is computed
+     * (illumination) and the interpolation is performed (integration of
+     * the illumination).
+     *
+     * @param point Interpolation point.
+     * @param samplePoints Sampling data points.
+     * @param sampleValues Sampling data values at the corresponding
+     * {@code samplePoints}.
+     * @param exponent Exponent used in the power law that computes
+     * the weights (distance dimming factor) of the sample data.
+     * @param noInterpolationTolerance When the distance between the
+     * {@code point} and one of the {@code samplePoints} is less than
+     * this value, no interpolation will be performed, and the value
+     * of the sample will just be returned.
+     * @return the estimated value at the given {@code point}.
+     * @throws NotPositiveException if {@code exponent < 0}.
+     */
+    public double value(double[] point,
+                        double[][] samplePoints,
+                        double[] sampleValues,
+                        double exponent,
+                        double noInterpolationTolerance) {
+        if (exponent < 0) {
+            throw new NotPositiveException(exponent);
+        }
+
+        clear();
+
+        // Contribution of each sample point to the illumination of the
+        // microsphere's facets.
+        final int numSamples = samplePoints.length;
+        for (int i = 0; i < numSamples; i++) {
+            // Vector between interpolation point and current sample point.
+            final double[] diff = MathArrays.ebeSubtract(samplePoints[i], point);
+            final double diffNorm = MathArrays.safeNorm(diff);
+
+            if (FastMath.abs(diffNorm) < noInterpolationTolerance) {
+                // No need to interpolate, as the interpolation point is
+                // actually (very close to) one of the sampled points.
+                return sampleValues[i];
+            }
+
+            final double weight = FastMath.pow(diffNorm, -exponent);
+            illuminate(diff, sampleValues[i], weight);
+        }
+
+        return interpolate();
+    }
+
+    /**
+     * Replace {@code i}-th facet of the microsphere.
+     * Method for initializing the microsphere facets.
+     *
+     * @param normal Facet's normal vector.
+     * @param copy Whether to copy the given array.
+     * @throws DimensionMismatchException if the length of {@code n}
+     * does not match the space dimension.
+     * @throws MaxCountExceededException if the method has been called
+     * more times than the size of the sphere.
+     */
+    protected void add(double[] normal,
+                       boolean copy) {
+        if (microsphere.size() >= size) {
+            throw new MaxCountExceededException(size);
+        }
+        if (normal.length > dimension) {
+            throw new DimensionMismatchException(normal.length, dimension);
+        }
+
+        microsphere.add(new Facet(copy ? normal.clone() : normal));
+        microsphereData.add(new FacetData(0d, 0d));
+    }
+
+    /**
+     * Interpolation.
+     *
+     * @return the value estimated from the current illumination of the
+     * microsphere.
+     */
+    private double interpolate() {
+        // Number of non-illuminated facets.
+        int darkCount = 0;
+
+        double value = 0;
+        double totalWeight = 0;
+        for (FacetData fd : microsphereData) {
+            final double iV = fd.illumination();
+            if (iV != 0d) {
+                value += iV * fd.sample();
+                totalWeight += iV;
+            } else {
+                ++darkCount;
+            }
+        }
+
+        final double darkFraction = darkCount / (double) size;
+
+        return darkFraction <= maxDarkFraction ?
+            value / totalWeight :
+            background;
+    }
+
+    /**
+     * Illumination.
+     *
+     * @param sampleDirection Vector whose origin is at the interpolation
+     * point and tail is at the sample location.
+     * @param sampleValue Data value of the sample.
+     * @param weight Weight.
+     */
+    private void illuminate(double[] sampleDirection,
+                            double sampleValue,
+                            double weight) {
+        for (int i = 0; i < size; i++) {
+            final double[] n = microsphere.get(i).getNormal();
+            final double cos = MathArrays.cosAngle(n, sampleDirection);
+
+            if (cos > 0) {
+                final double illumination = cos * weight;
+
+                if (illumination > darkThreshold &&
+                    illumination > microsphereData.get(i).illumination()) {
+                    microsphereData.set(i, new FacetData(illumination, sampleValue));
+                }
+            }
+        }
+    }
+
+    /**
+     * Reset the all the {@link Facet facets} data to zero.
+     */
+    private void clear() {
+        for (int i = 0; i < size; i++) {
+            microsphereData.set(i, new FacetData(0d, 0d));
+        }
+    }
+
+    /**
+     * Microsphere "facet" (surface element).
+     */
+    private static class Facet {
+        /** Normal vector characterizing a surface element. */
+        private final double[] normal;
+
+        /**
+         * @param n Normal vector characterizing a surface element
+         * of the microsphere. No copy is made.
+         */
+        Facet(double[] n) {
+            normal = n;
+        }
+
+        /**
+         * Return a reference to the vector normal to this facet.
+         *
+         * @return the normal vector.
+         */
+        public double[] getNormal() {
+            return normal;
+        }
+    }
+
+    /**
+     * Data associated with each {@link Facet}.
+     */
+    private static class FacetData {
+        /** Illumination received from the sample. */
+        private final double illumination;
+        /** Data value of the sample. */
+        private final double sample;
+
+        /**
+         * @param illumination Illumination.
+         * @param sample Data value.
+         */
+        FacetData(double illumination, double sample) {
+            this.illumination = illumination;
+            this.sample = sample;
+        }
+
+        /**
+         * Get the illumination.
+         * @return the illumination.
+         */
+        public double illumination() {
+            return illumination;
+        }
+
+        /**
+         * Get the data value.
+         * @return the data value.
+         */
+        public double sample() {
+            return sample;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/interpolation/InterpolatingMicrosphere2D.java b/src/main/java/org/apache/commons/math3/analysis/interpolation/InterpolatingMicrosphere2D.java
new file mode 100644
index 0000000..fdc01b2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/interpolation/InterpolatingMicrosphere2D.java
@@ -0,0 +1,87 @@
+/*
+ * 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.math3.analysis.interpolation;
+
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * Utility class for the {@link MicrosphereProjectionInterpolator} algorithm.
+ * For 2D interpolation, this class constructs the microsphere as a series of
+ * evenly spaced facets (rather than generating random normals as in the
+ * base implementation).
+ *
+ * @since 3.6
+ */
+public class InterpolatingMicrosphere2D extends InterpolatingMicrosphere {
+    /** Space dimension. */
+    private static final int DIMENSION = 2;
+
+    /**
+     * Create a sphere from vectors regularly sampled around a circle.
+     *
+     * @param size Number of surface elements of the sphere.
+     * @param maxDarkFraction Maximum fraction of the facets that can be dark.
+     * If the fraction of "non-illuminated" facets is larger, no estimation
+     * of the value will be performed, and the {@code background} value will
+     * be returned instead.
+     * @param darkThreshold Value of the illumination below which a facet is
+     * considered dark.
+     * @param background Value returned when the {@code maxDarkFraction}
+     * threshold is exceeded.
+     * @throws org.apache.commons.math3.exception.NotStrictlyPositiveException
+     * if {@code size <= 0}.
+     * @throws org.apache.commons.math3.exception.NotPositiveException if
+     * {@code darkThreshold < 0}.
+     * @throws org.apache.commons.math3.exception.OutOfRangeException if
+     * {@code maxDarkFraction} does not belong to the interval {@code [0, 1]}.
+     */
+    public InterpolatingMicrosphere2D(int size,
+                                      double maxDarkFraction,
+                                      double darkThreshold,
+                                      double background) {
+        super(DIMENSION, size, maxDarkFraction, darkThreshold, background);
+
+        // Generate the microsphere normals.
+        for (int i = 0; i < size; i++) {
+            final double angle = i * MathUtils.TWO_PI / size;
+
+            add(new double[] { FastMath.cos(angle),
+                               FastMath.sin(angle) },
+                false);
+        }
+    }
+
+    /**
+     * Copy constructor.
+     *
+     * @param other Instance to copy.
+     */
+    protected InterpolatingMicrosphere2D(InterpolatingMicrosphere2D other) {
+        super(other);
+    }
+
+    /**
+     * Perform a copy.
+     *
+     * @return a copy of this instance.
+     */
+    @Override
+    public InterpolatingMicrosphere2D copy() {
+        return new InterpolatingMicrosphere2D(this);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/interpolation/LinearInterpolator.java b/src/main/java/org/apache/commons/math3/analysis/interpolation/LinearInterpolator.java
new file mode 100644
index 0000000..7e0e69b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/interpolation/LinearInterpolator.java
@@ -0,0 +1,79 @@
+/*
+ * 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.math3.analysis.interpolation;
+
+import org.apache.commons.math3.analysis.polynomials.PolynomialFunction;
+import org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.NonMonotonicSequenceException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Implements a linear function for interpolation of real univariate functions.
+ *
+ */
+public class LinearInterpolator implements UnivariateInterpolator {
+    /**
+     * Computes a linear interpolating function for the data set.
+     *
+     * @param x the arguments for the interpolation points
+     * @param y the values for the interpolation points
+     * @return a function which interpolates the data set
+     * @throws DimensionMismatchException if {@code x} and {@code y}
+     * have different sizes.
+     * @throws NonMonotonicSequenceException if {@code x} is not sorted in
+     * strict increasing order.
+     * @throws NumberIsTooSmallException if the size of {@code x} is smaller
+     * than 2.
+     */
+    public PolynomialSplineFunction interpolate(double x[], double y[])
+        throws DimensionMismatchException,
+               NumberIsTooSmallException,
+               NonMonotonicSequenceException {
+        if (x.length != y.length) {
+            throw new DimensionMismatchException(x.length, y.length);
+        }
+
+        if (x.length < 2) {
+            throw new NumberIsTooSmallException(LocalizedFormats.NUMBER_OF_POINTS,
+                                                x.length, 2, true);
+        }
+
+        // Number of intervals.  The number of data points is n + 1.
+        int n = x.length - 1;
+
+        MathArrays.checkOrder(x);
+
+        // Slope of the lines between the datapoints.
+        final double m[] = new double[n];
+        for (int i = 0; i < n; i++) {
+            m[i] = (y[i + 1] - y[i]) / (x[i + 1] - x[i]);
+        }
+
+        final PolynomialFunction polynomials[] = new PolynomialFunction[n];
+        final double coefficients[] = new double[2];
+        for (int i = 0; i < n; i++) {
+            coefficients[0] = y[i];
+            coefficients[1] = m[i];
+            polynomials[i] = new PolynomialFunction(coefficients);
+        }
+
+        return new PolynomialSplineFunction(x, polynomials);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/interpolation/LoessInterpolator.java b/src/main/java/org/apache/commons/math3/analysis/interpolation/LoessInterpolator.java
new file mode 100644
index 0000000..3ffbe93
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/interpolation/LoessInterpolator.java
@@ -0,0 +1,473 @@
+/*
+ * 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.math3.analysis.interpolation;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+import org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NonMonotonicSequenceException;
+import org.apache.commons.math3.exception.NotFiniteNumberException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * 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://amstat.tandfonline.com/doi/abs/10.1080/01621459.1979.10481038">
+ * 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 one to build a spline on the obtained loess fit.
+ *
+ * @since 2.0
+ */
+public class LoessInterpolator
+    implements UnivariateInterpolator, Serializable {
+    /** 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;
+    /**
+     * Default value for accuracy.
+     * @since 2.1
+     */
+    public static final double DEFAULT_ACCURACY = 1e-12;
+    /** serializable version identifier. */
+    private static final long serialVersionUID = 5204927143605193821L;
+    /**
+     * 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.</p>
+     */
+    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.</p>
+     */
+    private final int robustnessIters;
+    /**
+     * If the median residual at a certain robustness iteration
+     * is less than this amount, no more iterations are done.
+     */
+    private final double accuracy;
+
+    /**
+     * Constructs a new {@link LoessInterpolator}
+     * with a bandwidth of {@link #DEFAULT_BANDWIDTH},
+     * {@link #DEFAULT_ROBUSTNESS_ITERS} robustness iterations
+     * and an accuracy of {#link #DEFAULT_ACCURACY}.
+     * See {@link #LoessInterpolator(double, int, double)} for an explanation of
+     * the parameters.
+     */
+    public LoessInterpolator() {
+        this.bandwidth = DEFAULT_BANDWIDTH;
+        this.robustnessIters = DEFAULT_ROBUSTNESS_ITERS;
+        this.accuracy = DEFAULT_ACCURACY;
+    }
+
+    /**
+     * Construct a new {@link LoessInterpolator}
+     * with given bandwidth and number of robustness iterations.
+     * <p>
+     * Calling this constructor is equivalent to calling {link {@link
+     * #LoessInterpolator(double, int, double) LoessInterpolator(bandwidth,
+     * robustnessIters, LoessInterpolator.DEFAULT_ACCURACY)}
+     * </p>
+     *
+     * @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.
+     * 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.
+     * A sensible value is usually 0 (just the initial fit without any
+     * robustness iterations) to 4, the default value is
+     * {@link #DEFAULT_ROBUSTNESS_ITERS}.
+
+     * @see #LoessInterpolator(double, int, double)
+     */
+    public LoessInterpolator(double bandwidth, int robustnessIters) {
+        this(bandwidth, robustnessIters, DEFAULT_ACCURACY);
+    }
+
+    /**
+     * Construct a new {@link LoessInterpolator}
+     * with given bandwidth, number of robustness iterations and accuracy.
+     *
+     * @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.
+     * 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.
+     * A sensible value is usually 0 (just the initial fit without any
+     * robustness iterations) to 4, the default value is
+     * {@link #DEFAULT_ROBUSTNESS_ITERS}.
+     * @param accuracy If the median residual at a certain robustness iteration
+     * is less than this amount, no more iterations are done.
+     * @throws OutOfRangeException if bandwidth does not lie in the interval [0,1].
+     * @throws NotPositiveException if {@code robustnessIters} is negative.
+     * @see #LoessInterpolator(double, int)
+     * @since 2.1
+     */
+    public LoessInterpolator(double bandwidth, int robustnessIters, double accuracy)
+        throws OutOfRangeException,
+               NotPositiveException {
+        if (bandwidth < 0 ||
+            bandwidth > 1) {
+            throw new OutOfRangeException(LocalizedFormats.BANDWIDTH, bandwidth, 0, 1);
+        }
+        this.bandwidth = bandwidth;
+        if (robustnessIters < 0) {
+            throw new NotPositiveException(LocalizedFormats.ROBUSTNESS_ITERATIONS, robustnessIters);
+        }
+        this.robustnessIters = robustnessIters;
+        this.accuracy = accuracy;
+    }
+
+    /**
+     * 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.math3.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 NonMonotonicSequenceException if {@code xval} not sorted in
+     * strictly increasing order.
+     * @throws DimensionMismatchException if {@code xval} and {@code yval} have
+     * different sizes.
+     * @throws NoDataException if {@code xval} or {@code yval} has zero size.
+     * @throws NotFiniteNumberException if any of the arguments and values are
+     * not finite real numbers.
+     * @throws NumberIsTooSmallException if the bandwidth is too small to
+     * accomodate the size of the input data (i.e. the bandwidth must be
+     * larger than 2/n).
+     */
+    public final PolynomialSplineFunction interpolate(final double[] xval,
+                                                      final double[] yval)
+        throws NonMonotonicSequenceException,
+               DimensionMismatchException,
+               NoDataException,
+               NotFiniteNumberException,
+               NumberIsTooSmallException {
+        return new SplineInterpolator().interpolate(xval, smooth(xval, yval));
+    }
+
+    /**
+     * Compute a weighted loess fit on the data at the original abscissae.
+     *
+     * @param xval Arguments for the interpolation points.
+     * @param yval Values for the interpolation points.
+     * @param weights point weights: coefficients by which the robustness weight
+     * of a point is multiplied.
+     * @return the values of the loess fit at corresponding original abscissae.
+     * @throws NonMonotonicSequenceException if {@code xval} not sorted in
+     * strictly increasing order.
+     * @throws DimensionMismatchException if {@code xval} and {@code yval} have
+     * different sizes.
+     * @throws NoDataException if {@code xval} or {@code yval} has zero size.
+     * @throws NotFiniteNumberException if any of the arguments and values are
+     not finite real numbers.
+     * @throws NumberIsTooSmallException if the bandwidth is too small to
+     * accomodate the size of the input data (i.e. the bandwidth must be
+     * larger than 2/n).
+     * @since 2.1
+     */
+    public final double[] smooth(final double[] xval, final double[] yval,
+                                 final double[] weights)
+        throws NonMonotonicSequenceException,
+               DimensionMismatchException,
+               NoDataException,
+               NotFiniteNumberException,
+               NumberIsTooSmallException {
+        if (xval.length != yval.length) {
+            throw new DimensionMismatchException(xval.length, yval.length);
+        }
+
+        final int n = xval.length;
+
+        if (n == 0) {
+            throw new NoDataException();
+        }
+
+        checkAllFiniteReal(xval);
+        checkAllFiniteReal(yval);
+        checkAllFiniteReal(weights);
+
+        MathArrays.checkOrder(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 NumberIsTooSmallException(LocalizedFormats.BANDWIDTH,
+                                                bandwidthInPoints, 2, true);
+        }
+
+        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, weights, 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;
+                double sumXSquared = 0;
+                double sumY = 0;
+                double sumXY = 0;
+                double denom = FastMath.abs(1.0 / (xval[edge] - x));
+                for (int k = ileft; k <= iright; ++k) {
+                    final double xk   = xval[k];
+                    final double yk   = yval[k];
+                    final double dist = (k < i) ? x - xk : xk - x;
+                    final double w    = tricube(dist * denom) * robustnessWeights[k] * weights[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 (FastMath.sqrt(FastMath.abs(meanXSquared - meanX * meanX)) < accuracy) {
+                    beta = 0;
+                } else {
+                    beta = (meanXY - meanX * meanY) / (meanXSquared - meanX * meanX);
+                }
+
+                final double alpha = meanY - beta * meanX;
+
+                res[i] = beta * x + alpha;
+                residuals[i] = FastMath.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 (FastMath.abs(medianResidual) < accuracy) {
+                break;
+            }
+
+            for (int i = 0; i < n; ++i) {
+                final double arg = residuals[i] / (6 * medianResidual);
+                if (arg >= 1) {
+                    robustnessWeights[i] = 0;
+                } else {
+                    final double w = 1 - arg * arg;
+                    robustnessWeights[i] = w * w;
+                }
+            }
+        }
+
+        return res;
+    }
+
+    /**
+     * 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 NonMonotonicSequenceException if {@code xval} not sorted in
+     * strictly increasing order.
+     * @throws DimensionMismatchException if {@code xval} and {@code yval} have
+     * different sizes.
+     * @throws NoDataException if {@code xval} or {@code yval} has zero size.
+     * @throws NotFiniteNumberException if any of the arguments and values are
+     * not finite real numbers.
+     * @throws NumberIsTooSmallException if the bandwidth is too small to
+     * accomodate the size of the input data (i.e. the bandwidth must be
+     * larger than 2/n).
+     */
+    public final double[] smooth(final double[] xval, final double[] yval)
+        throws NonMonotonicSequenceException,
+               DimensionMismatchException,
+               NoDataException,
+               NotFiniteNumberException,
+               NumberIsTooSmallException {
+        if (xval.length != yval.length) {
+            throw new DimensionMismatchException(xval.length, yval.length);
+        }
+
+        final double[] unitWeights = new double[xval.length];
+        Arrays.fill(unitWeights, 1.0);
+
+        return smooth(xval, yval, unitWeights);
+    }
+
+    /**
+     * Given an index interval into xval that embraces a certain number of
+     * points closest to {@code xval[i-1]}, update the interval so that it
+     * embraces the same number of points closest to {@code xval[i]},
+     * ignoring zero weights.
+     *
+     * @param xval Arguments array.
+     * @param weights Weights array.
+     * @param i Index around which the new interval should be computed.
+     * @param bandwidthInterval a two-element array {left, right} such that:
+     * {@code (left==0 or xval[i] - xval[left-1] > xval[right] - xval[i])}
+     * and
+     * {@code (right==xval.length-1 or xval[right+1] - xval[i] > xval[i] - xval[left])}.
+     * The array will be updated.
+     */
+    private static void updateBandwidthInterval(final double[] xval, final double[] weights,
+                                                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
+        int nextRight = nextNonzero(weights, right);
+        if (nextRight < xval.length && xval[nextRight] - xval[i] < xval[i] - xval[left]) {
+            int nextLeft = nextNonzero(weights, bandwidthInterval[0]);
+            bandwidthInterval[0] = nextLeft;
+            bandwidthInterval[1] = nextRight;
+        }
+    }
+
+    /**
+     * Return the smallest index {@code j} such that
+     * {@code j > i && (j == weights.length || weights[j] != 0)}.
+     *
+     * @param weights Weights array.
+     * @param i Index from which to start search.
+     * @return the smallest compliant index.
+     */
+    private static int nextNonzero(final double[] weights, final int i) {
+        int j = i + 1;
+        while(j < weights.length && weights[j] == 0) {
+            ++j;
+        }
+        return j;
+    }
+
+    /**
+     * Compute the
+     * <a href="http://en.wikipedia.org/wiki/Local_regression#Weight_function">tricube</a>
+     * weight function
+     *
+     * @param x Argument.
+     * @return <code>(1 - |x|<sup>3</sup>)<sup>3</sup></code> for |x| &lt; 1, 0 otherwise.
+     */
+    private static double tricube(final double x) {
+        final double absX = FastMath.abs(x);
+        if (absX >= 1.0) {
+            return 0.0;
+        }
+        final double tmp = 1 - absX * absX * absX;
+        return tmp * tmp * tmp;
+    }
+
+    /**
+     * Check that all elements of an array are finite real numbers.
+     *
+     * @param values Values array.
+     * @throws org.apache.commons.math3.exception.NotFiniteNumberException
+     * if one of the values is not a finite real number.
+     */
+    private static void checkAllFiniteReal(final double[] values) {
+        for (int i = 0; i < values.length; i++) {
+            MathUtils.checkFinite(values[i]);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/interpolation/MicrosphereInterpolatingFunction.java b/src/main/java/org/apache/commons/math3/analysis/interpolation/MicrosphereInterpolatingFunction.java
new file mode 100644
index 0000000..58be772
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/interpolation/MicrosphereInterpolatingFunction.java
@@ -0,0 +1,253 @@
+/*
+ * 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.math3.analysis.interpolation;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.math3.analysis.MultivariateFunction;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.linear.ArrayRealVector;
+import org.apache.commons.math3.linear.RealVector;
+import org.apache.commons.math3.random.UnitSphereRandomVectorGenerator;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Interpolating function that implements the
+ * <a href="http://www.dudziak.com/microsphere.php">Microsphere Projection</a>.
+ *
+ * @deprecated Code will be removed in 4.0.  Use {@link InterpolatingMicrosphere}
+ * and {@link MicrosphereProjectionInterpolator} instead.
+ */
+@Deprecated
+public class MicrosphereInterpolatingFunction
+    implements MultivariateFunction {
+    /**
+     * Space dimension.
+     */
+    private final int dimension;
+    /**
+     * Internal accounting data for the interpolation algorithm.
+     * Each element of the list corresponds to one surface element of
+     * the microsphere.
+     */
+    private final List<MicrosphereSurfaceElement> microsphere;
+    /**
+     * Exponent used in the power law that computes the weights of the
+     * sample data.
+     */
+    private final double brightnessExponent;
+    /**
+     * Sample data.
+     */
+    private final Map<RealVector, Double> samples;
+
+    /**
+     * Class for storing the accounting data needed to perform the
+     * microsphere projection.
+     */
+    private static class MicrosphereSurfaceElement {
+        /** Normal vector characterizing a surface element. */
+        private final RealVector normal;
+        /** Illumination received from the brightest sample. */
+        private double brightestIllumination;
+        /** Brightest sample. */
+        private Map.Entry<RealVector, Double> brightestSample;
+
+        /**
+         * @param n Normal vector characterizing a surface element
+         * of the microsphere.
+         */
+        MicrosphereSurfaceElement(double[] n) {
+            normal = new ArrayRealVector(n);
+        }
+
+        /**
+         * Return the normal vector.
+         * @return the normal vector
+         */
+        RealVector normal() {
+            return normal;
+        }
+
+        /**
+         * Reset "illumination" and "sampleIndex".
+         */
+        void reset() {
+            brightestIllumination = 0;
+            brightestSample = null;
+        }
+
+        /**
+         * Store the illumination and index of the brightest sample.
+         * @param illuminationFromSample illumination received from sample
+         * @param sample current sample illuminating the element
+         */
+        void store(final double illuminationFromSample,
+                   final Map.Entry<RealVector, Double> sample) {
+            if (illuminationFromSample > this.brightestIllumination) {
+                this.brightestIllumination = illuminationFromSample;
+                this.brightestSample = sample;
+            }
+        }
+
+        /**
+         * Get the illumination of the element.
+         * @return the illumination.
+         */
+        double illumination() {
+            return brightestIllumination;
+        }
+
+        /**
+         * Get the sample illuminating the element the most.
+         * @return the sample.
+         */
+        Map.Entry<RealVector, Double> sample() {
+            return brightestSample;
+        }
+    }
+
+    /**
+     * @param xval Arguments for the interpolation points.
+     * {@code xval[i][0]} is the first component of interpolation point
+     * {@code i}, {@code xval[i][1]} is the second component, and so on
+     * until {@code xval[i][d-1]}, the last component of that interpolation
+     * point (where {@code dimension} is thus the dimension of the sampled
+     * space).
+     * @param yval Values for the interpolation points.
+     * @param brightnessExponent Brightness dimming factor.
+     * @param microsphereElements Number of surface elements of the
+     * microsphere.
+     * @param rand Unit vector generator for creating the microsphere.
+     * @throws DimensionMismatchException if the lengths of {@code yval} and
+     * {@code xval} (equal to {@code n}, the number of interpolation points)
+     * do not match, or the the arrays {@code xval[0]} ... {@code xval[n]},
+     * have lengths different from {@code dimension}.
+     * @throws NoDataException if there an array has zero-length.
+     * @throws NullArgumentException if an argument is {@code null}.
+     */
+    public MicrosphereInterpolatingFunction(double[][] xval,
+                                            double[] yval,
+                                            int brightnessExponent,
+                                            int microsphereElements,
+                                            UnitSphereRandomVectorGenerator rand)
+        throws DimensionMismatchException,
+               NoDataException,
+               NullArgumentException {
+        if (xval == null ||
+            yval == null) {
+            throw new NullArgumentException();
+        }
+        if (xval.length == 0) {
+            throw new NoDataException();
+        }
+        if (xval.length != yval.length) {
+            throw new DimensionMismatchException(xval.length, yval.length);
+        }
+        if (xval[0] == null) {
+            throw new NullArgumentException();
+        }
+
+        dimension = xval[0].length;
+        this.brightnessExponent = brightnessExponent;
+
+        // Copy data samples.
+        samples = new HashMap<RealVector, Double>(yval.length);
+        for (int i = 0; i < xval.length; ++i) {
+            final double[] xvalI = xval[i];
+            if (xvalI == null) {
+                throw new NullArgumentException();
+            }
+            if (xvalI.length != dimension) {
+                throw new DimensionMismatchException(xvalI.length, dimension);
+            }
+
+            samples.put(new ArrayRealVector(xvalI), yval[i]);
+        }
+
+        microsphere = new ArrayList<MicrosphereSurfaceElement>(microsphereElements);
+        // Generate the microsphere, assuming that a fairly large number of
+        // randomly generated normals will represent a sphere.
+        for (int i = 0; i < microsphereElements; i++) {
+            microsphere.add(new MicrosphereSurfaceElement(rand.nextVector()));
+        }
+    }
+
+    /**
+     * @param point Interpolation point.
+     * @return the interpolated value.
+     * @throws DimensionMismatchException if point dimension does not math sample
+     */
+    public double value(double[] point) throws DimensionMismatchException {
+        final RealVector p = new ArrayRealVector(point);
+
+        // Reset.
+        for (MicrosphereSurfaceElement md : microsphere) {
+            md.reset();
+        }
+
+        // Compute contribution of each sample points to the microsphere elements illumination
+        for (Map.Entry<RealVector, Double> sd : samples.entrySet()) {
+
+            // Vector between interpolation point and current sample point.
+            final RealVector diff = sd.getKey().subtract(p);
+            final double diffNorm = diff.getNorm();
+
+            if (FastMath.abs(diffNorm) < FastMath.ulp(1d)) {
+                // No need to interpolate, as the interpolation point is
+                // actually (very close to) one of the sampled points.
+                return sd.getValue();
+            }
+
+            for (MicrosphereSurfaceElement md : microsphere) {
+                final double w = FastMath.pow(diffNorm, -brightnessExponent);
+                md.store(cosAngle(diff, md.normal()) * w, sd);
+            }
+
+        }
+
+        // Interpolation calculation.
+        double value = 0;
+        double totalWeight = 0;
+        for (MicrosphereSurfaceElement md : microsphere) {
+            final double iV = md.illumination();
+            final Map.Entry<RealVector, Double> sd = md.sample();
+            if (sd != null) {
+                value += iV * sd.getValue();
+                totalWeight += iV;
+            }
+        }
+
+        return value / totalWeight;
+    }
+
+    /**
+     * Compute the cosine of the angle between 2 vectors.
+     *
+     * @param v Vector.
+     * @param w Vector.
+     * @return the cosine of the angle between {@code v} and {@code w}.
+     */
+    private double cosAngle(final RealVector v, final RealVector w) {
+        return v.dotProduct(w) / (v.getNorm() * w.getNorm());
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/interpolation/MicrosphereInterpolator.java b/src/main/java/org/apache/commons/math3/analysis/interpolation/MicrosphereInterpolator.java
new file mode 100644
index 0000000..d9174bc
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/interpolation/MicrosphereInterpolator.java
@@ -0,0 +1,105 @@
+/*
+ * 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.math3.analysis.interpolation;
+
+import org.apache.commons.math3.analysis.MultivariateFunction;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.random.UnitSphereRandomVectorGenerator;
+
+/**
+ * Interpolator that implements the algorithm described in
+ * <em>William Dudziak</em>'s
+ * <a href="http://www.dudziak.com/microsphere.pdf">MS thesis</a>.
+ *
+ * @since 2.1
+ * @deprecated Code will be removed in 4.0.  Use {@link InterpolatingMicrosphere}
+ * and {@link MicrosphereProjectionInterpolator} instead.
+ */
+@Deprecated
+public class MicrosphereInterpolator
+    implements MultivariateInterpolator {
+    /**
+     * Default number of surface elements that composes the microsphere.
+     */
+    public static final int DEFAULT_MICROSPHERE_ELEMENTS = 2000;
+    /**
+     * Default exponent used the weights calculation.
+     */
+    public static final int DEFAULT_BRIGHTNESS_EXPONENT = 2;
+    /**
+     * Number of surface elements of the microsphere.
+     */
+    private final int microsphereElements;
+    /**
+     * Exponent used in the power law that computes the weights of the
+     * sample data.
+     */
+    private final int brightnessExponent;
+
+    /**
+     * Create a microsphere interpolator with default settings.
+     * Calling this constructor is equivalent to call {@link
+     * #MicrosphereInterpolator(int, int)
+     * MicrosphereInterpolator(MicrosphereInterpolator.DEFAULT_MICROSPHERE_ELEMENTS,
+     * MicrosphereInterpolator.DEFAULT_BRIGHTNESS_EXPONENT)}.
+     */
+    public MicrosphereInterpolator() {
+        this(DEFAULT_MICROSPHERE_ELEMENTS, DEFAULT_BRIGHTNESS_EXPONENT);
+    }
+
+    /** Create a microsphere interpolator.
+     * @param elements Number of surface elements of the microsphere.
+     * @param exponent Exponent used in the power law that computes the
+     * weights (distance dimming factor) of the sample data.
+     * @throws NotPositiveException if {@code exponent < 0}.
+     * @throws NotStrictlyPositiveException if {@code elements <= 0}.
+     */
+    public MicrosphereInterpolator(final int elements,
+                                   final int exponent)
+        throws NotPositiveException,
+               NotStrictlyPositiveException {
+        if (exponent < 0) {
+            throw new NotPositiveException(exponent);
+        }
+        if (elements <= 0) {
+            throw new NotStrictlyPositiveException(elements);
+        }
+
+        microsphereElements = elements;
+        brightnessExponent = exponent;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public MultivariateFunction interpolate(final double[][] xval,
+                                            final double[] yval)
+        throws DimensionMismatchException,
+               NoDataException,
+               NullArgumentException {
+        final UnitSphereRandomVectorGenerator rand
+            = new UnitSphereRandomVectorGenerator(xval[0].length);
+        return new MicrosphereInterpolatingFunction(xval, yval,
+                                                    brightnessExponent,
+                                                    microsphereElements,
+                                                    rand);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/interpolation/MicrosphereProjectionInterpolator.java b/src/main/java/org/apache/commons/math3/analysis/interpolation/MicrosphereProjectionInterpolator.java
new file mode 100644
index 0000000..28f5b26
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/interpolation/MicrosphereProjectionInterpolator.java
@@ -0,0 +1,164 @@
+/*
+ * 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.math3.analysis.interpolation;
+
+import org.apache.commons.math3.analysis.MultivariateFunction;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.random.UnitSphereRandomVectorGenerator;
+
+/**
+ * Interpolator that implements the algorithm described in
+ * <em>William Dudziak</em>'s
+ * <a href="http://www.dudziak.com/microsphere.pdf">MS thesis</a>.
+ *
+ * @since 3.6
+ */
+public class MicrosphereProjectionInterpolator
+    implements MultivariateInterpolator {
+    /** Brightness exponent. */
+    private final double exponent;
+    /** Microsphere. */
+    private final InterpolatingMicrosphere microsphere;
+    /** Whether to share the sphere. */
+    private final boolean sharedSphere;
+    /** Tolerance value below which no interpolation is necessary. */
+    private final double noInterpolationTolerance;
+
+    /**
+     * Create a microsphere interpolator.
+     *
+     * @param dimension Space dimension.
+     * @param elements Number of surface elements of the microsphere.
+     * @param exponent Exponent used in the power law that computes the
+     * @param maxDarkFraction Maximum fraction of the facets that can be dark.
+     * If the fraction of "non-illuminated" facets is larger, no estimation
+     * of the value will be performed, and the {@code background} value will
+     * be returned instead.
+     * @param darkThreshold Value of the illumination below which a facet is
+     * considered dark.
+     * @param background Value returned when the {@code maxDarkFraction}
+     * threshold is exceeded.
+     * @param sharedSphere Whether the sphere can be shared among the
+     * interpolating function instances.  If {@code true}, the instances
+     * will share the same data, and thus will <em>not</em> be thread-safe.
+     * @param noInterpolationTolerance When the distance between an
+     * interpolated point and one of the sample points is less than this
+     * value, no interpolation will be performed (the value of the sample
+     * will be returned).
+     * @throws org.apache.commons.math3.exception.NotStrictlyPositiveException
+     * if {@code dimension <= 0} or {@code elements <= 0}.
+     * @throws NotPositiveException if {@code exponent < 0}.
+     * @throws NotPositiveException if {@code darkThreshold < 0}.
+     * @throws org.apache.commons.math3.exception.OutOfRangeException if
+     * {@code maxDarkFraction} does not belong to the interval {@code [0, 1]}.
+     */
+    public MicrosphereProjectionInterpolator(int dimension,
+                                             int elements,
+                                             double maxDarkFraction,
+                                             double darkThreshold,
+                                             double background,
+                                             double exponent,
+                                             boolean sharedSphere,
+                                             double noInterpolationTolerance) {
+        this(new InterpolatingMicrosphere(dimension,
+                                          elements,
+                                          maxDarkFraction,
+                                          darkThreshold,
+                                          background,
+                                          new UnitSphereRandomVectorGenerator(dimension)),
+             exponent,
+             sharedSphere,
+             noInterpolationTolerance);
+    }
+
+    /**
+     * Create a microsphere interpolator.
+     *
+     * @param microsphere Microsphere.
+     * @param exponent Exponent used in the power law that computes the
+     * weights (distance dimming factor) of the sample data.
+     * @param sharedSphere Whether the sphere can be shared among the
+     * interpolating function instances.  If {@code true}, the instances
+     * will share the same data, and thus will <em>not</em> be thread-safe.
+     * @param noInterpolationTolerance When the distance between an
+     * interpolated point and one of the sample points is less than this
+     * value, no interpolation will be performed (the value of the sample
+     * will be returned).
+     * @throws NotPositiveException if {@code exponent < 0}.
+     */
+    public MicrosphereProjectionInterpolator(InterpolatingMicrosphere microsphere,
+                                             double exponent,
+                                             boolean sharedSphere,
+                                             double noInterpolationTolerance)
+        throws NotPositiveException {
+        if (exponent < 0) {
+            throw new NotPositiveException(exponent);
+        }
+
+        this.microsphere = microsphere;
+        this.exponent = exponent;
+        this.sharedSphere = sharedSphere;
+        this.noInterpolationTolerance = noInterpolationTolerance;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws DimensionMismatchException if the space dimension of the
+     * given samples does not match the space dimension of the microsphere.
+     */
+    public MultivariateFunction interpolate(final double[][] xval,
+                                            final double[] yval)
+        throws DimensionMismatchException,
+               NoDataException,
+               NullArgumentException {
+        if (xval == null ||
+            yval == null) {
+            throw new NullArgumentException();
+        }
+        if (xval.length == 0) {
+            throw new NoDataException();
+        }
+        if (xval.length != yval.length) {
+            throw new DimensionMismatchException(xval.length, yval.length);
+        }
+        if (xval[0] == null) {
+            throw new NullArgumentException();
+        }
+        final int dimension = microsphere.getDimension();
+        if (dimension != xval[0].length) {
+            throw new DimensionMismatchException(xval[0].length, dimension);
+        }
+
+        // Microsphere copy.
+        final InterpolatingMicrosphere m = sharedSphere ? microsphere : microsphere.copy();
+
+        return new MultivariateFunction() {
+            /** {inheritDoc} */
+            public double value(double[] point) {
+                return m.value(point,
+                               xval,
+                               yval,
+                               exponent,
+                               noInterpolationTolerance);
+            }
+        };
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/interpolation/MultivariateInterpolator.java b/src/main/java/org/apache/commons/math3/analysis/interpolation/MultivariateInterpolator.java
new file mode 100644
index 0000000..7d76374
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/interpolation/MultivariateInterpolator.java
@@ -0,0 +1,51 @@
+/*
+ * 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.math3.analysis.interpolation;
+
+import org.apache.commons.math3.analysis.MultivariateFunction;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NullArgumentException;
+
+/**
+ * Interface representing a univariate real interpolating function.
+ *
+ * @since 2.1
+ */
+public interface MultivariateInterpolator {
+
+    /**
+     * Computes an interpolating function for the data set.
+     *
+     * @param xval the arguments for the interpolation points.
+     * {@code xval[i][0]} is the first component of interpolation point
+     * {@code i}, {@code xval[i][1]} is the second component, and so on
+     * until {@code xval[i][d-1]}, the last component of that interpolation
+     * point (where {@code d} is thus the dimension of the space).
+     * @param yval the values for the interpolation points
+     * @return a function which interpolates the data set
+     * @throws MathIllegalArgumentException if the arguments violate assumptions
+     * made by the interpolation algorithm.
+     * @throws DimensionMismatchException when the array dimensions are not consistent.
+     * @throws NoDataException if an array has zero-length.
+     * @throws NullArgumentException if the arguments are {@code null}.
+     */
+    MultivariateFunction interpolate(double[][] xval, double[] yval)
+        throws MathIllegalArgumentException, DimensionMismatchException,
+               NoDataException, NullArgumentException;
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/interpolation/NevilleInterpolator.java b/src/main/java/org/apache/commons/math3/analysis/interpolation/NevilleInterpolator.java
new file mode 100644
index 0000000..6b47451
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/interpolation/NevilleInterpolator.java
@@ -0,0 +1,60 @@
+/*
+ * 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.math3.analysis.interpolation;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.analysis.polynomials.PolynomialFunctionLagrangeForm;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.NonMonotonicSequenceException;
+
+/**
+ * Implements the <a href="http://mathworld.wolfram.com/NevillesAlgorithm.html">
+ * Neville's Algorithm</a> for interpolation of real univariate functions. For
+ * reference, see <b>Introduction to Numerical Analysis</b>, ISBN 038795452X,
+ * chapter 2.
+ * <p>
+ * The actual code of Neville's algorithm is in PolynomialFunctionLagrangeForm,
+ * this class provides an easy-to-use interface to it.</p>
+ *
+ * @since 1.2
+ */
+public class NevilleInterpolator implements UnivariateInterpolator,
+    Serializable {
+
+    /** serializable version identifier */
+    static final long serialVersionUID = 3003707660147873733L;
+
+    /**
+     * Computes an interpolating function for the data set.
+     *
+     * @param x Interpolating points.
+     * @param y Interpolating values.
+     * @return a function which interpolates the data set
+     * @throws DimensionMismatchException if the array lengths are different.
+     * @throws NumberIsTooSmallException if the number of points is less than 2.
+     * @throws NonMonotonicSequenceException if two abscissae have the same
+     * value.
+     */
+    public PolynomialFunctionLagrangeForm interpolate(double x[], double y[])
+        throws DimensionMismatchException,
+               NumberIsTooSmallException,
+               NonMonotonicSequenceException {
+        return new PolynomialFunctionLagrangeForm(x, y);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/interpolation/PiecewiseBicubicSplineInterpolatingFunction.java b/src/main/java/org/apache/commons/math3/analysis/interpolation/PiecewiseBicubicSplineInterpolatingFunction.java
new file mode 100644
index 0000000..7dd135a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/interpolation/PiecewiseBicubicSplineInterpolatingFunction.java
@@ -0,0 +1,210 @@
+/*
+ * 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.math3.analysis.interpolation;
+
+import java.util.Arrays;
+import org.apache.commons.math3.analysis.BivariateFunction;
+import org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.InsufficientDataException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.NonMonotonicSequenceException;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * Function that implements the
+ * <a href="http://www.paulinternet.nl/?page=bicubic">bicubic spline</a>
+ * interpolation.
+ * This implementation currently uses {@link AkimaSplineInterpolator} as the
+ * underlying one-dimensional interpolator, which requires 5 sample points;
+ * insufficient data will raise an exception when the
+ * {@link #value(double,double) value} method is called.
+ *
+ * @since 3.4
+ */
+public class PiecewiseBicubicSplineInterpolatingFunction
+    implements BivariateFunction {
+    /** The minimum number of points that are needed to compute the function. */
+    private static final int MIN_NUM_POINTS = 5;
+    /** Samples x-coordinates */
+    private final double[] xval;
+    /** Samples y-coordinates */
+    private final double[] yval;
+    /** Set of cubic splines patching the whole data grid */
+    private final double[][] fval;
+
+    /**
+     * @param x Sample values of the x-coordinate, in increasing order.
+     * @param y Sample values of the y-coordinate, in increasing order.
+     * @param f Values of the function on every grid point. the expected number
+     *        of elements.
+     * @throws NonMonotonicSequenceException if {@code x} or {@code y} are not
+     *         strictly increasing.
+     * @throws NullArgumentException if any of the arguments are null
+     * @throws NoDataException if any of the arrays has zero length.
+     * @throws DimensionMismatchException if the length of x and y don't match the row, column
+     *         height of f
+     */
+    public PiecewiseBicubicSplineInterpolatingFunction(double[] x,
+                                                       double[] y,
+                                                       double[][] f)
+        throws DimensionMismatchException,
+               NullArgumentException,
+               NoDataException,
+               NonMonotonicSequenceException {
+        if (x == null ||
+            y == null ||
+            f == null ||
+            f[0] == null) {
+            throw new NullArgumentException();
+        }
+
+        final int xLen = x.length;
+        final int yLen = y.length;
+
+        if (xLen == 0 ||
+            yLen == 0 ||
+            f.length == 0 ||
+            f[0].length == 0) {
+            throw new NoDataException();
+        }
+
+        if (xLen < MIN_NUM_POINTS ||
+            yLen < MIN_NUM_POINTS ||
+            f.length < MIN_NUM_POINTS ||
+            f[0].length < MIN_NUM_POINTS) {
+            throw new InsufficientDataException();
+        }
+
+        if (xLen != f.length) {
+            throw new DimensionMismatchException(xLen, f.length);
+        }
+
+        if (yLen != f[0].length) {
+            throw new DimensionMismatchException(yLen, f[0].length);
+        }
+
+        MathArrays.checkOrder(x);
+        MathArrays.checkOrder(y);
+
+        xval = x.clone();
+        yval = y.clone();
+        fval = f.clone();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public double value(double x,
+                        double y)
+        throws OutOfRangeException {
+        final AkimaSplineInterpolator interpolator = new AkimaSplineInterpolator();
+        final int offset = 2;
+        final int count = offset + 3;
+        final int i = searchIndex(x, xval, offset, count);
+        final int j = searchIndex(y, yval, offset, count);
+
+        final double xArray[] = new double[count];
+        final double yArray[] = new double[count];
+        final double zArray[] = new double[count];
+        final double interpArray[] = new double[count];
+
+        for (int index = 0; index < count; index++) {
+            xArray[index] = xval[i + index];
+            yArray[index] = yval[j + index];
+        }
+
+        for (int zIndex = 0; zIndex < count; zIndex++) {
+            for (int index = 0; index < count; index++) {
+                zArray[index] = fval[i + index][j + zIndex];
+            }
+            final PolynomialSplineFunction spline = interpolator.interpolate(xArray, zArray);
+            interpArray[zIndex] = spline.value(x);
+        }
+
+        final PolynomialSplineFunction spline = interpolator.interpolate(yArray, interpArray);
+
+        double returnValue = spline.value(y);
+
+        return returnValue;
+    }
+
+    /**
+     * Indicates whether a point is within the interpolation range.
+     *
+     * @param x First coordinate.
+     * @param y Second coordinate.
+     * @return {@code true} if (x, y) is a valid point.
+     * @since 3.3
+     */
+    public boolean isValidPoint(double x,
+                                double y) {
+        if (x < xval[0] ||
+            x > xval[xval.length - 1] ||
+            y < yval[0] ||
+            y > yval[yval.length - 1]) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * @param c Coordinate.
+     * @param val Coordinate samples.
+     * @param offset how far back from found value to offset for querying
+     * @param count total number of elements forward from beginning that will be
+     *        queried
+     * @return the index in {@code val} corresponding to the interval containing
+     *         {@code c}.
+     * @throws OutOfRangeException if {@code c} is out of the range defined by
+     *         the boundary values of {@code val}.
+     */
+    private int searchIndex(double c,
+                            double[] val,
+                            int offset,
+                            int count) {
+        int r = Arrays.binarySearch(val, c);
+
+        if (r == -1 || r == -val.length - 1) {
+            throw new OutOfRangeException(c, val[0], val[val.length - 1]);
+        }
+
+        if (r < 0) {
+            // "c" in within an interpolation sub-interval, which returns
+            // negative
+            // need to remove the negative sign for consistency
+            r = -r - offset - 1;
+        } else {
+            r -= offset;
+        }
+
+        if (r < 0) {
+            r = 0;
+        }
+
+        if ((r + count) >= val.length) {
+            // "c" is the last sample of the range: Return the index
+            // of the sample at the lower end of the last sub-interval.
+            r = val.length - count;
+        }
+
+        return r;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/interpolation/PiecewiseBicubicSplineInterpolator.java b/src/main/java/org/apache/commons/math3/analysis/interpolation/PiecewiseBicubicSplineInterpolator.java
new file mode 100644
index 0000000..826f328
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/interpolation/PiecewiseBicubicSplineInterpolator.java
@@ -0,0 +1,61 @@
+/*
+ * 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.math3.analysis.interpolation;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NonMonotonicSequenceException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * Generates a piecewise-bicubic interpolating function.
+ *
+ * @since 2.2
+ */
+public class PiecewiseBicubicSplineInterpolator
+    implements BivariateGridInterpolator {
+
+    /**
+     * {@inheritDoc}
+     */
+    public PiecewiseBicubicSplineInterpolatingFunction interpolate( final double[] xval,
+                                                                    final double[] yval,
+                                                                    final double[][] fval)
+        throws DimensionMismatchException,
+               NullArgumentException,
+               NoDataException,
+               NonMonotonicSequenceException {
+        if ( xval == null ||
+             yval == null ||
+             fval == null ||
+             fval[0] == null ) {
+            throw new NullArgumentException();
+        }
+
+        if ( xval.length == 0 ||
+             yval.length == 0 ||
+             fval.length == 0 ) {
+            throw new NoDataException();
+        }
+
+        MathArrays.checkOrder(xval);
+        MathArrays.checkOrder(yval);
+
+        return new PiecewiseBicubicSplineInterpolatingFunction( xval, yval, fval );
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/interpolation/SmoothingPolynomialBicubicSplineInterpolator.java b/src/main/java/org/apache/commons/math3/analysis/interpolation/SmoothingPolynomialBicubicSplineInterpolator.java
new file mode 100644
index 0000000..e1639b2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/interpolation/SmoothingPolynomialBicubicSplineInterpolator.java
@@ -0,0 +1,171 @@
+/*
+ * 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.math3.analysis.interpolation;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NonMonotonicSequenceException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.util.Precision;
+import org.apache.commons.math3.optim.nonlinear.vector.jacobian.GaussNewtonOptimizer;
+import org.apache.commons.math3.fitting.PolynomialFitter;
+import org.apache.commons.math3.analysis.polynomials.PolynomialFunction;
+import org.apache.commons.math3.optim.SimpleVectorValueChecker;
+
+/**
+ * Generates a bicubic interpolation function.
+ * Prior to generating the interpolating function, the input is smoothed using
+ * polynomial fitting.
+ *
+ * @since 2.2
+ * @deprecated To be removed in 4.0 (see MATH-1166).
+ */
+@Deprecated
+public class SmoothingPolynomialBicubicSplineInterpolator
+    extends BicubicSplineInterpolator {
+    /** Fitter for x. */
+    private final PolynomialFitter xFitter;
+    /** Degree of the fitting polynomial. */
+    private final int xDegree;
+    /** Fitter for y. */
+    private final PolynomialFitter yFitter;
+    /** Degree of the fitting polynomial. */
+    private final int yDegree;
+
+    /**
+     * Default constructor. The degree of the fitting polynomials is set to 3.
+     */
+    public SmoothingPolynomialBicubicSplineInterpolator() {
+        this(3);
+    }
+
+    /**
+     * @param degree Degree of the polynomial fitting functions.
+     * @exception NotPositiveException if degree is not positive
+     */
+    public SmoothingPolynomialBicubicSplineInterpolator(int degree)
+        throws NotPositiveException {
+        this(degree, degree);
+    }
+
+    /**
+     * @param xDegree Degree of the polynomial fitting functions along the
+     * x-dimension.
+     * @param yDegree Degree of the polynomial fitting functions along the
+     * y-dimension.
+     * @exception NotPositiveException if degrees are not positive
+     */
+    public SmoothingPolynomialBicubicSplineInterpolator(int xDegree, int yDegree)
+        throws NotPositiveException {
+        if (xDegree < 0) {
+            throw new NotPositiveException(xDegree);
+        }
+        if (yDegree < 0) {
+            throw new NotPositiveException(yDegree);
+        }
+        this.xDegree = xDegree;
+        this.yDegree = yDegree;
+
+        final double safeFactor = 1e2;
+        final SimpleVectorValueChecker checker
+            = new SimpleVectorValueChecker(safeFactor * Precision.EPSILON,
+                                           safeFactor * Precision.SAFE_MIN);
+        xFitter = new PolynomialFitter(new GaussNewtonOptimizer(false, checker));
+        yFitter = new PolynomialFitter(new GaussNewtonOptimizer(false, checker));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public BicubicSplineInterpolatingFunction interpolate(final double[] xval,
+                                                          final double[] yval,
+                                                          final double[][] fval)
+        throws NoDataException, NullArgumentException,
+               DimensionMismatchException, NonMonotonicSequenceException {
+        if (xval.length == 0 || yval.length == 0 || fval.length == 0) {
+            throw new NoDataException();
+        }
+        if (xval.length != fval.length) {
+            throw new DimensionMismatchException(xval.length, fval.length);
+        }
+
+        final int xLen = xval.length;
+        final int yLen = yval.length;
+
+        for (int i = 0; i < xLen; i++) {
+            if (fval[i].length != yLen) {
+                throw new DimensionMismatchException(fval[i].length, yLen);
+            }
+        }
+
+        MathArrays.checkOrder(xval);
+        MathArrays.checkOrder(yval);
+
+        // For each line y[j] (0 <= j < yLen), construct a polynomial, with
+        // respect to variable x, fitting array fval[][j]
+        final PolynomialFunction[] yPolyX = new PolynomialFunction[yLen];
+        for (int j = 0; j < yLen; j++) {
+            xFitter.clearObservations();
+            for (int i = 0; i < xLen; i++) {
+                xFitter.addObservedPoint(1, xval[i], fval[i][j]);
+            }
+
+            // Initial guess for the fit is zero for each coefficients (of which
+            // there are "xDegree" + 1).
+            yPolyX[j] = new PolynomialFunction(xFitter.fit(new double[xDegree + 1]));
+        }
+
+        // For every knot (xval[i], yval[j]) of the grid, calculate corrected
+        // values fval_1
+        final double[][] fval_1 = new double[xLen][yLen];
+        for (int j = 0; j < yLen; j++) {
+            final PolynomialFunction f = yPolyX[j];
+            for (int i = 0; i < xLen; i++) {
+                fval_1[i][j] = f.value(xval[i]);
+            }
+        }
+
+        // For each line x[i] (0 <= i < xLen), construct a polynomial, with
+        // respect to variable y, fitting array fval_1[i][]
+        final PolynomialFunction[] xPolyY = new PolynomialFunction[xLen];
+        for (int i = 0; i < xLen; i++) {
+            yFitter.clearObservations();
+            for (int j = 0; j < yLen; j++) {
+                yFitter.addObservedPoint(1, yval[j], fval_1[i][j]);
+            }
+
+            // Initial guess for the fit is zero for each coefficients (of which
+            // there are "yDegree" + 1).
+            xPolyY[i] = new PolynomialFunction(yFitter.fit(new double[yDegree + 1]));
+        }
+
+        // For every knot (xval[i], yval[j]) of the grid, calculate corrected
+        // values fval_2
+        final double[][] fval_2 = new double[xLen][yLen];
+        for (int i = 0; i < xLen; i++) {
+            final PolynomialFunction f = xPolyY[i];
+            for (int j = 0; j < yLen; j++) {
+                fval_2[i][j] = f.value(yval[j]);
+            }
+        }
+
+        return super.interpolate(xval, yval, fval_2);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/interpolation/SplineInterpolator.java b/src/main/java/org/apache/commons/math3/analysis/interpolation/SplineInterpolator.java
new file mode 100644
index 0000000..f37e1b1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/interpolation/SplineInterpolator.java
@@ -0,0 +1,127 @@
+/*
+ * 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.math3.analysis.interpolation;
+
+import org.apache.commons.math3.analysis.polynomials.PolynomialFunction;
+import org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NonMonotonicSequenceException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * Computes a natural (also known as "free", "unclamped") cubic spline interpolation for the data set.
+ * <p>
+ * The {@link #interpolate(double[], double[])} method returns a {@link PolynomialSplineFunction}
+ * consisting of n cubic polynomials, defined over the subintervals determined by the x values,
+ * {@code x[0] < x[i] ... < x[n].}  The x values are referred to as "knot points."
+ * <p>
+ * The value of the PolynomialSplineFunction at a point x that is greater than or equal to the smallest
+ * knot point and strictly less than the largest knot point is computed by finding the subinterval to which
+ * x belongs and computing the value of the corresponding polynomial at <code>x - x[i] </code> where
+ * <code>i</code> is the index of the subinterval.  See {@link PolynomialSplineFunction} for more details.
+ * </p>
+ * <p>
+ * The interpolating polynomials satisfy: <ol>
+ * <li>The value of the PolynomialSplineFunction at each of the input x values equals the
+ *  corresponding y value.</li>
+ * <li>Adjacent polynomials are equal through two derivatives at the knot points (i.e., adjacent polynomials
+ *  "match up" at the knot points, as do their first and second derivatives).</li>
+ * </ol>
+ * <p>
+ * The cubic spline interpolation algorithm implemented is as described in R.L. Burden, J.D. Faires,
+ * <u>Numerical Analysis</u>, 4th Ed., 1989, PWS-Kent, ISBN 0-53491-585-X, pp 126-131.
+ * </p>
+ *
+ */
+public class SplineInterpolator implements UnivariateInterpolator {
+    /**
+     * Computes an interpolating function for the data set.
+     * @param x the arguments for the interpolation points
+     * @param y the values for the interpolation points
+     * @return a function which interpolates the data set
+     * @throws DimensionMismatchException if {@code x} and {@code y}
+     * have different sizes.
+     * @throws NonMonotonicSequenceException if {@code x} is not sorted in
+     * strict increasing order.
+     * @throws NumberIsTooSmallException if the size of {@code x} is smaller
+     * than 3.
+     */
+    public PolynomialSplineFunction interpolate(double x[], double y[])
+        throws DimensionMismatchException,
+               NumberIsTooSmallException,
+               NonMonotonicSequenceException {
+        if (x.length != y.length) {
+            throw new DimensionMismatchException(x.length, y.length);
+        }
+
+        if (x.length < 3) {
+            throw new NumberIsTooSmallException(LocalizedFormats.NUMBER_OF_POINTS,
+                                                x.length, 3, true);
+        }
+
+        // Number of intervals.  The number of data points is n + 1.
+        final int n = x.length - 1;
+
+        MathArrays.checkOrder(x);
+
+        // Differences between knot points
+        final double h[] = new double[n];
+        for (int i = 0; i < n; i++) {
+            h[i] = x[i + 1] - x[i];
+        }
+
+        final double mu[] = new double[n];
+        final double z[] = new double[n + 1];
+        mu[0] = 0d;
+        z[0] = 0d;
+        double g = 0;
+        for (int i = 1; i < n; i++) {
+            g = 2d * (x[i+1]  - x[i - 1]) - h[i - 1] * mu[i -1];
+            mu[i] = h[i] / g;
+            z[i] = (3d * (y[i + 1] * h[i - 1] - y[i] * (x[i + 1] - x[i - 1])+ y[i - 1] * h[i]) /
+                    (h[i - 1] * h[i]) - h[i - 1] * z[i - 1]) / g;
+        }
+
+        // cubic spline coefficients --  b is linear, c quadratic, d is cubic (original y's are constants)
+        final double b[] = new double[n];
+        final double c[] = new double[n + 1];
+        final double d[] = new double[n];
+
+        z[n] = 0d;
+        c[n] = 0d;
+
+        for (int j = n -1; j >=0; j--) {
+            c[j] = z[j] - mu[j] * c[j + 1];
+            b[j] = (y[j + 1] - y[j]) / h[j] - h[j] * (c[j + 1] + 2d * c[j]) / 3d;
+            d[j] = (c[j + 1] - c[j]) / (3d * h[j]);
+        }
+
+        final PolynomialFunction polynomials[] = new PolynomialFunction[n];
+        final double coefficients[] = new double[4];
+        for (int i = 0; i < n; i++) {
+            coefficients[0] = y[i];
+            coefficients[1] = b[i];
+            coefficients[2] = c[i];
+            coefficients[3] = d[i];
+            polynomials[i] = new PolynomialFunction(coefficients);
+        }
+
+        return new PolynomialSplineFunction(x, polynomials);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/interpolation/TricubicInterpolatingFunction.java b/src/main/java/org/apache/commons/math3/analysis/interpolation/TricubicInterpolatingFunction.java
new file mode 100644
index 0000000..27e9a65
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/interpolation/TricubicInterpolatingFunction.java
@@ -0,0 +1,508 @@
+/*
+ * 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.math3.analysis.interpolation;
+
+import org.apache.commons.math3.analysis.TrivariateFunction;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NonMonotonicSequenceException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * Function that implements the
+ * <a href="http://en.wikipedia.org/wiki/Tricubic_interpolation">
+ * tricubic spline interpolation</a>, as proposed in
+ * <blockquote>
+ *  Tricubic interpolation in three dimensions,
+ *  F. Lekien and J. Marsden,
+ *  <em>Int. J. Numer. Meth. Eng</em> 2005; <b>63</b>:455-471
+ * </blockquote>
+ *
+ * @since 3.4.
+ */
+public class TricubicInterpolatingFunction
+    implements TrivariateFunction {
+    /**
+     * Matrix to compute the spline coefficients from the function values
+     * and function derivatives values
+     */
+    private static final double[][] AINV = {
+        { 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { -3,3,0,0,0,0,0,0,-2,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 2,-2,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,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,0,0,0,0,0,0,0,0,-2,0,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 9,-9,-9,9,0,0,0,0,6,3,-6,-3,0,0,0,0,6,-6,3,-3,0,0,0,0,0,0,0,0,0,0,0,0,4,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { -6,6,6,-6,0,0,0,0,-3,-3,3,3,0,0,0,0,-4,4,-2,2,0,0,0,0,0,0,0,0,0,0,0,0,-2,-2,-1,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 2,0,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,2,0,-2,0,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { -6,6,6,-6,0,0,0,0,-4,-2,4,2,0,0,0,0,-3,3,-3,3,0,0,0,0,0,0,0,0,0,0,0,0,-2,-1,-2,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 4,-4,-4,4,0,0,0,0,2,2,-2,-2,0,0,0,0,2,-2,2,-2,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-3,3,0,0,0,0,0,0,-2,-1,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,-2,0,0,0,0,0,0,1,1,0,0,0,0,0,0 },
+        { 0,0,0,0,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,-1,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,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,0,0,0,0,0,0,0,0,-2,0,-1,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,-9,-9,9,0,0,0,0,0,0,0,0,0,0,0,0,6,3,-6,-3,0,0,0,0,6,-6,3,-3,0,0,0,0,4,2,2,1,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-6,6,6,-6,0,0,0,0,0,0,0,0,0,0,0,0,-3,-3,3,3,0,0,0,0,-4,4,-2,2,0,0,0,0,-2,-2,-1,-1,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,-2,0,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,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-6,6,6,-6,0,0,0,0,0,0,0,0,0,0,0,0,-4,-2,4,2,0,0,0,0,-3,3,-3,3,0,0,0,0,-2,-1,-2,-1,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,-4,-4,4,0,0,0,0,0,0,0,0,0,0,0,0,2,2,-2,-2,0,0,0,0,2,-2,2,-2,0,0,0,0,1,1,1,1,0,0,0,0 },
+        {-3,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,0,0,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,-3,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,0,0,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 9,-9,0,0,-9,9,0,0,6,3,0,0,-6,-3,0,0,0,0,0,0,0,0,0,0,6,-6,0,0,3,-3,0,0,0,0,0,0,0,0,0,0,4,2,0,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { -6,6,0,0,6,-6,0,0,-3,-3,0,0,3,3,0,0,0,0,0,0,0,0,0,0,-4,4,0,0,-2,2,0,0,0,0,0,0,0,0,0,0,-2,-2,0,0,-1,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-3,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,0,0,-1,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-3,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,0,0,-1,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,-9,0,0,-9,9,0,0,0,0,0,0,0,0,0,0,6,3,0,0,-6,-3,0,0,0,0,0,0,0,0,0,0,6,-6,0,0,3,-3,0,0,4,2,0,0,2,1,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-6,6,0,0,6,-6,0,0,0,0,0,0,0,0,0,0,-3,-3,0,0,3,3,0,0,0,0,0,0,0,0,0,0,-4,4,0,0,-2,2,0,0,-2,-2,0,0,-1,-1,0,0 },
+        { 9,0,-9,0,-9,0,9,0,0,0,0,0,0,0,0,0,6,0,3,0,-6,0,-3,0,6,0,-6,0,3,0,-3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,2,0,2,0,1,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,9,0,-9,0,-9,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,0,3,0,-6,0,-3,0,6,0,-6,0,3,0,-3,0,0,0,0,0,0,0,0,0,4,0,2,0,2,0,1,0 },
+        { -27,27,27,-27,27,-27,-27,27,-18,-9,18,9,18,9,-18,-9,-18,18,-9,9,18,-18,9,-9,-18,18,18,-18,-9,9,9,-9,-12,-6,-6,-3,12,6,6,3,-12,-6,12,6,-6,-3,6,3,-12,12,-6,6,-6,6,-3,3,-8,-4,-4,-2,-4,-2,-2,-1 },
+        { 18,-18,-18,18,-18,18,18,-18,9,9,-9,-9,-9,-9,9,9,12,-12,6,-6,-12,12,-6,6,12,-12,-12,12,6,-6,-6,6,6,6,3,3,-6,-6,-3,-3,6,6,-6,-6,3,3,-3,-3,8,-8,4,-4,4,-4,2,-2,4,4,2,2,2,2,1,1 },
+        { -6,0,6,0,6,0,-6,0,0,0,0,0,0,0,0,0,-3,0,-3,0,3,0,3,0,-4,0,4,0,-2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,-2,0,-1,0,-1,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,-6,0,6,0,6,0,-6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-3,0,-3,0,3,0,3,0,-4,0,4,0,-2,0,2,0,0,0,0,0,0,0,0,0,-2,0,-2,0,-1,0,-1,0 },
+        { 18,-18,-18,18,-18,18,18,-18,12,6,-12,-6,-12,-6,12,6,9,-9,9,-9,-9,9,-9,9,12,-12,-12,12,6,-6,-6,6,6,3,6,3,-6,-3,-6,-3,8,4,-8,-4,4,2,-4,-2,6,-6,6,-6,3,-3,3,-3,4,2,4,2,2,1,2,1 },
+        { -12,12,12,-12,12,-12,-12,12,-6,-6,6,6,6,6,-6,-6,-6,6,-6,6,6,-6,6,-6,-8,8,8,-8,-4,4,4,-4,-3,-3,-3,-3,3,3,3,3,-4,-4,4,4,-2,-2,2,2,-4,4,-4,4,-2,2,-2,2,-2,-2,-2,-2,-1,-1,-1,-1 },
+        { 2,0,0,0,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,2,0,0,0,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { -6,6,0,0,6,-6,0,0,-4,-2,0,0,4,2,0,0,0,0,0,0,0,0,0,0,-3,3,0,0,-3,3,0,0,0,0,0,0,0,0,0,0,-2,-1,0,0,-2,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 4,-4,0,0,-4,4,0,0,2,2,0,0,-2,-2,0,0,0,0,0,0,0,0,0,0,2,-2,0,0,2,-2,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-6,6,0,0,6,-6,0,0,0,0,0,0,0,0,0,0,-4,-2,0,0,4,2,0,0,0,0,0,0,0,0,0,0,-3,3,0,0,-3,3,0,0,-2,-1,0,0,-2,-1,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,-4,0,0,-4,4,0,0,0,0,0,0,0,0,0,0,2,2,0,0,-2,-2,0,0,0,0,0,0,0,0,0,0,2,-2,0,0,2,-2,0,0,1,1,0,0,1,1,0,0 },
+        { -6,0,6,0,6,0,-6,0,0,0,0,0,0,0,0,0,-4,0,-2,0,4,0,2,0,-3,0,3,0,-3,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,-1,0,-2,0,-1,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,-6,0,6,0,6,0,-6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-4,0,-2,0,4,0,2,0,-3,0,3,0,-3,0,3,0,0,0,0,0,0,0,0,0,-2,0,-1,0,-2,0,-1,0 },
+        { 18,-18,-18,18,-18,18,18,-18,12,6,-12,-6,-12,-6,12,6,12,-12,6,-6,-12,12,-6,6,9,-9,-9,9,9,-9,-9,9,8,4,4,2,-8,-4,-4,-2,6,3,-6,-3,6,3,-6,-3,6,-6,3,-3,6,-6,3,-3,4,2,2,1,4,2,2,1 },
+        { -12,12,12,-12,12,-12,-12,12,-6,-6,6,6,6,6,-6,-6,-8,8,-4,4,8,-8,4,-4,-6,6,6,-6,-6,6,6,-6,-4,-4,-2,-2,4,4,2,2,-3,-3,3,3,-3,-3,3,3,-4,4,-2,2,-4,4,-2,2,-2,-2,-1,-1,-2,-2,-1,-1 },
+        { 4,0,-4,0,-4,0,4,0,0,0,0,0,0,0,0,0,2,0,2,0,-2,0,-2,0,2,0,-2,0,2,0,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,1,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,4,0,-4,0,-4,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,2,0,-2,0,-2,0,2,0,-2,0,2,0,-2,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,1,0 },
+        { -12,12,12,-12,12,-12,-12,12,-8,-4,8,4,8,4,-8,-4,-6,6,-6,6,6,-6,6,-6,-6,6,6,-6,-6,6,6,-6,-4,-2,-4,-2,4,2,4,2,-4,-2,4,2,-4,-2,4,2,-3,3,-3,3,-3,3,-3,3,-2,-1,-2,-1,-2,-1,-2,-1 },
+        { 8,-8,-8,8,-8,8,8,-8,4,4,-4,-4,-4,-4,4,4,4,-4,4,-4,-4,4,-4,4,4,-4,-4,4,4,-4,-4,4,2,2,2,2,-2,-2,-2,-2,2,2,-2,-2,2,2,-2,-2,2,-2,2,-2,2,-2,2,-2,1,1,1,1,1,1,1,1 }
+    };
+
+    /** Samples x-coordinates */
+    private final double[] xval;
+    /** Samples y-coordinates */
+    private final double[] yval;
+    /** Samples z-coordinates */
+    private final double[] zval;
+    /** Set of cubic splines patching the whole data grid */
+    private final TricubicFunction[][][] splines;
+
+    /**
+     * @param x Sample values of the x-coordinate, in increasing order.
+     * @param y Sample values of the y-coordinate, in increasing order.
+     * @param z Sample values of the y-coordinate, in increasing order.
+     * @param f Values of the function on every grid point.
+     * @param dFdX Values of the partial derivative of function with respect to x on every grid point.
+     * @param dFdY Values of the partial derivative of function with respect to y on every grid point.
+     * @param dFdZ Values of the partial derivative of function with respect to z on every grid point.
+     * @param d2FdXdY Values of the cross partial derivative of function on every grid point.
+     * @param d2FdXdZ Values of the cross partial derivative of function on every grid point.
+     * @param d2FdYdZ Values of the cross partial derivative of function on every grid point.
+     * @param d3FdXdYdZ Values of the cross partial derivative of function on every grid point.
+     * @throws NoDataException if any of the arrays has zero length.
+     * @throws DimensionMismatchException if the various arrays do not contain the expected number of elements.
+     * @throws NonMonotonicSequenceException if {@code x}, {@code y} or {@code z} are not strictly increasing.
+     */
+    public TricubicInterpolatingFunction(double[] x,
+                                         double[] y,
+                                         double[] z,
+                                         double[][][] f,
+                                         double[][][] dFdX,
+                                         double[][][] dFdY,
+                                         double[][][] dFdZ,
+                                         double[][][] d2FdXdY,
+                                         double[][][] d2FdXdZ,
+                                         double[][][] d2FdYdZ,
+                                         double[][][] d3FdXdYdZ)
+        throws NoDataException,
+               DimensionMismatchException,
+               NonMonotonicSequenceException {
+        final int xLen = x.length;
+        final int yLen = y.length;
+        final int zLen = z.length;
+
+        if (xLen == 0 || yLen == 0 || z.length == 0 || f.length == 0 || f[0].length == 0) {
+            throw new NoDataException();
+        }
+        if (xLen != f.length) {
+            throw new DimensionMismatchException(xLen, f.length);
+        }
+        if (xLen != dFdX.length) {
+            throw new DimensionMismatchException(xLen, dFdX.length);
+        }
+        if (xLen != dFdY.length) {
+            throw new DimensionMismatchException(xLen, dFdY.length);
+        }
+        if (xLen != dFdZ.length) {
+            throw new DimensionMismatchException(xLen, dFdZ.length);
+        }
+        if (xLen != d2FdXdY.length) {
+            throw new DimensionMismatchException(xLen, d2FdXdY.length);
+        }
+        if (xLen != d2FdXdZ.length) {
+            throw new DimensionMismatchException(xLen, d2FdXdZ.length);
+        }
+        if (xLen != d2FdYdZ.length) {
+            throw new DimensionMismatchException(xLen, d2FdYdZ.length);
+        }
+        if (xLen != d3FdXdYdZ.length) {
+            throw new DimensionMismatchException(xLen, d3FdXdYdZ.length);
+        }
+
+        MathArrays.checkOrder(x);
+        MathArrays.checkOrder(y);
+        MathArrays.checkOrder(z);
+
+        xval = x.clone();
+        yval = y.clone();
+        zval = z.clone();
+
+        final int lastI = xLen - 1;
+        final int lastJ = yLen - 1;
+        final int lastK = zLen - 1;
+        splines = new TricubicFunction[lastI][lastJ][lastK];
+
+        for (int i = 0; i < lastI; i++) {
+            if (f[i].length != yLen) {
+                throw new DimensionMismatchException(f[i].length, yLen);
+            }
+            if (dFdX[i].length != yLen) {
+                throw new DimensionMismatchException(dFdX[i].length, yLen);
+            }
+            if (dFdY[i].length != yLen) {
+                throw new DimensionMismatchException(dFdY[i].length, yLen);
+            }
+            if (dFdZ[i].length != yLen) {
+                throw new DimensionMismatchException(dFdZ[i].length, yLen);
+            }
+            if (d2FdXdY[i].length != yLen) {
+                throw new DimensionMismatchException(d2FdXdY[i].length, yLen);
+            }
+            if (d2FdXdZ[i].length != yLen) {
+                throw new DimensionMismatchException(d2FdXdZ[i].length, yLen);
+            }
+            if (d2FdYdZ[i].length != yLen) {
+                throw new DimensionMismatchException(d2FdYdZ[i].length, yLen);
+            }
+            if (d3FdXdYdZ[i].length != yLen) {
+                throw new DimensionMismatchException(d3FdXdYdZ[i].length, yLen);
+            }
+
+            final int ip1 = i + 1;
+            final double xR = xval[ip1] - xval[i];
+            for (int j = 0; j < lastJ; j++) {
+                if (f[i][j].length != zLen) {
+                    throw new DimensionMismatchException(f[i][j].length, zLen);
+                }
+                if (dFdX[i][j].length != zLen) {
+                    throw new DimensionMismatchException(dFdX[i][j].length, zLen);
+                }
+                if (dFdY[i][j].length != zLen) {
+                    throw new DimensionMismatchException(dFdY[i][j].length, zLen);
+                }
+                if (dFdZ[i][j].length != zLen) {
+                    throw new DimensionMismatchException(dFdZ[i][j].length, zLen);
+                }
+                if (d2FdXdY[i][j].length != zLen) {
+                    throw new DimensionMismatchException(d2FdXdY[i][j].length, zLen);
+                }
+                if (d2FdXdZ[i][j].length != zLen) {
+                    throw new DimensionMismatchException(d2FdXdZ[i][j].length, zLen);
+                }
+                if (d2FdYdZ[i][j].length != zLen) {
+                    throw new DimensionMismatchException(d2FdYdZ[i][j].length, zLen);
+                }
+                if (d3FdXdYdZ[i][j].length != zLen) {
+                    throw new DimensionMismatchException(d3FdXdYdZ[i][j].length, zLen);
+                }
+
+                final int jp1 = j + 1;
+                final double yR = yval[jp1] - yval[j];
+                final double xRyR = xR * yR;
+                for (int k = 0; k < lastK; k++) {
+                    final int kp1 = k + 1;
+                    final double zR = zval[kp1] - zval[k];
+                    final double xRzR = xR * zR;
+                    final double yRzR = yR * zR;
+                    final double xRyRzR = xR * yRzR;
+
+                    final double[] beta = new double[] {
+                        f[i][j][k], f[ip1][j][k],
+                        f[i][jp1][k], f[ip1][jp1][k],
+                        f[i][j][kp1], f[ip1][j][kp1],
+                        f[i][jp1][kp1], f[ip1][jp1][kp1],
+
+                        dFdX[i][j][k] * xR, dFdX[ip1][j][k] * xR,
+                        dFdX[i][jp1][k] * xR, dFdX[ip1][jp1][k] * xR,
+                        dFdX[i][j][kp1] * xR, dFdX[ip1][j][kp1] * xR,
+                        dFdX[i][jp1][kp1] * xR, dFdX[ip1][jp1][kp1] * xR,
+
+                        dFdY[i][j][k] * yR, dFdY[ip1][j][k] * yR,
+                        dFdY[i][jp1][k] * yR, dFdY[ip1][jp1][k] * yR,
+                        dFdY[i][j][kp1] * yR, dFdY[ip1][j][kp1] * yR,
+                        dFdY[i][jp1][kp1] * yR, dFdY[ip1][jp1][kp1] * yR,
+
+                        dFdZ[i][j][k] * zR, dFdZ[ip1][j][k] * zR,
+                        dFdZ[i][jp1][k] * zR, dFdZ[ip1][jp1][k] * zR,
+                        dFdZ[i][j][kp1] * zR, dFdZ[ip1][j][kp1] * zR,
+                        dFdZ[i][jp1][kp1] * zR, dFdZ[ip1][jp1][kp1] * zR,
+
+                        d2FdXdY[i][j][k] * xRyR, d2FdXdY[ip1][j][k] * xRyR,
+                        d2FdXdY[i][jp1][k] * xRyR, d2FdXdY[ip1][jp1][k] * xRyR,
+                        d2FdXdY[i][j][kp1] * xRyR, d2FdXdY[ip1][j][kp1] * xRyR,
+                        d2FdXdY[i][jp1][kp1] * xRyR, d2FdXdY[ip1][jp1][kp1] * xRyR,
+
+                        d2FdXdZ[i][j][k] * xRzR, d2FdXdZ[ip1][j][k] * xRzR,
+                        d2FdXdZ[i][jp1][k] * xRzR, d2FdXdZ[ip1][jp1][k] * xRzR,
+                        d2FdXdZ[i][j][kp1] * xRzR, d2FdXdZ[ip1][j][kp1] * xRzR,
+                        d2FdXdZ[i][jp1][kp1] * xRzR, d2FdXdZ[ip1][jp1][kp1] * xRzR,
+
+                        d2FdYdZ[i][j][k] * yRzR, d2FdYdZ[ip1][j][k] * yRzR,
+                        d2FdYdZ[i][jp1][k] * yRzR, d2FdYdZ[ip1][jp1][k] * yRzR,
+                        d2FdYdZ[i][j][kp1] * yRzR, d2FdYdZ[ip1][j][kp1] * yRzR,
+                        d2FdYdZ[i][jp1][kp1] * yRzR, d2FdYdZ[ip1][jp1][kp1] * yRzR,
+
+                        d3FdXdYdZ[i][j][k] * xRyRzR, d3FdXdYdZ[ip1][j][k] * xRyRzR,
+                        d3FdXdYdZ[i][jp1][k] * xRyRzR, d3FdXdYdZ[ip1][jp1][k] * xRyRzR,
+                        d3FdXdYdZ[i][j][kp1] * xRyRzR, d3FdXdYdZ[ip1][j][kp1] * xRyRzR,
+                        d3FdXdYdZ[i][jp1][kp1] * xRyRzR, d3FdXdYdZ[ip1][jp1][kp1] * xRyRzR,
+                    };
+
+                    splines[i][j][k] = new TricubicFunction(computeCoefficients(beta));
+                }
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws OutOfRangeException if any of the variables is outside its interpolation range.
+     */
+    public double value(double x, double y, double z)
+        throws OutOfRangeException {
+        final int i = searchIndex(x, xval);
+        if (i == -1) {
+            throw new OutOfRangeException(x, xval[0], xval[xval.length - 1]);
+        }
+        final int j = searchIndex(y, yval);
+        if (j == -1) {
+            throw new OutOfRangeException(y, yval[0], yval[yval.length - 1]);
+        }
+        final int k = searchIndex(z, zval);
+        if (k == -1) {
+            throw new OutOfRangeException(z, zval[0], zval[zval.length - 1]);
+        }
+
+        final double xN = (x - xval[i]) / (xval[i + 1] - xval[i]);
+        final double yN = (y - yval[j]) / (yval[j + 1] - yval[j]);
+        final double zN = (z - zval[k]) / (zval[k + 1] - zval[k]);
+
+        return splines[i][j][k].value(xN, yN, zN);
+    }
+
+    /**
+     * Indicates whether a point is within the interpolation range.
+     *
+     * @param x First coordinate.
+     * @param y Second coordinate.
+     * @param z Third coordinate.
+     * @return {@code true} if (x, y, z) is a valid point.
+     */
+    public boolean isValidPoint(double x, double y, double z) {
+        if (x < xval[0] ||
+            x > xval[xval.length - 1] ||
+            y < yval[0] ||
+            y > yval[yval.length - 1] ||
+            z < zval[0] ||
+            z > zval[zval.length - 1]) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * @param c Coordinate.
+     * @param val Coordinate samples.
+     * @return the index in {@code val} corresponding to the interval containing {@code c}, or {@code -1}
+     *   if {@code c} is out of the range defined by the end values of {@code val}.
+     */
+    private int searchIndex(double c, double[] val) {
+        if (c < val[0]) {
+            return -1;
+        }
+
+        final int max = val.length;
+        for (int i = 1; i < max; i++) {
+            if (c <= val[i]) {
+                return i - 1;
+            }
+        }
+
+        return -1;
+    }
+
+    /**
+     * Compute the spline coefficients from the list of function values and
+     * function partial derivatives values at the four corners of a grid
+     * element. They must be specified in the following order:
+     * <ul>
+     *  <li>f(0,0,0)</li>
+     *  <li>f(1,0,0)</li>
+     *  <li>f(0,1,0)</li>
+     *  <li>f(1,1,0)</li>
+     *  <li>f(0,0,1)</li>
+     *  <li>f(1,0,1)</li>
+     *  <li>f(0,1,1)</li>
+     *  <li>f(1,1,1)</li>
+     *
+     *  <li>f<sub>x</sub>(0,0,0)</li>
+     *  <li>... <em>(same order as above)</em></li>
+     *  <li>f<sub>x</sub>(1,1,1)</li>
+     *
+     *  <li>f<sub>y</sub>(0,0,0)</li>
+     *  <li>... <em>(same order as above)</em></li>
+     *  <li>f<sub>y</sub>(1,1,1)</li>
+     *
+     *  <li>f<sub>z</sub>(0,0,0)</li>
+     *  <li>... <em>(same order as above)</em></li>
+     *  <li>f<sub>z</sub>(1,1,1)</li>
+     *
+     *  <li>f<sub>xy</sub>(0,0,0)</li>
+     *  <li>... <em>(same order as above)</em></li>
+     *  <li>f<sub>xy</sub>(1,1,1)</li>
+     *
+     *  <li>f<sub>xz</sub>(0,0,0)</li>
+     *  <li>... <em>(same order as above)</em></li>
+     *  <li>f<sub>xz</sub>(1,1,1)</li>
+     *
+     *  <li>f<sub>yz</sub>(0,0,0)</li>
+     *  <li>... <em>(same order as above)</em></li>
+     *  <li>f<sub>yz</sub>(1,1,1)</li>
+     *
+     *  <li>f<sub>xyz</sub>(0,0,0)</li>
+     *  <li>... <em>(same order as above)</em></li>
+     *  <li>f<sub>xyz</sub>(1,1,1)</li>
+     * </ul>
+     * where the subscripts indicate the partial derivative with respect to
+     * the corresponding variable(s).
+     *
+     * @param beta List of function values and function partial derivatives values.
+     * @return the spline coefficients.
+     */
+    private double[] computeCoefficients(double[] beta) {
+        final int sz = 64;
+        final double[] a = new double[sz];
+
+        for (int i = 0; i < sz; i++) {
+            double result = 0;
+            final double[] row = AINV[i];
+            for (int j = 0; j < sz; j++) {
+                result += row[j] * beta[j];
+            }
+            a[i] = result;
+        }
+
+        return a;
+    }
+}
+
+/**
+ * 3D-spline function.
+ *
+ */
+class TricubicFunction
+    implements TrivariateFunction {
+    /** Number of points. */
+    private static final short N = 4;
+    /** Coefficients */
+    private final double[][][] a = new double[N][N][N];
+
+    /**
+     * @param aV List of spline coefficients.
+     */
+    TricubicFunction(double[] aV) {
+        for (int i = 0; i < N; i++) {
+            for (int j = 0; j < N; j++) {
+                for (int k = 0; k < N; k++) {
+                    a[i][j][k] = aV[i + N * (j + N * k)];
+                }
+            }
+        }
+    }
+
+    /**
+     * @param x x-coordinate of the interpolation point.
+     * @param y y-coordinate of the interpolation point.
+     * @param z z-coordinate of the interpolation point.
+     * @return the interpolated value.
+     * @throws OutOfRangeException if {@code x}, {@code y} or
+     * {@code z} are not in the interval {@code [0, 1]}.
+     */
+    public double value(double x, double y, double z)
+        throws OutOfRangeException {
+        if (x < 0 || x > 1) {
+            throw new OutOfRangeException(x, 0, 1);
+        }
+        if (y < 0 || y > 1) {
+            throw new OutOfRangeException(y, 0, 1);
+        }
+        if (z < 0 || z > 1) {
+            throw new OutOfRangeException(z, 0, 1);
+        }
+
+        final double x2 = x * x;
+        final double x3 = x2 * x;
+        final double[] pX = { 1, x, x2, x3 };
+
+        final double y2 = y * y;
+        final double y3 = y2 * y;
+        final double[] pY = { 1, y, y2, y3 };
+
+        final double z2 = z * z;
+        final double z3 = z2 * z;
+        final double[] pZ = { 1, z, z2, z3 };
+
+        double result = 0;
+        for (int i = 0; i < N; i++) {
+            for (int j = 0; j < N; j++) {
+                for (int k = 0; k < N; k++) {
+                    result += a[i][j][k] * pX[i] * pY[j] * pZ[k];
+                }
+            }
+        }
+
+        return result;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/interpolation/TricubicInterpolator.java b/src/main/java/org/apache/commons/math3/analysis/interpolation/TricubicInterpolator.java
new file mode 100644
index 0000000..ec9e3bb
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/interpolation/TricubicInterpolator.java
@@ -0,0 +1,143 @@
+/*
+ * 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.math3.analysis.interpolation;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NonMonotonicSequenceException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * Generates a tricubic interpolating function.
+ *
+ * @since 3.4
+ */
+public class TricubicInterpolator
+    implements TrivariateGridInterpolator {
+    /**
+     * {@inheritDoc}
+     */
+    public TricubicInterpolatingFunction interpolate(final double[] xval,
+                                                     final double[] yval,
+                                                     final double[] zval,
+                                                     final double[][][] fval)
+        throws NoDataException, NumberIsTooSmallException,
+               DimensionMismatchException, NonMonotonicSequenceException {
+        if (xval.length == 0 || yval.length == 0 || zval.length == 0 || fval.length == 0) {
+            throw new NoDataException();
+        }
+        if (xval.length != fval.length) {
+            throw new DimensionMismatchException(xval.length, fval.length);
+        }
+
+        MathArrays.checkOrder(xval);
+        MathArrays.checkOrder(yval);
+        MathArrays.checkOrder(zval);
+
+        final int xLen = xval.length;
+        final int yLen = yval.length;
+        final int zLen = zval.length;
+
+        // Approximation to the partial derivatives using finite differences.
+        final double[][][] dFdX = new double[xLen][yLen][zLen];
+        final double[][][] dFdY = new double[xLen][yLen][zLen];
+        final double[][][] dFdZ = new double[xLen][yLen][zLen];
+        final double[][][] d2FdXdY = new double[xLen][yLen][zLen];
+        final double[][][] d2FdXdZ = new double[xLen][yLen][zLen];
+        final double[][][] d2FdYdZ = new double[xLen][yLen][zLen];
+        final double[][][] d3FdXdYdZ = new double[xLen][yLen][zLen];
+
+        for (int i = 1; i < xLen - 1; i++) {
+            if (yval.length != fval[i].length) {
+                throw new DimensionMismatchException(yval.length, fval[i].length);
+            }
+
+            final int nI = i + 1;
+            final int pI = i - 1;
+
+            final double nX = xval[nI];
+            final double pX = xval[pI];
+
+            final double deltaX = nX - pX;
+
+            for (int j = 1; j < yLen - 1; j++) {
+                if (zval.length != fval[i][j].length) {
+                    throw new DimensionMismatchException(zval.length, fval[i][j].length);
+                }
+
+                final int nJ = j + 1;
+                final int pJ = j - 1;
+
+                final double nY = yval[nJ];
+                final double pY = yval[pJ];
+
+                final double deltaY = nY - pY;
+                final double deltaXY = deltaX * deltaY;
+
+                for (int k = 1; k < zLen - 1; k++) {
+                    final int nK = k + 1;
+                    final int pK = k - 1;
+
+                    final double nZ = zval[nK];
+                    final double pZ = zval[pK];
+
+                    final double deltaZ = nZ - pZ;
+
+                    dFdX[i][j][k] = (fval[nI][j][k] - fval[pI][j][k]) / deltaX;
+                    dFdY[i][j][k] = (fval[i][nJ][k] - fval[i][pJ][k]) / deltaY;
+                    dFdZ[i][j][k] = (fval[i][j][nK] - fval[i][j][pK]) / deltaZ;
+
+                    final double deltaXZ = deltaX * deltaZ;
+                    final double deltaYZ = deltaY * deltaZ;
+
+                    d2FdXdY[i][j][k] = (fval[nI][nJ][k] - fval[nI][pJ][k] - fval[pI][nJ][k] + fval[pI][pJ][k]) / deltaXY;
+                    d2FdXdZ[i][j][k] = (fval[nI][j][nK] - fval[nI][j][pK] - fval[pI][j][nK] + fval[pI][j][pK]) / deltaXZ;
+                    d2FdYdZ[i][j][k] = (fval[i][nJ][nK] - fval[i][nJ][pK] - fval[i][pJ][nK] + fval[i][pJ][pK]) / deltaYZ;
+
+                    final double deltaXYZ = deltaXY * deltaZ;
+
+                    d3FdXdYdZ[i][j][k] = (fval[nI][nJ][nK] - fval[nI][pJ][nK] -
+                                          fval[pI][nJ][nK] + fval[pI][pJ][nK] -
+                                          fval[nI][nJ][pK] + fval[nI][pJ][pK] +
+                                          fval[pI][nJ][pK] - fval[pI][pJ][pK]) / deltaXYZ;
+                }
+            }
+        }
+
+        // Create the interpolating function.
+        return new TricubicInterpolatingFunction(xval, yval, zval, fval,
+                                                 dFdX, dFdY, dFdZ,
+                                                 d2FdXdY, d2FdXdZ, d2FdYdZ,
+                                                 d3FdXdYdZ) {
+            /** {@inheritDoc} */
+            @Override
+            public boolean isValidPoint(double x, double y, double z) {
+                if (x < xval[1] ||
+                    x > xval[xval.length - 2] ||
+                    y < yval[1] ||
+                    y > yval[yval.length - 2] ||
+                    z < zval[1] ||
+                    z > zval[zval.length - 2]) {
+                    return false;
+                } else {
+                    return true;
+                }
+            }
+        };
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/interpolation/TricubicSplineInterpolatingFunction.java b/src/main/java/org/apache/commons/math3/analysis/interpolation/TricubicSplineInterpolatingFunction.java
new file mode 100644
index 0000000..96aebd3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/interpolation/TricubicSplineInterpolatingFunction.java
@@ -0,0 +1,482 @@
+/*
+ * 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.math3.analysis.interpolation;
+
+import org.apache.commons.math3.analysis.TrivariateFunction;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NonMonotonicSequenceException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * Function that implements the
+ * <a href="http://en.wikipedia.org/wiki/Tricubic_interpolation">
+ * tricubic spline interpolation</a>, as proposed in
+ * <blockquote>
+ *  Tricubic interpolation in three dimensions,
+ *  F. Lekien and J. Marsden,
+ *  <em>Int. J. Numer. Meth. Engng</em> 2005; <b>63</b>:455-471
+ * </blockquote>
+ *
+ * @since 2.2
+ * @deprecated To be removed in 4.0 (see MATH-1166).
+ */
+@Deprecated
+public class TricubicSplineInterpolatingFunction
+    implements TrivariateFunction {
+    /**
+     * Matrix to compute the spline coefficients from the function values
+     * and function derivatives values
+     */
+    private static final double[][] AINV = {
+        { 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { -3,3,0,0,0,0,0,0,-2,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 2,-2,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,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,0,0,0,0,0,0,0,0,-2,0,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 9,-9,-9,9,0,0,0,0,6,3,-6,-3,0,0,0,0,6,-6,3,-3,0,0,0,0,0,0,0,0,0,0,0,0,4,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { -6,6,6,-6,0,0,0,0,-3,-3,3,3,0,0,0,0,-4,4,-2,2,0,0,0,0,0,0,0,0,0,0,0,0,-2,-2,-1,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 2,0,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,2,0,-2,0,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { -6,6,6,-6,0,0,0,0,-4,-2,4,2,0,0,0,0,-3,3,-3,3,0,0,0,0,0,0,0,0,0,0,0,0,-2,-1,-2,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 4,-4,-4,4,0,0,0,0,2,2,-2,-2,0,0,0,0,2,-2,2,-2,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-3,3,0,0,0,0,0,0,-2,-1,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,-2,0,0,0,0,0,0,1,1,0,0,0,0,0,0 },
+        { 0,0,0,0,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,-1,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,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,0,0,0,0,0,0,0,0,-2,0,-1,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,-9,-9,9,0,0,0,0,0,0,0,0,0,0,0,0,6,3,-6,-3,0,0,0,0,6,-6,3,-3,0,0,0,0,4,2,2,1,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-6,6,6,-6,0,0,0,0,0,0,0,0,0,0,0,0,-3,-3,3,3,0,0,0,0,-4,4,-2,2,0,0,0,0,-2,-2,-1,-1,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,-2,0,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,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-6,6,6,-6,0,0,0,0,0,0,0,0,0,0,0,0,-4,-2,4,2,0,0,0,0,-3,3,-3,3,0,0,0,0,-2,-1,-2,-1,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,-4,-4,4,0,0,0,0,0,0,0,0,0,0,0,0,2,2,-2,-2,0,0,0,0,2,-2,2,-2,0,0,0,0,1,1,1,1,0,0,0,0 },
+        {-3,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,0,0,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,-3,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,0,0,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 9,-9,0,0,-9,9,0,0,6,3,0,0,-6,-3,0,0,0,0,0,0,0,0,0,0,6,-6,0,0,3,-3,0,0,0,0,0,0,0,0,0,0,4,2,0,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { -6,6,0,0,6,-6,0,0,-3,-3,0,0,3,3,0,0,0,0,0,0,0,0,0,0,-4,4,0,0,-2,2,0,0,0,0,0,0,0,0,0,0,-2,-2,0,0,-1,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-3,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,0,0,-1,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-3,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,0,0,-1,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,-9,0,0,-9,9,0,0,0,0,0,0,0,0,0,0,6,3,0,0,-6,-3,0,0,0,0,0,0,0,0,0,0,6,-6,0,0,3,-3,0,0,4,2,0,0,2,1,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-6,6,0,0,6,-6,0,0,0,0,0,0,0,0,0,0,-3,-3,0,0,3,3,0,0,0,0,0,0,0,0,0,0,-4,4,0,0,-2,2,0,0,-2,-2,0,0,-1,-1,0,0 },
+        { 9,0,-9,0,-9,0,9,0,0,0,0,0,0,0,0,0,6,0,3,0,-6,0,-3,0,6,0,-6,0,3,0,-3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,2,0,2,0,1,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,9,0,-9,0,-9,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,0,3,0,-6,0,-3,0,6,0,-6,0,3,0,-3,0,0,0,0,0,0,0,0,0,4,0,2,0,2,0,1,0 },
+        { -27,27,27,-27,27,-27,-27,27,-18,-9,18,9,18,9,-18,-9,-18,18,-9,9,18,-18,9,-9,-18,18,18,-18,-9,9,9,-9,-12,-6,-6,-3,12,6,6,3,-12,-6,12,6,-6,-3,6,3,-12,12,-6,6,-6,6,-3,3,-8,-4,-4,-2,-4,-2,-2,-1 },
+        { 18,-18,-18,18,-18,18,18,-18,9,9,-9,-9,-9,-9,9,9,12,-12,6,-6,-12,12,-6,6,12,-12,-12,12,6,-6,-6,6,6,6,3,3,-6,-6,-3,-3,6,6,-6,-6,3,3,-3,-3,8,-8,4,-4,4,-4,2,-2,4,4,2,2,2,2,1,1 },
+        { -6,0,6,0,6,0,-6,0,0,0,0,0,0,0,0,0,-3,0,-3,0,3,0,3,0,-4,0,4,0,-2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,-2,0,-1,0,-1,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,-6,0,6,0,6,0,-6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-3,0,-3,0,3,0,3,0,-4,0,4,0,-2,0,2,0,0,0,0,0,0,0,0,0,-2,0,-2,0,-1,0,-1,0 },
+        { 18,-18,-18,18,-18,18,18,-18,12,6,-12,-6,-12,-6,12,6,9,-9,9,-9,-9,9,-9,9,12,-12,-12,12,6,-6,-6,6,6,3,6,3,-6,-3,-6,-3,8,4,-8,-4,4,2,-4,-2,6,-6,6,-6,3,-3,3,-3,4,2,4,2,2,1,2,1 },
+        { -12,12,12,-12,12,-12,-12,12,-6,-6,6,6,6,6,-6,-6,-6,6,-6,6,6,-6,6,-6,-8,8,8,-8,-4,4,4,-4,-3,-3,-3,-3,3,3,3,3,-4,-4,4,4,-2,-2,2,2,-4,4,-4,4,-2,2,-2,2,-2,-2,-2,-2,-1,-1,-1,-1 },
+        { 2,0,0,0,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,2,0,0,0,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { -6,6,0,0,6,-6,0,0,-4,-2,0,0,4,2,0,0,0,0,0,0,0,0,0,0,-3,3,0,0,-3,3,0,0,0,0,0,0,0,0,0,0,-2,-1,0,0,-2,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 4,-4,0,0,-4,4,0,0,2,2,0,0,-2,-2,0,0,0,0,0,0,0,0,0,0,2,-2,0,0,2,-2,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-6,6,0,0,6,-6,0,0,0,0,0,0,0,0,0,0,-4,-2,0,0,4,2,0,0,0,0,0,0,0,0,0,0,-3,3,0,0,-3,3,0,0,-2,-1,0,0,-2,-1,0,0 },
+        { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,-4,0,0,-4,4,0,0,0,0,0,0,0,0,0,0,2,2,0,0,-2,-2,0,0,0,0,0,0,0,0,0,0,2,-2,0,0,2,-2,0,0,1,1,0,0,1,1,0,0 },
+        { -6,0,6,0,6,0,-6,0,0,0,0,0,0,0,0,0,-4,0,-2,0,4,0,2,0,-3,0,3,0,-3,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,0,-1,0,-2,0,-1,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,-6,0,6,0,6,0,-6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-4,0,-2,0,4,0,2,0,-3,0,3,0,-3,0,3,0,0,0,0,0,0,0,0,0,-2,0,-1,0,-2,0,-1,0 },
+        { 18,-18,-18,18,-18,18,18,-18,12,6,-12,-6,-12,-6,12,6,12,-12,6,-6,-12,12,-6,6,9,-9,-9,9,9,-9,-9,9,8,4,4,2,-8,-4,-4,-2,6,3,-6,-3,6,3,-6,-3,6,-6,3,-3,6,-6,3,-3,4,2,2,1,4,2,2,1 },
+        { -12,12,12,-12,12,-12,-12,12,-6,-6,6,6,6,6,-6,-6,-8,8,-4,4,8,-8,4,-4,-6,6,6,-6,-6,6,6,-6,-4,-4,-2,-2,4,4,2,2,-3,-3,3,3,-3,-3,3,3,-4,4,-2,2,-4,4,-2,2,-2,-2,-1,-1,-2,-2,-1,-1 },
+        { 4,0,-4,0,-4,0,4,0,0,0,0,0,0,0,0,0,2,0,2,0,-2,0,-2,0,2,0,-2,0,2,0,-2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,1,0,0,0,0,0,0,0,0,0 },
+        { 0,0,0,0,0,0,0,0,4,0,-4,0,-4,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,2,0,-2,0,-2,0,2,0,-2,0,2,0,-2,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,1,0 },
+        { -12,12,12,-12,12,-12,-12,12,-8,-4,8,4,8,4,-8,-4,-6,6,-6,6,6,-6,6,-6,-6,6,6,-6,-6,6,6,-6,-4,-2,-4,-2,4,2,4,2,-4,-2,4,2,-4,-2,4,2,-3,3,-3,3,-3,3,-3,3,-2,-1,-2,-1,-2,-1,-2,-1 },
+        { 8,-8,-8,8,-8,8,8,-8,4,4,-4,-4,-4,-4,4,4,4,-4,4,-4,-4,4,-4,4,4,-4,-4,4,4,-4,-4,4,2,2,2,2,-2,-2,-2,-2,2,2,-2,-2,2,2,-2,-2,2,-2,2,-2,2,-2,2,-2,1,1,1,1,1,1,1,1 }
+    };
+
+    /** Samples x-coordinates */
+    private final double[] xval;
+    /** Samples y-coordinates */
+    private final double[] yval;
+    /** Samples z-coordinates */
+    private final double[] zval;
+    /** Set of cubic splines pacthing the whole data grid */
+    private final TricubicSplineFunction[][][] splines;
+
+    /**
+     * @param x Sample values of the x-coordinate, in increasing order.
+     * @param y Sample values of the y-coordinate, in increasing order.
+     * @param z Sample values of the y-coordinate, in increasing order.
+     * @param f Values of the function on every grid point.
+     * @param dFdX Values of the partial derivative of function with respect to x on every grid point.
+     * @param dFdY Values of the partial derivative of function with respect to y on every grid point.
+     * @param dFdZ Values of the partial derivative of function with respect to z on every grid point.
+     * @param d2FdXdY Values of the cross partial derivative of function on every grid point.
+     * @param d2FdXdZ Values of the cross partial derivative of function on every grid point.
+     * @param d2FdYdZ Values of the cross partial derivative of function on every grid point.
+     * @param d3FdXdYdZ Values of the cross partial derivative of function on every grid point.
+     * @throws NoDataException if any of the arrays has zero length.
+     * @throws DimensionMismatchException if the various arrays do not contain the expected number of elements.
+     * @throws NonMonotonicSequenceException if {@code x}, {@code y} or {@code z} are not strictly increasing.
+     */
+    public TricubicSplineInterpolatingFunction(double[] x,
+                                               double[] y,
+                                               double[] z,
+                                               double[][][] f,
+                                               double[][][] dFdX,
+                                               double[][][] dFdY,
+                                               double[][][] dFdZ,
+                                               double[][][] d2FdXdY,
+                                               double[][][] d2FdXdZ,
+                                               double[][][] d2FdYdZ,
+                                               double[][][] d3FdXdYdZ)
+        throws NoDataException,
+               DimensionMismatchException,
+               NonMonotonicSequenceException {
+        final int xLen = x.length;
+        final int yLen = y.length;
+        final int zLen = z.length;
+
+        if (xLen == 0 || yLen == 0 || z.length == 0 || f.length == 0 || f[0].length == 0) {
+            throw new NoDataException();
+        }
+        if (xLen != f.length) {
+            throw new DimensionMismatchException(xLen, f.length);
+        }
+        if (xLen != dFdX.length) {
+            throw new DimensionMismatchException(xLen, dFdX.length);
+        }
+        if (xLen != dFdY.length) {
+            throw new DimensionMismatchException(xLen, dFdY.length);
+        }
+        if (xLen != dFdZ.length) {
+            throw new DimensionMismatchException(xLen, dFdZ.length);
+        }
+        if (xLen != d2FdXdY.length) {
+            throw new DimensionMismatchException(xLen, d2FdXdY.length);
+        }
+        if (xLen != d2FdXdZ.length) {
+            throw new DimensionMismatchException(xLen, d2FdXdZ.length);
+        }
+        if (xLen != d2FdYdZ.length) {
+            throw new DimensionMismatchException(xLen, d2FdYdZ.length);
+        }
+        if (xLen != d3FdXdYdZ.length) {
+            throw new DimensionMismatchException(xLen, d3FdXdYdZ.length);
+        }
+
+        MathArrays.checkOrder(x);
+        MathArrays.checkOrder(y);
+        MathArrays.checkOrder(z);
+
+        xval = x.clone();
+        yval = y.clone();
+        zval = z.clone();
+
+        final int lastI = xLen - 1;
+        final int lastJ = yLen - 1;
+        final int lastK = zLen - 1;
+        splines = new TricubicSplineFunction[lastI][lastJ][lastK];
+
+        for (int i = 0; i < lastI; i++) {
+            if (f[i].length != yLen) {
+                throw new DimensionMismatchException(f[i].length, yLen);
+            }
+            if (dFdX[i].length != yLen) {
+                throw new DimensionMismatchException(dFdX[i].length, yLen);
+            }
+            if (dFdY[i].length != yLen) {
+                throw new DimensionMismatchException(dFdY[i].length, yLen);
+            }
+            if (dFdZ[i].length != yLen) {
+                throw new DimensionMismatchException(dFdZ[i].length, yLen);
+            }
+            if (d2FdXdY[i].length != yLen) {
+                throw new DimensionMismatchException(d2FdXdY[i].length, yLen);
+            }
+            if (d2FdXdZ[i].length != yLen) {
+                throw new DimensionMismatchException(d2FdXdZ[i].length, yLen);
+            }
+            if (d2FdYdZ[i].length != yLen) {
+                throw new DimensionMismatchException(d2FdYdZ[i].length, yLen);
+            }
+            if (d3FdXdYdZ[i].length != yLen) {
+                throw new DimensionMismatchException(d3FdXdYdZ[i].length, yLen);
+            }
+
+            final int ip1 = i + 1;
+            for (int j = 0; j < lastJ; j++) {
+                if (f[i][j].length != zLen) {
+                    throw new DimensionMismatchException(f[i][j].length, zLen);
+                }
+                if (dFdX[i][j].length != zLen) {
+                    throw new DimensionMismatchException(dFdX[i][j].length, zLen);
+                }
+                if (dFdY[i][j].length != zLen) {
+                    throw new DimensionMismatchException(dFdY[i][j].length, zLen);
+                }
+                if (dFdZ[i][j].length != zLen) {
+                    throw new DimensionMismatchException(dFdZ[i][j].length, zLen);
+                }
+                if (d2FdXdY[i][j].length != zLen) {
+                    throw new DimensionMismatchException(d2FdXdY[i][j].length, zLen);
+                }
+                if (d2FdXdZ[i][j].length != zLen) {
+                    throw new DimensionMismatchException(d2FdXdZ[i][j].length, zLen);
+                }
+                if (d2FdYdZ[i][j].length != zLen) {
+                    throw new DimensionMismatchException(d2FdYdZ[i][j].length, zLen);
+                }
+                if (d3FdXdYdZ[i][j].length != zLen) {
+                    throw new DimensionMismatchException(d3FdXdYdZ[i][j].length, zLen);
+                }
+
+                final int jp1 = j + 1;
+                for (int k = 0; k < lastK; k++) {
+                    final int kp1 = k + 1;
+
+                    final double[] beta = new double[] {
+                        f[i][j][k], f[ip1][j][k],
+                        f[i][jp1][k], f[ip1][jp1][k],
+                        f[i][j][kp1], f[ip1][j][kp1],
+                        f[i][jp1][kp1], f[ip1][jp1][kp1],
+
+                        dFdX[i][j][k], dFdX[ip1][j][k],
+                        dFdX[i][jp1][k], dFdX[ip1][jp1][k],
+                        dFdX[i][j][kp1], dFdX[ip1][j][kp1],
+                        dFdX[i][jp1][kp1], dFdX[ip1][jp1][kp1],
+
+                        dFdY[i][j][k], dFdY[ip1][j][k],
+                        dFdY[i][jp1][k], dFdY[ip1][jp1][k],
+                        dFdY[i][j][kp1], dFdY[ip1][j][kp1],
+                        dFdY[i][jp1][kp1], dFdY[ip1][jp1][kp1],
+
+                        dFdZ[i][j][k], dFdZ[ip1][j][k],
+                        dFdZ[i][jp1][k], dFdZ[ip1][jp1][k],
+                        dFdZ[i][j][kp1], dFdZ[ip1][j][kp1],
+                        dFdZ[i][jp1][kp1], dFdZ[ip1][jp1][kp1],
+
+                        d2FdXdY[i][j][k], d2FdXdY[ip1][j][k],
+                        d2FdXdY[i][jp1][k], d2FdXdY[ip1][jp1][k],
+                        d2FdXdY[i][j][kp1], d2FdXdY[ip1][j][kp1],
+                        d2FdXdY[i][jp1][kp1], d2FdXdY[ip1][jp1][kp1],
+
+                        d2FdXdZ[i][j][k], d2FdXdZ[ip1][j][k],
+                        d2FdXdZ[i][jp1][k], d2FdXdZ[ip1][jp1][k],
+                        d2FdXdZ[i][j][kp1], d2FdXdZ[ip1][j][kp1],
+                        d2FdXdZ[i][jp1][kp1], d2FdXdZ[ip1][jp1][kp1],
+
+                        d2FdYdZ[i][j][k], d2FdYdZ[ip1][j][k],
+                        d2FdYdZ[i][jp1][k], d2FdYdZ[ip1][jp1][k],
+                        d2FdYdZ[i][j][kp1], d2FdYdZ[ip1][j][kp1],
+                        d2FdYdZ[i][jp1][kp1], d2FdYdZ[ip1][jp1][kp1],
+
+                        d3FdXdYdZ[i][j][k], d3FdXdYdZ[ip1][j][k],
+                        d3FdXdYdZ[i][jp1][k], d3FdXdYdZ[ip1][jp1][k],
+                        d3FdXdYdZ[i][j][kp1], d3FdXdYdZ[ip1][j][kp1],
+                        d3FdXdYdZ[i][jp1][kp1], d3FdXdYdZ[ip1][jp1][kp1],
+                    };
+
+                    splines[i][j][k] = new TricubicSplineFunction(computeSplineCoefficients(beta));
+                }
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws OutOfRangeException if any of the variables is outside its interpolation range.
+     */
+    public double value(double x, double y, double z)
+        throws OutOfRangeException {
+        final int i = searchIndex(x, xval);
+        if (i == -1) {
+            throw new OutOfRangeException(x, xval[0], xval[xval.length - 1]);
+        }
+        final int j = searchIndex(y, yval);
+        if (j == -1) {
+            throw new OutOfRangeException(y, yval[0], yval[yval.length - 1]);
+        }
+        final int k = searchIndex(z, zval);
+        if (k == -1) {
+            throw new OutOfRangeException(z, zval[0], zval[zval.length - 1]);
+        }
+
+        final double xN = (x - xval[i]) / (xval[i + 1] - xval[i]);
+        final double yN = (y - yval[j]) / (yval[j + 1] - yval[j]);
+        final double zN = (z - zval[k]) / (zval[k + 1] - zval[k]);
+
+        return splines[i][j][k].value(xN, yN, zN);
+    }
+
+    /**
+     * @param c Coordinate.
+     * @param val Coordinate samples.
+     * @return the index in {@code val} corresponding to the interval containing {@code c}, or {@code -1}
+     *   if {@code c} is out of the range defined by the end values of {@code val}.
+     */
+    private int searchIndex(double c, double[] val) {
+        if (c < val[0]) {
+            return -1;
+        }
+
+        final int max = val.length;
+        for (int i = 1; i < max; i++) {
+            if (c <= val[i]) {
+                return i - 1;
+            }
+        }
+
+        return -1;
+    }
+
+    /**
+     * Compute the spline coefficients from the list of function values and
+     * function partial derivatives values at the four corners of a grid
+     * element. They must be specified in the following order:
+     * <ul>
+     *  <li>f(0,0,0)</li>
+     *  <li>f(1,0,0)</li>
+     *  <li>f(0,1,0)</li>
+     *  <li>f(1,1,0)</li>
+     *  <li>f(0,0,1)</li>
+     *  <li>f(1,0,1)</li>
+     *  <li>f(0,1,1)</li>
+     *  <li>f(1,1,1)</li>
+     *
+     *  <li>f<sub>x</sub>(0,0,0)</li>
+     *  <li>... <em>(same order as above)</em></li>
+     *  <li>f<sub>x</sub>(1,1,1)</li>
+     *
+     *  <li>f<sub>y</sub>(0,0,0)</li>
+     *  <li>... <em>(same order as above)</em></li>
+     *  <li>f<sub>y</sub>(1,1,1)</li>
+     *
+     *  <li>f<sub>z</sub>(0,0,0)</li>
+     *  <li>... <em>(same order as above)</em></li>
+     *  <li>f<sub>z</sub>(1,1,1)</li>
+     *
+     *  <li>f<sub>xy</sub>(0,0,0)</li>
+     *  <li>... <em>(same order as above)</em></li>
+     *  <li>f<sub>xy</sub>(1,1,1)</li>
+     *
+     *  <li>f<sub>xz</sub>(0,0,0)</li>
+     *  <li>... <em>(same order as above)</em></li>
+     *  <li>f<sub>xz</sub>(1,1,1)</li>
+     *
+     *  <li>f<sub>yz</sub>(0,0,0)</li>
+     *  <li>... <em>(same order as above)</em></li>
+     *  <li>f<sub>yz</sub>(1,1,1)</li>
+     *
+     *  <li>f<sub>xyz</sub>(0,0,0)</li>
+     *  <li>... <em>(same order as above)</em></li>
+     *  <li>f<sub>xyz</sub>(1,1,1)</li>
+     * </ul>
+     * where the subscripts indicate the partial derivative with respect to
+     * the corresponding variable(s).
+     *
+     * @param beta List of function values and function partial derivatives values.
+     * @return the spline coefficients.
+     */
+    private double[] computeSplineCoefficients(double[] beta) {
+        final int sz = 64;
+        final double[] a = new double[sz];
+
+        for (int i = 0; i < sz; i++) {
+            double result = 0;
+            final double[] row = AINV[i];
+            for (int j = 0; j < sz; j++) {
+                result += row[j] * beta[j];
+            }
+            a[i] = result;
+        }
+
+        return a;
+    }
+}
+
+/**
+ * 3D-spline function.
+ *
+ */
+class TricubicSplineFunction
+    implements TrivariateFunction {
+    /** Number of points. */
+    private static final short N = 4;
+    /** Coefficients */
+    private final double[][][] a = new double[N][N][N];
+
+    /**
+     * @param aV List of spline coefficients.
+     */
+    TricubicSplineFunction(double[] aV) {
+        for (int i = 0; i < N; i++) {
+            for (int j = 0; j < N; j++) {
+                for (int k = 0; k < N; k++) {
+                    a[i][j][k] = aV[i + N * (j + N * k)];
+                }
+            }
+        }
+    }
+
+    /**
+     * @param x x-coordinate of the interpolation point.
+     * @param y y-coordinate of the interpolation point.
+     * @param z z-coordinate of the interpolation point.
+     * @return the interpolated value.
+     * @throws OutOfRangeException if {@code x}, {@code y} or
+     * {@code z} are not in the interval {@code [0, 1]}.
+     */
+    public double value(double x, double y, double z)
+        throws OutOfRangeException {
+        if (x < 0 || x > 1) {
+            throw new OutOfRangeException(x, 0, 1);
+        }
+        if (y < 0 || y > 1) {
+            throw new OutOfRangeException(y, 0, 1);
+        }
+        if (z < 0 || z > 1) {
+            throw new OutOfRangeException(z, 0, 1);
+        }
+
+        final double x2 = x * x;
+        final double x3 = x2 * x;
+        final double[] pX = { 1, x, x2, x3 };
+
+        final double y2 = y * y;
+        final double y3 = y2 * y;
+        final double[] pY = { 1, y, y2, y3 };
+
+        final double z2 = z * z;
+        final double z3 = z2 * z;
+        final double[] pZ = { 1, z, z2, z3 };
+
+        double result = 0;
+        for (int i = 0; i < N; i++) {
+            for (int j = 0; j < N; j++) {
+                for (int k = 0; k < N; k++) {
+                    result += a[i][j][k] * pX[i] * pY[j] * pZ[k];
+                }
+            }
+        }
+
+        return result;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/interpolation/TricubicSplineInterpolator.java b/src/main/java/org/apache/commons/math3/analysis/interpolation/TricubicSplineInterpolator.java
new file mode 100644
index 0000000..7f43e6f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/interpolation/TricubicSplineInterpolator.java
@@ -0,0 +1,201 @@
+/*
+ * 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.math3.analysis.interpolation;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NonMonotonicSequenceException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * Generates a tricubic interpolating function.
+ *
+ * @since 2.2
+ * @deprecated To be removed in 4.0 (see MATH-1166).
+ */
+@Deprecated
+public class TricubicSplineInterpolator
+    implements TrivariateGridInterpolator {
+    /**
+     * {@inheritDoc}
+     */
+    public TricubicSplineInterpolatingFunction interpolate(final double[] xval,
+                                                           final double[] yval,
+                                                           final double[] zval,
+                                                           final double[][][] fval)
+        throws NoDataException, NumberIsTooSmallException,
+               DimensionMismatchException, NonMonotonicSequenceException {
+        if (xval.length == 0 || yval.length == 0 || zval.length == 0 || fval.length == 0) {
+            throw new NoDataException();
+        }
+        if (xval.length != fval.length) {
+            throw new DimensionMismatchException(xval.length, fval.length);
+        }
+
+        MathArrays.checkOrder(xval);
+        MathArrays.checkOrder(yval);
+        MathArrays.checkOrder(zval);
+
+        final int xLen = xval.length;
+        final int yLen = yval.length;
+        final int zLen = zval.length;
+
+        // Samples, re-ordered as (z, x, y) and (y, z, x) tuplets
+        // fvalXY[k][i][j] = f(xval[i], yval[j], zval[k])
+        // fvalZX[j][k][i] = f(xval[i], yval[j], zval[k])
+        final double[][][] fvalXY = new double[zLen][xLen][yLen];
+        final double[][][] fvalZX = new double[yLen][zLen][xLen];
+        for (int i = 0; i < xLen; i++) {
+            if (fval[i].length != yLen) {
+                throw new DimensionMismatchException(fval[i].length, yLen);
+            }
+
+            for (int j = 0; j < yLen; j++) {
+                if (fval[i][j].length != zLen) {
+                    throw new DimensionMismatchException(fval[i][j].length, zLen);
+                }
+
+                for (int k = 0; k < zLen; k++) {
+                    final double v = fval[i][j][k];
+                    fvalXY[k][i][j] = v;
+                    fvalZX[j][k][i] = v;
+                }
+            }
+        }
+
+        final BicubicSplineInterpolator bsi = new BicubicSplineInterpolator(true);
+
+        // For each line x[i] (0 <= i < xLen), construct a 2D spline in y and z
+        final BicubicSplineInterpolatingFunction[] xSplineYZ
+            = new BicubicSplineInterpolatingFunction[xLen];
+        for (int i = 0; i < xLen; i++) {
+            xSplineYZ[i] = bsi.interpolate(yval, zval, fval[i]);
+        }
+
+        // For each line y[j] (0 <= j < yLen), construct a 2D spline in z and x
+        final BicubicSplineInterpolatingFunction[] ySplineZX
+            = new BicubicSplineInterpolatingFunction[yLen];
+        for (int j = 0; j < yLen; j++) {
+            ySplineZX[j] = bsi.interpolate(zval, xval, fvalZX[j]);
+        }
+
+        // For each line z[k] (0 <= k < zLen), construct a 2D spline in x and y
+        final BicubicSplineInterpolatingFunction[] zSplineXY
+            = new BicubicSplineInterpolatingFunction[zLen];
+        for (int k = 0; k < zLen; k++) {
+            zSplineXY[k] = bsi.interpolate(xval, yval, fvalXY[k]);
+        }
+
+        // Partial derivatives wrt x and wrt y
+        final double[][][] dFdX = new double[xLen][yLen][zLen];
+        final double[][][] dFdY = new double[xLen][yLen][zLen];
+        final double[][][] d2FdXdY = new double[xLen][yLen][zLen];
+        for (int k = 0; k < zLen; k++) {
+            final BicubicSplineInterpolatingFunction f = zSplineXY[k];
+            for (int i = 0; i < xLen; i++) {
+                final double x = xval[i];
+                for (int j = 0; j < yLen; j++) {
+                    final double y = yval[j];
+                    dFdX[i][j][k] = f.partialDerivativeX(x, y);
+                    dFdY[i][j][k] = f.partialDerivativeY(x, y);
+                    d2FdXdY[i][j][k] = f.partialDerivativeXY(x, y);
+                }
+            }
+        }
+
+        // Partial derivatives wrt y and wrt z
+        final double[][][] dFdZ = new double[xLen][yLen][zLen];
+        final double[][][] d2FdYdZ = new double[xLen][yLen][zLen];
+        for (int i = 0; i < xLen; i++) {
+            final BicubicSplineInterpolatingFunction f = xSplineYZ[i];
+            for (int j = 0; j < yLen; j++) {
+                final double y = yval[j];
+                for (int k = 0; k < zLen; k++) {
+                    final double z = zval[k];
+                    dFdZ[i][j][k] = f.partialDerivativeY(y, z);
+                    d2FdYdZ[i][j][k] = f.partialDerivativeXY(y, z);
+                }
+            }
+        }
+
+        // Partial derivatives wrt x and wrt z
+        final double[][][] d2FdZdX = new double[xLen][yLen][zLen];
+        for (int j = 0; j < yLen; j++) {
+            final BicubicSplineInterpolatingFunction f = ySplineZX[j];
+            for (int k = 0; k < zLen; k++) {
+                final double z = zval[k];
+                for (int i = 0; i < xLen; i++) {
+                    final double x = xval[i];
+                    d2FdZdX[i][j][k] = f.partialDerivativeXY(z, x);
+                }
+            }
+        }
+
+        // Third partial cross-derivatives
+        final double[][][] d3FdXdYdZ = new double[xLen][yLen][zLen];
+        for (int i = 0; i < xLen ; i++) {
+            final int nI = nextIndex(i, xLen);
+            final int pI = previousIndex(i);
+            for (int j = 0; j < yLen; j++) {
+                final int nJ = nextIndex(j, yLen);
+                final int pJ = previousIndex(j);
+                for (int k = 0; k < zLen; k++) {
+                    final int nK = nextIndex(k, zLen);
+                    final int pK = previousIndex(k);
+
+                    // XXX Not sure about this formula
+                    d3FdXdYdZ[i][j][k] = (fval[nI][nJ][nK] - fval[nI][pJ][nK] -
+                                          fval[pI][nJ][nK] + fval[pI][pJ][nK] -
+                                          fval[nI][nJ][pK] + fval[nI][pJ][pK] +
+                                          fval[pI][nJ][pK] - fval[pI][pJ][pK]) /
+                        ((xval[nI] - xval[pI]) * (yval[nJ] - yval[pJ]) * (zval[nK] - zval[pK])) ;
+                }
+            }
+        }
+
+        // Create the interpolating splines
+        return new TricubicSplineInterpolatingFunction(xval, yval, zval, fval,
+                                                       dFdX, dFdY, dFdZ,
+                                                       d2FdXdY, d2FdZdX, d2FdYdZ,
+                                                       d3FdXdYdZ);
+    }
+
+    /**
+     * Compute the next index of an array, clipping if necessary.
+     * It is assumed (but not checked) that {@code i} is larger than or equal to 0.
+     *
+     * @param i Index
+     * @param max Upper limit of the array
+     * @return the next index
+     */
+    private int nextIndex(int i, int max) {
+        final int index = i + 1;
+        return index < max ? index : index - 1;
+    }
+    /**
+     * Compute the previous index of an array, clipping if necessary.
+     * It is assumed (but not checked) that {@code i} is smaller than the size of the array.
+     *
+     * @param i Index
+     * @return the previous index
+     */
+    private int previousIndex(int i) {
+        final int index = i - 1;
+        return index >= 0 ? index : 0;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/interpolation/TrivariateGridInterpolator.java b/src/main/java/org/apache/commons/math3/analysis/interpolation/TrivariateGridInterpolator.java
new file mode 100644
index 0000000..ec69715
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/interpolation/TrivariateGridInterpolator.java
@@ -0,0 +1,54 @@
+/*
+ * 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.math3.analysis.interpolation;
+
+import org.apache.commons.math3.analysis.TrivariateFunction;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NonMonotonicSequenceException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+
+/**
+ * Interface representing a trivariate real interpolating function where the
+ * sample points must be specified on a regular grid.
+ *
+ * @since 2.2
+ */
+public interface TrivariateGridInterpolator {
+    /**
+     * Compute an interpolating function for the dataset.
+     *
+     * @param xval All the x-coordinates of the interpolation points, sorted
+     * in increasing order.
+     * @param yval All the y-coordinates of the interpolation points, sorted
+     * in increasing order.
+     * @param zval All the z-coordinates of the interpolation points, sorted
+     * in increasing order.
+     * @param fval the values of the interpolation points on all the grid knots:
+     * {@code fval[i][j][k] = f(xval[i], yval[j], zval[k])}.
+     * @return a function that interpolates the data set.
+     * @throws NoDataException if any of the arrays has zero length.
+     * @throws DimensionMismatchException if the array lengths are inconsistent.
+     * @throws NonMonotonicSequenceException if arrays are not sorted
+     * @throws NumberIsTooSmallException if the number of points is too small for
+     * the order of the interpolation
+     */
+    TrivariateFunction interpolate(double[] xval, double[] yval, double[] zval,
+                                   double[][][] fval)
+        throws NoDataException, NumberIsTooSmallException,
+               DimensionMismatchException, NonMonotonicSequenceException;
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/interpolation/UnivariateInterpolator.java b/src/main/java/org/apache/commons/math3/analysis/interpolation/UnivariateInterpolator.java
new file mode 100644
index 0000000..f7a1bd1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/interpolation/UnivariateInterpolator.java
@@ -0,0 +1,41 @@
+/*
+ * 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.math3.analysis.interpolation;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+
+/**
+ * Interface representing a univariate real interpolating function.
+ *
+ */
+public interface UnivariateInterpolator {
+    /**
+     * Compute an interpolating function for the dataset.
+     *
+     * @param xval Arguments for the interpolation points.
+     * @param yval Values for the interpolation points.
+     * @return a function which interpolates the dataset.
+     * @throws MathIllegalArgumentException
+     * if the arguments violate assumptions made by the interpolation
+     * algorithm.
+     * @throws DimensionMismatchException if arrays lengthes do not match
+     */
+    UnivariateFunction interpolate(double xval[], double yval[])
+        throws MathIllegalArgumentException, DimensionMismatchException;
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/interpolation/UnivariatePeriodicInterpolator.java b/src/main/java/org/apache/commons/math3/analysis/interpolation/UnivariatePeriodicInterpolator.java
new file mode 100644
index 0000000..88760f5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/interpolation/UnivariatePeriodicInterpolator.java
@@ -0,0 +1,124 @@
+/*
+ * 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.math3.analysis.interpolation;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.util.MathUtils;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NonMonotonicSequenceException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+
+/**
+ * Adapter for classes implementing the {@link UnivariateInterpolator}
+ * interface.
+ * The data to be interpolated is assumed to be periodic. Thus values that are
+ * outside of the range can be passed to the interpolation function: They will
+ * be wrapped into the initial range before being passed to the class that
+ * actually computes the interpolation.
+ *
+ */
+public class UnivariatePeriodicInterpolator
+    implements UnivariateInterpolator {
+    /** Default number of extension points of the samples array. */
+    public static final int DEFAULT_EXTEND = 5;
+    /** Interpolator. */
+    private final UnivariateInterpolator interpolator;
+    /** Period. */
+    private final double period;
+    /** Number of extension points. */
+    private final int extend;
+
+    /**
+     * Builds an interpolator.
+     *
+     * @param interpolator Interpolator.
+     * @param period Period.
+     * @param extend Number of points to be appended at the beginning and
+     * end of the sample arrays in order to avoid interpolation failure at
+     * the (periodic) boundaries of the orginal interval. The value is the
+     * number of sample points which the original {@code interpolator} needs
+     * on each side of the interpolated point.
+     */
+    public UnivariatePeriodicInterpolator(UnivariateInterpolator interpolator,
+                                          double period,
+                                          int extend) {
+        this.interpolator = interpolator;
+        this.period = period;
+        this.extend = extend;
+    }
+
+    /**
+     * Builds an interpolator.
+     * Uses {@link #DEFAULT_EXTEND} as the number of extension points on each side
+     * of the original abscissae range.
+     *
+     * @param interpolator Interpolator.
+     * @param period Period.
+     */
+    public UnivariatePeriodicInterpolator(UnivariateInterpolator interpolator,
+                                          double period) {
+        this(interpolator, period, DEFAULT_EXTEND);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws NumberIsTooSmallException if the number of extension points
+     * is larger than the size of {@code xval}.
+     */
+    public UnivariateFunction interpolate(double[] xval,
+                                          double[] yval)
+        throws NumberIsTooSmallException, NonMonotonicSequenceException {
+        if (xval.length < extend) {
+            throw new NumberIsTooSmallException(xval.length, extend, true);
+        }
+
+        MathArrays.checkOrder(xval);
+        final double offset = xval[0];
+
+        final int len = xval.length + extend * 2;
+        final double[] x = new double[len];
+        final double[] y = new double[len];
+        for (int i = 0; i < xval.length; i++) {
+            final int index = i + extend;
+            x[index] = MathUtils.reduce(xval[i], period, offset);
+            y[index] = yval[i];
+        }
+
+        // Wrap to enable interpolation at the boundaries.
+        for (int i = 0; i < extend; i++) {
+            int index = xval.length - extend + i;
+            x[i] = MathUtils.reduce(xval[index], period, offset) - period;
+            y[i] = yval[index];
+
+            index = len - extend + i;
+            x[index] = MathUtils.reduce(xval[i], period, offset) + period;
+            y[index] = yval[i];
+        }
+
+        MathArrays.sortInPlace(x, y);
+
+        final UnivariateFunction f = interpolator.interpolate(x, y);
+        return new UnivariateFunction() {
+            /** {@inheritDoc} */
+            public double value(final double x) throws MathIllegalArgumentException {
+                return f.value(MathUtils.reduce(x, period, offset));
+            }
+        };
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/interpolation/package-info.java b/src/main/java/org/apache/commons/math3/analysis/interpolation/package-info.java
new file mode 100644
index 0000000..b4b25dd
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/interpolation/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+/**
+ *
+ *     Univariate real functions interpolation algorithms.
+ *
+ */
+package org.apache.commons.math3.analysis.interpolation;
diff --git a/src/main/java/org/apache/commons/math3/analysis/package-info.java b/src/main/java/org/apache/commons/math3/analysis/package-info.java
new file mode 100644
index 0000000..e122bbb
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/package-info.java
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+/**
+ * Parent package for common numerical analysis procedures, including root finding, function
+ * interpolation and integration. Note that optimization (i.e. minimization and maximization) is a
+ * separate top-level package.
+ *
+ * <p>Function interfaces are intended to be implemented by user code to represent domain problems.
+ * The algorithms provided by the library operate on these functions to find their roots, or
+ * integrate them, or ... Functions can be multivariate or univariate, real vectorial or
+ * matrix-valued, and they can be differentiable or not.
+ */
+package org.apache.commons.math3.analysis;
diff --git a/src/main/java/org/apache/commons/math3/analysis/polynomials/PolynomialFunction.java b/src/main/java/org/apache/commons/math3/analysis/polynomials/PolynomialFunction.java
new file mode 100644
index 0000000..69be04a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/polynomials/PolynomialFunction.java
@@ -0,0 +1,412 @@
+/*
+ * 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.math3.analysis.polynomials;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.analysis.ParametricUnivariateFunction;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * Immutable representation of a real polynomial function with real coefficients.
+ * <p>
+ * <a href="http://mathworld.wolfram.com/HornersMethod.html">Horner's Method</a>
+ * is used to evaluate the function.</p>
+ *
+ */
+public class PolynomialFunction implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction, Serializable {
+    /**
+     * Serialization identifier
+     */
+    private static final long serialVersionUID = -7726511984200295583L;
+    /**
+     * The coefficients of the polynomial, ordered by degree -- i.e.,
+     * coefficients[0] is the constant term and coefficients[n] is the
+     * coefficient of x^n where n is the degree of the polynomial.
+     */
+    private final double coefficients[];
+
+    /**
+     * Construct a polynomial with the given coefficients.  The first element
+     * of the coefficients array is the constant term.  Higher degree
+     * coefficients follow in sequence.  The degree of the resulting polynomial
+     * is the index of the last non-null element of the array, or 0 if all elements
+     * are null.
+     * <p>
+     * The constructor makes a copy of the input array and assigns the copy to
+     * the coefficients property.</p>
+     *
+     * @param c Polynomial coefficients.
+     * @throws NullArgumentException if {@code c} is {@code null}.
+     * @throws NoDataException if {@code c} is empty.
+     */
+    public PolynomialFunction(double c[])
+        throws NullArgumentException, NoDataException {
+        super();
+        MathUtils.checkNotNull(c);
+        int n = c.length;
+        if (n == 0) {
+            throw new NoDataException(LocalizedFormats.EMPTY_POLYNOMIALS_COEFFICIENTS_ARRAY);
+        }
+        while ((n > 1) && (c[n - 1] == 0)) {
+            --n;
+        }
+        this.coefficients = new double[n];
+        System.arraycopy(c, 0, this.coefficients, 0, n);
+    }
+
+    /**
+     * Compute the value of the function for the given argument.
+     * <p>
+     *  The value returned is </p><p>
+     *  {@code coefficients[n] * x^n + ... + coefficients[1] * x  + coefficients[0]}
+     * </p>
+     *
+     * @param x Argument for which the function value should be computed.
+     * @return the value of the polynomial at the given point.
+     * @see UnivariateFunction#value(double)
+     */
+    public double value(double x) {
+       return evaluate(coefficients, x);
+    }
+
+    /**
+     * Returns the degree of the polynomial.
+     *
+     * @return the degree of the polynomial.
+     */
+    public int degree() {
+        return coefficients.length - 1;
+    }
+
+    /**
+     * Returns a copy of the coefficients array.
+     * <p>
+     * Changes made to the returned copy will not affect the coefficients of
+     * the polynomial.</p>
+     *
+     * @return a fresh copy of the coefficients array.
+     */
+    public double[] getCoefficients() {
+        return coefficients.clone();
+    }
+
+    /**
+     * Uses Horner's Method to evaluate the polynomial with the given coefficients at
+     * the argument.
+     *
+     * @param coefficients Coefficients of the polynomial to evaluate.
+     * @param argument Input value.
+     * @return the value of the polynomial.
+     * @throws NoDataException if {@code coefficients} is empty.
+     * @throws NullArgumentException if {@code coefficients} is {@code null}.
+     */
+    protected static double evaluate(double[] coefficients, double argument)
+        throws NullArgumentException, NoDataException {
+        MathUtils.checkNotNull(coefficients);
+        int n = coefficients.length;
+        if (n == 0) {
+            throw new NoDataException(LocalizedFormats.EMPTY_POLYNOMIALS_COEFFICIENTS_ARRAY);
+        }
+        double result = coefficients[n - 1];
+        for (int j = n - 2; j >= 0; j--) {
+            result = argument * result + coefficients[j];
+        }
+        return result;
+    }
+
+
+    /** {@inheritDoc}
+     * @since 3.1
+     * @throws NoDataException if {@code coefficients} is empty.
+     * @throws NullArgumentException if {@code coefficients} is {@code null}.
+     */
+    public DerivativeStructure value(final DerivativeStructure t)
+        throws NullArgumentException, NoDataException {
+        MathUtils.checkNotNull(coefficients);
+        int n = coefficients.length;
+        if (n == 0) {
+            throw new NoDataException(LocalizedFormats.EMPTY_POLYNOMIALS_COEFFICIENTS_ARRAY);
+        }
+        DerivativeStructure result =
+                new DerivativeStructure(t.getFreeParameters(), t.getOrder(), coefficients[n - 1]);
+        for (int j = n - 2; j >= 0; j--) {
+            result = result.multiply(t).add(coefficients[j]);
+        }
+        return result;
+    }
+
+    /**
+     * Add a polynomial to the instance.
+     *
+     * @param p Polynomial to add.
+     * @return a new polynomial which is the sum of the instance and {@code p}.
+     */
+    public PolynomialFunction add(final PolynomialFunction p) {
+        // identify the lowest degree polynomial
+        final int lowLength  = FastMath.min(coefficients.length, p.coefficients.length);
+        final int highLength = FastMath.max(coefficients.length, p.coefficients.length);
+
+        // build the coefficients array
+        double[] newCoefficients = new double[highLength];
+        for (int i = 0; i < lowLength; ++i) {
+            newCoefficients[i] = coefficients[i] + p.coefficients[i];
+        }
+        System.arraycopy((coefficients.length < p.coefficients.length) ?
+                         p.coefficients : coefficients,
+                         lowLength,
+                         newCoefficients, lowLength,
+                         highLength - lowLength);
+
+        return new PolynomialFunction(newCoefficients);
+    }
+
+    /**
+     * Subtract a polynomial from the instance.
+     *
+     * @param p Polynomial to subtract.
+     * @return a new polynomial which is the instance minus {@code p}.
+     */
+    public PolynomialFunction subtract(final PolynomialFunction p) {
+        // identify the lowest degree polynomial
+        int lowLength  = FastMath.min(coefficients.length, p.coefficients.length);
+        int highLength = FastMath.max(coefficients.length, p.coefficients.length);
+
+        // build the coefficients array
+        double[] newCoefficients = new double[highLength];
+        for (int i = 0; i < lowLength; ++i) {
+            newCoefficients[i] = coefficients[i] - p.coefficients[i];
+        }
+        if (coefficients.length < p.coefficients.length) {
+            for (int i = lowLength; i < highLength; ++i) {
+                newCoefficients[i] = -p.coefficients[i];
+            }
+        } else {
+            System.arraycopy(coefficients, lowLength, newCoefficients, lowLength,
+                             highLength - lowLength);
+        }
+
+        return new PolynomialFunction(newCoefficients);
+    }
+
+    /**
+     * Negate the instance.
+     *
+     * @return a new polynomial with all coefficients negated
+     */
+    public PolynomialFunction negate() {
+        double[] newCoefficients = new double[coefficients.length];
+        for (int i = 0; i < coefficients.length; ++i) {
+            newCoefficients[i] = -coefficients[i];
+        }
+        return new PolynomialFunction(newCoefficients);
+    }
+
+    /**
+     * Multiply the instance by a polynomial.
+     *
+     * @param p Polynomial to multiply by.
+     * @return a new polynomial equal to this times {@code p}
+     */
+    public PolynomialFunction multiply(final PolynomialFunction p) {
+        double[] newCoefficients = new double[coefficients.length + p.coefficients.length - 1];
+
+        for (int i = 0; i < newCoefficients.length; ++i) {
+            newCoefficients[i] = 0.0;
+            for (int j = FastMath.max(0, i + 1 - p.coefficients.length);
+                 j < FastMath.min(coefficients.length, i + 1);
+                 ++j) {
+                newCoefficients[i] += coefficients[j] * p.coefficients[i-j];
+            }
+        }
+
+        return new PolynomialFunction(newCoefficients);
+    }
+
+    /**
+     * Returns the coefficients of the derivative of the polynomial with the given coefficients.
+     *
+     * @param coefficients Coefficients of the polynomial to differentiate.
+     * @return the coefficients of the derivative or {@code null} if coefficients has length 1.
+     * @throws NoDataException if {@code coefficients} is empty.
+     * @throws NullArgumentException if {@code coefficients} is {@code null}.
+     */
+    protected static double[] differentiate(double[] coefficients)
+        throws NullArgumentException, NoDataException {
+        MathUtils.checkNotNull(coefficients);
+        int n = coefficients.length;
+        if (n == 0) {
+            throw new NoDataException(LocalizedFormats.EMPTY_POLYNOMIALS_COEFFICIENTS_ARRAY);
+        }
+        if (n == 1) {
+            return new double[]{0};
+        }
+        double[] result = new double[n - 1];
+        for (int i = n - 1; i > 0; i--) {
+            result[i - 1] = i * coefficients[i];
+        }
+        return result;
+    }
+
+    /**
+     * Returns the derivative as a {@link PolynomialFunction}.
+     *
+     * @return the derivative polynomial.
+     */
+    public PolynomialFunction polynomialDerivative() {
+        return new PolynomialFunction(differentiate(coefficients));
+    }
+
+    /**
+     * Returns the derivative as a {@link UnivariateFunction}.
+     *
+     * @return the derivative function.
+     */
+    public UnivariateFunction derivative() {
+        return polynomialDerivative();
+    }
+
+    /**
+     * Returns a string representation of the polynomial.
+     *
+     * <p>The representation is user oriented. Terms are displayed lowest
+     * degrees first. The multiplications signs, coefficients equals to
+     * one and null terms are not displayed (except if the polynomial is 0,
+     * in which case the 0 constant term is displayed). Addition of terms
+     * with negative coefficients are replaced by subtraction of terms
+     * with positive coefficients except for the first displayed term
+     * (i.e. we display <code>-3</code> for a constant negative polynomial,
+     * but <code>1 - 3 x + x^2</code> if the negative coefficient is not
+     * the first one displayed).</p>
+     *
+     * @return a string representation of the polynomial.
+     */
+    @Override
+    public String toString() {
+        StringBuilder s = new StringBuilder();
+        if (coefficients[0] == 0.0) {
+            if (coefficients.length == 1) {
+                return "0";
+            }
+        } else {
+            s.append(toString(coefficients[0]));
+        }
+
+        for (int i = 1; i < coefficients.length; ++i) {
+            if (coefficients[i] != 0) {
+                if (s.length() > 0) {
+                    if (coefficients[i] < 0) {
+                        s.append(" - ");
+                    } else {
+                        s.append(" + ");
+                    }
+                } else {
+                    if (coefficients[i] < 0) {
+                        s.append("-");
+                    }
+                }
+
+                double absAi = FastMath.abs(coefficients[i]);
+                if ((absAi - 1) != 0) {
+                    s.append(toString(absAi));
+                    s.append(' ');
+                }
+
+                s.append("x");
+                if (i > 1) {
+                    s.append('^');
+                    s.append(Integer.toString(i));
+                }
+            }
+        }
+
+        return s.toString();
+    }
+
+    /**
+     * Creates a string representing a coefficient, removing ".0" endings.
+     *
+     * @param coeff Coefficient.
+     * @return a string representation of {@code coeff}.
+     */
+    private static String toString(double coeff) {
+        final String c = Double.toString(coeff);
+        if (c.endsWith(".0")) {
+            return c.substring(0, c.length() - 2);
+        } else {
+            return c;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + Arrays.hashCode(coefficients);
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof PolynomialFunction)) {
+            return false;
+        }
+        PolynomialFunction other = (PolynomialFunction) obj;
+        if (!Arrays.equals(coefficients, other.coefficients)) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Dedicated parametric polynomial class.
+     *
+     * @since 3.0
+     */
+    public static class Parametric implements ParametricUnivariateFunction {
+        /** {@inheritDoc} */
+        public double[] gradient(double x, double ... parameters) {
+            final double[] gradient = new double[parameters.length];
+            double xn = 1.0;
+            for (int i = 0; i < parameters.length; ++i) {
+                gradient[i] = xn;
+                xn *= x;
+            }
+            return gradient;
+        }
+
+        /** {@inheritDoc} */
+        public double value(final double x, final double ... parameters)
+            throws NoDataException {
+            return PolynomialFunction.evaluate(parameters, x);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/polynomials/PolynomialFunctionLagrangeForm.java b/src/main/java/org/apache/commons/math3/analysis/polynomials/PolynomialFunctionLagrangeForm.java
new file mode 100644
index 0000000..9d812df
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/polynomials/PolynomialFunctionLagrangeForm.java
@@ -0,0 +1,326 @@
+/*
+ * 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.math3.analysis.polynomials;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NonMonotonicSequenceException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Implements the representation of a real polynomial function in
+ * <a href="http://mathworld.wolfram.com/LagrangeInterpolatingPolynomial.html">
+ * Lagrange Form</a>. For reference, see <b>Introduction to Numerical
+ * Analysis</b>, ISBN 038795452X, chapter 2.
+ * <p>
+ * The approximated function should be smooth enough for Lagrange polynomial
+ * to work well. Otherwise, consider using splines instead.</p>
+ *
+ * @since 1.2
+ */
+public class PolynomialFunctionLagrangeForm implements UnivariateFunction {
+    /**
+     * The coefficients of the polynomial, ordered by degree -- i.e.
+     * coefficients[0] is the constant term and coefficients[n] is the
+     * coefficient of x^n where n is the degree of the polynomial.
+     */
+    private double coefficients[];
+    /**
+     * Interpolating points (abscissas).
+     */
+    private final double x[];
+    /**
+     * Function values at interpolating points.
+     */
+    private final double y[];
+    /**
+     * Whether the polynomial coefficients are available.
+     */
+    private boolean coefficientsComputed;
+
+    /**
+     * Construct a Lagrange polynomial with the given abscissas and function
+     * values. The order of interpolating points are not important.
+     * <p>
+     * The constructor makes copy of the input arrays and assigns them.</p>
+     *
+     * @param x interpolating points
+     * @param y function values at interpolating points
+     * @throws DimensionMismatchException if the array lengths are different.
+     * @throws NumberIsTooSmallException if the number of points is less than 2.
+     * @throws NonMonotonicSequenceException
+     * if two abscissae have the same value.
+     */
+    public PolynomialFunctionLagrangeForm(double x[], double y[])
+        throws DimensionMismatchException, NumberIsTooSmallException, NonMonotonicSequenceException {
+        this.x = new double[x.length];
+        this.y = new double[y.length];
+        System.arraycopy(x, 0, this.x, 0, x.length);
+        System.arraycopy(y, 0, this.y, 0, y.length);
+        coefficientsComputed = false;
+
+        if (!verifyInterpolationArray(x, y, false)) {
+            MathArrays.sortInPlace(this.x, this.y);
+            // Second check in case some abscissa is duplicated.
+            verifyInterpolationArray(this.x, this.y, true);
+        }
+    }
+
+    /**
+     * Calculate the function value at the given point.
+     *
+     * @param z Point at which the function value is to be computed.
+     * @return the function value.
+     * @throws DimensionMismatchException if {@code x} and {@code y} have
+     * different lengths.
+     * @throws org.apache.commons.math3.exception.NonMonotonicSequenceException
+     * if {@code x} is not sorted in strictly increasing order.
+     * @throws NumberIsTooSmallException if the size of {@code x} is less
+     * than 2.
+     */
+    public double value(double z) {
+        return evaluateInternal(x, y, z);
+    }
+
+    /**
+     * Returns the degree of the polynomial.
+     *
+     * @return the degree of the polynomial
+     */
+    public int degree() {
+        return x.length - 1;
+    }
+
+    /**
+     * Returns a copy of the interpolating points array.
+     * <p>
+     * Changes made to the returned copy will not affect the polynomial.</p>
+     *
+     * @return a fresh copy of the interpolating points array
+     */
+    public double[] getInterpolatingPoints() {
+        double[] out = new double[x.length];
+        System.arraycopy(x, 0, out, 0, x.length);
+        return out;
+    }
+
+    /**
+     * Returns a copy of the interpolating values array.
+     * <p>
+     * Changes made to the returned copy will not affect the polynomial.</p>
+     *
+     * @return a fresh copy of the interpolating values array
+     */
+    public double[] getInterpolatingValues() {
+        double[] out = new double[y.length];
+        System.arraycopy(y, 0, out, 0, y.length);
+        return out;
+    }
+
+    /**
+     * Returns a copy of the coefficients array.
+     * <p>
+     * Changes made to the returned copy will not affect the polynomial.</p>
+     * <p>
+     * Note that coefficients computation can be ill-conditioned. Use with caution
+     * and only when it is necessary.</p>
+     *
+     * @return a fresh copy of the coefficients array
+     */
+    public double[] getCoefficients() {
+        if (!coefficientsComputed) {
+            computeCoefficients();
+        }
+        double[] out = new double[coefficients.length];
+        System.arraycopy(coefficients, 0, out, 0, coefficients.length);
+        return out;
+    }
+
+    /**
+     * Evaluate the Lagrange polynomial using
+     * <a href="http://mathworld.wolfram.com/NevillesAlgorithm.html">
+     * Neville's Algorithm</a>. It takes O(n^2) time.
+     *
+     * @param x Interpolating points array.
+     * @param y Interpolating values array.
+     * @param z Point at which the function value is to be computed.
+     * @return the function value.
+     * @throws DimensionMismatchException if {@code x} and {@code y} have
+     * different lengths.
+     * @throws NonMonotonicSequenceException
+     * if {@code x} is not sorted in strictly increasing order.
+     * @throws NumberIsTooSmallException if the size of {@code x} is less
+     * than 2.
+     */
+    public static double evaluate(double x[], double y[], double z)
+        throws DimensionMismatchException, NumberIsTooSmallException, NonMonotonicSequenceException {
+        if (verifyInterpolationArray(x, y, false)) {
+            return evaluateInternal(x, y, z);
+        }
+
+        // Array is not sorted.
+        final double[] xNew = new double[x.length];
+        final double[] yNew = new double[y.length];
+        System.arraycopy(x, 0, xNew, 0, x.length);
+        System.arraycopy(y, 0, yNew, 0, y.length);
+
+        MathArrays.sortInPlace(xNew, yNew);
+        // Second check in case some abscissa is duplicated.
+        verifyInterpolationArray(xNew, yNew, true);
+        return evaluateInternal(xNew, yNew, z);
+    }
+
+    /**
+     * Evaluate the Lagrange polynomial using
+     * <a href="http://mathworld.wolfram.com/NevillesAlgorithm.html">
+     * Neville's Algorithm</a>. It takes O(n^2) time.
+     *
+     * @param x Interpolating points array.
+     * @param y Interpolating values array.
+     * @param z Point at which the function value is to be computed.
+     * @return the function value.
+     * @throws DimensionMismatchException if {@code x} and {@code y} have
+     * different lengths.
+     * @throws org.apache.commons.math3.exception.NonMonotonicSequenceException
+     * if {@code x} is not sorted in strictly increasing order.
+     * @throws NumberIsTooSmallException if the size of {@code x} is less
+     * than 2.
+     */
+    private static double evaluateInternal(double x[], double y[], double z) {
+        int nearest = 0;
+        final int n = x.length;
+        final double[] c = new double[n];
+        final double[] d = new double[n];
+        double min_dist = Double.POSITIVE_INFINITY;
+        for (int i = 0; i < n; i++) {
+            // initialize the difference arrays
+            c[i] = y[i];
+            d[i] = y[i];
+            // find out the abscissa closest to z
+            final double dist = FastMath.abs(z - x[i]);
+            if (dist < min_dist) {
+                nearest = i;
+                min_dist = dist;
+            }
+        }
+
+        // initial approximation to the function value at z
+        double value = y[nearest];
+
+        for (int i = 1; i < n; i++) {
+            for (int j = 0; j < n-i; j++) {
+                final double tc = x[j] - z;
+                final double td = x[i+j] - z;
+                final double divider = x[j] - x[i+j];
+                // update the difference arrays
+                final double w = (c[j+1] - d[j]) / divider;
+                c[j] = tc * w;
+                d[j] = td * w;
+            }
+            // sum up the difference terms to get the final value
+            if (nearest < 0.5*(n-i+1)) {
+                value += c[nearest];    // fork down
+            } else {
+                nearest--;
+                value += d[nearest];    // fork up
+            }
+        }
+
+        return value;
+    }
+
+    /**
+     * Calculate the coefficients of Lagrange polynomial from the
+     * interpolation data. It takes O(n^2) time.
+     * Note that this computation can be ill-conditioned: Use with caution
+     * and only when it is necessary.
+     */
+    protected void computeCoefficients() {
+        final int n = degree() + 1;
+        coefficients = new double[n];
+        for (int i = 0; i < n; i++) {
+            coefficients[i] = 0.0;
+        }
+
+        // c[] are the coefficients of P(x) = (x-x[0])(x-x[1])...(x-x[n-1])
+        final double[] c = new double[n+1];
+        c[0] = 1.0;
+        for (int i = 0; i < n; i++) {
+            for (int j = i; j > 0; j--) {
+                c[j] = c[j-1] - c[j] * x[i];
+            }
+            c[0] *= -x[i];
+            c[i+1] = 1;
+        }
+
+        final double[] tc = new double[n];
+        for (int i = 0; i < n; i++) {
+            // d = (x[i]-x[0])...(x[i]-x[i-1])(x[i]-x[i+1])...(x[i]-x[n-1])
+            double d = 1;
+            for (int j = 0; j < n; j++) {
+                if (i != j) {
+                    d *= x[i] - x[j];
+                }
+            }
+            final double t = y[i] / d;
+            // Lagrange polynomial is the sum of n terms, each of which is a
+            // polynomial of degree n-1. tc[] are the coefficients of the i-th
+            // numerator Pi(x) = (x-x[0])...(x-x[i-1])(x-x[i+1])...(x-x[n-1]).
+            tc[n-1] = c[n];     // actually c[n] = 1
+            coefficients[n-1] += t * tc[n-1];
+            for (int j = n-2; j >= 0; j--) {
+                tc[j] = c[j+1] + tc[j+1] * x[i];
+                coefficients[j] += t * tc[j];
+            }
+        }
+
+        coefficientsComputed = true;
+    }
+
+    /**
+     * Check that the interpolation arrays are valid.
+     * The arrays features checked by this method are that both arrays have the
+     * same length and this length is at least 2.
+     *
+     * @param x Interpolating points array.
+     * @param y Interpolating values array.
+     * @param abort Whether to throw an exception if {@code x} is not sorted.
+     * @throws DimensionMismatchException if the array lengths are different.
+     * @throws NumberIsTooSmallException if the number of points is less than 2.
+     * @throws org.apache.commons.math3.exception.NonMonotonicSequenceException
+     * if {@code x} is not sorted in strictly increasing order and {@code abort}
+     * is {@code true}.
+     * @return {@code false} if the {@code x} is not sorted in increasing order,
+     * {@code true} otherwise.
+     * @see #evaluate(double[], double[], double)
+     * @see #computeCoefficients()
+     */
+    public static boolean verifyInterpolationArray(double x[], double y[], boolean abort)
+        throws DimensionMismatchException, NumberIsTooSmallException, NonMonotonicSequenceException {
+        if (x.length != y.length) {
+            throw new DimensionMismatchException(x.length, y.length);
+        }
+        if (x.length < 2) {
+            throw new NumberIsTooSmallException(LocalizedFormats.WRONG_NUMBER_OF_POINTS, 2, x.length, true);
+        }
+
+        return MathArrays.checkOrder(x, MathArrays.OrderDirection.INCREASING, true, abort);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/polynomials/PolynomialFunctionNewtonForm.java b/src/main/java/org/apache/commons/math3/analysis/polynomials/PolynomialFunctionNewtonForm.java
new file mode 100644
index 0000000..fc2f1fd
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/polynomials/PolynomialFunctionNewtonForm.java
@@ -0,0 +1,245 @@
+/*
+ * 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.math3.analysis.polynomials;
+
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * Implements the representation of a real polynomial function in
+ * Newton Form. For reference, see <b>Elementary Numerical Analysis</b>,
+ * ISBN 0070124477, chapter 2.
+ * <p>
+ * The formula of polynomial in Newton form is
+ *     p(x) = a[0] + a[1](x-c[0]) + a[2](x-c[0])(x-c[1]) + ... +
+ *            a[n](x-c[0])(x-c[1])...(x-c[n-1])
+ * Note that the length of a[] is one more than the length of c[]</p>
+ *
+ * @since 1.2
+ */
+public class PolynomialFunctionNewtonForm implements UnivariateDifferentiableFunction {
+
+    /**
+     * The coefficients of the polynomial, ordered by degree -- i.e.
+     * coefficients[0] is the constant term and coefficients[n] is the
+     * coefficient of x^n where n is the degree of the polynomial.
+     */
+    private double coefficients[];
+
+    /**
+     * Centers of the Newton polynomial.
+     */
+    private final double c[];
+
+    /**
+     * When all c[i] = 0, a[] becomes normal polynomial coefficients,
+     * i.e. a[i] = coefficients[i].
+     */
+    private final double a[];
+
+    /**
+     * Whether the polynomial coefficients are available.
+     */
+    private boolean coefficientsComputed;
+
+    /**
+     * Construct a Newton polynomial with the given a[] and c[]. The order of
+     * centers are important in that if c[] shuffle, then values of a[] would
+     * completely change, not just a permutation of old a[].
+     * <p>
+     * The constructor makes copy of the input arrays and assigns them.</p>
+     *
+     * @param a Coefficients in Newton form formula.
+     * @param c Centers.
+     * @throws NullArgumentException if any argument is {@code null}.
+     * @throws NoDataException if any array has zero length.
+     * @throws DimensionMismatchException if the size difference between
+     * {@code a} and {@code c} is not equal to 1.
+     */
+    public PolynomialFunctionNewtonForm(double a[], double c[])
+        throws NullArgumentException, NoDataException, DimensionMismatchException {
+
+        verifyInputArray(a, c);
+        this.a = new double[a.length];
+        this.c = new double[c.length];
+        System.arraycopy(a, 0, this.a, 0, a.length);
+        System.arraycopy(c, 0, this.c, 0, c.length);
+        coefficientsComputed = false;
+    }
+
+    /**
+     * Calculate the function value at the given point.
+     *
+     * @param z Point at which the function value is to be computed.
+     * @return the function value.
+     */
+    public double value(double z) {
+       return evaluate(a, c, z);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @since 3.1
+     */
+    public DerivativeStructure value(final DerivativeStructure t) {
+        verifyInputArray(a, c);
+
+        final int n = c.length;
+        DerivativeStructure value = new DerivativeStructure(t.getFreeParameters(), t.getOrder(), a[n]);
+        for (int i = n - 1; i >= 0; i--) {
+            value = t.subtract(c[i]).multiply(value).add(a[i]);
+        }
+
+        return value;
+
+    }
+
+    /**
+     * Returns the degree of the polynomial.
+     *
+     * @return the degree of the polynomial
+     */
+    public int degree() {
+        return c.length;
+    }
+
+    /**
+     * Returns a copy of coefficients in Newton form formula.
+     * <p>
+     * Changes made to the returned copy will not affect the polynomial.</p>
+     *
+     * @return a fresh copy of coefficients in Newton form formula
+     */
+    public double[] getNewtonCoefficients() {
+        double[] out = new double[a.length];
+        System.arraycopy(a, 0, out, 0, a.length);
+        return out;
+    }
+
+    /**
+     * Returns a copy of the centers array.
+     * <p>
+     * Changes made to the returned copy will not affect the polynomial.</p>
+     *
+     * @return a fresh copy of the centers array.
+     */
+    public double[] getCenters() {
+        double[] out = new double[c.length];
+        System.arraycopy(c, 0, out, 0, c.length);
+        return out;
+    }
+
+    /**
+     * Returns a copy of the coefficients array.
+     * <p>
+     * Changes made to the returned copy will not affect the polynomial.</p>
+     *
+     * @return a fresh copy of the coefficients array.
+     */
+    public double[] getCoefficients() {
+        if (!coefficientsComputed) {
+            computeCoefficients();
+        }
+        double[] out = new double[coefficients.length];
+        System.arraycopy(coefficients, 0, out, 0, coefficients.length);
+        return out;
+    }
+
+    /**
+     * Evaluate the Newton polynomial using nested multiplication. It is
+     * also called <a href="http://mathworld.wolfram.com/HornersRule.html">
+     * Horner's Rule</a> and takes O(N) time.
+     *
+     * @param a Coefficients in Newton form formula.
+     * @param c Centers.
+     * @param z Point at which the function value is to be computed.
+     * @return the function value.
+     * @throws NullArgumentException if any argument is {@code null}.
+     * @throws NoDataException if any array has zero length.
+     * @throws DimensionMismatchException if the size difference between
+     * {@code a} and {@code c} is not equal to 1.
+     */
+    public static double evaluate(double a[], double c[], double z)
+        throws NullArgumentException, DimensionMismatchException, NoDataException {
+        verifyInputArray(a, c);
+
+        final int n = c.length;
+        double value = a[n];
+        for (int i = n - 1; i >= 0; i--) {
+            value = a[i] + (z - c[i]) * value;
+        }
+
+        return value;
+    }
+
+    /**
+     * Calculate the normal polynomial coefficients given the Newton form.
+     * It also uses nested multiplication but takes O(N^2) time.
+     */
+    protected void computeCoefficients() {
+        final int n = degree();
+
+        coefficients = new double[n+1];
+        for (int i = 0; i <= n; i++) {
+            coefficients[i] = 0.0;
+        }
+
+        coefficients[0] = a[n];
+        for (int i = n-1; i >= 0; i--) {
+            for (int j = n-i; j > 0; j--) {
+                coefficients[j] = coefficients[j-1] - c[i] * coefficients[j];
+            }
+            coefficients[0] = a[i] - c[i] * coefficients[0];
+        }
+
+        coefficientsComputed = true;
+    }
+
+    /**
+     * Verifies that the input arrays are valid.
+     * <p>
+     * The centers must be distinct for interpolation purposes, but not
+     * for general use. Thus it is not verified here.</p>
+     *
+     * @param a the coefficients in Newton form formula
+     * @param c the centers
+     * @throws NullArgumentException if any argument is {@code null}.
+     * @throws NoDataException if any array has zero length.
+     * @throws DimensionMismatchException if the size difference between
+     * {@code a} and {@code c} is not equal to 1.
+     * @see org.apache.commons.math3.analysis.interpolation.DividedDifferenceInterpolator#computeDividedDifference(double[],
+     * double[])
+     */
+    protected static void verifyInputArray(double a[], double c[])
+        throws NullArgumentException, NoDataException, DimensionMismatchException {
+        MathUtils.checkNotNull(a);
+        MathUtils.checkNotNull(c);
+        if (a.length == 0 || c.length == 0) {
+            throw new NoDataException(LocalizedFormats.EMPTY_POLYNOMIALS_COEFFICIENTS_ARRAY);
+        }
+        if (a.length != c.length + 1) {
+            throw new DimensionMismatchException(LocalizedFormats.ARRAY_SIZES_SHOULD_HAVE_DIFFERENCE_1,
+                                                 a.length, c.length);
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/polynomials/PolynomialSplineFunction.java b/src/main/java/org/apache/commons/math3/analysis/polynomials/PolynomialSplineFunction.java
new file mode 100644
index 0000000..ed5a4f9
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/polynomials/PolynomialSplineFunction.java
@@ -0,0 +1,246 @@
+/*
+ * 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.math3.analysis.polynomials;
+
+import java.util.Arrays;
+
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NonMonotonicSequenceException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * Represents a polynomial spline function.
+ * <p>
+ * A <strong>polynomial spline function</strong> consists of a set of
+ * <i>interpolating polynomials</i> and an ascending array of domain
+ * <i>knot points</i>, determining the intervals over which the spline function
+ * is defined by the constituent polynomials.  The polynomials are assumed to
+ * have been computed to match the values of another function at the knot
+ * points.  The value consistency constraints are not currently enforced by
+ * <code>PolynomialSplineFunction</code> itself, but are assumed to hold among
+ * the polynomials and knot points passed to the constructor.</p>
+ * <p>
+ * N.B.:  The polynomials in the <code>polynomials</code> property must be
+ * centered on the knot points to compute the spline function values.
+ * See below.</p>
+ * <p>
+ * The domain of the polynomial spline function is
+ * <code>[smallest knot, largest knot]</code>.  Attempts to evaluate the
+ * function at values outside of this range generate IllegalArgumentExceptions.
+ * </p>
+ * <p>
+ * The value of the polynomial spline function for an argument <code>x</code>
+ * is computed as follows:
+ * <ol>
+ * <li>The knot array is searched to find the segment to which <code>x</code>
+ * belongs.  If <code>x</code> is less than the smallest knot point or greater
+ * than the largest one, an <code>IllegalArgumentException</code>
+ * is thrown.</li>
+ * <li> Let <code>j</code> be the index of the largest knot point that is less
+ * than or equal to <code>x</code>.  The value returned is
+ * {@code polynomials[j](x - knot[j])}</li></ol>
+ *
+ */
+public class PolynomialSplineFunction implements UnivariateDifferentiableFunction, DifferentiableUnivariateFunction {
+    /**
+     * Spline segment interval delimiters (knots).
+     * Size is n + 1 for n segments.
+     */
+    private final double knots[];
+    /**
+     * The polynomial functions that make up the spline.  The first element
+     * determines the value of the spline over the first subinterval, the
+     * second over the second, etc.   Spline function values are determined by
+     * evaluating these functions at {@code (x - knot[i])} where i is the
+     * knot segment to which x belongs.
+     */
+    private final PolynomialFunction polynomials[];
+    /**
+     * Number of spline segments. It is equal to the number of polynomials and
+     * to the number of partition points - 1.
+     */
+    private final int n;
+
+
+    /**
+     * Construct a polynomial spline function with the given segment delimiters
+     * and interpolating polynomials.
+     * The constructor copies both arrays and assigns the copies to the knots
+     * and polynomials properties, respectively.
+     *
+     * @param knots Spline segment interval delimiters.
+     * @param polynomials Polynomial functions that make up the spline.
+     * @throws NullArgumentException if either of the input arrays is {@code null}.
+     * @throws NumberIsTooSmallException if knots has length less than 2.
+     * @throws DimensionMismatchException if {@code polynomials.length != knots.length - 1}.
+     * @throws NonMonotonicSequenceException if the {@code knots} array is not strictly increasing.
+     *
+     */
+    public PolynomialSplineFunction(double knots[], PolynomialFunction polynomials[])
+        throws NullArgumentException, NumberIsTooSmallException,
+               DimensionMismatchException, NonMonotonicSequenceException{
+        if (knots == null ||
+            polynomials == null) {
+            throw new NullArgumentException();
+        }
+        if (knots.length < 2) {
+            throw new NumberIsTooSmallException(LocalizedFormats.NOT_ENOUGH_POINTS_IN_SPLINE_PARTITION,
+                                                2, knots.length, false);
+        }
+        if (knots.length - 1 != polynomials.length) {
+            throw new DimensionMismatchException(polynomials.length, knots.length);
+        }
+        MathArrays.checkOrder(knots);
+
+        this.n = knots.length -1;
+        this.knots = new double[n + 1];
+        System.arraycopy(knots, 0, this.knots, 0, n + 1);
+        this.polynomials = new PolynomialFunction[n];
+        System.arraycopy(polynomials, 0, this.polynomials, 0, n);
+    }
+
+    /**
+     * Compute the value for the function.
+     * See {@link PolynomialSplineFunction} for details on the algorithm for
+     * computing the value of the function.
+     *
+     * @param v Point for which the function value should be computed.
+     * @return the value.
+     * @throws OutOfRangeException if {@code v} is outside of the domain of the
+     * spline function (smaller than the smallest knot point or larger than the
+     * largest knot point).
+     */
+    public double value(double v) {
+        if (v < knots[0] || v > knots[n]) {
+            throw new OutOfRangeException(v, knots[0], knots[n]);
+        }
+        int i = Arrays.binarySearch(knots, v);
+        if (i < 0) {
+            i = -i - 2;
+        }
+        // This will handle the case where v is the last knot value
+        // There are only n-1 polynomials, so if v is the last knot
+        // then we will use the last polynomial to calculate the value.
+        if ( i >= polynomials.length ) {
+            i--;
+        }
+        return polynomials[i].value(v - knots[i]);
+    }
+
+    /**
+     * Get the derivative of the polynomial spline function.
+     *
+     * @return the derivative function.
+     */
+    public UnivariateFunction derivative() {
+        return polynomialSplineDerivative();
+    }
+
+    /**
+     * Get the derivative of the polynomial spline function.
+     *
+     * @return the derivative function.
+     */
+    public PolynomialSplineFunction polynomialSplineDerivative() {
+        PolynomialFunction derivativePolynomials[] = new PolynomialFunction[n];
+        for (int i = 0; i < n; i++) {
+            derivativePolynomials[i] = polynomials[i].polynomialDerivative();
+        }
+        return new PolynomialSplineFunction(knots, derivativePolynomials);
+    }
+
+
+    /** {@inheritDoc}
+     * @since 3.1
+     */
+    public DerivativeStructure value(final DerivativeStructure t) {
+        final double t0 = t.getValue();
+        if (t0 < knots[0] || t0 > knots[n]) {
+            throw new OutOfRangeException(t0, knots[0], knots[n]);
+        }
+        int i = Arrays.binarySearch(knots, t0);
+        if (i < 0) {
+            i = -i - 2;
+        }
+        // This will handle the case where t is the last knot value
+        // There are only n-1 polynomials, so if t is the last knot
+        // then we will use the last polynomial to calculate the value.
+        if ( i >= polynomials.length ) {
+            i--;
+        }
+        return polynomials[i].value(t.subtract(knots[i]));
+    }
+
+    /**
+     * Get the number of spline segments.
+     * It is also the number of polynomials and the number of knot points - 1.
+     *
+     * @return the number of spline segments.
+     */
+    public int getN() {
+        return n;
+    }
+
+    /**
+     * Get a copy of the interpolating polynomials array.
+     * It returns a fresh copy of the array. Changes made to the copy will
+     * not affect the polynomials property.
+     *
+     * @return the interpolating polynomials.
+     */
+    public PolynomialFunction[] getPolynomials() {
+        PolynomialFunction p[] = new PolynomialFunction[n];
+        System.arraycopy(polynomials, 0, p, 0, n);
+        return p;
+    }
+
+    /**
+     * Get an array copy of the knot points.
+     * It returns a fresh copy of the array. Changes made to the copy
+     * will not affect the knots property.
+     *
+     * @return the knot points.
+     */
+    public double[] getKnots() {
+        double out[] = new double[n + 1];
+        System.arraycopy(knots, 0, out, 0, n + 1);
+        return out;
+    }
+
+    /**
+     * Indicates whether a point is within the interpolation range.
+     *
+     * @param x Point.
+     * @return {@code true} if {@code x} is a valid point.
+     */
+    public boolean isValidPoint(double x) {
+        if (x < knots[0] ||
+            x > knots[n]) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/polynomials/PolynomialsUtils.java b/src/main/java/org/apache/commons/math3/analysis/polynomials/PolynomialsUtils.java
new file mode 100644
index 0000000..bb697b8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/polynomials/PolynomialsUtils.java
@@ -0,0 +1,444 @@
+/*
+ * 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.math3.analysis.polynomials;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.math3.fraction.BigFraction;
+import org.apache.commons.math3.util.CombinatoricsUtils;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * A collection of static methods that operate on or return polynomials.
+ *
+ * @since 2.0
+ */
+public class PolynomialsUtils {
+
+    /** Coefficients for Chebyshev polynomials. */
+    private static final List<BigFraction> CHEBYSHEV_COEFFICIENTS;
+
+    /** Coefficients for Hermite polynomials. */
+    private static final List<BigFraction> HERMITE_COEFFICIENTS;
+
+    /** Coefficients for Laguerre polynomials. */
+    private static final List<BigFraction> LAGUERRE_COEFFICIENTS;
+
+    /** Coefficients for Legendre polynomials. */
+    private static final List<BigFraction> LEGENDRE_COEFFICIENTS;
+
+    /** Coefficients for Jacobi polynomials. */
+    private static final Map<JacobiKey, List<BigFraction>> JACOBI_COEFFICIENTS;
+
+    static {
+
+        // initialize recurrence for Chebyshev polynomials
+        // T0(X) = 1, T1(X) = 0 + 1 * X
+        CHEBYSHEV_COEFFICIENTS = new ArrayList<BigFraction>();
+        CHEBYSHEV_COEFFICIENTS.add(BigFraction.ONE);
+        CHEBYSHEV_COEFFICIENTS.add(BigFraction.ZERO);
+        CHEBYSHEV_COEFFICIENTS.add(BigFraction.ONE);
+
+        // initialize recurrence for Hermite polynomials
+        // H0(X) = 1, H1(X) = 0 + 2 * X
+        HERMITE_COEFFICIENTS = new ArrayList<BigFraction>();
+        HERMITE_COEFFICIENTS.add(BigFraction.ONE);
+        HERMITE_COEFFICIENTS.add(BigFraction.ZERO);
+        HERMITE_COEFFICIENTS.add(BigFraction.TWO);
+
+        // initialize recurrence for Laguerre polynomials
+        // L0(X) = 1, L1(X) = 1 - 1 * X
+        LAGUERRE_COEFFICIENTS = new ArrayList<BigFraction>();
+        LAGUERRE_COEFFICIENTS.add(BigFraction.ONE);
+        LAGUERRE_COEFFICIENTS.add(BigFraction.ONE);
+        LAGUERRE_COEFFICIENTS.add(BigFraction.MINUS_ONE);
+
+        // initialize recurrence for Legendre polynomials
+        // P0(X) = 1, P1(X) = 0 + 1 * X
+        LEGENDRE_COEFFICIENTS = new ArrayList<BigFraction>();
+        LEGENDRE_COEFFICIENTS.add(BigFraction.ONE);
+        LEGENDRE_COEFFICIENTS.add(BigFraction.ZERO);
+        LEGENDRE_COEFFICIENTS.add(BigFraction.ONE);
+
+        // initialize map for Jacobi polynomials
+        JACOBI_COEFFICIENTS = new HashMap<JacobiKey, List<BigFraction>>();
+
+    }
+
+    /**
+     * Private constructor, to prevent instantiation.
+     */
+    private PolynomialsUtils() {
+    }
+
+    /**
+     * Create a Chebyshev polynomial of the first kind.
+     * <p><a href="https://en.wikipedia.org/wiki/Chebyshev_polynomials">Chebyshev
+     * polynomials of the first kind</a> are orthogonal polynomials.
+     * They can be defined by the following recurrence relations:</p><p>
+     * \(
+     *    T_0(x) = 1 \\
+     *    T_1(x) = x \\
+     *    T_{k+1}(x) = 2x T_k(x) - T_{k-1}(x)
+     * \)
+     * </p>
+     * @param degree degree of the polynomial
+     * @return Chebyshev polynomial of specified degree
+     */
+    public static PolynomialFunction createChebyshevPolynomial(final int degree) {
+        return buildPolynomial(degree, CHEBYSHEV_COEFFICIENTS,
+                new RecurrenceCoefficientsGenerator() {
+            /** Fixed recurrence coefficients. */
+            private final BigFraction[] coeffs = { BigFraction.ZERO, BigFraction.TWO, BigFraction.ONE };
+            /** {@inheritDoc} */
+            public BigFraction[] generate(int k) {
+                return coeffs;
+            }
+        });
+    }
+
+    /**
+     * Create a Hermite polynomial.
+     * <p><a href="http://mathworld.wolfram.com/HermitePolynomial.html">Hermite
+     * polynomials</a> are orthogonal polynomials.
+     * They can be defined by the following recurrence relations:</p><p>
+     * \(
+     *  H_0(x) = 1 \\
+     *  H_1(x) = 2x \\
+     *  H_{k+1}(x) = 2x H_k(X) - 2k H_{k-1}(x)
+     * \)
+     * </p>
+
+     * @param degree degree of the polynomial
+     * @return Hermite polynomial of specified degree
+     */
+    public static PolynomialFunction createHermitePolynomial(final int degree) {
+        return buildPolynomial(degree, HERMITE_COEFFICIENTS,
+                new RecurrenceCoefficientsGenerator() {
+            /** {@inheritDoc} */
+            public BigFraction[] generate(int k) {
+                return new BigFraction[] {
+                        BigFraction.ZERO,
+                        BigFraction.TWO,
+                        new BigFraction(2 * k)};
+            }
+        });
+    }
+
+    /**
+     * Create a Laguerre polynomial.
+     * <p><a href="http://mathworld.wolfram.com/LaguerrePolynomial.html">Laguerre
+     * polynomials</a> are orthogonal polynomials.
+     * They can be defined by the following recurrence relations:</p><p>
+     * \(
+     *   L_0(x) = 1 \\
+     *   L_1(x) = 1 - x \\
+     *   (k+1) L_{k+1}(x) = (2k + 1 - x) L_k(x) - k L_{k-1}(x)
+     * \)
+     * </p>
+     * @param degree degree of the polynomial
+     * @return Laguerre polynomial of specified degree
+     */
+    public static PolynomialFunction createLaguerrePolynomial(final int degree) {
+        return buildPolynomial(degree, LAGUERRE_COEFFICIENTS,
+                new RecurrenceCoefficientsGenerator() {
+            /** {@inheritDoc} */
+            public BigFraction[] generate(int k) {
+                final int kP1 = k + 1;
+                return new BigFraction[] {
+                        new BigFraction(2 * k + 1, kP1),
+                        new BigFraction(-1, kP1),
+                        new BigFraction(k, kP1)};
+            }
+        });
+    }
+
+    /**
+     * Create a Legendre polynomial.
+     * <p><a href="http://mathworld.wolfram.com/LegendrePolynomial.html">Legendre
+     * polynomials</a> are orthogonal polynomials.
+     * They can be defined by the following recurrence relations:</p><p>
+     * \(
+     *   P_0(x) = 1 \\
+     *   P_1(x) = x \\
+     *   (k+1) P_{k+1}(x) = (2k+1) x P_k(x) - k P_{k-1}(x)
+     * \)
+     * </p>
+     * @param degree degree of the polynomial
+     * @return Legendre polynomial of specified degree
+     */
+    public static PolynomialFunction createLegendrePolynomial(final int degree) {
+        return buildPolynomial(degree, LEGENDRE_COEFFICIENTS,
+                               new RecurrenceCoefficientsGenerator() {
+            /** {@inheritDoc} */
+            public BigFraction[] generate(int k) {
+                final int kP1 = k + 1;
+                return new BigFraction[] {
+                        BigFraction.ZERO,
+                        new BigFraction(k + kP1, kP1),
+                        new BigFraction(k, kP1)};
+            }
+        });
+    }
+
+    /**
+     * Create a Jacobi polynomial.
+     * <p><a href="http://mathworld.wolfram.com/JacobiPolynomial.html">Jacobi
+     * polynomials</a> are orthogonal polynomials.
+     * They can be defined by the following recurrence relations:</p><p>
+     * \(
+     *    P_0^{vw}(x) = 1 \\
+     *    P_{-1}^{vw}(x) = 0 \\
+     *    2k(k + v + w)(2k + v + w - 2) P_k^{vw}(x) = \\
+     *    (2k + v + w - 1)[(2k + v + w)(2k + v + w - 2) x + v^2 - w^2] P_{k-1}^{vw}(x) \\
+     *  - 2(k + v - 1)(k + w - 1)(2k + v + w) P_{k-2}^{vw}(x)
+     * \)
+     * </p>
+     * @param degree degree of the polynomial
+     * @param v first exponent
+     * @param w second exponent
+     * @return Jacobi polynomial of specified degree
+     */
+    public static PolynomialFunction createJacobiPolynomial(final int degree, final int v, final int w) {
+
+        // select the appropriate list
+        final JacobiKey key = new JacobiKey(v, w);
+
+        if (!JACOBI_COEFFICIENTS.containsKey(key)) {
+
+            // allocate a new list for v, w
+            final List<BigFraction> list = new ArrayList<BigFraction>();
+            JACOBI_COEFFICIENTS.put(key, list);
+
+            // Pv,w,0(x) = 1;
+            list.add(BigFraction.ONE);
+
+            // P1(x) = (v - w) / 2 + (2 + v + w) * X / 2
+            list.add(new BigFraction(v - w, 2));
+            list.add(new BigFraction(2 + v + w, 2));
+
+        }
+
+        return buildPolynomial(degree, JACOBI_COEFFICIENTS.get(key),
+                               new RecurrenceCoefficientsGenerator() {
+            /** {@inheritDoc} */
+            public BigFraction[] generate(int k) {
+                k++;
+                final int kvw      = k + v + w;
+                final int twoKvw   = kvw + k;
+                final int twoKvwM1 = twoKvw - 1;
+                final int twoKvwM2 = twoKvw - 2;
+                final int den      = 2 * k *  kvw * twoKvwM2;
+
+                return new BigFraction[] {
+                    new BigFraction(twoKvwM1 * (v * v - w * w), den),
+                    new BigFraction(twoKvwM1 * twoKvw * twoKvwM2, den),
+                    new BigFraction(2 * (k + v - 1) * (k + w - 1) * twoKvw, den)
+                };
+            }
+        });
+
+    }
+
+    /** Inner class for Jacobi polynomials keys. */
+    private static class JacobiKey {
+
+        /** First exponent. */
+        private final int v;
+
+        /** Second exponent. */
+        private final int w;
+
+        /** Simple constructor.
+         * @param v first exponent
+         * @param w second exponent
+         */
+        JacobiKey(final int v, final int w) {
+            this.v = v;
+            this.w = w;
+        }
+
+        /** Get hash code.
+         * @return hash code
+         */
+        @Override
+        public int hashCode() {
+            return (v << 16) ^ w;
+        }
+
+        /** Check if the instance represent the same key as another instance.
+         * @param key other key
+         * @return true if the instance and the other key refer to the same polynomial
+         */
+        @Override
+        public boolean equals(final Object key) {
+
+            if ((key == null) || !(key instanceof JacobiKey)) {
+                return false;
+            }
+
+            final JacobiKey otherK = (JacobiKey) key;
+            return (v == otherK.v) && (w == otherK.w);
+
+        }
+    }
+
+    /**
+     * Compute the coefficients of the polynomial \(P_s(x)\)
+     * whose values at point {@code x} will be the same as the those from the
+     * original polynomial \(P(x)\) when computed at {@code x + shift}.
+     * <p>
+     * More precisely, let \(\Delta = \) {@code shift} and let
+     * \(P_s(x) = P(x + \Delta)\).  The returned array
+     * consists of the coefficients of \(P_s\).  So if \(a_0, ..., a_{n-1}\)
+     * are the coefficients of \(P\), then the returned array
+     * \(b_0, ..., b_{n-1}\) satisfies the identity
+     * \(\sum_{i=0}^{n-1} b_i x^i = \sum_{i=0}^{n-1} a_i (x + \Delta)^i\) for all \(x\).
+     *
+     * @param coefficients Coefficients of the original polynomial.
+     * @param shift Shift value.
+     * @return the coefficients \(b_i\) of the shifted
+     * polynomial.
+     */
+    public static double[] shift(final double[] coefficients,
+                                 final double shift) {
+        final int dp1 = coefficients.length;
+        final double[] newCoefficients = new double[dp1];
+
+        // Pascal triangle.
+        final int[][] coeff = new int[dp1][dp1];
+        for (int i = 0; i < dp1; i++){
+            for(int j = 0; j <= i; j++){
+                coeff[i][j] = (int) CombinatoricsUtils.binomialCoefficient(i, j);
+            }
+        }
+
+        // First polynomial coefficient.
+        for (int i = 0; i < dp1; i++){
+            newCoefficients[0] += coefficients[i] * FastMath.pow(shift, i);
+        }
+
+        // Superior order.
+        final int d = dp1 - 1;
+        for (int i = 0; i < d; i++) {
+            for (int j = i; j < d; j++){
+                newCoefficients[i + 1] += coeff[j + 1][j - i] *
+                    coefficients[j + 1] * FastMath.pow(shift, j - i);
+            }
+        }
+
+        return newCoefficients;
+    }
+
+
+    /** Get the coefficients array for a given degree.
+     * @param degree degree of the polynomial
+     * @param coefficients list where the computed coefficients are stored
+     * @param generator recurrence coefficients generator
+     * @return coefficients array
+     */
+    private static PolynomialFunction buildPolynomial(final int degree,
+                                                      final List<BigFraction> coefficients,
+                                                      final RecurrenceCoefficientsGenerator generator) {
+        synchronized (coefficients) {
+            final int maxDegree = (int) FastMath.floor(FastMath.sqrt(2 * coefficients.size())) - 1;
+            if (degree > maxDegree) {
+                computeUpToDegree(degree, maxDegree, generator, coefficients);
+            }
+        }
+
+        // coefficient  for polynomial 0 is  l [0]
+        // coefficients for polynomial 1 are l [1] ... l [2] (degrees 0 ... 1)
+        // coefficients for polynomial 2 are l [3] ... l [5] (degrees 0 ... 2)
+        // coefficients for polynomial 3 are l [6] ... l [9] (degrees 0 ... 3)
+        // coefficients for polynomial 4 are l[10] ... l[14] (degrees 0 ... 4)
+        // coefficients for polynomial 5 are l[15] ... l[20] (degrees 0 ... 5)
+        // coefficients for polynomial 6 are l[21] ... l[27] (degrees 0 ... 6)
+        // ...
+        final int start = degree * (degree + 1) / 2;
+
+        final double[] a = new double[degree + 1];
+        for (int i = 0; i <= degree; ++i) {
+            a[i] = coefficients.get(start + i).doubleValue();
+        }
+
+        // build the polynomial
+        return new PolynomialFunction(a);
+
+    }
+
+    /** Compute polynomial coefficients up to a given degree.
+     * @param degree maximal degree
+     * @param maxDegree current maximal degree
+     * @param generator recurrence coefficients generator
+     * @param coefficients list where the computed coefficients should be appended
+     */
+    private static void computeUpToDegree(final int degree, final int maxDegree,
+                                          final RecurrenceCoefficientsGenerator generator,
+                                          final List<BigFraction> coefficients) {
+
+        int startK = (maxDegree - 1) * maxDegree / 2;
+        for (int k = maxDegree; k < degree; ++k) {
+
+            // start indices of two previous polynomials Pk(X) and Pk-1(X)
+            int startKm1 = startK;
+            startK += k;
+
+            // Pk+1(X) = (a[0] + a[1] X) Pk(X) - a[2] Pk-1(X)
+            BigFraction[] ai = generator.generate(k);
+
+            BigFraction ck     = coefficients.get(startK);
+            BigFraction ckm1   = coefficients.get(startKm1);
+
+            // degree 0 coefficient
+            coefficients.add(ck.multiply(ai[0]).subtract(ckm1.multiply(ai[2])));
+
+            // degree 1 to degree k-1 coefficients
+            for (int i = 1; i < k; ++i) {
+                final BigFraction ckPrev = ck;
+                ck     = coefficients.get(startK + i);
+                ckm1   = coefficients.get(startKm1 + i);
+                coefficients.add(ck.multiply(ai[0]).add(ckPrev.multiply(ai[1])).subtract(ckm1.multiply(ai[2])));
+            }
+
+            // degree k coefficient
+            final BigFraction ckPrev = ck;
+            ck = coefficients.get(startK + k);
+            coefficients.add(ck.multiply(ai[0]).add(ckPrev.multiply(ai[1])));
+
+            // degree k+1 coefficient
+            coefficients.add(ck.multiply(ai[1]));
+
+        }
+
+    }
+
+    /** Interface for recurrence coefficients generation. */
+    private interface RecurrenceCoefficientsGenerator {
+        /**
+         * Generate recurrence coefficients.
+         * @param k highest degree of the polynomials used in the recurrence
+         * @return an array of three coefficients such that
+         * \( P_{k+1}(x) = (a[0] + a[1] x) P_k(x) - a[2] P_{k-1}(x) \)
+         */
+        BigFraction[] generate(int k);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/polynomials/package-info.java b/src/main/java/org/apache/commons/math3/analysis/polynomials/package-info.java
new file mode 100644
index 0000000..85b99f7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/polynomials/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+/**
+ *
+ *     Univariate real polynomials implementations, seen as differentiable
+ *     univariate real functions.
+ *
+ */
+package org.apache.commons.math3.analysis.polynomials;
diff --git a/src/main/java/org/apache/commons/math3/analysis/solvers/AbstractDifferentiableUnivariateSolver.java b/src/main/java/org/apache/commons/math3/analysis/solvers/AbstractDifferentiableUnivariateSolver.java
new file mode 100644
index 0000000..d0fda00
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/solvers/AbstractDifferentiableUnivariateSolver.java
@@ -0,0 +1,82 @@
+/*
+ * 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.math3.analysis.solvers;
+
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+
+/**
+ * Provide a default implementation for several functions useful to generic
+ * solvers.
+ *
+ * @since 3.0
+ * @deprecated as of 3.1, replaced by {@link AbstractUnivariateDifferentiableSolver}
+ */
+@Deprecated
+public abstract class AbstractDifferentiableUnivariateSolver
+    extends BaseAbstractUnivariateSolver<DifferentiableUnivariateFunction>
+    implements DifferentiableUnivariateSolver {
+    /** Derivative of the function to solve. */
+    private UnivariateFunction functionDerivative;
+
+    /**
+     * Construct a solver with given absolute accuracy.
+     *
+     * @param absoluteAccuracy Maximum absolute error.
+     */
+    protected AbstractDifferentiableUnivariateSolver(final double absoluteAccuracy) {
+        super(absoluteAccuracy);
+    }
+
+    /**
+     * Construct a solver with given accuracies.
+     *
+     * @param relativeAccuracy Maximum relative error.
+     * @param absoluteAccuracy Maximum absolute error.
+     * @param functionValueAccuracy Maximum function value error.
+     */
+    protected AbstractDifferentiableUnivariateSolver(final double relativeAccuracy,
+                                                     final double absoluteAccuracy,
+                                                     final double functionValueAccuracy) {
+        super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy);
+    }
+
+    /**
+     * Compute the objective function value.
+     *
+     * @param point Point at which the objective function must be evaluated.
+     * @return the objective function value at specified point.
+     * @throws TooManyEvaluationsException if the maximal number of evaluations is exceeded.
+     */
+    protected double computeDerivativeObjectiveValue(double point)
+        throws TooManyEvaluationsException {
+        incrementEvaluationCount();
+        return functionDerivative.value(point);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void setup(int maxEval, DifferentiableUnivariateFunction f,
+                         double min, double max, double startValue) {
+        super.setup(maxEval, f, min, max, startValue);
+        functionDerivative = f.derivative();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/solvers/AbstractPolynomialSolver.java b/src/main/java/org/apache/commons/math3/analysis/solvers/AbstractPolynomialSolver.java
new file mode 100644
index 0000000..d641e87
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/solvers/AbstractPolynomialSolver.java
@@ -0,0 +1,80 @@
+/*
+ * 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.math3.analysis.solvers;
+
+import org.apache.commons.math3.analysis.polynomials.PolynomialFunction;
+
+/**
+ * Base class for solvers.
+ *
+ * @since 3.0
+ */
+public abstract class AbstractPolynomialSolver
+    extends BaseAbstractUnivariateSolver<PolynomialFunction>
+    implements PolynomialSolver {
+    /** Function. */
+    private PolynomialFunction polynomialFunction;
+
+    /**
+     * Construct a solver with given absolute accuracy.
+     *
+     * @param absoluteAccuracy Maximum absolute error.
+     */
+    protected AbstractPolynomialSolver(final double absoluteAccuracy) {
+        super(absoluteAccuracy);
+    }
+    /**
+     * Construct a solver with given accuracies.
+     *
+     * @param relativeAccuracy Maximum relative error.
+     * @param absoluteAccuracy Maximum absolute error.
+     */
+    protected AbstractPolynomialSolver(final double relativeAccuracy,
+                                       final double absoluteAccuracy) {
+        super(relativeAccuracy, absoluteAccuracy);
+    }
+    /**
+     * Construct a solver with given accuracies.
+     *
+     * @param relativeAccuracy Maximum relative error.
+     * @param absoluteAccuracy Maximum absolute error.
+     * @param functionValueAccuracy Maximum function value error.
+     */
+    protected AbstractPolynomialSolver(final double relativeAccuracy,
+                                       final double absoluteAccuracy,
+                                       final double functionValueAccuracy) {
+        super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void setup(int maxEval, PolynomialFunction f,
+                             double min, double max, double startValue) {
+        super.setup(maxEval, f, min, max, startValue);
+        polynomialFunction = f;
+    }
+
+    /**
+     * @return the coefficients of the polynomial function.
+     */
+    protected double[] getCoefficients() {
+        return polynomialFunction.getCoefficients();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/solvers/AbstractUnivariateDifferentiableSolver.java b/src/main/java/org/apache/commons/math3/analysis/solvers/AbstractUnivariateDifferentiableSolver.java
new file mode 100644
index 0000000..9745e9b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/solvers/AbstractUnivariateDifferentiableSolver.java
@@ -0,0 +1,82 @@
+/*
+ * 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.math3.analysis.solvers;
+
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+
+/**
+ * Provide a default implementation for several functions useful to generic
+ * solvers.
+ *
+ * @since 3.1
+ */
+public abstract class AbstractUnivariateDifferentiableSolver
+    extends BaseAbstractUnivariateSolver<UnivariateDifferentiableFunction>
+    implements UnivariateDifferentiableSolver {
+
+    /** Function to solve. */
+    private UnivariateDifferentiableFunction function;
+
+    /**
+     * Construct a solver with given absolute accuracy.
+     *
+     * @param absoluteAccuracy Maximum absolute error.
+     */
+    protected AbstractUnivariateDifferentiableSolver(final double absoluteAccuracy) {
+        super(absoluteAccuracy);
+    }
+
+    /**
+     * Construct a solver with given accuracies.
+     *
+     * @param relativeAccuracy Maximum relative error.
+     * @param absoluteAccuracy Maximum absolute error.
+     * @param functionValueAccuracy Maximum function value error.
+     */
+    protected AbstractUnivariateDifferentiableSolver(final double relativeAccuracy,
+                                                     final double absoluteAccuracy,
+                                                     final double functionValueAccuracy) {
+        super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy);
+    }
+
+    /**
+     * Compute the objective function value.
+     *
+     * @param point Point at which the objective function must be evaluated.
+     * @return the objective function value and derivative at specified point.
+     * @throws TooManyEvaluationsException
+     * if the maximal number of evaluations is exceeded.
+     */
+    protected DerivativeStructure computeObjectiveValueAndDerivative(double point)
+        throws TooManyEvaluationsException {
+        incrementEvaluationCount();
+        return function.value(new DerivativeStructure(1, 1, 0, point));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void setup(int maxEval, UnivariateDifferentiableFunction f,
+                         double min, double max, double startValue) {
+        super.setup(maxEval, f, min, max, startValue);
+        function = f;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/solvers/AbstractUnivariateSolver.java b/src/main/java/org/apache/commons/math3/analysis/solvers/AbstractUnivariateSolver.java
new file mode 100644
index 0000000..078c70f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/solvers/AbstractUnivariateSolver.java
@@ -0,0 +1,60 @@
+/*
+ * 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.math3.analysis.solvers;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+
+/**
+ * Base class for solvers.
+ *
+ * @since 3.0
+ */
+public abstract class AbstractUnivariateSolver
+    extends BaseAbstractUnivariateSolver<UnivariateFunction>
+    implements UnivariateSolver {
+    /**
+     * Construct a solver with given absolute accuracy.
+     *
+     * @param absoluteAccuracy Maximum absolute error.
+     */
+    protected AbstractUnivariateSolver(final double absoluteAccuracy) {
+        super(absoluteAccuracy);
+    }
+    /**
+     * Construct a solver with given accuracies.
+     *
+     * @param relativeAccuracy Maximum relative error.
+     * @param absoluteAccuracy Maximum absolute error.
+     */
+    protected AbstractUnivariateSolver(final double relativeAccuracy,
+                                       final double absoluteAccuracy) {
+        super(relativeAccuracy, absoluteAccuracy);
+    }
+    /**
+     * Construct a solver with given accuracies.
+     *
+     * @param relativeAccuracy Maximum relative error.
+     * @param absoluteAccuracy Maximum absolute error.
+     * @param functionValueAccuracy Maximum function value error.
+     */
+    protected AbstractUnivariateSolver(final double relativeAccuracy,
+                                       final double absoluteAccuracy,
+                                       final double functionValueAccuracy) {
+        super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/solvers/AllowedSolution.java b/src/main/java/org/apache/commons/math3/analysis/solvers/AllowedSolution.java
new file mode 100644
index 0000000..a02a29b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/solvers/AllowedSolution.java
@@ -0,0 +1,75 @@
+/*
+ * 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.math3.analysis.solvers;
+
+
+/** The kinds of solutions that a {@link BracketedUnivariateSolver
+ * (bracketed univariate real) root-finding algorithm} may accept as solutions.
+ * This basically controls whether or not under-approximations and
+ * over-approximations are allowed.
+ *
+ * <p>If all solutions are accepted ({@link #ANY_SIDE}), then the solution
+ * that the root-finding algorithm returns for a given root may be equal to the
+ * actual root, but it may also be an approximation that is slightly smaller
+ * or slightly larger than the actual root. Root-finding algorithms generally
+ * only guarantee that the returned solution is within the requested
+ * tolerances. In certain cases however, in particular for
+ * {@link org.apache.commons.math3.ode.events.EventHandler state events} of
+ * {@link org.apache.commons.math3.ode.ODEIntegrator ODE solvers}, it
+ * may be necessary to guarantee that a solution is returned that lies on a
+ * specific side the solution.</p>
+ *
+ * @see BracketedUnivariateSolver
+ * @since 3.0
+ */
+public enum AllowedSolution {
+    /** There are no additional side restriction on the solutions for
+     * root-finding. That is, both under-approximations and over-approximations
+     * are allowed. So, if a function f(x) has a root at x = x0, then the
+     * root-finding result s may be smaller than x0, equal to x0, or greater
+     * than x0.
+     */
+    ANY_SIDE,
+
+    /** Only solutions that are less than or equal to the actual root are
+     * acceptable as solutions for root-finding. In other words,
+     * over-approximations are not allowed. So, if a function f(x) has a root
+     * at x = x0, then the root-finding result s must satisfy s &lt;= x0.
+     */
+    LEFT_SIDE,
+
+    /** Only solutions that are greater than or equal to the actual root are
+     * acceptable as solutions for root-finding. In other words,
+     * under-approximations are not allowed. So, if a function f(x) has a root
+     * at x = x0, then the root-finding result s must satisfy s &gt;= x0.
+     */
+    RIGHT_SIDE,
+
+    /** Only solutions for which values are less than or equal to zero are
+     * acceptable as solutions for root-finding. So, if a function f(x) has
+     * a root at x = x0, then the root-finding result s must satisfy f(s) &lt;= 0.
+     */
+    BELOW_SIDE,
+
+    /** Only solutions for which values are greater than or equal to zero are
+     * acceptable as solutions for root-finding. So, if a function f(x) has
+     * a root at x = x0, then the root-finding result s must satisfy f(s) &gt;= 0.
+     */
+    ABOVE_SIDE;
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/solvers/BaseAbstractUnivariateSolver.java b/src/main/java/org/apache/commons/math3/analysis/solvers/BaseAbstractUnivariateSolver.java
new file mode 100644
index 0000000..12b30c6
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/solvers/BaseAbstractUnivariateSolver.java
@@ -0,0 +1,318 @@
+/*
+ * 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.math3.analysis.solvers;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NoBracketingException;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.util.IntegerSequence;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * Provide a default implementation for several functions useful to generic
+ * solvers.
+ * The default values for relative and function tolerances are 1e-14
+ * and 1e-15, respectively. It is however highly recommended to not
+ * rely on the default, but rather carefully consider values that match
+ * user's expectations, as well as the specifics of each implementation.
+ *
+ * @param <FUNC> Type of function to solve.
+ *
+ * @since 2.0
+ */
+public abstract class BaseAbstractUnivariateSolver<FUNC extends UnivariateFunction>
+    implements BaseUnivariateSolver<FUNC> {
+    /** Default relative accuracy. */
+    private static final double DEFAULT_RELATIVE_ACCURACY = 1e-14;
+    /** Default function value accuracy. */
+    private static final double DEFAULT_FUNCTION_VALUE_ACCURACY = 1e-15;
+    /** Function value accuracy. */
+    private final double functionValueAccuracy;
+    /** Absolute accuracy. */
+    private final double absoluteAccuracy;
+    /** Relative accuracy. */
+    private final double relativeAccuracy;
+    /** Evaluations counter. */
+    private IntegerSequence.Incrementor evaluations;
+    /** Lower end of search interval. */
+    private double searchMin;
+    /** Higher end of search interval. */
+    private double searchMax;
+    /** Initial guess. */
+    private double searchStart;
+    /** Function to solve. */
+    private FUNC function;
+
+    /**
+     * Construct a solver with given absolute accuracy.
+     *
+     * @param absoluteAccuracy Maximum absolute error.
+     */
+    protected BaseAbstractUnivariateSolver(final double absoluteAccuracy) {
+        this(DEFAULT_RELATIVE_ACCURACY,
+             absoluteAccuracy,
+             DEFAULT_FUNCTION_VALUE_ACCURACY);
+    }
+
+    /**
+     * Construct a solver with given accuracies.
+     *
+     * @param relativeAccuracy Maximum relative error.
+     * @param absoluteAccuracy Maximum absolute error.
+     */
+    protected BaseAbstractUnivariateSolver(final double relativeAccuracy,
+                                           final double absoluteAccuracy) {
+        this(relativeAccuracy,
+             absoluteAccuracy,
+             DEFAULT_FUNCTION_VALUE_ACCURACY);
+    }
+
+    /**
+     * Construct a solver with given accuracies.
+     *
+     * @param relativeAccuracy Maximum relative error.
+     * @param absoluteAccuracy Maximum absolute error.
+     * @param functionValueAccuracy Maximum function value error.
+     */
+    protected BaseAbstractUnivariateSolver(final double relativeAccuracy,
+                                           final double absoluteAccuracy,
+                                           final double functionValueAccuracy) {
+        this.absoluteAccuracy      = absoluteAccuracy;
+        this.relativeAccuracy      = relativeAccuracy;
+        this.functionValueAccuracy = functionValueAccuracy;
+        this.evaluations           = IntegerSequence.Incrementor.create();
+    }
+
+    /** {@inheritDoc} */
+    public int getMaxEvaluations() {
+        return evaluations.getMaximalCount();
+    }
+    /** {@inheritDoc} */
+    public int getEvaluations() {
+        return evaluations.getCount();
+    }
+    /**
+     * @return the lower end of the search interval.
+     */
+    public double getMin() {
+        return searchMin;
+    }
+    /**
+     * @return the higher end of the search interval.
+     */
+    public double getMax() {
+        return searchMax;
+    }
+    /**
+     * @return the initial guess.
+     */
+    public double getStartValue() {
+        return searchStart;
+    }
+    /**
+     * {@inheritDoc}
+     */
+    public double getAbsoluteAccuracy() {
+        return absoluteAccuracy;
+    }
+    /**
+     * {@inheritDoc}
+     */
+    public double getRelativeAccuracy() {
+        return relativeAccuracy;
+    }
+    /**
+     * {@inheritDoc}
+     */
+    public double getFunctionValueAccuracy() {
+        return functionValueAccuracy;
+    }
+
+    /**
+     * Compute the objective function value.
+     *
+     * @param point Point at which the objective function must be evaluated.
+     * @return the objective function value at specified point.
+     * @throws TooManyEvaluationsException if the maximal number of evaluations
+     * is exceeded.
+     */
+    protected double computeObjectiveValue(double point)
+        throws TooManyEvaluationsException {
+        incrementEvaluationCount();
+        return function.value(point);
+    }
+
+    /**
+     * Prepare for computation.
+     * Subclasses must call this method if they override any of the
+     * {@code solve} methods.
+     *
+     * @param f Function to solve.
+     * @param min Lower bound for the interval.
+     * @param max Upper bound for the interval.
+     * @param startValue Start value to use.
+     * @param maxEval Maximum number of evaluations.
+     * @exception NullArgumentException if f is null
+     */
+    protected void setup(int maxEval,
+                         FUNC f,
+                         double min, double max,
+                         double startValue)
+        throws NullArgumentException {
+        // Checks.
+        MathUtils.checkNotNull(f);
+
+        // Reset.
+        searchMin = min;
+        searchMax = max;
+        searchStart = startValue;
+        function = f;
+        evaluations = evaluations.withMaximalCount(maxEval).withStart(0);
+    }
+
+    /** {@inheritDoc} */
+    public double solve(int maxEval, FUNC f, double min, double max, double startValue)
+        throws TooManyEvaluationsException,
+               NoBracketingException {
+        // Initialization.
+        setup(maxEval, f, min, max, startValue);
+
+        // Perform computation.
+        return doSolve();
+    }
+
+    /** {@inheritDoc} */
+    public double solve(int maxEval, FUNC f, double min, double max) {
+        return solve(maxEval, f, min, max, min + 0.5 * (max - min));
+    }
+
+    /** {@inheritDoc} */
+    public double solve(int maxEval, FUNC f, double startValue)
+        throws TooManyEvaluationsException,
+               NoBracketingException {
+        return solve(maxEval, f, Double.NaN, Double.NaN, startValue);
+    }
+
+    /**
+     * Method for implementing actual optimization algorithms in derived
+     * classes.
+     *
+     * @return the root.
+     * @throws TooManyEvaluationsException if the maximal number of evaluations
+     * is exceeded.
+     * @throws NoBracketingException if the initial search interval does not bracket
+     * a root and the solver requires it.
+     */
+    protected abstract double doSolve()
+        throws TooManyEvaluationsException, NoBracketingException;
+
+    /**
+     * Check whether the function takes opposite signs at the endpoints.
+     *
+     * @param lower Lower endpoint.
+     * @param upper Upper endpoint.
+     * @return {@code true} if the function values have opposite signs at the
+     * given points.
+     */
+    protected boolean isBracketing(final double lower,
+                                   final double upper) {
+        return UnivariateSolverUtils.isBracketing(function, lower, upper);
+    }
+
+    /**
+     * Check whether the arguments form a (strictly) increasing sequence.
+     *
+     * @param start First number.
+     * @param mid Second number.
+     * @param end Third number.
+     * @return {@code true} if the arguments form an increasing sequence.
+     */
+    protected boolean isSequence(final double start,
+                                 final double mid,
+                                 final double end) {
+        return UnivariateSolverUtils.isSequence(start, mid, end);
+    }
+
+    /**
+     * Check that the endpoints specify an interval.
+     *
+     * @param lower Lower endpoint.
+     * @param upper Upper endpoint.
+     * @throws NumberIsTooLargeException if {@code lower >= upper}.
+     */
+    protected void verifyInterval(final double lower,
+                                  final double upper)
+        throws NumberIsTooLargeException {
+        UnivariateSolverUtils.verifyInterval(lower, upper);
+    }
+
+    /**
+     * Check that {@code lower < initial < upper}.
+     *
+     * @param lower Lower endpoint.
+     * @param initial Initial value.
+     * @param upper Upper endpoint.
+     * @throws NumberIsTooLargeException if {@code lower >= initial} or
+     * {@code initial >= upper}.
+     */
+    protected void verifySequence(final double lower,
+                                  final double initial,
+                                  final double upper)
+        throws NumberIsTooLargeException {
+        UnivariateSolverUtils.verifySequence(lower, initial, upper);
+    }
+
+    /**
+     * Check that the endpoints specify an interval and the function takes
+     * opposite signs at the endpoints.
+     *
+     * @param lower Lower endpoint.
+     * @param upper Upper endpoint.
+     * @throws NullArgumentException if the function has not been set.
+     * @throws NoBracketingException if the function has the same sign at
+     * the endpoints.
+     */
+    protected void verifyBracketing(final double lower,
+                                    final double upper)
+        throws NullArgumentException,
+               NoBracketingException {
+        UnivariateSolverUtils.verifyBracketing(function, lower, upper);
+    }
+
+    /**
+     * Increment the evaluation count by one.
+     * Method {@link #computeObjectiveValue(double)} calls this method internally.
+     * It is provided for subclasses that do not exclusively use
+     * {@code computeObjectiveValue} to solve the function.
+     * See e.g. {@link AbstractUnivariateDifferentiableSolver}.
+     *
+     * @throws TooManyEvaluationsException when the allowed number of function
+     * evaluations has been exhausted.
+     */
+    protected void incrementEvaluationCount()
+        throws TooManyEvaluationsException {
+        try {
+            evaluations.increment();
+        } catch (MaxCountExceededException e) {
+            throw new TooManyEvaluationsException(e.getMax());
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/solvers/BaseSecantSolver.java b/src/main/java/org/apache/commons/math3/analysis/solvers/BaseSecantSolver.java
new file mode 100644
index 0000000..44a2173
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/solvers/BaseSecantSolver.java
@@ -0,0 +1,278 @@
+/*
+ * 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.math3.analysis.solvers;
+
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.exception.ConvergenceException;
+import org.apache.commons.math3.exception.MathInternalError;
+
+/**
+ * Base class for all bracketing <em>Secant</em>-based methods for root-finding
+ * (approximating a zero of a univariate real function).
+ *
+ * <p>Implementation of the {@link RegulaFalsiSolver <em>Regula Falsi</em>} and
+ * {@link IllinoisSolver <em>Illinois</em>} methods is based on the
+ * following article: M. Dowell and P. Jarratt,
+ * <em>A modified regula falsi method for computing the root of an
+ * equation</em>, BIT Numerical Mathematics, volume 11, number 2,
+ * pages 168-174, Springer, 1971.</p>
+ *
+ * <p>Implementation of the {@link PegasusSolver <em>Pegasus</em>} method is
+ * based on the following article: M. Dowell and P. Jarratt,
+ * <em>The "Pegasus" method for computing the root of an equation</em>,
+ * BIT Numerical Mathematics, volume 12, number 4, pages 503-508, Springer,
+ * 1972.</p>
+ *
+ * <p>The {@link SecantSolver <em>Secant</em>} method is <em>not</em> a
+ * bracketing method, so it is not implemented here. It has a separate
+ * implementation.</p>
+ *
+ * @since 3.0
+ */
+public abstract class BaseSecantSolver
+    extends AbstractUnivariateSolver
+    implements BracketedUnivariateSolver<UnivariateFunction> {
+
+    /** Default absolute accuracy. */
+    protected static final double DEFAULT_ABSOLUTE_ACCURACY = 1e-6;
+
+    /** The kinds of solutions that the algorithm may accept. */
+    private AllowedSolution allowed;
+
+    /** The <em>Secant</em>-based root-finding method to use. */
+    private final Method method;
+
+    /**
+     * Construct a solver.
+     *
+     * @param absoluteAccuracy Absolute accuracy.
+     * @param method <em>Secant</em>-based root-finding method to use.
+     */
+    protected BaseSecantSolver(final double absoluteAccuracy, final Method method) {
+        super(absoluteAccuracy);
+        this.allowed = AllowedSolution.ANY_SIDE;
+        this.method = method;
+    }
+
+    /**
+     * Construct a solver.
+     *
+     * @param relativeAccuracy Relative accuracy.
+     * @param absoluteAccuracy Absolute accuracy.
+     * @param method <em>Secant</em>-based root-finding method to use.
+     */
+    protected BaseSecantSolver(final double relativeAccuracy,
+                               final double absoluteAccuracy,
+                               final Method method) {
+        super(relativeAccuracy, absoluteAccuracy);
+        this.allowed = AllowedSolution.ANY_SIDE;
+        this.method = method;
+    }
+
+    /**
+     * Construct a solver.
+     *
+     * @param relativeAccuracy Maximum relative error.
+     * @param absoluteAccuracy Maximum absolute error.
+     * @param functionValueAccuracy Maximum function value error.
+     * @param method <em>Secant</em>-based root-finding method to use
+     */
+    protected BaseSecantSolver(final double relativeAccuracy,
+                               final double absoluteAccuracy,
+                               final double functionValueAccuracy,
+                               final Method method) {
+        super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy);
+        this.allowed = AllowedSolution.ANY_SIDE;
+        this.method = method;
+    }
+
+    /** {@inheritDoc} */
+    public double solve(final int maxEval, final UnivariateFunction f,
+                        final double min, final double max,
+                        final AllowedSolution allowedSolution) {
+        return solve(maxEval, f, min, max, min + 0.5 * (max - min), allowedSolution);
+    }
+
+    /** {@inheritDoc} */
+    public double solve(final int maxEval, final UnivariateFunction f,
+                        final double min, final double max, final double startValue,
+                        final AllowedSolution allowedSolution) {
+        this.allowed = allowedSolution;
+        return super.solve(maxEval, f, min, max, startValue);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double solve(final int maxEval, final UnivariateFunction f,
+                        final double min, final double max, final double startValue) {
+        return solve(maxEval, f, min, max, startValue, AllowedSolution.ANY_SIDE);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws ConvergenceException if the algorithm failed due to finite
+     * precision.
+     */
+    @Override
+    protected final double doSolve()
+        throws ConvergenceException {
+        // Get initial solution
+        double x0 = getMin();
+        double x1 = getMax();
+        double f0 = computeObjectiveValue(x0);
+        double f1 = computeObjectiveValue(x1);
+
+        // If one of the bounds is the exact root, return it. Since these are
+        // not under-approximations or over-approximations, we can return them
+        // regardless of the allowed solutions.
+        if (f0 == 0.0) {
+            return x0;
+        }
+        if (f1 == 0.0) {
+            return x1;
+        }
+
+        // Verify bracketing of initial solution.
+        verifyBracketing(x0, x1);
+
+        // Get accuracies.
+        final double ftol = getFunctionValueAccuracy();
+        final double atol = getAbsoluteAccuracy();
+        final double rtol = getRelativeAccuracy();
+
+        // Keep track of inverted intervals, meaning that the left bound is
+        // larger than the right bound.
+        boolean inverted = false;
+
+        // Keep finding better approximations.
+        while (true) {
+            // Calculate the next approximation.
+            final double x = x1 - ((f1 * (x1 - x0)) / (f1 - f0));
+            final double fx = computeObjectiveValue(x);
+
+            // If the new approximation is the exact root, return it. Since
+            // this is not an under-approximation or an over-approximation,
+            // we can return it regardless of the allowed solutions.
+            if (fx == 0.0) {
+                return x;
+            }
+
+            // Update the bounds with the new approximation.
+            if (f1 * fx < 0) {
+                // The value of x1 has switched to the other bound, thus inverting
+                // the interval.
+                x0 = x1;
+                f0 = f1;
+                inverted = !inverted;
+            } else {
+                switch (method) {
+                case ILLINOIS:
+                    f0 *= 0.5;
+                    break;
+                case PEGASUS:
+                    f0 *= f1 / (f1 + fx);
+                    break;
+                case REGULA_FALSI:
+                    // Detect early that algorithm is stuck, instead of waiting
+                    // for the maximum number of iterations to be exceeded.
+                    if (x == x1) {
+                        throw new ConvergenceException();
+                    }
+                    break;
+                default:
+                    // Should never happen.
+                    throw new MathInternalError();
+                }
+            }
+            // Update from [x0, x1] to [x0, x].
+            x1 = x;
+            f1 = fx;
+
+            // If the function value of the last approximation is too small,
+            // given the function value accuracy, then we can't get closer to
+            // the root than we already are.
+            if (FastMath.abs(f1) <= ftol) {
+                switch (allowed) {
+                case ANY_SIDE:
+                    return x1;
+                case LEFT_SIDE:
+                    if (inverted) {
+                        return x1;
+                    }
+                    break;
+                case RIGHT_SIDE:
+                    if (!inverted) {
+                        return x1;
+                    }
+                    break;
+                case BELOW_SIDE:
+                    if (f1 <= 0) {
+                        return x1;
+                    }
+                    break;
+                case ABOVE_SIDE:
+                    if (f1 >= 0) {
+                        return x1;
+                    }
+                    break;
+                default:
+                    throw new MathInternalError();
+                }
+            }
+
+            // If the current interval is within the given accuracies, we
+            // are satisfied with the current approximation.
+            if (FastMath.abs(x1 - x0) < FastMath.max(rtol * FastMath.abs(x1),
+                                                     atol)) {
+                switch (allowed) {
+                case ANY_SIDE:
+                    return x1;
+                case LEFT_SIDE:
+                    return inverted ? x1 : x0;
+                case RIGHT_SIDE:
+                    return inverted ? x0 : x1;
+                case BELOW_SIDE:
+                    return (f1 <= 0) ? x1 : x0;
+                case ABOVE_SIDE:
+                    return (f1 >= 0) ? x1 : x0;
+                default:
+                    throw new MathInternalError();
+                }
+            }
+        }
+    }
+
+    /** <em>Secant</em>-based root-finding methods. */
+    protected enum Method {
+
+        /**
+         * The {@link RegulaFalsiSolver <em>Regula Falsi</em>} or
+         * <em>False Position</em> method.
+         */
+        REGULA_FALSI,
+
+        /** The {@link IllinoisSolver <em>Illinois</em>} method. */
+        ILLINOIS,
+
+        /** The {@link PegasusSolver <em>Pegasus</em>} method. */
+        PEGASUS;
+
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/solvers/BaseUnivariateSolver.java b/src/main/java/org/apache/commons/math3/analysis/solvers/BaseUnivariateSolver.java
new file mode 100644
index 0000000..f00590e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/solvers/BaseUnivariateSolver.java
@@ -0,0 +1,142 @@
+/*
+ * 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.math3.analysis.solvers;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+
+
+/**
+ * Interface for (univariate real) rootfinding algorithms.
+ * Implementations will search for only one zero in the given interval.
+ *
+ * This class is not intended for use outside of the Apache Commons Math
+ * library, regular user should rely on more specific interfaces like
+ * {@link UnivariateSolver}, {@link PolynomialSolver} or {@link
+ * DifferentiableUnivariateSolver}.
+ * @param <FUNC> Type of function to solve.
+ *
+ * @since 3.0
+ * @see UnivariateSolver
+ * @see PolynomialSolver
+ * @see DifferentiableUnivariateSolver
+ */
+public interface BaseUnivariateSolver<FUNC extends UnivariateFunction> {
+    /**
+     * Get the maximum number of function evaluations.
+     *
+     * @return the maximum number of function evaluations.
+     */
+    int getMaxEvaluations();
+
+    /**
+     * Get the number of evaluations of the objective function.
+     * The number of evaluations corresponds to the last call to the
+     * {@code optimize} method. It is 0 if the method has not been
+     * called yet.
+     *
+     * @return the number of evaluations of the objective function.
+     */
+    int getEvaluations();
+
+    /**
+     * Get the absolute accuracy of the solver.  Solutions returned by the
+     * solver should be accurate to this tolerance, i.e., if &epsilon; is the
+     * absolute accuracy of the solver and {@code v} is a value returned by
+     * one of the {@code solve} methods, then a root of the function should
+     * exist somewhere in the interval ({@code v} - &epsilon;, {@code v} + &epsilon;).
+     *
+     * @return the absolute accuracy.
+     */
+    double getAbsoluteAccuracy();
+
+    /**
+     * Get the relative accuracy of the solver.  The contract for relative
+     * accuracy is the same as {@link #getAbsoluteAccuracy()}, but using
+     * relative, rather than absolute error.  If &rho; is the relative accuracy
+     * configured for a solver and {@code v} is a value returned, then a root
+     * of the function should exist somewhere in the interval
+     * ({@code v} - &rho; {@code v}, {@code v} + &rho; {@code v}).
+     *
+     * @return the relative accuracy.
+     */
+    double getRelativeAccuracy();
+
+    /**
+     * Get the function value accuracy of the solver.  If {@code v} is
+     * a value returned by the solver for a function {@code f},
+     * then by contract, {@code |f(v)|} should be less than or equal to
+     * the function value accuracy configured for the solver.
+     *
+     * @return the function value accuracy.
+     */
+    double getFunctionValueAccuracy();
+
+    /**
+     * Solve for a zero root in the given interval.
+     * A solver may require that the interval brackets a single zero root.
+     * Solvers that do require bracketing should be able to handle the case
+     * where one of the endpoints is itself a root.
+     *
+     * @param maxEval Maximum number of evaluations.
+     * @param f Function to solve.
+     * @param min Lower bound for the interval.
+     * @param max Upper bound for the interval.
+     * @return a value where the function is zero.
+     * @throws MathIllegalArgumentException
+     * if the arguments do not satisfy the requirements specified by the solver.
+     * @throws TooManyEvaluationsException if
+     * the allowed number of evaluations is exceeded.
+     */
+    double solve(int maxEval, FUNC f, double min, double max)
+        throws MathIllegalArgumentException, TooManyEvaluationsException;
+
+    /**
+     * Solve for a zero in the given interval, start at {@code startValue}.
+     * A solver may require that the interval brackets a single zero root.
+     * Solvers that do require bracketing should be able to handle the case
+     * where one of the endpoints is itself a root.
+     *
+     * @param maxEval Maximum number of evaluations.
+     * @param f Function to solve.
+     * @param min Lower bound for the interval.
+     * @param max Upper bound for the interval.
+     * @param startValue Start value to use.
+     * @return a value where the function is zero.
+     * @throws MathIllegalArgumentException
+     * if the arguments do not satisfy the requirements specified by the solver.
+     * @throws TooManyEvaluationsException if
+     * the allowed number of evaluations is exceeded.
+     */
+    double solve(int maxEval, FUNC f, double min, double max, double startValue)
+        throws MathIllegalArgumentException, TooManyEvaluationsException;
+
+    /**
+     * Solve for a zero in the vicinity of {@code startValue}.
+     *
+     * @param f Function to solve.
+     * @param startValue Start value to use.
+     * @return a value where the function is zero.
+     * @param maxEval Maximum number of evaluations.
+     * @throws org.apache.commons.math3.exception.MathIllegalArgumentException
+     * if the arguments do not satisfy the requirements specified by the solver.
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException if
+     * the allowed number of evaluations is exceeded.
+     */
+    double solve(int maxEval, FUNC f, double startValue);
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/solvers/BisectionSolver.java b/src/main/java/org/apache/commons/math3/analysis/solvers/BisectionSolver.java
new file mode 100644
index 0000000..49f4057
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/solvers/BisectionSolver.java
@@ -0,0 +1,91 @@
+/*
+ * 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.math3.analysis.solvers;
+
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+
+/**
+ * Implements the <a href="http://mathworld.wolfram.com/Bisection.html">
+ * bisection algorithm</a> for finding zeros of univariate real functions.
+ * <p>
+ * The function should be continuous but not necessarily smooth.</p>
+ *
+ */
+public class BisectionSolver extends AbstractUnivariateSolver {
+    /** Default absolute accuracy. */
+    private static final double DEFAULT_ABSOLUTE_ACCURACY = 1e-6;
+
+    /**
+     * Construct a solver with default accuracy (1e-6).
+     */
+    public BisectionSolver() {
+        this(DEFAULT_ABSOLUTE_ACCURACY);
+    }
+    /**
+     * Construct a solver.
+     *
+     * @param absoluteAccuracy Absolute accuracy.
+     */
+    public BisectionSolver(double absoluteAccuracy) {
+        super(absoluteAccuracy);
+    }
+    /**
+     * Construct a solver.
+     *
+     * @param relativeAccuracy Relative accuracy.
+     * @param absoluteAccuracy Absolute accuracy.
+     */
+    public BisectionSolver(double relativeAccuracy,
+                           double absoluteAccuracy) {
+        super(relativeAccuracy, absoluteAccuracy);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected double doSolve()
+        throws TooManyEvaluationsException {
+        double min = getMin();
+        double max = getMax();
+        verifyInterval(min, max);
+        final double absoluteAccuracy = getAbsoluteAccuracy();
+        double m;
+        double fm;
+        double fmin;
+
+        while (true) {
+            m = UnivariateSolverUtils.midpoint(min, max);
+            fmin = computeObjectiveValue(min);
+            fm = computeObjectiveValue(m);
+
+            if (fm * fmin > 0) {
+                // max and m bracket the root.
+                min = m;
+            } else {
+                // min and m bracket the root.
+                max = m;
+            }
+
+            if (FastMath.abs(max - min) <= absoluteAccuracy) {
+                m = UnivariateSolverUtils.midpoint(min, max);
+                return m;
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/solvers/BracketedRealFieldUnivariateSolver.java b/src/main/java/org/apache/commons/math3/analysis/solvers/BracketedRealFieldUnivariateSolver.java
new file mode 100644
index 0000000..55562e5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/solvers/BracketedRealFieldUnivariateSolver.java
@@ -0,0 +1,142 @@
+/*
+ * 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.math3.analysis.solvers;
+
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.analysis.RealFieldUnivariateFunction;
+
+/** Interface for {@link UnivariateSolver (univariate real) root-finding
+ * algorithms} that maintain a bracketed solution. There are several advantages
+ * to having such root-finding algorithms:
+ * <ul>
+ *  <li>The bracketed solution guarantees that the root is kept within the
+ *      interval. As such, these algorithms generally also guarantee
+ *      convergence.</li>
+ *  <li>The bracketed solution means that we have the opportunity to only
+ *      return roots that are greater than or equal to the actual root, or
+ *      are less than or equal to the actual root. That is, we can control
+ *      whether under-approximations and over-approximations are
+ *      {@link AllowedSolution allowed solutions}. Other root-finding
+ *      algorithms can usually only guarantee that the solution (the root that
+ *      was found) is around the actual root.</li>
+ * </ul>
+ *
+ * <p>For backwards compatibility, all root-finding algorithms must have
+ * {@link AllowedSolution#ANY_SIDE ANY_SIDE} as default for the allowed
+ * solutions.</p>
+ *
+ * @see AllowedSolution
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+public interface BracketedRealFieldUnivariateSolver<T extends RealFieldElement<T>> {
+
+    /**
+     * Get the maximum number of function evaluations.
+     *
+     * @return the maximum number of function evaluations.
+     */
+    int getMaxEvaluations();
+
+    /**
+     * Get the number of evaluations of the objective function.
+     * The number of evaluations corresponds to the last call to the
+     * {@code optimize} method. It is 0 if the method has not been
+     * called yet.
+     *
+     * @return the number of evaluations of the objective function.
+     */
+    int getEvaluations();
+
+    /**
+     * Get the absolute accuracy of the solver.  Solutions returned by the
+     * solver should be accurate to this tolerance, i.e., if &epsilon; is the
+     * absolute accuracy of the solver and {@code v} is a value returned by
+     * one of the {@code solve} methods, then a root of the function should
+     * exist somewhere in the interval ({@code v} - &epsilon;, {@code v} + &epsilon;).
+     *
+     * @return the absolute accuracy.
+     */
+    T getAbsoluteAccuracy();
+
+    /**
+     * Get the relative accuracy of the solver.  The contract for relative
+     * accuracy is the same as {@link #getAbsoluteAccuracy()}, but using
+     * relative, rather than absolute error.  If &rho; is the relative accuracy
+     * configured for a solver and {@code v} is a value returned, then a root
+     * of the function should exist somewhere in the interval
+     * ({@code v} - &rho; {@code v}, {@code v} + &rho; {@code v}).
+     *
+     * @return the relative accuracy.
+     */
+    T getRelativeAccuracy();
+
+    /**
+     * Get the function value accuracy of the solver.  If {@code v} is
+     * a value returned by the solver for a function {@code f},
+     * then by contract, {@code |f(v)|} should be less than or equal to
+     * the function value accuracy configured for the solver.
+     *
+     * @return the function value accuracy.
+     */
+    T getFunctionValueAccuracy();
+
+    /**
+     * Solve for a zero in the given interval.
+     * A solver may require that the interval brackets a single zero root.
+     * Solvers that do require bracketing should be able to handle the case
+     * where one of the endpoints is itself a root.
+     *
+     * @param maxEval Maximum number of evaluations.
+     * @param f Function to solve.
+     * @param min Lower bound for the interval.
+     * @param max Upper bound for the interval.
+     * @param allowedSolution The kind of solutions that the root-finding algorithm may
+     * accept as solutions.
+     * @return A value where the function is zero.
+     * @throws org.apache.commons.math3.exception.MathIllegalArgumentException
+     * if the arguments do not satisfy the requirements specified by the solver.
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException if
+     * the allowed number of evaluations is exceeded.
+     */
+    T solve(int maxEval, RealFieldUnivariateFunction<T> f, T min, T max,
+            AllowedSolution allowedSolution);
+
+    /**
+     * Solve for a zero in the given interval, start at {@code startValue}.
+     * A solver may require that the interval brackets a single zero root.
+     * Solvers that do require bracketing should be able to handle the case
+     * where one of the endpoints is itself a root.
+     *
+     * @param maxEval Maximum number of evaluations.
+     * @param f Function to solve.
+     * @param min Lower bound for the interval.
+     * @param max Upper bound for the interval.
+     * @param startValue Start value to use.
+     * @param allowedSolution The kind of solutions that the root-finding algorithm may
+     * accept as solutions.
+     * @return A value where the function is zero.
+     * @throws org.apache.commons.math3.exception.MathIllegalArgumentException
+     * if the arguments do not satisfy the requirements specified by the solver.
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException if
+     * the allowed number of evaluations is exceeded.
+     */
+    T solve(int maxEval, RealFieldUnivariateFunction<T> f, T min, T max, T startValue,
+            AllowedSolution allowedSolution);
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/solvers/BracketedUnivariateSolver.java b/src/main/java/org/apache/commons/math3/analysis/solvers/BracketedUnivariateSolver.java
new file mode 100644
index 0000000..789fc99
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/solvers/BracketedUnivariateSolver.java
@@ -0,0 +1,92 @@
+/*
+ * 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.math3.analysis.solvers;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+
+/** Interface for {@link UnivariateSolver (univariate real) root-finding
+ * algorithms} that maintain a bracketed solution. There are several advantages
+ * to having such root-finding algorithms:
+ * <ul>
+ *  <li>The bracketed solution guarantees that the root is kept within the
+ *      interval. As such, these algorithms generally also guarantee
+ *      convergence.</li>
+ *  <li>The bracketed solution means that we have the opportunity to only
+ *      return roots that are greater than or equal to the actual root, or
+ *      are less than or equal to the actual root. That is, we can control
+ *      whether under-approximations and over-approximations are
+ *      {@link AllowedSolution allowed solutions}. Other root-finding
+ *      algorithms can usually only guarantee that the solution (the root that
+ *      was found) is around the actual root.</li>
+ * </ul>
+ *
+ * <p>For backwards compatibility, all root-finding algorithms must have
+ * {@link AllowedSolution#ANY_SIDE ANY_SIDE} as default for the allowed
+ * solutions.</p>
+ * @param <FUNC> Type of function to solve.
+ *
+ * @see AllowedSolution
+ * @since 3.0
+ */
+public interface BracketedUnivariateSolver<FUNC extends UnivariateFunction>
+    extends BaseUnivariateSolver<FUNC> {
+
+    /**
+     * Solve for a zero in the given interval.
+     * A solver may require that the interval brackets a single zero root.
+     * Solvers that do require bracketing should be able to handle the case
+     * where one of the endpoints is itself a root.
+     *
+     * @param maxEval Maximum number of evaluations.
+     * @param f Function to solve.
+     * @param min Lower bound for the interval.
+     * @param max Upper bound for the interval.
+     * @param allowedSolution The kind of solutions that the root-finding algorithm may
+     * accept as solutions.
+     * @return A value where the function is zero.
+     * @throws org.apache.commons.math3.exception.MathIllegalArgumentException
+     * if the arguments do not satisfy the requirements specified by the solver.
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException if
+     * the allowed number of evaluations is exceeded.
+     */
+    double solve(int maxEval, FUNC f, double min, double max,
+                 AllowedSolution allowedSolution);
+
+    /**
+     * Solve for a zero in the given interval, start at {@code startValue}.
+     * A solver may require that the interval brackets a single zero root.
+     * Solvers that do require bracketing should be able to handle the case
+     * where one of the endpoints is itself a root.
+     *
+     * @param maxEval Maximum number of evaluations.
+     * @param f Function to solve.
+     * @param min Lower bound for the interval.
+     * @param max Upper bound for the interval.
+     * @param startValue Start value to use.
+     * @param allowedSolution The kind of solutions that the root-finding algorithm may
+     * accept as solutions.
+     * @return A value where the function is zero.
+     * @throws org.apache.commons.math3.exception.MathIllegalArgumentException
+     * if the arguments do not satisfy the requirements specified by the solver.
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException if
+     * the allowed number of evaluations is exceeded.
+     */
+    double solve(int maxEval, FUNC f, double min, double max, double startValue,
+                 AllowedSolution allowedSolution);
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/solvers/BracketingNthOrderBrentSolver.java b/src/main/java/org/apache/commons/math3/analysis/solvers/BracketingNthOrderBrentSolver.java
new file mode 100644
index 0000000..956bf9e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/solvers/BracketingNthOrderBrentSolver.java
@@ -0,0 +1,411 @@
+/*
+ * 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.math3.analysis.solvers;
+
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.exception.MathInternalError;
+import org.apache.commons.math3.exception.NoBracketingException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.Precision;
+
+/**
+ * This class implements a modification of the <a
+ * href="http://mathworld.wolfram.com/BrentsMethod.html"> Brent algorithm</a>.
+ * <p>
+ * The changes with respect to the original Brent algorithm are:
+ * <ul>
+ *   <li>the returned value is chosen in the current interval according
+ *   to user specified {@link AllowedSolution},</li>
+ *   <li>the maximal order for the invert polynomial root search is
+ *   user-specified instead of being invert quadratic only</li>
+ * </ul><p>
+ * The given interval must bracket the root.</p>
+ *
+ */
+public class BracketingNthOrderBrentSolver
+    extends AbstractUnivariateSolver
+    implements BracketedUnivariateSolver<UnivariateFunction> {
+
+    /** Default absolute accuracy. */
+    private static final double DEFAULT_ABSOLUTE_ACCURACY = 1e-6;
+
+    /** Default maximal order. */
+    private static final int DEFAULT_MAXIMAL_ORDER = 5;
+
+    /** Maximal aging triggering an attempt to balance the bracketing interval. */
+    private static final int MAXIMAL_AGING = 2;
+
+    /** Reduction factor for attempts to balance the bracketing interval. */
+    private static final double REDUCTION_FACTOR = 1.0 / 16.0;
+
+    /** Maximal order. */
+    private final int maximalOrder;
+
+    /** The kinds of solutions that the algorithm may accept. */
+    private AllowedSolution allowed;
+
+    /**
+     * Construct a solver with default accuracy and maximal order (1e-6 and 5 respectively)
+     */
+    public BracketingNthOrderBrentSolver() {
+        this(DEFAULT_ABSOLUTE_ACCURACY, DEFAULT_MAXIMAL_ORDER);
+    }
+
+    /**
+     * Construct a solver.
+     *
+     * @param absoluteAccuracy Absolute accuracy.
+     * @param maximalOrder maximal order.
+     * @exception NumberIsTooSmallException if maximal order is lower than 2
+     */
+    public BracketingNthOrderBrentSolver(final double absoluteAccuracy,
+                                         final int maximalOrder)
+        throws NumberIsTooSmallException {
+        super(absoluteAccuracy);
+        if (maximalOrder < 2) {
+            throw new NumberIsTooSmallException(maximalOrder, 2, true);
+        }
+        this.maximalOrder = maximalOrder;
+        this.allowed = AllowedSolution.ANY_SIDE;
+    }
+
+    /**
+     * Construct a solver.
+     *
+     * @param relativeAccuracy Relative accuracy.
+     * @param absoluteAccuracy Absolute accuracy.
+     * @param maximalOrder maximal order.
+     * @exception NumberIsTooSmallException if maximal order is lower than 2
+     */
+    public BracketingNthOrderBrentSolver(final double relativeAccuracy,
+                                         final double absoluteAccuracy,
+                                         final int maximalOrder)
+        throws NumberIsTooSmallException {
+        super(relativeAccuracy, absoluteAccuracy);
+        if (maximalOrder < 2) {
+            throw new NumberIsTooSmallException(maximalOrder, 2, true);
+        }
+        this.maximalOrder = maximalOrder;
+        this.allowed = AllowedSolution.ANY_SIDE;
+    }
+
+    /**
+     * Construct a solver.
+     *
+     * @param relativeAccuracy Relative accuracy.
+     * @param absoluteAccuracy Absolute accuracy.
+     * @param functionValueAccuracy Function value accuracy.
+     * @param maximalOrder maximal order.
+     * @exception NumberIsTooSmallException if maximal order is lower than 2
+     */
+    public BracketingNthOrderBrentSolver(final double relativeAccuracy,
+                                         final double absoluteAccuracy,
+                                         final double functionValueAccuracy,
+                                         final int maximalOrder)
+        throws NumberIsTooSmallException {
+        super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy);
+        if (maximalOrder < 2) {
+            throw new NumberIsTooSmallException(maximalOrder, 2, true);
+        }
+        this.maximalOrder = maximalOrder;
+        this.allowed = AllowedSolution.ANY_SIDE;
+    }
+
+    /** Get the maximal order.
+     * @return maximal order
+     */
+    public int getMaximalOrder() {
+        return maximalOrder;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected double doSolve()
+        throws TooManyEvaluationsException,
+               NumberIsTooLargeException,
+               NoBracketingException {
+        // prepare arrays with the first points
+        final double[] x = new double[maximalOrder + 1];
+        final double[] y = new double[maximalOrder + 1];
+        x[0] = getMin();
+        x[1] = getStartValue();
+        x[2] = getMax();
+        verifySequence(x[0], x[1], x[2]);
+
+        // evaluate initial guess
+        y[1] = computeObjectiveValue(x[1]);
+        if (Precision.equals(y[1], 0.0, 1)) {
+            // return the initial guess if it is a perfect root.
+            return x[1];
+        }
+
+        // evaluate first  endpoint
+        y[0] = computeObjectiveValue(x[0]);
+        if (Precision.equals(y[0], 0.0, 1)) {
+            // return the first endpoint if it is a perfect root.
+            return x[0];
+        }
+
+        int nbPoints;
+        int signChangeIndex;
+        if (y[0] * y[1] < 0) {
+
+            // reduce interval if it brackets the root
+            nbPoints        = 2;
+            signChangeIndex = 1;
+
+        } else {
+
+            // evaluate second endpoint
+            y[2] = computeObjectiveValue(x[2]);
+            if (Precision.equals(y[2], 0.0, 1)) {
+                // return the second endpoint if it is a perfect root.
+                return x[2];
+            }
+
+            if (y[1] * y[2] < 0) {
+                // use all computed point as a start sampling array for solving
+                nbPoints        = 3;
+                signChangeIndex = 2;
+            } else {
+                throw new NoBracketingException(x[0], x[2], y[0], y[2]);
+            }
+
+        }
+
+        // prepare a work array for inverse polynomial interpolation
+        final double[] tmpX = new double[x.length];
+
+        // current tightest bracketing of the root
+        double xA    = x[signChangeIndex - 1];
+        double yA    = y[signChangeIndex - 1];
+        double absYA = FastMath.abs(yA);
+        int agingA   = 0;
+        double xB    = x[signChangeIndex];
+        double yB    = y[signChangeIndex];
+        double absYB = FastMath.abs(yB);
+        int agingB   = 0;
+
+        // search loop
+        while (true) {
+
+            // check convergence of bracketing interval
+            final double xTol = getAbsoluteAccuracy() +
+                                getRelativeAccuracy() * FastMath.max(FastMath.abs(xA), FastMath.abs(xB));
+            if (((xB - xA) <= xTol) || (FastMath.max(absYA, absYB) < getFunctionValueAccuracy())) {
+                switch (allowed) {
+                case ANY_SIDE :
+                    return absYA < absYB ? xA : xB;
+                case LEFT_SIDE :
+                    return xA;
+                case RIGHT_SIDE :
+                    return xB;
+                case BELOW_SIDE :
+                    return (yA <= 0) ? xA : xB;
+                case ABOVE_SIDE :
+                    return (yA <  0) ? xB : xA;
+                default :
+                    // this should never happen
+                    throw new MathInternalError();
+                }
+            }
+
+            // target for the next evaluation point
+            double targetY;
+            if (agingA >= MAXIMAL_AGING) {
+                // we keep updating the high bracket, try to compensate this
+                final int p = agingA - MAXIMAL_AGING;
+                final double weightA = (1 << p) - 1;
+                final double weightB = p + 1;
+                targetY = (weightA * yA - weightB * REDUCTION_FACTOR * yB) / (weightA + weightB);
+            } else if (agingB >= MAXIMAL_AGING) {
+                // we keep updating the low bracket, try to compensate this
+                final int p = agingB - MAXIMAL_AGING;
+                final double weightA = p + 1;
+                final double weightB = (1 << p) - 1;
+                targetY = (weightB * yB - weightA * REDUCTION_FACTOR * yA) / (weightA + weightB);
+            } else {
+                // bracketing is balanced, try to find the root itself
+                targetY = 0;
+            }
+
+            // make a few attempts to guess a root,
+            double nextX;
+            int start = 0;
+            int end   = nbPoints;
+            do {
+
+                // guess a value for current target, using inverse polynomial interpolation
+                System.arraycopy(x, start, tmpX, start, end - start);
+                nextX = guessX(targetY, tmpX, y, start, end);
+
+                if (!((nextX > xA) && (nextX < xB))) {
+                    // the guessed root is not strictly inside of the tightest bracketing interval
+
+                    // the guessed root is either not strictly inside the interval or it
+                    // is a NaN (which occurs when some sampling points share the same y)
+                    // we try again with a lower interpolation order
+                    if (signChangeIndex - start >= end - signChangeIndex) {
+                        // we have more points before the sign change, drop the lowest point
+                        ++start;
+                    } else {
+                        // we have more points after sign change, drop the highest point
+                        --end;
+                    }
+
+                    // we need to do one more attempt
+                    nextX = Double.NaN;
+
+                }
+
+            } while (Double.isNaN(nextX) && (end - start > 1));
+
+            if (Double.isNaN(nextX)) {
+                // fall back to bisection
+                nextX = xA + 0.5 * (xB - xA);
+                start = signChangeIndex - 1;
+                end   = signChangeIndex;
+            }
+
+            // evaluate the function at the guessed root
+            final double nextY = computeObjectiveValue(nextX);
+            if (Precision.equals(nextY, 0.0, 1)) {
+                // we have found an exact root, since it is not an approximation
+                // we don't need to bother about the allowed solutions setting
+                return nextX;
+            }
+
+            if ((nbPoints > 2) && (end - start != nbPoints)) {
+
+                // we have been forced to ignore some points to keep bracketing,
+                // they are probably too far from the root, drop them from now on
+                nbPoints = end - start;
+                System.arraycopy(x, start, x, 0, nbPoints);
+                System.arraycopy(y, start, y, 0, nbPoints);
+                signChangeIndex -= start;
+
+            } else  if (nbPoints == x.length) {
+
+                // we have to drop one point in order to insert the new one
+                nbPoints--;
+
+                // keep the tightest bracketing interval as centered as possible
+                if (signChangeIndex >= (x.length + 1) / 2) {
+                    // we drop the lowest point, we have to shift the arrays and the index
+                    System.arraycopy(x, 1, x, 0, nbPoints);
+                    System.arraycopy(y, 1, y, 0, nbPoints);
+                    --signChangeIndex;
+                }
+
+            }
+
+            // insert the last computed point
+            //(by construction, we know it lies inside the tightest bracketing interval)
+            System.arraycopy(x, signChangeIndex, x, signChangeIndex + 1, nbPoints - signChangeIndex);
+            x[signChangeIndex] = nextX;
+            System.arraycopy(y, signChangeIndex, y, signChangeIndex + 1, nbPoints - signChangeIndex);
+            y[signChangeIndex] = nextY;
+            ++nbPoints;
+
+            // update the bracketing interval
+            if (nextY * yA <= 0) {
+                // the sign change occurs before the inserted point
+                xB = nextX;
+                yB = nextY;
+                absYB = FastMath.abs(yB);
+                ++agingA;
+                agingB = 0;
+            } else {
+                // the sign change occurs after the inserted point
+                xA = nextX;
+                yA = nextY;
+                absYA = FastMath.abs(yA);
+                agingA = 0;
+                ++agingB;
+
+                // update the sign change index
+                signChangeIndex++;
+
+            }
+
+        }
+
+    }
+
+    /** Guess an x value by n<sup>th</sup> order inverse polynomial interpolation.
+     * <p>
+     * The x value is guessed by evaluating polynomial Q(y) at y = targetY, where Q
+     * is built such that for all considered points (x<sub>i</sub>, y<sub>i</sub>),
+     * Q(y<sub>i</sub>) = x<sub>i</sub>.
+     * </p>
+     * @param targetY target value for y
+     * @param x reference points abscissas for interpolation,
+     * note that this array <em>is</em> modified during computation
+     * @param y reference points ordinates for interpolation
+     * @param start start index of the points to consider (inclusive)
+     * @param end end index of the points to consider (exclusive)
+     * @return guessed root (will be a NaN if two points share the same y)
+     */
+    private double guessX(final double targetY, final double[] x, final double[] y,
+                          final int start, final int end) {
+
+        // compute Q Newton coefficients by divided differences
+        for (int i = start; i < end - 1; ++i) {
+            final int delta = i + 1 - start;
+            for (int j = end - 1; j > i; --j) {
+                x[j] = (x[j] - x[j-1]) / (y[j] - y[j - delta]);
+            }
+        }
+
+        // evaluate Q(targetY)
+        double x0 = 0;
+        for (int j = end - 1; j >= start; --j) {
+            x0 = x[j] + x0 * (targetY - y[j]);
+        }
+
+        return x0;
+
+    }
+
+    /** {@inheritDoc} */
+    public double solve(int maxEval, UnivariateFunction f, double min,
+                        double max, AllowedSolution allowedSolution)
+        throws TooManyEvaluationsException,
+               NumberIsTooLargeException,
+               NoBracketingException {
+        this.allowed = allowedSolution;
+        return super.solve(maxEval, f, min, max);
+    }
+
+    /** {@inheritDoc} */
+    public double solve(int maxEval, UnivariateFunction f, double min,
+                        double max, double startValue,
+                        AllowedSolution allowedSolution)
+        throws TooManyEvaluationsException,
+               NumberIsTooLargeException,
+               NoBracketingException {
+        this.allowed = allowedSolution;
+        return super.solve(maxEval, f, min, max, startValue);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/solvers/BrentSolver.java b/src/main/java/org/apache/commons/math3/analysis/solvers/BrentSolver.java
new file mode 100644
index 0000000..cf69410
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/solvers/BrentSolver.java
@@ -0,0 +1,243 @@
+/*
+ * 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.math3.analysis.solvers;
+
+
+import org.apache.commons.math3.exception.NoBracketingException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.Precision;
+
+/**
+ * This class implements the <a href="http://mathworld.wolfram.com/BrentsMethod.html">
+ * Brent algorithm</a> for finding zeros of real univariate functions.
+ * The function should be continuous but not necessarily smooth.
+ * The {@code solve} method returns a zero {@code x} of the function {@code f}
+ * in the given interval {@code [a, b]} to within a tolerance
+ * {@code 2 eps abs(x) + t} where {@code eps} is the relative accuracy and
+ * {@code t} is the absolute accuracy.
+ * <p>The given interval must bracket the root.</p>
+ * <p>
+ *  The reference implementation is given in chapter 4 of
+ *  <blockquote>
+ *   <b>Algorithms for Minimization Without Derivatives</b>,
+ *   <em>Richard P. Brent</em>,
+ *   Dover, 2002
+ *  </blockquote>
+ *
+ * @see BaseAbstractUnivariateSolver
+ */
+public class BrentSolver extends AbstractUnivariateSolver {
+
+    /** Default absolute accuracy. */
+    private static final double DEFAULT_ABSOLUTE_ACCURACY = 1e-6;
+
+    /**
+     * Construct a solver with default absolute accuracy (1e-6).
+     */
+    public BrentSolver() {
+        this(DEFAULT_ABSOLUTE_ACCURACY);
+    }
+    /**
+     * Construct a solver.
+     *
+     * @param absoluteAccuracy Absolute accuracy.
+     */
+    public BrentSolver(double absoluteAccuracy) {
+        super(absoluteAccuracy);
+    }
+    /**
+     * Construct a solver.
+     *
+     * @param relativeAccuracy Relative accuracy.
+     * @param absoluteAccuracy Absolute accuracy.
+     */
+    public BrentSolver(double relativeAccuracy,
+                       double absoluteAccuracy) {
+        super(relativeAccuracy, absoluteAccuracy);
+    }
+    /**
+     * Construct a solver.
+     *
+     * @param relativeAccuracy Relative accuracy.
+     * @param absoluteAccuracy Absolute accuracy.
+     * @param functionValueAccuracy Function value accuracy.
+     *
+     * @see BaseAbstractUnivariateSolver#BaseAbstractUnivariateSolver(double,double,double)
+     */
+    public BrentSolver(double relativeAccuracy,
+                       double absoluteAccuracy,
+                       double functionValueAccuracy) {
+        super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected double doSolve()
+        throws NoBracketingException,
+               TooManyEvaluationsException,
+               NumberIsTooLargeException {
+        double min = getMin();
+        double max = getMax();
+        final double initial = getStartValue();
+        final double functionValueAccuracy = getFunctionValueAccuracy();
+
+        verifySequence(min, initial, max);
+
+        // Return the initial guess if it is good enough.
+        double yInitial = computeObjectiveValue(initial);
+        if (FastMath.abs(yInitial) <= functionValueAccuracy) {
+            return initial;
+        }
+
+        // Return the first endpoint if it is good enough.
+        double yMin = computeObjectiveValue(min);
+        if (FastMath.abs(yMin) <= functionValueAccuracy) {
+            return min;
+        }
+
+        // Reduce interval if min and initial bracket the root.
+        if (yInitial * yMin < 0) {
+            return brent(min, initial, yMin, yInitial);
+        }
+
+        // Return the second endpoint if it is good enough.
+        double yMax = computeObjectiveValue(max);
+        if (FastMath.abs(yMax) <= functionValueAccuracy) {
+            return max;
+        }
+
+        // Reduce interval if initial and max bracket the root.
+        if (yInitial * yMax < 0) {
+            return brent(initial, max, yInitial, yMax);
+        }
+
+        throw new NoBracketingException(min, max, yMin, yMax);
+    }
+
+    /**
+     * Search for a zero inside the provided interval.
+     * This implementation is based on the algorithm described at page 58 of
+     * the book
+     * <blockquote>
+     *  <b>Algorithms for Minimization Without Derivatives</b>,
+     *  <it>Richard P. Brent</it>,
+     *  Dover 0-486-41998-3
+     * </blockquote>
+     *
+     * @param lo Lower bound of the search interval.
+     * @param hi Higher bound of the search interval.
+     * @param fLo Function value at the lower bound of the search interval.
+     * @param fHi Function value at the higher bound of the search interval.
+     * @return the value where the function is zero.
+     */
+    private double brent(double lo, double hi,
+                         double fLo, double fHi) {
+        double a = lo;
+        double fa = fLo;
+        double b = hi;
+        double fb = fHi;
+        double c = a;
+        double fc = fa;
+        double d = b - a;
+        double e = d;
+
+        final double t = getAbsoluteAccuracy();
+        final double eps = getRelativeAccuracy();
+
+        while (true) {
+            if (FastMath.abs(fc) < FastMath.abs(fb)) {
+                a = b;
+                b = c;
+                c = a;
+                fa = fb;
+                fb = fc;
+                fc = fa;
+            }
+
+            final double tol = 2 * eps * FastMath.abs(b) + t;
+            final double m = 0.5 * (c - b);
+
+            if (FastMath.abs(m) <= tol ||
+                Precision.equals(fb, 0))  {
+                return b;
+            }
+            if (FastMath.abs(e) < tol ||
+                FastMath.abs(fa) <= FastMath.abs(fb)) {
+                // Force bisection.
+                d = m;
+                e = d;
+            } else {
+                double s = fb / fa;
+                double p;
+                double q;
+                // The equality test (a == c) is intentional,
+                // it is part of the original Brent's method and
+                // it should NOT be replaced by proximity test.
+                if (a == c) {
+                    // Linear interpolation.
+                    p = 2 * m * s;
+                    q = 1 - s;
+                } else {
+                    // Inverse quadratic interpolation.
+                    q = fa / fc;
+                    final double r = fb / fc;
+                    p = s * (2 * m * q * (q - r) - (b - a) * (r - 1));
+                    q = (q - 1) * (r - 1) * (s - 1);
+                }
+                if (p > 0) {
+                    q = -q;
+                } else {
+                    p = -p;
+                }
+                s = e;
+                e = d;
+                if (p >= 1.5 * m * q - FastMath.abs(tol * q) ||
+                    p >= FastMath.abs(0.5 * s * q)) {
+                    // Inverse quadratic interpolation gives a value
+                    // in the wrong direction, or progress is slow.
+                    // Fall back to bisection.
+                    d = m;
+                    e = d;
+                } else {
+                    d = p / q;
+                }
+            }
+            a = b;
+            fa = fb;
+
+            if (FastMath.abs(d) > tol) {
+                b += d;
+            } else if (m > 0) {
+                b += tol;
+            } else {
+                b -= tol;
+            }
+            fb = computeObjectiveValue(b);
+            if ((fb > 0 && fc > 0) ||
+                (fb <= 0 && fc <= 0)) {
+                c = a;
+                fc = fa;
+                d = b - a;
+                e = d;
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/solvers/DifferentiableUnivariateSolver.java b/src/main/java/org/apache/commons/math3/analysis/solvers/DifferentiableUnivariateSolver.java
new file mode 100644
index 0000000..b9ae158
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/solvers/DifferentiableUnivariateSolver.java
@@ -0,0 +1,30 @@
+/*
+ * 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.math3.analysis.solvers;
+
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+
+
+/**
+ * Interface for (univariate real) rootfinding algorithms.
+ * Implementations will search for only one zero in the given interval.
+ *
+ * @deprecated as of 3.1, replaced by {@link UnivariateDifferentiableSolver}
+ */
+@Deprecated
+public interface DifferentiableUnivariateSolver
+    extends BaseUnivariateSolver<DifferentiableUnivariateFunction> {}
diff --git a/src/main/java/org/apache/commons/math3/analysis/solvers/FieldBracketingNthOrderBrentSolver.java b/src/main/java/org/apache/commons/math3/analysis/solvers/FieldBracketingNthOrderBrentSolver.java
new file mode 100644
index 0000000..f0ca8b9
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/solvers/FieldBracketingNthOrderBrentSolver.java
@@ -0,0 +1,446 @@
+/*
+ * 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.math3.analysis.solvers;
+
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.analysis.RealFieldUnivariateFunction;
+import org.apache.commons.math3.exception.MathInternalError;
+import org.apache.commons.math3.exception.NoBracketingException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.util.IntegerSequence;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.util.MathUtils;
+import org.apache.commons.math3.util.Precision;
+
+/**
+ * This class implements a modification of the <a
+ * href="http://mathworld.wolfram.com/BrentsMethod.html"> Brent algorithm</a>.
+ * <p>
+ * The changes with respect to the original Brent algorithm are:
+ * <ul>
+ *   <li>the returned value is chosen in the current interval according
+ *   to user specified {@link AllowedSolution}</li>
+ *   <li>the maximal order for the invert polynomial root search is
+ *   user-specified instead of being invert quadratic only</li>
+ * </ul><p>
+ * The given interval must bracket the root.</p>
+ *
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+public class FieldBracketingNthOrderBrentSolver<T extends RealFieldElement<T>>
+    implements BracketedRealFieldUnivariateSolver<T> {
+
+   /** Maximal aging triggering an attempt to balance the bracketing interval. */
+    private static final int MAXIMAL_AGING = 2;
+
+    /** Field to which the elements belong. */
+    private final Field<T> field;
+
+    /** Maximal order. */
+    private final int maximalOrder;
+
+    /** Function value accuracy. */
+    private final T functionValueAccuracy;
+
+    /** Absolute accuracy. */
+    private final T absoluteAccuracy;
+
+    /** Relative accuracy. */
+    private final T relativeAccuracy;
+
+    /** Evaluations counter. */
+    private IntegerSequence.Incrementor evaluations;
+
+    /**
+     * Construct a solver.
+     *
+     * @param relativeAccuracy Relative accuracy.
+     * @param absoluteAccuracy Absolute accuracy.
+     * @param functionValueAccuracy Function value accuracy.
+     * @param maximalOrder maximal order.
+     * @exception NumberIsTooSmallException if maximal order is lower than 2
+     */
+    public FieldBracketingNthOrderBrentSolver(final T relativeAccuracy,
+                                              final T absoluteAccuracy,
+                                              final T functionValueAccuracy,
+                                              final int maximalOrder)
+        throws NumberIsTooSmallException {
+        if (maximalOrder < 2) {
+            throw new NumberIsTooSmallException(maximalOrder, 2, true);
+        }
+        this.field                 = relativeAccuracy.getField();
+        this.maximalOrder          = maximalOrder;
+        this.absoluteAccuracy      = absoluteAccuracy;
+        this.relativeAccuracy      = relativeAccuracy;
+        this.functionValueAccuracy = functionValueAccuracy;
+        this.evaluations           = IntegerSequence.Incrementor.create();
+    }
+
+    /** Get the maximal order.
+     * @return maximal order
+     */
+    public int getMaximalOrder() {
+        return maximalOrder;
+    }
+
+    /**
+     * Get the maximal number of function evaluations.
+     *
+     * @return the maximal number of function evaluations.
+     */
+    public int getMaxEvaluations() {
+        return evaluations.getMaximalCount();
+    }
+
+    /**
+     * Get the number of evaluations of the objective function.
+     * The number of evaluations corresponds to the last call to the
+     * {@code optimize} method. It is 0 if the method has not been
+     * called yet.
+     *
+     * @return the number of evaluations of the objective function.
+     */
+    public int getEvaluations() {
+        return evaluations.getCount();
+    }
+
+    /**
+     * Get the absolute accuracy.
+     * @return absolute accuracy
+     */
+    public T getAbsoluteAccuracy() {
+        return absoluteAccuracy;
+    }
+
+    /**
+     * Get the relative accuracy.
+     * @return relative accuracy
+     */
+    public T getRelativeAccuracy() {
+        return relativeAccuracy;
+    }
+
+    /**
+     * Get the function accuracy.
+     * @return function accuracy
+     */
+    public T getFunctionValueAccuracy() {
+        return functionValueAccuracy;
+    }
+
+    /**
+     * Solve for a zero in the given interval.
+     * A solver may require that the interval brackets a single zero root.
+     * Solvers that do require bracketing should be able to handle the case
+     * where one of the endpoints is itself a root.
+     *
+     * @param maxEval Maximum number of evaluations.
+     * @param f Function to solve.
+     * @param min Lower bound for the interval.
+     * @param max Upper bound for the interval.
+     * @param allowedSolution The kind of solutions that the root-finding algorithm may
+     * accept as solutions.
+     * @return a value where the function is zero.
+     * @exception NullArgumentException if f is null.
+     * @exception NoBracketingException if root cannot be bracketed
+     */
+    public T solve(final int maxEval, final RealFieldUnivariateFunction<T> f,
+                   final T min, final T max, final AllowedSolution allowedSolution)
+        throws NullArgumentException, NoBracketingException {
+        return solve(maxEval, f, min, max, min.add(max).divide(2), allowedSolution);
+    }
+
+    /**
+     * Solve for a zero in the given interval, start at {@code startValue}.
+     * A solver may require that the interval brackets a single zero root.
+     * Solvers that do require bracketing should be able to handle the case
+     * where one of the endpoints is itself a root.
+     *
+     * @param maxEval Maximum number of evaluations.
+     * @param f Function to solve.
+     * @param min Lower bound for the interval.
+     * @param max Upper bound for the interval.
+     * @param startValue Start value to use.
+     * @param allowedSolution The kind of solutions that the root-finding algorithm may
+     * accept as solutions.
+     * @return a value where the function is zero.
+     * @exception NullArgumentException if f is null.
+     * @exception NoBracketingException if root cannot be bracketed
+     */
+    public T solve(final int maxEval, final RealFieldUnivariateFunction<T> f,
+                   final T min, final T max, final T startValue,
+                   final AllowedSolution allowedSolution)
+        throws NullArgumentException, NoBracketingException {
+
+        // Checks.
+        MathUtils.checkNotNull(f);
+
+        // Reset.
+        evaluations = evaluations.withMaximalCount(maxEval).withStart(0);
+        T zero = field.getZero();
+        T nan  = zero.add(Double.NaN);
+
+        // prepare arrays with the first points
+        final T[] x = MathArrays.buildArray(field, maximalOrder + 1);
+        final T[] y = MathArrays.buildArray(field, maximalOrder + 1);
+        x[0] = min;
+        x[1] = startValue;
+        x[2] = max;
+
+        // evaluate initial guess
+        evaluations.increment();
+        y[1] = f.value(x[1]);
+        if (Precision.equals(y[1].getReal(), 0.0, 1)) {
+            // return the initial guess if it is a perfect root.
+            return x[1];
+        }
+
+        // evaluate first endpoint
+        evaluations.increment();
+        y[0] = f.value(x[0]);
+        if (Precision.equals(y[0].getReal(), 0.0, 1)) {
+            // return the first endpoint if it is a perfect root.
+            return x[0];
+        }
+
+        int nbPoints;
+        int signChangeIndex;
+        if (y[0].multiply(y[1]).getReal() < 0) {
+
+            // reduce interval if it brackets the root
+            nbPoints        = 2;
+            signChangeIndex = 1;
+
+        } else {
+
+            // evaluate second endpoint
+            evaluations.increment();
+            y[2] = f.value(x[2]);
+            if (Precision.equals(y[2].getReal(), 0.0, 1)) {
+                // return the second endpoint if it is a perfect root.
+                return x[2];
+            }
+
+            if (y[1].multiply(y[2]).getReal() < 0) {
+                // use all computed point as a start sampling array for solving
+                nbPoints        = 3;
+                signChangeIndex = 2;
+            } else {
+                throw new NoBracketingException(x[0].getReal(), x[2].getReal(),
+                                                y[0].getReal(), y[2].getReal());
+            }
+
+        }
+
+        // prepare a work array for inverse polynomial interpolation
+        final T[] tmpX = MathArrays.buildArray(field, x.length);
+
+        // current tightest bracketing of the root
+        T xA    = x[signChangeIndex - 1];
+        T yA    = y[signChangeIndex - 1];
+        T absXA = xA.abs();
+        T absYA = yA.abs();
+        int agingA   = 0;
+        T xB    = x[signChangeIndex];
+        T yB    = y[signChangeIndex];
+        T absXB = xB.abs();
+        T absYB = yB.abs();
+        int agingB   = 0;
+
+        // search loop
+        while (true) {
+
+            // check convergence of bracketing interval
+            T maxX = absXA.subtract(absXB).getReal() < 0 ? absXB : absXA;
+            T maxY = absYA.subtract(absYB).getReal() < 0 ? absYB : absYA;
+            final T xTol = absoluteAccuracy.add(relativeAccuracy.multiply(maxX));
+            if (xB.subtract(xA).subtract(xTol).getReal() <= 0 ||
+                maxY.subtract(functionValueAccuracy).getReal() < 0) {
+                switch (allowedSolution) {
+                case ANY_SIDE :
+                    return absYA.subtract(absYB).getReal() < 0 ? xA : xB;
+                case LEFT_SIDE :
+                    return xA;
+                case RIGHT_SIDE :
+                    return xB;
+                case BELOW_SIDE :
+                    return yA.getReal() <= 0 ? xA : xB;
+                case ABOVE_SIDE :
+                    return yA.getReal() < 0 ? xB : xA;
+                default :
+                    // this should never happen
+                    throw new MathInternalError(null);
+                }
+            }
+
+            // target for the next evaluation point
+            T targetY;
+            if (agingA >= MAXIMAL_AGING) {
+                // we keep updating the high bracket, try to compensate this
+                targetY = yB.divide(16).negate();
+            } else if (agingB >= MAXIMAL_AGING) {
+                // we keep updating the low bracket, try to compensate this
+                targetY = yA.divide(16).negate();
+            } else {
+                // bracketing is balanced, try to find the root itself
+                targetY = zero;
+            }
+
+            // make a few attempts to guess a root,
+            T nextX;
+            int start = 0;
+            int end   = nbPoints;
+            do {
+
+                // guess a value for current target, using inverse polynomial interpolation
+                System.arraycopy(x, start, tmpX, start, end - start);
+                nextX = guessX(targetY, tmpX, y, start, end);
+
+                if (!((nextX.subtract(xA).getReal() > 0) && (nextX.subtract(xB).getReal() < 0))) {
+                    // the guessed root is not strictly inside of the tightest bracketing interval
+
+                    // the guessed root is either not strictly inside the interval or it
+                    // is a NaN (which occurs when some sampling points share the same y)
+                    // we try again with a lower interpolation order
+                    if (signChangeIndex - start >= end - signChangeIndex) {
+                        // we have more points before the sign change, drop the lowest point
+                        ++start;
+                    } else {
+                        // we have more points after sign change, drop the highest point
+                        --end;
+                    }
+
+                    // we need to do one more attempt
+                    nextX = nan;
+
+                }
+
+            } while (Double.isNaN(nextX.getReal()) && (end - start > 1));
+
+            if (Double.isNaN(nextX.getReal())) {
+                // fall back to bisection
+                nextX = xA.add(xB.subtract(xA).divide(2));
+                start = signChangeIndex - 1;
+                end   = signChangeIndex;
+            }
+
+            // evaluate the function at the guessed root
+            evaluations.increment();
+            final T nextY = f.value(nextX);
+            if (Precision.equals(nextY.getReal(), 0.0, 1)) {
+                // we have found an exact root, since it is not an approximation
+                // we don't need to bother about the allowed solutions setting
+                return nextX;
+            }
+
+            if ((nbPoints > 2) && (end - start != nbPoints)) {
+
+                // we have been forced to ignore some points to keep bracketing,
+                // they are probably too far from the root, drop them from now on
+                nbPoints = end - start;
+                System.arraycopy(x, start, x, 0, nbPoints);
+                System.arraycopy(y, start, y, 0, nbPoints);
+                signChangeIndex -= start;
+
+            } else  if (nbPoints == x.length) {
+
+                // we have to drop one point in order to insert the new one
+                nbPoints--;
+
+                // keep the tightest bracketing interval as centered as possible
+                if (signChangeIndex >= (x.length + 1) / 2) {
+                    // we drop the lowest point, we have to shift the arrays and the index
+                    System.arraycopy(x, 1, x, 0, nbPoints);
+                    System.arraycopy(y, 1, y, 0, nbPoints);
+                    --signChangeIndex;
+                }
+
+            }
+
+            // insert the last computed point
+            //(by construction, we know it lies inside the tightest bracketing interval)
+            System.arraycopy(x, signChangeIndex, x, signChangeIndex + 1, nbPoints - signChangeIndex);
+            x[signChangeIndex] = nextX;
+            System.arraycopy(y, signChangeIndex, y, signChangeIndex + 1, nbPoints - signChangeIndex);
+            y[signChangeIndex] = nextY;
+            ++nbPoints;
+
+            // update the bracketing interval
+            if (nextY.multiply(yA).getReal() <= 0) {
+                // the sign change occurs before the inserted point
+                xB = nextX;
+                yB = nextY;
+                absYB = yB.abs();
+                ++agingA;
+                agingB = 0;
+            } else {
+                // the sign change occurs after the inserted point
+                xA = nextX;
+                yA = nextY;
+                absYA = yA.abs();
+                agingA = 0;
+                ++agingB;
+
+                // update the sign change index
+                signChangeIndex++;
+
+            }
+
+        }
+
+    }
+
+    /** Guess an x value by n<sup>th</sup> order inverse polynomial interpolation.
+     * <p>
+     * The x value is guessed by evaluating polynomial Q(y) at y = targetY, where Q
+     * is built such that for all considered points (x<sub>i</sub>, y<sub>i</sub>),
+     * Q(y<sub>i</sub>) = x<sub>i</sub>.
+     * </p>
+     * @param targetY target value for y
+     * @param x reference points abscissas for interpolation,
+     * note that this array <em>is</em> modified during computation
+     * @param y reference points ordinates for interpolation
+     * @param start start index of the points to consider (inclusive)
+     * @param end end index of the points to consider (exclusive)
+     * @return guessed root (will be a NaN if two points share the same y)
+     */
+    private T guessX(final T targetY, final T[] x, final T[] y,
+                     final int start, final int end) {
+
+        // compute Q Newton coefficients by divided differences
+        for (int i = start; i < end - 1; ++i) {
+            final int delta = i + 1 - start;
+            for (int j = end - 1; j > i; --j) {
+                x[j] = x[j].subtract(x[j-1]).divide(y[j].subtract(y[j - delta]));
+            }
+        }
+
+        // evaluate Q(targetY)
+        T x0 = field.getZero();
+        for (int j = end - 1; j >= start; --j) {
+            x0 = x[j].add(x0.multiply(targetY.subtract(y[j])));
+        }
+
+        return x0;
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/solvers/IllinoisSolver.java b/src/main/java/org/apache/commons/math3/analysis/solvers/IllinoisSolver.java
new file mode 100644
index 0000000..bd3bc71
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/solvers/IllinoisSolver.java
@@ -0,0 +1,82 @@
+/*
+ * 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.math3.analysis.solvers;
+
+
+/**
+ * Implements the <em>Illinois</em> method for root-finding (approximating
+ * a zero of a univariate real function). It is a modified
+ * {@link RegulaFalsiSolver <em>Regula Falsi</em>} method.
+ *
+ * <p>Like the <em>Regula Falsi</em> method, convergence is guaranteed by
+ * maintaining a bracketed solution. The <em>Illinois</em> method however,
+ * should converge much faster than the original <em>Regula Falsi</em>
+ * method. Furthermore, this implementation of the <em>Illinois</em> method
+ * should not suffer from the same implementation issues as the <em>Regula
+ * Falsi</em> method, which may fail to convergence in certain cases.</p>
+ *
+ * <p>The <em>Illinois</em> method assumes that the function is continuous,
+ * but not necessarily smooth.</p>
+ *
+ * <p>Implementation based on the following article: M. Dowell and P. Jarratt,
+ * <em>A modified regula falsi method for computing the root of an
+ * equation</em>, BIT Numerical Mathematics, volume 11, number 2,
+ * pages 168-174, Springer, 1971.</p>
+ *
+ * @since 3.0
+ */
+public class IllinoisSolver extends BaseSecantSolver {
+
+    /** Construct a solver with default accuracy (1e-6). */
+    public IllinoisSolver() {
+        super(DEFAULT_ABSOLUTE_ACCURACY, Method.ILLINOIS);
+    }
+
+    /**
+     * Construct a solver.
+     *
+     * @param absoluteAccuracy Absolute accuracy.
+     */
+    public IllinoisSolver(final double absoluteAccuracy) {
+        super(absoluteAccuracy, Method.ILLINOIS);
+    }
+
+    /**
+     * Construct a solver.
+     *
+     * @param relativeAccuracy Relative accuracy.
+     * @param absoluteAccuracy Absolute accuracy.
+     */
+    public IllinoisSolver(final double relativeAccuracy,
+                          final double absoluteAccuracy) {
+        super(relativeAccuracy, absoluteAccuracy, Method.ILLINOIS);
+    }
+
+    /**
+     * Construct a solver.
+     *
+     * @param relativeAccuracy Relative accuracy.
+     * @param absoluteAccuracy Absolute accuracy.
+     * @param functionValueAccuracy Maximum function value error.
+     */
+    public IllinoisSolver(final double relativeAccuracy,
+                          final double absoluteAccuracy,
+                          final double functionValueAccuracy) {
+        super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy, Method.PEGASUS);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/solvers/LaguerreSolver.java b/src/main/java/org/apache/commons/math3/analysis/solvers/LaguerreSolver.java
new file mode 100644
index 0000000..5312dac
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/solvers/LaguerreSolver.java
@@ -0,0 +1,440 @@
+/*
+ * 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.math3.analysis.solvers;
+
+import org.apache.commons.math3.analysis.polynomials.PolynomialFunction;
+import org.apache.commons.math3.complex.Complex;
+import org.apache.commons.math3.complex.ComplexUtils;
+import org.apache.commons.math3.exception.NoBracketingException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Implements the <a href="http://mathworld.wolfram.com/LaguerresMethod.html">
+ * Laguerre's Method</a> for root finding of real coefficient polynomials.
+ * For reference, see
+ * <blockquote>
+ *  <b>A First Course in Numerical Analysis</b>,
+ *  ISBN 048641454X, chapter 8.
+ * </blockquote>
+ * Laguerre's method is global in the sense that it can start with any initial
+ * approximation and be able to solve all roots from that point.
+ * The algorithm requires a bracketing condition.
+ *
+ * @since 1.2
+ */
+public class LaguerreSolver extends AbstractPolynomialSolver {
+    /** Default absolute accuracy. */
+    private static final double DEFAULT_ABSOLUTE_ACCURACY = 1e-6;
+    /** Complex solver. */
+    private final ComplexSolver complexSolver = new ComplexSolver();
+
+    /**
+     * Construct a solver with default accuracy (1e-6).
+     */
+    public LaguerreSolver() {
+        this(DEFAULT_ABSOLUTE_ACCURACY);
+    }
+    /**
+     * Construct a solver.
+     *
+     * @param absoluteAccuracy Absolute accuracy.
+     */
+    public LaguerreSolver(double absoluteAccuracy) {
+        super(absoluteAccuracy);
+    }
+    /**
+     * Construct a solver.
+     *
+     * @param relativeAccuracy Relative accuracy.
+     * @param absoluteAccuracy Absolute accuracy.
+     */
+    public LaguerreSolver(double relativeAccuracy,
+                          double absoluteAccuracy) {
+        super(relativeAccuracy, absoluteAccuracy);
+    }
+    /**
+     * Construct a solver.
+     *
+     * @param relativeAccuracy Relative accuracy.
+     * @param absoluteAccuracy Absolute accuracy.
+     * @param functionValueAccuracy Function value accuracy.
+     */
+    public LaguerreSolver(double relativeAccuracy,
+                          double absoluteAccuracy,
+                          double functionValueAccuracy) {
+        super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double doSolve()
+        throws TooManyEvaluationsException,
+               NumberIsTooLargeException,
+               NoBracketingException {
+        final double min = getMin();
+        final double max = getMax();
+        final double initial = getStartValue();
+        final double functionValueAccuracy = getFunctionValueAccuracy();
+
+        verifySequence(min, initial, max);
+
+        // Return the initial guess if it is good enough.
+        final double yInitial = computeObjectiveValue(initial);
+        if (FastMath.abs(yInitial) <= functionValueAccuracy) {
+            return initial;
+        }
+
+        // Return the first endpoint if it is good enough.
+        final double yMin = computeObjectiveValue(min);
+        if (FastMath.abs(yMin) <= functionValueAccuracy) {
+            return min;
+        }
+
+        // Reduce interval if min and initial bracket the root.
+        if (yInitial * yMin < 0) {
+            return laguerre(min, initial, yMin, yInitial);
+        }
+
+        // Return the second endpoint if it is good enough.
+        final double yMax = computeObjectiveValue(max);
+        if (FastMath.abs(yMax) <= functionValueAccuracy) {
+            return max;
+        }
+
+        // Reduce interval if initial and max bracket the root.
+        if (yInitial * yMax < 0) {
+            return laguerre(initial, max, yInitial, yMax);
+        }
+
+        throw new NoBracketingException(min, max, yMin, yMax);
+    }
+
+    /**
+     * Find a real root in the given interval.
+     *
+     * Despite the bracketing condition, the root returned by
+     * {@link LaguerreSolver.ComplexSolver#solve(Complex[],Complex)} may
+     * not be a real zero inside {@code [min, max]}.
+     * For example, <code> p(x) = x<sup>3</sup> + 1, </code>
+     * with {@code min = -2}, {@code max = 2}, {@code initial = 0}.
+     * When it occurs, this code calls
+     * {@link LaguerreSolver.ComplexSolver#solveAll(Complex[],Complex)}
+     * in order to obtain all roots and picks up one real root.
+     *
+     * @param lo Lower bound of the search interval.
+     * @param hi Higher bound of the search interval.
+     * @param fLo Function value at the lower bound of the search interval.
+     * @param fHi Function value at the higher bound of the search interval.
+     * @return the point at which the function value is zero.
+     * @deprecated This method should not be part of the public API: It will
+     * be made private in version 4.0.
+     */
+    @Deprecated
+    public double laguerre(double lo, double hi,
+                           double fLo, double fHi) {
+        final Complex c[] = ComplexUtils.convertToComplex(getCoefficients());
+
+        final Complex initial = new Complex(0.5 * (lo + hi), 0);
+        final Complex z = complexSolver.solve(c, initial);
+        if (complexSolver.isRoot(lo, hi, z)) {
+            return z.getReal();
+        } else {
+            double r = Double.NaN;
+            // Solve all roots and select the one we are seeking.
+            Complex[] root = complexSolver.solveAll(c, initial);
+            for (int i = 0; i < root.length; i++) {
+                if (complexSolver.isRoot(lo, hi, root[i])) {
+                    r = root[i].getReal();
+                    break;
+                }
+            }
+            return r;
+        }
+    }
+
+    /**
+     * Find all complex roots for the polynomial with the given
+     * coefficients, starting from the given initial value.
+     * <p>
+     * Note: This method is not part of the API of {@link BaseUnivariateSolver}.</p>
+     *
+     * @param coefficients Polynomial coefficients.
+     * @param initial Start value.
+     * @return the full set of complex roots of the polynomial
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException
+     * if the maximum number of evaluations is exceeded when solving for one of the roots
+     * @throws NullArgumentException if the {@code coefficients} is
+     * {@code null}.
+     * @throws NoDataException if the {@code coefficients} array is empty.
+     * @since 3.1
+     */
+    public Complex[] solveAllComplex(double[] coefficients,
+                                     double initial)
+        throws NullArgumentException,
+               NoDataException,
+               TooManyEvaluationsException {
+       return solveAllComplex(coefficients, initial, Integer.MAX_VALUE);
+    }
+
+    /**
+     * Find all complex roots for the polynomial with the given
+     * coefficients, starting from the given initial value.
+     * <p>
+     * Note: This method is not part of the API of {@link BaseUnivariateSolver}.</p>
+     *
+     * @param coefficients polynomial coefficients
+     * @param initial start value
+     * @param maxEval maximum number of evaluations
+     * @return the full set of complex roots of the polynomial
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException
+     * if the maximum number of evaluations is exceeded when solving for one of the roots
+     * @throws NullArgumentException if the {@code coefficients} is
+     * {@code null}
+     * @throws NoDataException if the {@code coefficients} array is empty
+     * @since 3.5
+     */
+    public Complex[] solveAllComplex(double[] coefficients,
+                                     double initial, int maxEval)
+        throws NullArgumentException,
+               NoDataException,
+               TooManyEvaluationsException {
+        setup(maxEval,
+              new PolynomialFunction(coefficients),
+              Double.NEGATIVE_INFINITY,
+              Double.POSITIVE_INFINITY,
+              initial);
+        return complexSolver.solveAll(ComplexUtils.convertToComplex(coefficients),
+                                      new Complex(initial, 0d));
+    }
+
+    /**
+     * Find a complex root for the polynomial with the given coefficients,
+     * starting from the given initial value.
+     * <p>
+     * Note: This method is not part of the API of {@link BaseUnivariateSolver}.</p>
+     *
+     * @param coefficients Polynomial coefficients.
+     * @param initial Start value.
+     * @return a complex root of the polynomial
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException
+     * if the maximum number of evaluations is exceeded.
+     * @throws NullArgumentException if the {@code coefficients} is
+     * {@code null}.
+     * @throws NoDataException if the {@code coefficients} array is empty.
+     * @since 3.1
+     */
+    public Complex solveComplex(double[] coefficients,
+                                double initial)
+        throws NullArgumentException,
+               NoDataException,
+               TooManyEvaluationsException {
+       return solveComplex(coefficients, initial, Integer.MAX_VALUE);
+    }
+
+    /**
+     * Find a complex root for the polynomial with the given coefficients,
+     * starting from the given initial value.
+     * <p>
+     * Note: This method is not part of the API of {@link BaseUnivariateSolver}.</p>
+     *
+     * @param coefficients polynomial coefficients
+     * @param initial start value
+     * @param maxEval maximum number of evaluations
+     * @return a complex root of the polynomial
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException
+     * if the maximum number of evaluations is exceeded
+     * @throws NullArgumentException if the {@code coefficients} is
+     * {@code null}
+     * @throws NoDataException if the {@code coefficients} array is empty
+     * @since 3.1
+     */
+    public Complex solveComplex(double[] coefficients,
+                                double initial, int maxEval)
+        throws NullArgumentException,
+               NoDataException,
+               TooManyEvaluationsException {
+        setup(maxEval,
+              new PolynomialFunction(coefficients),
+              Double.NEGATIVE_INFINITY,
+              Double.POSITIVE_INFINITY,
+              initial);
+        return complexSolver.solve(ComplexUtils.convertToComplex(coefficients),
+                                   new Complex(initial, 0d));
+    }
+
+    /**
+     * Class for searching all (complex) roots.
+     */
+    private class ComplexSolver {
+        /**
+         * Check whether the given complex root is actually a real zero
+         * in the given interval, within the solver tolerance level.
+         *
+         * @param min Lower bound for the interval.
+         * @param max Upper bound for the interval.
+         * @param z Complex root.
+         * @return {@code true} if z is a real zero.
+         */
+        public boolean isRoot(double min, double max, Complex z) {
+            if (isSequence(min, z.getReal(), max)) {
+                double tolerance = FastMath.max(getRelativeAccuracy() * z.abs(), getAbsoluteAccuracy());
+                return (FastMath.abs(z.getImaginary()) <= tolerance) ||
+                     (z.abs() <= getFunctionValueAccuracy());
+            }
+            return false;
+        }
+
+        /**
+         * Find all complex roots for the polynomial with the given
+         * coefficients, starting from the given initial value.
+         *
+         * @param coefficients Polynomial coefficients.
+         * @param initial Start value.
+         * @return the point at which the function value is zero.
+         * @throws org.apache.commons.math3.exception.TooManyEvaluationsException
+         * if the maximum number of evaluations is exceeded.
+         * @throws NullArgumentException if the {@code coefficients} is
+         * {@code null}.
+         * @throws NoDataException if the {@code coefficients} array is empty.
+         */
+        public Complex[] solveAll(Complex coefficients[], Complex initial)
+            throws NullArgumentException,
+                   NoDataException,
+                   TooManyEvaluationsException {
+            if (coefficients == null) {
+                throw new NullArgumentException();
+            }
+            final int n = coefficients.length - 1;
+            if (n == 0) {
+                throw new NoDataException(LocalizedFormats.POLYNOMIAL);
+            }
+            // Coefficients for deflated polynomial.
+            final Complex c[] = new Complex[n + 1];
+            for (int i = 0; i <= n; i++) {
+                c[i] = coefficients[i];
+            }
+
+            // Solve individual roots successively.
+            final Complex root[] = new Complex[n];
+            for (int i = 0; i < n; i++) {
+                final Complex subarray[] = new Complex[n - i + 1];
+                System.arraycopy(c, 0, subarray, 0, subarray.length);
+                root[i] = solve(subarray, initial);
+                // Polynomial deflation using synthetic division.
+                Complex newc = c[n - i];
+                Complex oldc = null;
+                for (int j = n - i - 1; j >= 0; j--) {
+                    oldc = c[j];
+                    c[j] = newc;
+                    newc = oldc.add(newc.multiply(root[i]));
+                }
+            }
+
+            return root;
+        }
+
+        /**
+         * Find a complex root for the polynomial with the given coefficients,
+         * starting from the given initial value.
+         *
+         * @param coefficients Polynomial coefficients.
+         * @param initial Start value.
+         * @return the point at which the function value is zero.
+         * @throws org.apache.commons.math3.exception.TooManyEvaluationsException
+         * if the maximum number of evaluations is exceeded.
+         * @throws NullArgumentException if the {@code coefficients} is
+         * {@code null}.
+         * @throws NoDataException if the {@code coefficients} array is empty.
+         */
+        public Complex solve(Complex coefficients[], Complex initial)
+            throws NullArgumentException,
+                   NoDataException,
+                   TooManyEvaluationsException {
+            if (coefficients == null) {
+                throw new NullArgumentException();
+            }
+
+            final int n = coefficients.length - 1;
+            if (n == 0) {
+                throw new NoDataException(LocalizedFormats.POLYNOMIAL);
+            }
+
+            final double absoluteAccuracy = getAbsoluteAccuracy();
+            final double relativeAccuracy = getRelativeAccuracy();
+            final double functionValueAccuracy = getFunctionValueAccuracy();
+
+            final Complex nC  = new Complex(n, 0);
+            final Complex n1C = new Complex(n - 1, 0);
+
+            Complex z = initial;
+            Complex oldz = new Complex(Double.POSITIVE_INFINITY,
+                                       Double.POSITIVE_INFINITY);
+            while (true) {
+                // Compute pv (polynomial value), dv (derivative value), and
+                // d2v (second derivative value) simultaneously.
+                Complex pv = coefficients[n];
+                Complex dv = Complex.ZERO;
+                Complex d2v = Complex.ZERO;
+                for (int j = n-1; j >= 0; j--) {
+                    d2v = dv.add(z.multiply(d2v));
+                    dv = pv.add(z.multiply(dv));
+                    pv = coefficients[j].add(z.multiply(pv));
+                }
+                d2v = d2v.multiply(new Complex(2.0, 0.0));
+
+                // Check for convergence.
+                final double tolerance = FastMath.max(relativeAccuracy * z.abs(),
+                                                      absoluteAccuracy);
+                if ((z.subtract(oldz)).abs() <= tolerance) {
+                    return z;
+                }
+                if (pv.abs() <= functionValueAccuracy) {
+                    return z;
+                }
+
+                // Now pv != 0, calculate the new approximation.
+                final Complex G = dv.divide(pv);
+                final Complex G2 = G.multiply(G);
+                final Complex H = G2.subtract(d2v.divide(pv));
+                final Complex delta = n1C.multiply((nC.multiply(H)).subtract(G2));
+                // Choose a denominator larger in magnitude.
+                final Complex deltaSqrt = delta.sqrt();
+                final Complex dplus = G.add(deltaSqrt);
+                final Complex dminus = G.subtract(deltaSqrt);
+                final Complex denominator = dplus.abs() > dminus.abs() ? dplus : dminus;
+                // Perturb z if denominator is zero, for instance,
+                // p(x) = x^3 + 1, z = 0.
+                if (denominator.equals(new Complex(0.0, 0.0))) {
+                    z = z.add(new Complex(absoluteAccuracy, absoluteAccuracy));
+                    oldz = new Complex(Double.POSITIVE_INFINITY,
+                                       Double.POSITIVE_INFINITY);
+                } else {
+                    oldz = z;
+                    z = z.subtract(nC.divide(denominator));
+                }
+                incrementEvaluationCount();
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/solvers/MullerSolver.java b/src/main/java/org/apache/commons/math3/analysis/solvers/MullerSolver.java
new file mode 100644
index 0000000..2e59ed4
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/solvers/MullerSolver.java
@@ -0,0 +1,202 @@
+/*
+ * 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.math3.analysis.solvers;
+
+import org.apache.commons.math3.exception.NoBracketingException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * This class implements the <a href="http://mathworld.wolfram.com/MullersMethod.html">
+ * Muller's Method</a> for root finding of real univariate functions. For
+ * reference, see <b>Elementary Numerical Analysis</b>, ISBN 0070124477,
+ * chapter 3.
+ * <p>
+ * Muller's method applies to both real and complex functions, but here we
+ * restrict ourselves to real functions.
+ * This class differs from {@link MullerSolver} in the way it avoids complex
+ * operations.</p><p>
+ * Muller's original method would have function evaluation at complex point.
+ * Since our f(x) is real, we have to find ways to avoid that. Bracketing
+ * condition is one way to go: by requiring bracketing in every iteration,
+ * the newly computed approximation is guaranteed to be real.</p>
+ * <p>
+ * Normally Muller's method converges quadratically in the vicinity of a
+ * zero, however it may be very slow in regions far away from zeros. For
+ * example, f(x) = exp(x) - 1, min = -50, max = 100. In such case we use
+ * bisection as a safety backup if it performs very poorly.</p>
+ * <p>
+ * The formulas here use divided differences directly.</p>
+ *
+ * @since 1.2
+ * @see MullerSolver2
+ */
+public class MullerSolver extends AbstractUnivariateSolver {
+
+    /** Default absolute accuracy. */
+    private static final double DEFAULT_ABSOLUTE_ACCURACY = 1e-6;
+
+    /**
+     * Construct a solver with default accuracy (1e-6).
+     */
+    public MullerSolver() {
+        this(DEFAULT_ABSOLUTE_ACCURACY);
+    }
+    /**
+     * Construct a solver.
+     *
+     * @param absoluteAccuracy Absolute accuracy.
+     */
+    public MullerSolver(double absoluteAccuracy) {
+        super(absoluteAccuracy);
+    }
+    /**
+     * Construct a solver.
+     *
+     * @param relativeAccuracy Relative accuracy.
+     * @param absoluteAccuracy Absolute accuracy.
+     */
+    public MullerSolver(double relativeAccuracy,
+                        double absoluteAccuracy) {
+        super(relativeAccuracy, absoluteAccuracy);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected double doSolve()
+        throws TooManyEvaluationsException,
+               NumberIsTooLargeException,
+               NoBracketingException {
+        final double min = getMin();
+        final double max = getMax();
+        final double initial = getStartValue();
+
+        final double functionValueAccuracy = getFunctionValueAccuracy();
+
+        verifySequence(min, initial, max);
+
+        // check for zeros before verifying bracketing
+        final double fMin = computeObjectiveValue(min);
+        if (FastMath.abs(fMin) < functionValueAccuracy) {
+            return min;
+        }
+        final double fMax = computeObjectiveValue(max);
+        if (FastMath.abs(fMax) < functionValueAccuracy) {
+            return max;
+        }
+        final double fInitial = computeObjectiveValue(initial);
+        if (FastMath.abs(fInitial) <  functionValueAccuracy) {
+            return initial;
+        }
+
+        verifyBracketing(min, max);
+
+        if (isBracketing(min, initial)) {
+            return solve(min, initial, fMin, fInitial);
+        } else {
+            return solve(initial, max, fInitial, fMax);
+        }
+    }
+
+    /**
+     * Find a real root in the given interval.
+     *
+     * @param min Lower bound for the interval.
+     * @param max Upper bound for the interval.
+     * @param fMin function value at the lower bound.
+     * @param fMax function value at the upper bound.
+     * @return the point at which the function value is zero.
+     * @throws TooManyEvaluationsException if the allowed number of calls to
+     * the function to be solved has been exhausted.
+     */
+    private double solve(double min, double max,
+                         double fMin, double fMax)
+        throws TooManyEvaluationsException {
+        final double relativeAccuracy = getRelativeAccuracy();
+        final double absoluteAccuracy = getAbsoluteAccuracy();
+        final double functionValueAccuracy = getFunctionValueAccuracy();
+
+        // [x0, x2] is the bracketing interval in each iteration
+        // x1 is the last approximation and an interpolation point in (x0, x2)
+        // x is the new root approximation and new x1 for next round
+        // d01, d12, d012 are divided differences
+
+        double x0 = min;
+        double y0 = fMin;
+        double x2 = max;
+        double y2 = fMax;
+        double x1 = 0.5 * (x0 + x2);
+        double y1 = computeObjectiveValue(x1);
+
+        double oldx = Double.POSITIVE_INFINITY;
+        while (true) {
+            // Muller's method employs quadratic interpolation through
+            // x0, x1, x2 and x is the zero of the interpolating parabola.
+            // Due to bracketing condition, this parabola must have two
+            // real roots and we choose one in [x0, x2] to be x.
+            final double d01 = (y1 - y0) / (x1 - x0);
+            final double d12 = (y2 - y1) / (x2 - x1);
+            final double d012 = (d12 - d01) / (x2 - x0);
+            final double c1 = d01 + (x1 - x0) * d012;
+            final double delta = c1 * c1 - 4 * y1 * d012;
+            final double xplus = x1 + (-2.0 * y1) / (c1 + FastMath.sqrt(delta));
+            final double xminus = x1 + (-2.0 * y1) / (c1 - FastMath.sqrt(delta));
+            // xplus and xminus are two roots of parabola and at least
+            // one of them should lie in (x0, x2)
+            final double x = isSequence(x0, xplus, x2) ? xplus : xminus;
+            final double y = computeObjectiveValue(x);
+
+            // check for convergence
+            final double tolerance = FastMath.max(relativeAccuracy * FastMath.abs(x), absoluteAccuracy);
+            if (FastMath.abs(x - oldx) <= tolerance ||
+                FastMath.abs(y) <= functionValueAccuracy) {
+                return x;
+            }
+
+            // Bisect if convergence is too slow. Bisection would waste
+            // our calculation of x, hopefully it won't happen often.
+            // the real number equality test x == x1 is intentional and
+            // completes the proximity tests above it
+            boolean bisect = (x < x1 && (x1 - x0) > 0.95 * (x2 - x0)) ||
+                             (x > x1 && (x2 - x1) > 0.95 * (x2 - x0)) ||
+                             (x == x1);
+            // prepare the new bracketing interval for next iteration
+            if (!bisect) {
+                x0 = x < x1 ? x0 : x1;
+                y0 = x < x1 ? y0 : y1;
+                x2 = x > x1 ? x2 : x1;
+                y2 = x > x1 ? y2 : y1;
+                x1 = x; y1 = y;
+                oldx = x;
+            } else {
+                double xm = 0.5 * (x0 + x2);
+                double ym = computeObjectiveValue(xm);
+                if (FastMath.signum(y0) + FastMath.signum(ym) == 0.0) {
+                    x2 = xm; y2 = ym;
+                } else {
+                    x0 = xm; y0 = ym;
+                }
+                x1 = 0.5 * (x0 + x2);
+                y1 = computeObjectiveValue(x1);
+                oldx = Double.POSITIVE_INFINITY;
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/solvers/MullerSolver2.java b/src/main/java/org/apache/commons/math3/analysis/solvers/MullerSolver2.java
new file mode 100644
index 0000000..864cfd5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/solvers/MullerSolver2.java
@@ -0,0 +1,168 @@
+/*
+ * 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.math3.analysis.solvers;
+
+import org.apache.commons.math3.exception.NoBracketingException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * This class implements the <a href="http://mathworld.wolfram.com/MullersMethod.html">
+ * Muller's Method</a> for root finding of real univariate functions. For
+ * reference, see <b>Elementary Numerical Analysis</b>, ISBN 0070124477,
+ * chapter 3.
+ * <p>
+ * Muller's method applies to both real and complex functions, but here we
+ * restrict ourselves to real functions.
+ * This class differs from {@link MullerSolver} in the way it avoids complex
+ * operations.</p><p>
+ * Except for the initial [min, max], it does not require bracketing
+ * condition, e.g. f(x0), f(x1), f(x2) can have the same sign. If a complex
+ * number arises in the computation, we simply use its modulus as a real
+ * approximation.</p>
+ * <p>
+ * Because the interval may not be bracketing, the bisection alternative is
+ * not applicable here. However in practice our treatment usually works
+ * well, especially near real zeroes where the imaginary part of the complex
+ * approximation is often negligible.</p>
+ * <p>
+ * The formulas here do not use divided differences directly.</p>
+ *
+ * @since 1.2
+ * @see MullerSolver
+ */
+public class MullerSolver2 extends AbstractUnivariateSolver {
+
+    /** Default absolute accuracy. */
+    private static final double DEFAULT_ABSOLUTE_ACCURACY = 1e-6;
+
+    /**
+     * Construct a solver with default accuracy (1e-6).
+     */
+    public MullerSolver2() {
+        this(DEFAULT_ABSOLUTE_ACCURACY);
+    }
+    /**
+     * Construct a solver.
+     *
+     * @param absoluteAccuracy Absolute accuracy.
+     */
+    public MullerSolver2(double absoluteAccuracy) {
+        super(absoluteAccuracy);
+    }
+    /**
+     * Construct a solver.
+     *
+     * @param relativeAccuracy Relative accuracy.
+     * @param absoluteAccuracy Absolute accuracy.
+     */
+    public MullerSolver2(double relativeAccuracy,
+                        double absoluteAccuracy) {
+        super(relativeAccuracy, absoluteAccuracy);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected double doSolve()
+        throws TooManyEvaluationsException,
+               NumberIsTooLargeException,
+               NoBracketingException {
+        final double min = getMin();
+        final double max = getMax();
+
+        verifyInterval(min, max);
+
+        final double relativeAccuracy = getRelativeAccuracy();
+        final double absoluteAccuracy = getAbsoluteAccuracy();
+        final double functionValueAccuracy = getFunctionValueAccuracy();
+
+        // x2 is the last root approximation
+        // x is the new approximation and new x2 for next round
+        // x0 < x1 < x2 does not hold here
+
+        double x0 = min;
+        double y0 = computeObjectiveValue(x0);
+        if (FastMath.abs(y0) < functionValueAccuracy) {
+            return x0;
+        }
+        double x1 = max;
+        double y1 = computeObjectiveValue(x1);
+        if (FastMath.abs(y1) < functionValueAccuracy) {
+            return x1;
+        }
+
+        if(y0 * y1 > 0) {
+            throw new NoBracketingException(x0, x1, y0, y1);
+        }
+
+        double x2 = 0.5 * (x0 + x1);
+        double y2 = computeObjectiveValue(x2);
+
+        double oldx = Double.POSITIVE_INFINITY;
+        while (true) {
+            // quadratic interpolation through x0, x1, x2
+            final double q = (x2 - x1) / (x1 - x0);
+            final double a = q * (y2 - (1 + q) * y1 + q * y0);
+            final double b = (2 * q + 1) * y2 - (1 + q) * (1 + q) * y1 + q * q * y0;
+            final double c = (1 + q) * y2;
+            final double delta = b * b - 4 * a * c;
+            double x;
+            final double denominator;
+            if (delta >= 0.0) {
+                // choose a denominator larger in magnitude
+                double dplus = b + FastMath.sqrt(delta);
+                double dminus = b - FastMath.sqrt(delta);
+                denominator = FastMath.abs(dplus) > FastMath.abs(dminus) ? dplus : dminus;
+            } else {
+                // take the modulus of (B +/- FastMath.sqrt(delta))
+                denominator = FastMath.sqrt(b * b - delta);
+            }
+            if (denominator != 0) {
+                x = x2 - 2.0 * c * (x2 - x1) / denominator;
+                // perturb x if it exactly coincides with x1 or x2
+                // the equality tests here are intentional
+                while (x == x1 || x == x2) {
+                    x += absoluteAccuracy;
+                }
+            } else {
+                // extremely rare case, get a random number to skip it
+                x = min + FastMath.random() * (max - min);
+                oldx = Double.POSITIVE_INFINITY;
+            }
+            final double y = computeObjectiveValue(x);
+
+            // check for convergence
+            final double tolerance = FastMath.max(relativeAccuracy * FastMath.abs(x), absoluteAccuracy);
+            if (FastMath.abs(x - oldx) <= tolerance ||
+                FastMath.abs(y) <= functionValueAccuracy) {
+                return x;
+            }
+
+            // prepare the next iteration
+            x0 = x1;
+            y0 = y1;
+            x1 = x2;
+            y1 = y2;
+            x2 = x;
+            y2 = y;
+            oldx = x;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/solvers/NewtonRaphsonSolver.java b/src/main/java/org/apache/commons/math3/analysis/solvers/NewtonRaphsonSolver.java
new file mode 100644
index 0000000..4cf2688
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/solvers/NewtonRaphsonSolver.java
@@ -0,0 +1,92 @@
+/*
+ * 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.math3.analysis.solvers;
+
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+
+/**
+ * Implements <a href="http://mathworld.wolfram.com/NewtonsMethod.html">
+ * Newton's Method</a> for finding zeros of real univariate differentiable
+ * functions.
+ *
+ * @since 3.1
+ */
+public class NewtonRaphsonSolver extends AbstractUnivariateDifferentiableSolver {
+    /** Default absolute accuracy. */
+    private static final double DEFAULT_ABSOLUTE_ACCURACY = 1e-6;
+
+    /**
+     * Construct a solver.
+     */
+    public NewtonRaphsonSolver() {
+        this(DEFAULT_ABSOLUTE_ACCURACY);
+    }
+    /**
+     * Construct a solver.
+     *
+     * @param absoluteAccuracy Absolute accuracy.
+     */
+    public NewtonRaphsonSolver(double absoluteAccuracy) {
+        super(absoluteAccuracy);
+    }
+
+    /**
+     * Find a zero near the midpoint of {@code min} and {@code max}.
+     *
+     * @param f Function to solve.
+     * @param min Lower bound for the interval.
+     * @param max Upper bound for the interval.
+     * @param maxEval Maximum number of evaluations.
+     * @return the value where the function is zero.
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException
+     * if the maximum evaluation count is exceeded.
+     * @throws org.apache.commons.math3.exception.NumberIsTooLargeException
+     * if {@code min >= max}.
+     */
+    @Override
+    public double solve(int maxEval, final UnivariateDifferentiableFunction f,
+                        final double min, final double max)
+        throws TooManyEvaluationsException {
+        return super.solve(maxEval, f, UnivariateSolverUtils.midpoint(min, max));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected double doSolve()
+        throws TooManyEvaluationsException {
+        final double startValue = getStartValue();
+        final double absoluteAccuracy = getAbsoluteAccuracy();
+
+        double x0 = startValue;
+        double x1;
+        while (true) {
+            final DerivativeStructure y0 = computeObjectiveValueAndDerivative(x0);
+            x1 = x0 - (y0.getValue() / y0.getPartialDerivative(1));
+            if (FastMath.abs(x1 - x0) <= absoluteAccuracy) {
+                return x1;
+            }
+
+            x0 = x1;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/solvers/NewtonSolver.java b/src/main/java/org/apache/commons/math3/analysis/solvers/NewtonSolver.java
new file mode 100644
index 0000000..3ba7bf2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/solvers/NewtonSolver.java
@@ -0,0 +1,92 @@
+/*
+ * 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.math3.analysis.solvers;
+
+import org.apache.commons.math3.analysis.DifferentiableUnivariateFunction;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+
+/**
+ * Implements <a href="http://mathworld.wolfram.com/NewtonsMethod.html">
+ * Newton's Method</a> for finding zeros of real univariate functions.
+ * <p>
+ * The function should be continuous but not necessarily smooth.</p>
+ *
+ * @deprecated as of 3.1, replaced by {@link NewtonRaphsonSolver}
+ */
+@Deprecated
+public class NewtonSolver extends AbstractDifferentiableUnivariateSolver {
+    /** Default absolute accuracy. */
+    private static final double DEFAULT_ABSOLUTE_ACCURACY = 1e-6;
+
+    /**
+     * Construct a solver.
+     */
+    public NewtonSolver() {
+        this(DEFAULT_ABSOLUTE_ACCURACY);
+    }
+    /**
+     * Construct a solver.
+     *
+     * @param absoluteAccuracy Absolute accuracy.
+     */
+    public NewtonSolver(double absoluteAccuracy) {
+        super(absoluteAccuracy);
+    }
+
+    /**
+     * Find a zero near the midpoint of {@code min} and {@code max}.
+     *
+     * @param f Function to solve.
+     * @param min Lower bound for the interval.
+     * @param max Upper bound for the interval.
+     * @param maxEval Maximum number of evaluations.
+     * @return the value where the function is zero.
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException
+     * if the maximum evaluation count is exceeded.
+     * @throws org.apache.commons.math3.exception.NumberIsTooLargeException
+     * if {@code min >= max}.
+     */
+    @Override
+    public double solve(int maxEval, final DifferentiableUnivariateFunction f,
+                        final double min, final double max)
+        throws TooManyEvaluationsException {
+        return super.solve(maxEval, f, UnivariateSolverUtils.midpoint(min, max));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected double doSolve()
+        throws TooManyEvaluationsException {
+        final double startValue = getStartValue();
+        final double absoluteAccuracy = getAbsoluteAccuracy();
+
+        double x0 = startValue;
+        double x1;
+        while (true) {
+            x1 = x0 - (computeObjectiveValue(x0) / computeDerivativeObjectiveValue(x0));
+            if (FastMath.abs(x1 - x0) <= absoluteAccuracy) {
+                return x1;
+            }
+
+            x0 = x1;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/solvers/PegasusSolver.java b/src/main/java/org/apache/commons/math3/analysis/solvers/PegasusSolver.java
new file mode 100644
index 0000000..0d80895
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/solvers/PegasusSolver.java
@@ -0,0 +1,84 @@
+/*
+ * 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.math3.analysis.solvers;
+
+/**
+ * Implements the <em>Pegasus</em> method for root-finding (approximating
+ * a zero of a univariate real function). It is a modified
+ * {@link RegulaFalsiSolver <em>Regula Falsi</em>} method.
+ *
+ * <p>Like the <em>Regula Falsi</em> method, convergence is guaranteed by
+ * maintaining a bracketed solution. The <em>Pegasus</em> method however,
+ * should converge much faster than the original <em>Regula Falsi</em>
+ * method. Furthermore, this implementation of the <em>Pegasus</em> method
+ * should not suffer from the same implementation issues as the <em>Regula
+ * Falsi</em> method, which may fail to convergence in certain cases. Also,
+ * the <em>Pegasus</em> method should converge faster than the
+ * {@link IllinoisSolver <em>Illinois</em>} method, another <em>Regula
+ * Falsi</em>-based method.</p>
+ *
+ * <p>The <em>Pegasus</em> method assumes that the function is continuous,
+ * but not necessarily smooth.</p>
+ *
+ * <p>Implementation based on the following article: M. Dowell and P. Jarratt,
+ * <em>The "Pegasus" method for computing the root of an equation</em>,
+ * BIT Numerical Mathematics, volume 12, number 4, pages 503-508, Springer,
+ * 1972.</p>
+ *
+ * @since 3.0
+ */
+public class PegasusSolver extends BaseSecantSolver {
+
+    /** Construct a solver with default accuracy (1e-6). */
+    public PegasusSolver() {
+        super(DEFAULT_ABSOLUTE_ACCURACY, Method.PEGASUS);
+    }
+
+    /**
+     * Construct a solver.
+     *
+     * @param absoluteAccuracy Absolute accuracy.
+     */
+    public PegasusSolver(final double absoluteAccuracy) {
+        super(absoluteAccuracy, Method.PEGASUS);
+    }
+
+    /**
+     * Construct a solver.
+     *
+     * @param relativeAccuracy Relative accuracy.
+     * @param absoluteAccuracy Absolute accuracy.
+     */
+    public PegasusSolver(final double relativeAccuracy,
+                         final double absoluteAccuracy) {
+        super(relativeAccuracy, absoluteAccuracy, Method.PEGASUS);
+    }
+
+    /**
+     * Construct a solver.
+     *
+     * @param relativeAccuracy Relative accuracy.
+     * @param absoluteAccuracy Absolute accuracy.
+     * @param functionValueAccuracy Maximum function value error.
+     */
+    public PegasusSolver(final double relativeAccuracy,
+                         final double absoluteAccuracy,
+                         final double functionValueAccuracy) {
+        super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy, Method.PEGASUS);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/solvers/PolynomialSolver.java b/src/main/java/org/apache/commons/math3/analysis/solvers/PolynomialSolver.java
new file mode 100644
index 0000000..c21f076
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/solvers/PolynomialSolver.java
@@ -0,0 +1,28 @@
+/*
+ * 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.math3.analysis.solvers;
+
+import org.apache.commons.math3.analysis.polynomials.PolynomialFunction;
+
+/**
+ * Interface for (polynomial) root-finding algorithms.
+ * Implementations will search for only one zero in the given interval.
+ *
+ * @since 3.0
+ */
+public interface PolynomialSolver
+    extends BaseUnivariateSolver<PolynomialFunction> {}
diff --git a/src/main/java/org/apache/commons/math3/analysis/solvers/RegulaFalsiSolver.java b/src/main/java/org/apache/commons/math3/analysis/solvers/RegulaFalsiSolver.java
new file mode 100644
index 0000000..cfb7055
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/solvers/RegulaFalsiSolver.java
@@ -0,0 +1,94 @@
+/*
+ * 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.math3.analysis.solvers;
+
+/**
+ * Implements the <em>Regula Falsi</em> or <em>False position</em> method for
+ * root-finding (approximating a zero of a univariate real function). It is a
+ * modified {@link SecantSolver <em>Secant</em>} method.
+ *
+ * <p>The <em>Regula Falsi</em> method is included for completeness, for
+ * testing purposes, for educational purposes, for comparison to other
+ * algorithms, etc. It is however <strong>not</strong> intended to be used
+ * for actual problems, as one of the bounds often remains fixed, resulting
+ * in very slow convergence. Instead, one of the well-known modified
+ * <em>Regula Falsi</em> algorithms can be used ({@link IllinoisSolver
+ * <em>Illinois</em>} or {@link PegasusSolver <em>Pegasus</em>}). These two
+ * algorithms solve the fundamental issues of the original <em>Regula
+ * Falsi</em> algorithm, and greatly out-performs it for most, if not all,
+ * (practical) functions.
+ *
+ * <p>Unlike the <em>Secant</em> method, the <em>Regula Falsi</em> guarantees
+ * convergence, by maintaining a bracketed solution. Note however, that due to
+ * the finite/limited precision of Java's {@link Double double} type, which is
+ * used in this implementation, the algorithm may get stuck in a situation
+ * where it no longer makes any progress. Such cases are detected and result
+ * in a {@code ConvergenceException} exception being thrown. In other words,
+ * the algorithm theoretically guarantees convergence, but the implementation
+ * does not.</p>
+ *
+ * <p>The <em>Regula Falsi</em> method assumes that the function is continuous,
+ * but not necessarily smooth.</p>
+ *
+ * <p>Implementation based on the following article: M. Dowell and P. Jarratt,
+ * <em>A modified regula falsi method for computing the root of an
+ * equation</em>, BIT Numerical Mathematics, volume 11, number 2,
+ * pages 168-174, Springer, 1971.</p>
+ *
+ * @since 3.0
+ */
+public class RegulaFalsiSolver extends BaseSecantSolver {
+
+    /** Construct a solver with default accuracy (1e-6). */
+    public RegulaFalsiSolver() {
+        super(DEFAULT_ABSOLUTE_ACCURACY, Method.REGULA_FALSI);
+    }
+
+    /**
+     * Construct a solver.
+     *
+     * @param absoluteAccuracy Absolute accuracy.
+     */
+    public RegulaFalsiSolver(final double absoluteAccuracy) {
+        super(absoluteAccuracy, Method.REGULA_FALSI);
+    }
+
+    /**
+     * Construct a solver.
+     *
+     * @param relativeAccuracy Relative accuracy.
+     * @param absoluteAccuracy Absolute accuracy.
+     */
+    public RegulaFalsiSolver(final double relativeAccuracy,
+                             final double absoluteAccuracy) {
+        super(relativeAccuracy, absoluteAccuracy, Method.REGULA_FALSI);
+    }
+
+    /**
+     * Construct a solver.
+     *
+     * @param relativeAccuracy Relative accuracy.
+     * @param absoluteAccuracy Absolute accuracy.
+     * @param functionValueAccuracy Maximum function value error.
+     */
+    public RegulaFalsiSolver(final double relativeAccuracy,
+                             final double absoluteAccuracy,
+                             final double functionValueAccuracy) {
+        super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy, Method.REGULA_FALSI);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/solvers/RiddersSolver.java b/src/main/java/org/apache/commons/math3/analysis/solvers/RiddersSolver.java
new file mode 100644
index 0000000..d83f595
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/solvers/RiddersSolver.java
@@ -0,0 +1,142 @@
+/*
+ * 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.math3.analysis.solvers;
+
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.exception.NoBracketingException;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+
+/**
+ * Implements the <a href="http://mathworld.wolfram.com/RiddersMethod.html">
+ * Ridders' Method</a> for root finding of real univariate functions. For
+ * reference, see C. Ridders, <i>A new algorithm for computing a single root
+ * of a real continuous function </i>, IEEE Transactions on Circuits and
+ * Systems, 26 (1979), 979 - 980.
+ * <p>
+ * The function should be continuous but not necessarily smooth.</p>
+ *
+ * @since 1.2
+ */
+public class RiddersSolver extends AbstractUnivariateSolver {
+    /** Default absolute accuracy. */
+    private static final double DEFAULT_ABSOLUTE_ACCURACY = 1e-6;
+
+    /**
+     * Construct a solver with default accuracy (1e-6).
+     */
+    public RiddersSolver() {
+        this(DEFAULT_ABSOLUTE_ACCURACY);
+    }
+    /**
+     * Construct a solver.
+     *
+     * @param absoluteAccuracy Absolute accuracy.
+     */
+    public RiddersSolver(double absoluteAccuracy) {
+        super(absoluteAccuracy);
+    }
+    /**
+     * Construct a solver.
+     *
+     * @param relativeAccuracy Relative accuracy.
+     * @param absoluteAccuracy Absolute accuracy.
+     */
+    public RiddersSolver(double relativeAccuracy,
+                         double absoluteAccuracy) {
+        super(relativeAccuracy, absoluteAccuracy);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected double doSolve()
+        throws TooManyEvaluationsException,
+               NoBracketingException {
+        double min = getMin();
+        double max = getMax();
+        // [x1, x2] is the bracketing interval in each iteration
+        // x3 is the midpoint of [x1, x2]
+        // x is the new root approximation and an endpoint of the new interval
+        double x1 = min;
+        double y1 = computeObjectiveValue(x1);
+        double x2 = max;
+        double y2 = computeObjectiveValue(x2);
+
+        // check for zeros before verifying bracketing
+        if (y1 == 0) {
+            return min;
+        }
+        if (y2 == 0) {
+            return max;
+        }
+        verifyBracketing(min, max);
+
+        final double absoluteAccuracy = getAbsoluteAccuracy();
+        final double functionValueAccuracy = getFunctionValueAccuracy();
+        final double relativeAccuracy = getRelativeAccuracy();
+
+        double oldx = Double.POSITIVE_INFINITY;
+        while (true) {
+            // calculate the new root approximation
+            final double x3 = 0.5 * (x1 + x2);
+            final double y3 = computeObjectiveValue(x3);
+            if (FastMath.abs(y3) <= functionValueAccuracy) {
+                return x3;
+            }
+            final double delta = 1 - (y1 * y2) / (y3 * y3);  // delta > 1 due to bracketing
+            final double correction = (FastMath.signum(y2) * FastMath.signum(y3)) *
+                                      (x3 - x1) / FastMath.sqrt(delta);
+            final double x = x3 - correction;                // correction != 0
+            final double y = computeObjectiveValue(x);
+
+            // check for convergence
+            final double tolerance = FastMath.max(relativeAccuracy * FastMath.abs(x), absoluteAccuracy);
+            if (FastMath.abs(x - oldx) <= tolerance) {
+                return x;
+            }
+            if (FastMath.abs(y) <= functionValueAccuracy) {
+                return x;
+            }
+
+            // prepare the new interval for next iteration
+            // Ridders' method guarantees x1 < x < x2
+            if (correction > 0.0) {             // x1 < x < x3
+                if (FastMath.signum(y1) + FastMath.signum(y) == 0.0) {
+                    x2 = x;
+                    y2 = y;
+                } else {
+                    x1 = x;
+                    x2 = x3;
+                    y1 = y;
+                    y2 = y3;
+                }
+            } else {                            // x3 < x < x2
+                if (FastMath.signum(y2) + FastMath.signum(y) == 0.0) {
+                    x1 = x;
+                    y1 = y;
+                } else {
+                    x1 = x3;
+                    x2 = x;
+                    y1 = y3;
+                    y2 = y;
+                }
+            }
+            oldx = x;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/solvers/SecantSolver.java b/src/main/java/org/apache/commons/math3/analysis/solvers/SecantSolver.java
new file mode 100644
index 0000000..d866cf8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/solvers/SecantSolver.java
@@ -0,0 +1,135 @@
+/*
+ * 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.math3.analysis.solvers;
+
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.exception.NoBracketingException;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+
+/**
+ * Implements the <em>Secant</em> method for root-finding (approximating a
+ * zero of a univariate real function). The solution that is maintained is
+ * not bracketed, and as such convergence is not guaranteed.
+ *
+ * <p>Implementation based on the following article: M. Dowell and P. Jarratt,
+ * <em>A modified regula falsi method for computing the root of an
+ * equation</em>, BIT Numerical Mathematics, volume 11, number 2,
+ * pages 168-174, Springer, 1971.</p>
+ *
+ * <p>Note that since release 3.0 this class implements the actual
+ * <em>Secant</em> algorithm, and not a modified one. As such, the 3.0 version
+ * is not backwards compatible with previous versions. To use an algorithm
+ * similar to the pre-3.0 releases, use the
+ * {@link IllinoisSolver <em>Illinois</em>} algorithm or the
+ * {@link PegasusSolver <em>Pegasus</em>} algorithm.</p>
+ *
+ */
+public class SecantSolver extends AbstractUnivariateSolver {
+
+    /** Default absolute accuracy. */
+    protected static final double DEFAULT_ABSOLUTE_ACCURACY = 1e-6;
+
+    /** Construct a solver with default accuracy (1e-6). */
+    public SecantSolver() {
+        super(DEFAULT_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Construct a solver.
+     *
+     * @param absoluteAccuracy absolute accuracy
+     */
+    public SecantSolver(final double absoluteAccuracy) {
+        super(absoluteAccuracy);
+    }
+
+    /**
+     * Construct a solver.
+     *
+     * @param relativeAccuracy relative accuracy
+     * @param absoluteAccuracy absolute accuracy
+     */
+    public SecantSolver(final double relativeAccuracy,
+                        final double absoluteAccuracy) {
+        super(relativeAccuracy, absoluteAccuracy);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected final double doSolve()
+        throws TooManyEvaluationsException,
+               NoBracketingException {
+        // Get initial solution
+        double x0 = getMin();
+        double x1 = getMax();
+        double f0 = computeObjectiveValue(x0);
+        double f1 = computeObjectiveValue(x1);
+
+        // If one of the bounds is the exact root, return it. Since these are
+        // not under-approximations or over-approximations, we can return them
+        // regardless of the allowed solutions.
+        if (f0 == 0.0) {
+            return x0;
+        }
+        if (f1 == 0.0) {
+            return x1;
+        }
+
+        // Verify bracketing of initial solution.
+        verifyBracketing(x0, x1);
+
+        // Get accuracies.
+        final double ftol = getFunctionValueAccuracy();
+        final double atol = getAbsoluteAccuracy();
+        final double rtol = getRelativeAccuracy();
+
+        // Keep finding better approximations.
+        while (true) {
+            // Calculate the next approximation.
+            final double x = x1 - ((f1 * (x1 - x0)) / (f1 - f0));
+            final double fx = computeObjectiveValue(x);
+
+            // If the new approximation is the exact root, return it. Since
+            // this is not an under-approximation or an over-approximation,
+            // we can return it regardless of the allowed solutions.
+            if (fx == 0.0) {
+                return x;
+            }
+
+            // Update the bounds with the new approximation.
+            x0 = x1;
+            f0 = f1;
+            x1 = x;
+            f1 = fx;
+
+            // If the function value of the last approximation is too small,
+            // given the function value accuracy, then we can't get closer to
+            // the root than we already are.
+            if (FastMath.abs(f1) <= ftol) {
+                return x1;
+            }
+
+            // If the current interval is within the given accuracies, we
+            // are satisfied with the current approximation.
+            if (FastMath.abs(x1 - x0) < FastMath.max(rtol * FastMath.abs(x1), atol)) {
+                return x1;
+            }
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/solvers/UnivariateDifferentiableSolver.java b/src/main/java/org/apache/commons/math3/analysis/solvers/UnivariateDifferentiableSolver.java
new file mode 100644
index 0000000..82bbead
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/solvers/UnivariateDifferentiableSolver.java
@@ -0,0 +1,29 @@
+/*
+ * 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.math3.analysis.solvers;
+
+import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
+
+
+/**
+ * Interface for (univariate real) rootfinding algorithms.
+ * Implementations will search for only one zero in the given interval.
+ *
+ * @since 3.1
+ */
+public interface UnivariateDifferentiableSolver
+    extends BaseUnivariateSolver<UnivariateDifferentiableFunction> {}
diff --git a/src/main/java/org/apache/commons/math3/analysis/solvers/UnivariateSolver.java b/src/main/java/org/apache/commons/math3/analysis/solvers/UnivariateSolver.java
new file mode 100644
index 0000000..484e67a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/solvers/UnivariateSolver.java
@@ -0,0 +1,28 @@
+/*
+ * 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.math3.analysis.solvers;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+
+
+/**
+ * Interface for (univariate real) root-finding algorithms.
+ * Implementations will search for only one zero in the given interval.
+ *
+ */
+public interface UnivariateSolver
+    extends BaseUnivariateSolver<UnivariateFunction> {}
diff --git a/src/main/java/org/apache/commons/math3/analysis/solvers/UnivariateSolverUtils.java b/src/main/java/org/apache/commons/math3/analysis/solvers/UnivariateSolverUtils.java
new file mode 100644
index 0000000..71b34c5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/solvers/UnivariateSolverUtils.java
@@ -0,0 +1,467 @@
+/*
+ * 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.math3.analysis.solvers;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.exception.NoBracketingException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Utility routines for {@link UnivariateSolver} objects.
+ *
+ */
+public class UnivariateSolverUtils {
+    /**
+     * Class contains only static methods.
+     */
+    private UnivariateSolverUtils() {}
+
+    /**
+     * Convenience method to find a zero of a univariate real function.  A default
+     * solver is used.
+     *
+     * @param function Function.
+     * @param x0 Lower bound for the interval.
+     * @param x1 Upper bound for the interval.
+     * @return a value where the function is zero.
+     * @throws NoBracketingException if the function has the same sign at the
+     * endpoints.
+     * @throws NullArgumentException if {@code function} is {@code null}.
+     */
+    public static double solve(UnivariateFunction function, double x0, double x1)
+        throws NullArgumentException,
+               NoBracketingException {
+        if (function == null) {
+            throw new NullArgumentException(LocalizedFormats.FUNCTION);
+        }
+        final UnivariateSolver solver = new BrentSolver();
+        return solver.solve(Integer.MAX_VALUE, function, x0, x1);
+    }
+
+    /**
+     * Convenience method to find a zero of a univariate real function.  A default
+     * solver is used.
+     *
+     * @param function Function.
+     * @param x0 Lower bound for the interval.
+     * @param x1 Upper bound for the interval.
+     * @param absoluteAccuracy Accuracy to be used by the solver.
+     * @return a value where the function is zero.
+     * @throws NoBracketingException if the function has the same sign at the
+     * endpoints.
+     * @throws NullArgumentException if {@code function} is {@code null}.
+     */
+    public static double solve(UnivariateFunction function,
+                               double x0, double x1,
+                               double absoluteAccuracy)
+        throws NullArgumentException,
+               NoBracketingException {
+        if (function == null) {
+            throw new NullArgumentException(LocalizedFormats.FUNCTION);
+        }
+        final UnivariateSolver solver = new BrentSolver(absoluteAccuracy);
+        return solver.solve(Integer.MAX_VALUE, function, x0, x1);
+    }
+
+    /**
+     * Force a root found by a non-bracketing solver to lie on a specified side,
+     * as if the solver were a bracketing one.
+     *
+     * @param maxEval maximal number of new evaluations of the function
+     * (evaluations already done for finding the root should have already been subtracted
+     * from this number)
+     * @param f function to solve
+     * @param bracketing bracketing solver to use for shifting the root
+     * @param baseRoot original root found by a previous non-bracketing solver
+     * @param min minimal bound of the search interval
+     * @param max maximal bound of the search interval
+     * @param allowedSolution the kind of solutions that the root-finding algorithm may
+     * accept as solutions.
+     * @return a root approximation, on the specified side of the exact root
+     * @throws NoBracketingException if the function has the same sign at the
+     * endpoints.
+     */
+    public static double forceSide(final int maxEval, final UnivariateFunction f,
+                                   final BracketedUnivariateSolver<UnivariateFunction> bracketing,
+                                   final double baseRoot, final double min, final double max,
+                                   final AllowedSolution allowedSolution)
+        throws NoBracketingException {
+
+        if (allowedSolution == AllowedSolution.ANY_SIDE) {
+            // no further bracketing required
+            return baseRoot;
+        }
+
+        // find a very small interval bracketing the root
+        final double step = FastMath.max(bracketing.getAbsoluteAccuracy(),
+                                         FastMath.abs(baseRoot * bracketing.getRelativeAccuracy()));
+        double xLo        = FastMath.max(min, baseRoot - step);
+        double fLo        = f.value(xLo);
+        double xHi        = FastMath.min(max, baseRoot + step);
+        double fHi        = f.value(xHi);
+        int remainingEval = maxEval - 2;
+        while (remainingEval > 0) {
+
+            if ((fLo >= 0 && fHi <= 0) || (fLo <= 0 && fHi >= 0)) {
+                // compute the root on the selected side
+                return bracketing.solve(remainingEval, f, xLo, xHi, baseRoot, allowedSolution);
+            }
+
+            // try increasing the interval
+            boolean changeLo = false;
+            boolean changeHi = false;
+            if (fLo < fHi) {
+                // increasing function
+                if (fLo >= 0) {
+                    changeLo = true;
+                } else {
+                    changeHi = true;
+                }
+            } else if (fLo > fHi) {
+                // decreasing function
+                if (fLo <= 0) {
+                    changeLo = true;
+                } else {
+                    changeHi = true;
+                }
+            } else {
+                // unknown variation
+                changeLo = true;
+                changeHi = true;
+            }
+
+            // update the lower bound
+            if (changeLo) {
+                xLo = FastMath.max(min, xLo - step);
+                fLo  = f.value(xLo);
+                remainingEval--;
+            }
+
+            // update the higher bound
+            if (changeHi) {
+                xHi = FastMath.min(max, xHi + step);
+                fHi  = f.value(xHi);
+                remainingEval--;
+            }
+
+        }
+
+        throw new NoBracketingException(LocalizedFormats.FAILED_BRACKETING,
+                                        xLo, xHi, fLo, fHi,
+                                        maxEval - remainingEval, maxEval, baseRoot,
+                                        min, max);
+
+    }
+
+    /**
+     * This method simply calls {@link #bracket(UnivariateFunction, double, double, double,
+     * double, double, int) bracket(function, initial, lowerBound, upperBound, q, r, maximumIterations)}
+     * with {@code q} and {@code r} set to 1.0 and {@code maximumIterations} set to {@code Integer.MAX_VALUE}.
+     * <p>
+     * <strong>Note: </strong> this method can take {@code Integer.MAX_VALUE}
+     * iterations to throw a {@code ConvergenceException.}  Unless you are
+     * confident that there is a root between {@code lowerBound} and
+     * {@code upperBound} near {@code initial}, it is better to use
+     * {@link #bracket(UnivariateFunction, double, double, double, double,double, int)
+     * bracket(function, initial, lowerBound, upperBound, q, r, maximumIterations)},
+     * explicitly specifying the maximum number of iterations.</p>
+     *
+     * @param function Function.
+     * @param initial Initial midpoint of interval being expanded to
+     * bracket a root.
+     * @param lowerBound Lower bound (a is never lower than this value)
+     * @param upperBound Upper bound (b never is greater than this
+     * value).
+     * @return a two-element array holding a and b.
+     * @throws NoBracketingException if a root cannot be bracketted.
+     * @throws NotStrictlyPositiveException if {@code maximumIterations <= 0}.
+     * @throws NullArgumentException if {@code function} is {@code null}.
+     */
+    public static double[] bracket(UnivariateFunction function,
+                                   double initial,
+                                   double lowerBound, double upperBound)
+        throws NullArgumentException,
+               NotStrictlyPositiveException,
+               NoBracketingException {
+        return bracket(function, initial, lowerBound, upperBound, 1.0, 1.0, Integer.MAX_VALUE);
+    }
+
+     /**
+     * This method simply calls {@link #bracket(UnivariateFunction, double, double, double,
+     * double, double, int) bracket(function, initial, lowerBound, upperBound, q, r, maximumIterations)}
+     * with {@code q} and {@code r} set to 1.0.
+     * @param function Function.
+     * @param initial Initial midpoint of interval being expanded to
+     * bracket a root.
+     * @param lowerBound Lower bound (a is never lower than this value).
+     * @param upperBound Upper bound (b never is greater than this
+     * value).
+     * @param maximumIterations Maximum number of iterations to perform
+     * @return a two element array holding a and b.
+     * @throws NoBracketingException if the algorithm fails to find a and b
+     * satisfying the desired conditions.
+     * @throws NotStrictlyPositiveException if {@code maximumIterations <= 0}.
+     * @throws NullArgumentException if {@code function} is {@code null}.
+     */
+    public static double[] bracket(UnivariateFunction function,
+                                   double initial,
+                                   double lowerBound, double upperBound,
+                                   int maximumIterations)
+        throws NullArgumentException,
+               NotStrictlyPositiveException,
+               NoBracketingException {
+        return bracket(function, initial, lowerBound, upperBound, 1.0, 1.0, maximumIterations);
+    }
+
+    /**
+     * This method attempts to find two values a and b satisfying <ul>
+     * <li> {@code lowerBound <= a < initial < b <= upperBound} </li>
+     * <li> {@code f(a) * f(b) <= 0} </li>
+     * </ul>
+     * If {@code f} is continuous on {@code [a,b]}, this means that {@code a}
+     * and {@code b} bracket a root of {@code f}.
+     * <p>
+     * The algorithm checks the sign of \( f(l_k) \) and \( f(u_k) \) for increasing
+     * values of k, where \( l_k = max(lower, initial - \delta_k) \),
+     * \( u_k = min(upper, initial + \delta_k) \), using recurrence
+     * \( \delta_{k+1} = r \delta_k + q, \delta_0 = 0\) and starting search with \( k=1 \).
+     * The algorithm stops when one of the following happens: <ul>
+     * <li> at least one positive and one negative value have been found --  success!</li>
+     * <li> both endpoints have reached their respective limits -- NoBracketingException </li>
+     * <li> {@code maximumIterations} iterations elapse -- NoBracketingException </li></ul>
+     * <p>
+     * If different signs are found at first iteration ({@code k=1}), then the returned
+     * interval will be \( [a, b] = [l_1, u_1] \). If different signs are found at a later
+     * iteration {@code k>1}, then the returned interval will be either
+     * \( [a, b] = [l_{k+1}, l_{k}] \) or \( [a, b] = [u_{k}, u_{k+1}] \). A root solver called
+     * with these parameters will therefore start with the smallest bracketing interval known
+     * at this step.
+     * </p>
+     * <p>
+     * Interval expansion rate is tuned by changing the recurrence parameters {@code r} and
+     * {@code q}. When the multiplicative factor {@code r} is set to 1, the sequence is a
+     * simple arithmetic sequence with linear increase. When the multiplicative factor {@code r}
+     * is larger than 1, the sequence has an asymptotically exponential rate. Note than the
+     * additive parameter {@code q} should never be set to zero, otherwise the interval would
+     * degenerate to the single initial point for all values of {@code k}.
+     * </p>
+     * <p>
+     * As a rule of thumb, when the location of the root is expected to be approximately known
+     * within some error margin, {@code r} should be set to 1 and {@code q} should be set to the
+     * order of magnitude of the error margin. When the location of the root is really a wild guess,
+     * then {@code r} should be set to a value larger than 1 (typically 2 to double the interval
+     * length at each iteration) and {@code q} should be set according to half the initial
+     * search interval length.
+     * </p>
+     * <p>
+     * As an example, if we consider the trivial function {@code f(x) = 1 - x} and use
+     * {@code initial = 4}, {@code r = 1}, {@code q = 2}, the algorithm will compute
+     * {@code f(4-2) = f(2) = -1} and {@code f(4+2) = f(6) = -5} for {@code k = 1}, then
+     * {@code f(4-4) = f(0) = +1} and {@code f(4+4) = f(8) = -7} for {@code k = 2}. Then it will
+     * return the interval {@code [0, 2]} as the smallest one known to be bracketing the root.
+     * As shown by this example, the initial value (here {@code 4}) may lie outside of the returned
+     * bracketing interval.
+     * </p>
+     * @param function function to check
+     * @param initial Initial midpoint of interval being expanded to
+     * bracket a root.
+     * @param lowerBound Lower bound (a is never lower than this value).
+     * @param upperBound Upper bound (b never is greater than this
+     * value).
+     * @param q additive offset used to compute bounds sequence (must be strictly positive)
+     * @param r multiplicative factor used to compute bounds sequence
+     * @param maximumIterations Maximum number of iterations to perform
+     * @return a two element array holding the bracketing values.
+     * @exception NoBracketingException if function cannot be bracketed in the search interval
+     */
+    public static double[] bracket(final UnivariateFunction function, final double initial,
+                                   final double lowerBound, final double upperBound,
+                                   final double q, final double r, final int maximumIterations)
+        throws NoBracketingException {
+
+        if (function == null) {
+            throw new NullArgumentException(LocalizedFormats.FUNCTION);
+        }
+        if (q <= 0)  {
+            throw new NotStrictlyPositiveException(q);
+        }
+        if (maximumIterations <= 0)  {
+            throw new NotStrictlyPositiveException(LocalizedFormats.INVALID_MAX_ITERATIONS, maximumIterations);
+        }
+        verifySequence(lowerBound, initial, upperBound);
+
+        // initialize the recurrence
+        double a     = initial;
+        double b     = initial;
+        double fa    = Double.NaN;
+        double fb    = Double.NaN;
+        double delta = 0;
+
+        for (int numIterations = 0;
+             (numIterations < maximumIterations) && (a > lowerBound || b < upperBound);
+             ++numIterations) {
+
+            final double previousA  = a;
+            final double previousFa = fa;
+            final double previousB  = b;
+            final double previousFb = fb;
+
+            delta = r * delta + q;
+            a     = FastMath.max(initial - delta, lowerBound);
+            b     = FastMath.min(initial + delta, upperBound);
+            fa    = function.value(a);
+            fb    = function.value(b);
+
+            if (numIterations == 0) {
+                // at first iteration, we don't have a previous interval
+                // we simply compare both sides of the initial interval
+                if (fa * fb <= 0) {
+                    // the first interval already brackets a root
+                    return new double[] { a, b };
+                }
+            } else {
+                // we have a previous interval with constant sign and expand it,
+                // we expect sign changes to occur at boundaries
+                if (fa * previousFa <= 0) {
+                    // sign change detected at near lower bound
+                    return new double[] { a, previousA };
+                } else if (fb * previousFb <= 0) {
+                    // sign change detected at near upper bound
+                    return new double[] { previousB, b };
+                }
+            }
+
+        }
+
+        // no bracketing found
+        throw new NoBracketingException(a, b, fa, fb);
+
+    }
+
+    /**
+     * Compute the midpoint of two values.
+     *
+     * @param a first value.
+     * @param b second value.
+     * @return the midpoint.
+     */
+    public static double midpoint(double a, double b) {
+        return (a + b) * 0.5;
+    }
+
+    /**
+     * Check whether the interval bounds bracket a root. That is, if the
+     * values at the endpoints are not equal to zero, then the function takes
+     * opposite signs at the endpoints.
+     *
+     * @param function Function.
+     * @param lower Lower endpoint.
+     * @param upper Upper endpoint.
+     * @return {@code true} if the function values have opposite signs at the
+     * given points.
+     * @throws NullArgumentException if {@code function} is {@code null}.
+     */
+    public static boolean isBracketing(UnivariateFunction function,
+                                       final double lower,
+                                       final double upper)
+        throws NullArgumentException {
+        if (function == null) {
+            throw new NullArgumentException(LocalizedFormats.FUNCTION);
+        }
+        final double fLo = function.value(lower);
+        final double fHi = function.value(upper);
+        return (fLo >= 0 && fHi <= 0) || (fLo <= 0 && fHi >= 0);
+    }
+
+    /**
+     * Check whether the arguments form a (strictly) increasing sequence.
+     *
+     * @param start First number.
+     * @param mid Second number.
+     * @param end Third number.
+     * @return {@code true} if the arguments form an increasing sequence.
+     */
+    public static boolean isSequence(final double start,
+                                     final double mid,
+                                     final double end) {
+        return (start < mid) && (mid < end);
+    }
+
+    /**
+     * Check that the endpoints specify an interval.
+     *
+     * @param lower Lower endpoint.
+     * @param upper Upper endpoint.
+     * @throws NumberIsTooLargeException if {@code lower >= upper}.
+     */
+    public static void verifyInterval(final double lower,
+                                      final double upper)
+        throws NumberIsTooLargeException {
+        if (lower >= upper) {
+            throw new NumberIsTooLargeException(LocalizedFormats.ENDPOINTS_NOT_AN_INTERVAL,
+                                                lower, upper, false);
+        }
+    }
+
+    /**
+     * Check that {@code lower < initial < upper}.
+     *
+     * @param lower Lower endpoint.
+     * @param initial Initial value.
+     * @param upper Upper endpoint.
+     * @throws NumberIsTooLargeException if {@code lower >= initial} or
+     * {@code initial >= upper}.
+     */
+    public static void verifySequence(final double lower,
+                                      final double initial,
+                                      final double upper)
+        throws NumberIsTooLargeException {
+        verifyInterval(lower, initial);
+        verifyInterval(initial, upper);
+    }
+
+    /**
+     * Check that the endpoints specify an interval and the end points
+     * bracket a root.
+     *
+     * @param function Function.
+     * @param lower Lower endpoint.
+     * @param upper Upper endpoint.
+     * @throws NoBracketingException if the function has the same sign at the
+     * endpoints.
+     * @throws NullArgumentException if {@code function} is {@code null}.
+     */
+    public static void verifyBracketing(UnivariateFunction function,
+                                        final double lower,
+                                        final double upper)
+        throws NullArgumentException,
+               NoBracketingException {
+        if (function == null) {
+            throw new NullArgumentException(LocalizedFormats.FUNCTION);
+        }
+        verifyInterval(lower, upper);
+        if (!isBracketing(function, lower, upper)) {
+            throw new NoBracketingException(lower, upper,
+                                            function.value(lower),
+                                            function.value(upper));
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/analysis/solvers/package-info.java b/src/main/java/org/apache/commons/math3/analysis/solvers/package-info.java
new file mode 100644
index 0000000..eb15fbc
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/analysis/solvers/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+/**
+ *
+ *     Root finding algorithms, for univariate real functions.
+ *
+ */
+package org.apache.commons.math3.analysis.solvers;
diff --git a/src/main/java/org/apache/commons/math3/complex/Complex.java b/src/main/java/org/apache/commons/math3/complex/Complex.java
new file mode 100644
index 0000000..cd8d794
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/complex/Complex.java
@@ -0,0 +1,1219 @@
+/*
+ * 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.math3.complex;
+
+import org.apache.commons.math3.FieldElement;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+import org.apache.commons.math3.util.Precision;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Representation of a Complex number, i.e. a number which has both a real and imaginary part.
+ *
+ * <p>Implementations of arithmetic operations handle {@code NaN} and infinite values according to
+ * the rules for {@link java.lang.Double}, i.e. {@link #equals} is an equivalence relation for all
+ * instances that have a {@code NaN} in either real or imaginary part, e.g. the following are
+ * considered equal:
+ *
+ * <ul>
+ *   <li>{@code 1 + NaNi}
+ *   <li>{@code NaN + i}
+ *   <li>{@code NaN + NaNi}
+ * </ul>
+ *
+ * <p>Note that this contradicts the IEEE-754 standard for floating point numbers (according to
+ * which the test {@code x == x} must fail if {@code x} is {@code NaN}). The method {@link
+ * org.apache.commons.math3.util.Precision#equals(double,double,int) equals for primitive double} in
+ * {@link org.apache.commons.math3.util.Precision} conforms with IEEE-754 while this class conforms
+ * with the standard behavior for Java object types.
+ */
+public class Complex implements FieldElement<Complex>, Serializable {
+    /** The square root of -1. A number representing "0.0 + 1.0i" */
+    public static final Complex I = new Complex(0.0, 1.0);
+
+    // CHECKSTYLE: stop ConstantName
+    /** A complex number representing "NaN + NaNi" */
+    public static final Complex NaN = new Complex(Double.NaN, Double.NaN);
+
+    // CHECKSTYLE: resume ConstantName
+    /** A complex number representing "+INF + INFi" */
+    public static final Complex INF =
+            new Complex(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
+
+    /** A complex number representing "1.0 + 0.0i" */
+    public static final Complex ONE = new Complex(1.0, 0.0);
+
+    /** A complex number representing "0.0 + 0.0i" */
+    public static final Complex ZERO = new Complex(0.0, 0.0);
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -6195664516687396620L;
+
+    /** The imaginary part. */
+    private final double imaginary;
+
+    /** The real part. */
+    private final double real;
+
+    /** Record whether this complex number is equal to NaN. */
+    private final transient boolean isNaN;
+
+    /** Record whether this complex number is infinite. */
+    private final transient boolean isInfinite;
+
+    /**
+     * Create a complex number given only the real part.
+     *
+     * @param real Real part.
+     */
+    public Complex(double real) {
+        this(real, 0.0);
+    }
+
+    /**
+     * Create a complex number given the real and imaginary parts.
+     *
+     * @param real Real part.
+     * @param imaginary Imaginary part.
+     */
+    public Complex(double real, double imaginary) {
+        this.real = real;
+        this.imaginary = imaginary;
+
+        isNaN = Double.isNaN(real) || Double.isNaN(imaginary);
+        isInfinite = !isNaN && (Double.isInfinite(real) || Double.isInfinite(imaginary));
+    }
+
+    /**
+     * Return the absolute value of this complex number. Returns {@code NaN} if either real or
+     * imaginary part is {@code NaN} and {@code Double.POSITIVE_INFINITY} if neither part is {@code
+     * NaN}, but at least one part is infinite.
+     *
+     * @return the absolute value.
+     */
+    public double abs() {
+        if (isNaN) {
+            return Double.NaN;
+        }
+        if (isInfinite()) {
+            return Double.POSITIVE_INFINITY;
+        }
+        if (FastMath.abs(real) < FastMath.abs(imaginary)) {
+            if (imaginary == 0.0) {
+                return FastMath.abs(real);
+            }
+            double q = real / imaginary;
+            return FastMath.abs(imaginary) * FastMath.sqrt(1 + q * q);
+        } else {
+            if (real == 0.0) {
+                return FastMath.abs(imaginary);
+            }
+            double q = imaginary / real;
+            return FastMath.abs(real) * FastMath.sqrt(1 + q * q);
+        }
+    }
+
+    /**
+     * Returns a {@code Complex} whose value is {@code (this + addend)}. Uses the definitional
+     * formula
+     *
+     * <p>{@code (a + bi) + (c + di) = (a+c) + (b+d)i} If either {@code this} or {@code addend} has
+     * a {@code NaN} value in either part, {@link #NaN} is returned; otherwise {@code Infinite} and
+     * {@code NaN} values are returned in the parts of the result according to the rules for {@link
+     * java.lang.Double} arithmetic.
+     *
+     * @param addend Value to be added to this {@code Complex}.
+     * @return {@code this + addend}.
+     * @throws NullArgumentException if {@code addend} is {@code null}.
+     */
+    public Complex add(Complex addend) throws NullArgumentException {
+        MathUtils.checkNotNull(addend);
+        if (isNaN || addend.isNaN) {
+            return NaN;
+        }
+
+        return createComplex(real + addend.getReal(), imaginary + addend.getImaginary());
+    }
+
+    /**
+     * Returns a {@code Complex} whose value is {@code (this + addend)}, with {@code addend}
+     * interpreted as a real number.
+     *
+     * @param addend Value to be added to this {@code Complex}.
+     * @return {@code this + addend}.
+     * @see #add(Complex)
+     */
+    public Complex add(double addend) {
+        if (isNaN || Double.isNaN(addend)) {
+            return NaN;
+        }
+
+        return createComplex(real + addend, imaginary);
+    }
+
+    /**
+     * Returns the conjugate of this complex number. The conjugate of {@code a + bi} is {@code a -
+     * bi}.
+     *
+     * <p>{@link #NaN} is returned if either the real or imaginary part of this Complex number
+     * equals {@code Double.NaN}.
+     *
+     * <p>If the imaginary part is infinite, and the real part is not {@code NaN}, the returned
+     * value has infinite imaginary part of the opposite sign, e.g. the conjugate of {@code 1 +
+     * POSITIVE_INFINITY i} is {@code 1 - NEGATIVE_INFINITY i}.
+     *
+     * @return the conjugate of this Complex object.
+     */
+    public Complex conjugate() {
+        if (isNaN) {
+            return NaN;
+        }
+
+        return createComplex(real, -imaginary);
+    }
+
+    /**
+     * Returns a {@code Complex} whose value is {@code (this / divisor)}. Implements the
+     * definitional formula
+     *
+     * <pre>
+     *  <code>
+     *    a + bi          ac + bd + (bc - ad)i
+     *    ----------- = -------------------------
+     *    c + di         c<sup>2</sup> + d<sup>2</sup>
+     *  </code>
+     * </pre>
+     *
+     * but uses <a href="http://doi.acm.org/10.1145/1039813.1039814">prescaling of operands</a> to
+     * limit the effects of overflows and underflows in the computation.
+     *
+     * <p>{@code Infinite} and {@code NaN} values are handled according to the following rules,
+     * applied in the order presented:
+     *
+     * <ul>
+     *   <li>If either {@code this} or {@code divisor} has a {@code NaN} value in either part,
+     *       {@link #NaN} is returned.
+     *   <li>If {@code divisor} equals {@link #ZERO}, {@link #NaN} is returned.
+     *   <li>If {@code this} and {@code divisor} are both infinite, {@link #NaN} is returned.
+     *   <li>If {@code this} is finite (i.e., has no {@code Infinite} or {@code NaN} parts) and
+     *       {@code divisor} is infinite (one or both parts infinite), {@link #ZERO} is returned.
+     *   <li>If {@code this} is infinite and {@code divisor} is finite, {@code NaN} values are
+     *       returned in the parts of the result if the {@link java.lang.Double} rules applied to
+     *       the definitional formula force {@code NaN} results.
+     * </ul>
+     *
+     * @param divisor Value by which this {@code Complex} is to be divided.
+     * @return {@code this / divisor}.
+     * @throws NullArgumentException if {@code divisor} is {@code null}.
+     */
+    public Complex divide(Complex divisor) throws NullArgumentException {
+        MathUtils.checkNotNull(divisor);
+        if (isNaN || divisor.isNaN) {
+            return NaN;
+        }
+
+        final double c = divisor.getReal();
+        final double d = divisor.getImaginary();
+        if (c == 0.0 && d == 0.0) {
+            return NaN;
+        }
+
+        if (divisor.isInfinite() && !isInfinite()) {
+            return ZERO;
+        }
+
+        if (FastMath.abs(c) < FastMath.abs(d)) {
+            double q = c / d;
+            double denominator = c * q + d;
+            return createComplex(
+                    (real * q + imaginary) / denominator, (imaginary * q - real) / denominator);
+        } else {
+            double q = d / c;
+            double denominator = d * q + c;
+            return createComplex(
+                    (imaginary * q + real) / denominator, (imaginary - real * q) / denominator);
+        }
+    }
+
+    /**
+     * Returns a {@code Complex} whose value is {@code (this / divisor)}, with {@code divisor}
+     * interpreted as a real number.
+     *
+     * @param divisor Value by which this {@code Complex} is to be divided.
+     * @return {@code this / divisor}.
+     * @see #divide(Complex)
+     */
+    public Complex divide(double divisor) {
+        if (isNaN || Double.isNaN(divisor)) {
+            return NaN;
+        }
+        if (divisor == 0d) {
+            return NaN;
+        }
+        if (Double.isInfinite(divisor)) {
+            return !isInfinite() ? ZERO : NaN;
+        }
+        return createComplex(real / divisor, imaginary / divisor);
+    }
+
+    /** {@inheritDoc} */
+    public Complex reciprocal() {
+        if (isNaN) {
+            return NaN;
+        }
+
+        if (real == 0.0 && imaginary == 0.0) {
+            return INF;
+        }
+
+        if (isInfinite) {
+            return ZERO;
+        }
+
+        if (FastMath.abs(real) < FastMath.abs(imaginary)) {
+            double q = real / imaginary;
+            double scale = 1. / (real * q + imaginary);
+            return createComplex(scale * q, -scale);
+        } else {
+            double q = imaginary / real;
+            double scale = 1. / (imaginary * q + real);
+            return createComplex(scale, -scale * q);
+        }
+    }
+
+    /**
+     * Test for equality with another object. If both the real and imaginary parts of two complex
+     * numbers are exactly the same, and neither is {@code Double.NaN}, the two Complex objects are
+     * considered to be equal. The behavior is the same as for JDK's {@link Double#equals(Object)
+     * Double}:
+     *
+     * <ul>
+     *   <li>All {@code NaN} values are considered to be equal, i.e, if either (or both) real and
+     *       imaginary parts of the complex number are equal to {@code Double.NaN}, the complex
+     *       number is equal to {@code NaN}.
+     *   <li>Instances constructed with different representations of zero (i.e. either "0" or "-0")
+     *       are <em>not</em> considered to be equal.
+     * </ul>
+     *
+     * @param other Object to test for equality with this instance.
+     * @return {@code true} if the objects are equal, {@code false} if object is {@code null}, not
+     *     an instance of {@code Complex}, or not equal to this instance.
+     */
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+        if (other instanceof Complex) {
+            Complex c = (Complex) other;
+            if (c.isNaN) {
+                return isNaN;
+            } else {
+                return MathUtils.equals(real, c.real) && MathUtils.equals(imaginary, c.imaginary);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Test for the floating-point equality between Complex objects. It returns {@code true} if both
+     * arguments are equal or within the range of allowed error (inclusive).
+     *
+     * @param x First value (cannot be {@code null}).
+     * @param y Second value (cannot be {@code null}).
+     * @param maxUlps {@code (maxUlps - 1)} is the number of floating point values between the real
+     *     (resp. imaginary) parts of {@code x} and {@code y}.
+     * @return {@code true} if there are fewer than {@code maxUlps} floating point values between
+     *     the real (resp. imaginary) parts of {@code x} and {@code y}.
+     * @see Precision#equals(double,double,int)
+     * @since 3.3
+     */
+    public static boolean equals(Complex x, Complex y, int maxUlps) {
+        return Precision.equals(x.real, y.real, maxUlps)
+                && Precision.equals(x.imaginary, y.imaginary, maxUlps);
+    }
+
+    /**
+     * Returns {@code true} iff the values are equal as defined by {@link
+     * #equals(Complex,Complex,int) equals(x, y, 1)}.
+     *
+     * @param x First value (cannot be {@code null}).
+     * @param y Second value (cannot be {@code null}).
+     * @return {@code true} if the values are equal.
+     * @since 3.3
+     */
+    public static boolean equals(Complex x, Complex y) {
+        return equals(x, y, 1);
+    }
+
+    /**
+     * Returns {@code true} if, both for the real part and for the imaginary part, there is no
+     * double value strictly between the arguments or the difference between them is within the
+     * range of allowed error (inclusive). Returns {@code false} if either of the arguments is NaN.
+     *
+     * @param x First value (cannot be {@code null}).
+     * @param y Second value (cannot be {@code null}).
+     * @param eps Amount of allowed absolute error.
+     * @return {@code true} if the values are two adjacent floating point numbers or they are within
+     *     range of each other.
+     * @see Precision#equals(double,double,double)
+     * @since 3.3
+     */
+    public static boolean equals(Complex x, Complex y, double eps) {
+        return Precision.equals(x.real, y.real, eps)
+                && Precision.equals(x.imaginary, y.imaginary, eps);
+    }
+
+    /**
+     * Returns {@code true} if, both for the real part and for the imaginary part, there is no
+     * double value strictly between the arguments or the relative difference between them is
+     * smaller or equal to the given tolerance. Returns {@code false} if either of the arguments is
+     * NaN.
+     *
+     * @param x First value (cannot be {@code null}).
+     * @param y Second value (cannot be {@code null}).
+     * @param eps Amount of allowed relative error.
+     * @return {@code true} if the values are two adjacent floating point numbers or they are within
+     *     range of each other.
+     * @see Precision#equalsWithRelativeTolerance(double,double,double)
+     * @since 3.3
+     */
+    public static boolean equalsWithRelativeTolerance(Complex x, Complex y, double eps) {
+        return Precision.equalsWithRelativeTolerance(x.real, y.real, eps)
+                && Precision.equalsWithRelativeTolerance(x.imaginary, y.imaginary, eps);
+    }
+
+    /**
+     * Get a hashCode for the complex number. Any {@code Double.NaN} value in real or imaginary part
+     * produces the same hash code {@code 7}.
+     *
+     * @return a hash code value for this object.
+     */
+    @Override
+    public int hashCode() {
+        if (isNaN) {
+            return 7;
+        }
+        return 37 * (17 * MathUtils.hash(imaginary) + MathUtils.hash(real));
+    }
+
+    /**
+     * Access the imaginary part.
+     *
+     * @return the imaginary part.
+     */
+    public double getImaginary() {
+        return imaginary;
+    }
+
+    /**
+     * Access the real part.
+     *
+     * @return the real part.
+     */
+    public double getReal() {
+        return real;
+    }
+
+    /**
+     * Checks whether either or both parts of this complex number is {@code NaN}.
+     *
+     * @return true if either or both parts of this complex number is {@code NaN}; false otherwise.
+     */
+    public boolean isNaN() {
+        return isNaN;
+    }
+
+    /**
+     * Checks whether either the real or imaginary part of this complex number takes an infinite
+     * value (either {@code Double.POSITIVE_INFINITY} or {@code Double.NEGATIVE_INFINITY}) and
+     * neither part is {@code NaN}.
+     *
+     * @return true if one or both parts of this complex number are infinite and neither part is
+     *     {@code NaN}.
+     */
+    public boolean isInfinite() {
+        return isInfinite;
+    }
+
+    /**
+     * Returns a {@code Complex} whose value is {@code this * factor}. Implements preliminary checks
+     * for {@code NaN} and infinity followed by the definitional formula:
+     *
+     * <p>{@code (a + bi)(c + di) = (ac - bd) + (ad + bc)i} Returns {@link #NaN} if either {@code
+     * this} or {@code factor} has one or more {@code NaN} parts.
+     *
+     * <p>Returns {@link #INF} if neither {@code this} nor {@code factor} has one or more {@code
+     * NaN} parts and if either {@code this} or {@code factor} has one or more infinite parts (same
+     * result is returned regardless of the sign of the components).
+     *
+     * <p>Returns finite values in components of the result per the definitional formula in all
+     * remaining cases.
+     *
+     * @param factor value to be multiplied by this {@code Complex}.
+     * @return {@code this * factor}.
+     * @throws NullArgumentException if {@code factor} is {@code null}.
+     */
+    public Complex multiply(Complex factor) throws NullArgumentException {
+        MathUtils.checkNotNull(factor);
+        if (isNaN || factor.isNaN) {
+            return NaN;
+        }
+        if (Double.isInfinite(real)
+                || Double.isInfinite(imaginary)
+                || Double.isInfinite(factor.real)
+                || Double.isInfinite(factor.imaginary)) {
+            // we don't use isInfinite() to avoid testing for NaN again
+            return INF;
+        }
+        return createComplex(
+                real * factor.real - imaginary * factor.imaginary,
+                real * factor.imaginary + imaginary * factor.real);
+    }
+
+    /**
+     * Returns a {@code Complex} whose value is {@code this * factor}, with {@code factor}
+     * interpreted as a integer number.
+     *
+     * @param factor value to be multiplied by this {@code Complex}.
+     * @return {@code this * factor}.
+     * @see #multiply(Complex)
+     */
+    public Complex multiply(final int factor) {
+        if (isNaN) {
+            return NaN;
+        }
+        if (Double.isInfinite(real) || Double.isInfinite(imaginary)) {
+            return INF;
+        }
+        return createComplex(real * factor, imaginary * factor);
+    }
+
+    /**
+     * Returns a {@code Complex} whose value is {@code this * factor}, with {@code factor}
+     * interpreted as a real number.
+     *
+     * @param factor value to be multiplied by this {@code Complex}.
+     * @return {@code this * factor}.
+     * @see #multiply(Complex)
+     */
+    public Complex multiply(double factor) {
+        if (isNaN || Double.isNaN(factor)) {
+            return NaN;
+        }
+        if (Double.isInfinite(real) || Double.isInfinite(imaginary) || Double.isInfinite(factor)) {
+            // we don't use isInfinite() to avoid testing for NaN again
+            return INF;
+        }
+        return createComplex(real * factor, imaginary * factor);
+    }
+
+    /**
+     * Returns a {@code Complex} whose value is {@code (-this)}. Returns {@code NaN} if either real
+     * or imaginary part of this Complex number is {@code Double.NaN}.
+     *
+     * @return {@code -this}.
+     */
+    public Complex negate() {
+        if (isNaN) {
+            return NaN;
+        }
+
+        return createComplex(-real, -imaginary);
+    }
+
+    /**
+     * Returns a {@code Complex} whose value is {@code (this - subtrahend)}. Uses the definitional
+     * formula
+     *
+     * <p>{@code (a + bi) - (c + di) = (a-c) + (b-d)i} If either {@code this} or {@code subtrahend}
+     * has a {@code NaN]} value in either part, {@link #NaN} is returned; otherwise infinite and
+     * {@code NaN} values are returned in the parts of the result according to the rules for {@link
+     * java.lang.Double} arithmetic.
+     *
+     * @param subtrahend value to be subtracted from this {@code Complex}.
+     * @return {@code this - subtrahend}.
+     * @throws NullArgumentException if {@code subtrahend} is {@code null}.
+     */
+    public Complex subtract(Complex subtrahend) throws NullArgumentException {
+        MathUtils.checkNotNull(subtrahend);
+        if (isNaN || subtrahend.isNaN) {
+            return NaN;
+        }
+
+        return createComplex(real - subtrahend.getReal(), imaginary - subtrahend.getImaginary());
+    }
+
+    /**
+     * Returns a {@code Complex} whose value is {@code (this - subtrahend)}.
+     *
+     * @param subtrahend value to be subtracted from this {@code Complex}.
+     * @return {@code this - subtrahend}.
+     * @see #subtract(Complex)
+     */
+    public Complex subtract(double subtrahend) {
+        if (isNaN || Double.isNaN(subtrahend)) {
+            return NaN;
+        }
+        return createComplex(real - subtrahend, imaginary);
+    }
+
+    /**
+     * Compute the <a href="http://mathworld.wolfram.com/InverseCosine.html" TARGET="_top"> inverse
+     * cosine</a> of this complex number. Implements the formula:
+     *
+     * <p>{@code acos(z) = -i (log(z + i (sqrt(1 - z<sup>2</sup>))))} Returns {@link Complex#NaN} if
+     * either real or imaginary part of the input argument is {@code NaN} or infinite.
+     *
+     * @return the inverse cosine of this complex number.
+     * @since 1.2
+     */
+    public Complex acos() {
+        if (isNaN) {
+            return NaN;
+        }
+
+        return this.add(this.sqrt1z().multiply(I)).log().multiply(I.negate());
+    }
+
+    /**
+     * Compute the <a href="http://mathworld.wolfram.com/InverseSine.html" TARGET="_top"> inverse
+     * sine</a> of this complex number. Implements the formula:
+     *
+     * <p>{@code asin(z) = -i (log(sqrt(1 - z<sup>2</sup>) + iz))}
+     *
+     * <p>Returns {@link Complex#NaN} if either real or imaginary part of the input argument is
+     * {@code NaN} or infinite.
+     *
+     * @return the inverse sine of this complex number.
+     * @since 1.2
+     */
+    public Complex asin() {
+        if (isNaN) {
+            return NaN;
+        }
+
+        return sqrt1z().add(this.multiply(I)).log().multiply(I.negate());
+    }
+
+    /**
+     * Compute the <a href="http://mathworld.wolfram.com/InverseTangent.html" TARGET="_top"> inverse
+     * tangent</a> of this complex number. Implements the formula:
+     *
+     * <p>{@code atan(z) = (i/2) log((i + z)/(i - z))}
+     *
+     * <p>Returns {@link Complex#NaN} if either real or imaginary part of the input argument is
+     * {@code NaN} or infinite.
+     *
+     * @return the inverse tangent of this complex number
+     * @since 1.2
+     */
+    public Complex atan() {
+        if (isNaN) {
+            return NaN;
+        }
+
+        return this.add(I)
+                .divide(I.subtract(this))
+                .log()
+                .multiply(I.divide(createComplex(2.0, 0.0)));
+    }
+
+    /**
+     * Compute the <a href="http://mathworld.wolfram.com/Cosine.html" TARGET="_top"> cosine</a> of
+     * this complex number. Implements the formula:
+     *
+     * <p>{@code cos(a + bi) = cos(a)cosh(b) - sin(a)sinh(b)i}
+     *
+     * <p>where the (real) functions on the right-hand side are {@link FastMath#sin}, {@link
+     * FastMath#cos}, {@link FastMath#cosh} and {@link FastMath#sinh}.
+     *
+     * <p>Returns {@link Complex#NaN} if either real or imaginary part of the input argument is
+     * {@code NaN}.
+     *
+     * <p>Infinite values in real or imaginary parts of the input may result in infinite or NaN
+     * values returned in parts of the result.
+     *
+     * <pre>
+     *  Examples:
+     *  <code>
+     *   cos(1 &plusmn; INFINITY i) = 1 \u2213 INFINITY i
+     *   cos(&plusmn;INFINITY + i) = NaN + NaN i
+     *   cos(&plusmn;INFINITY &plusmn; INFINITY i) = NaN + NaN i
+     *  </code>
+     * </pre>
+     *
+     * @return the cosine of this complex number.
+     * @since 1.2
+     */
+    public Complex cos() {
+        if (isNaN) {
+            return NaN;
+        }
+
+        return createComplex(
+                FastMath.cos(real) * FastMath.cosh(imaginary),
+                -FastMath.sin(real) * FastMath.sinh(imaginary));
+    }
+
+    /**
+     * Compute the <a href="http://mathworld.wolfram.com/HyperbolicCosine.html" TARGET="_top">
+     * hyperbolic cosine</a> of this complex number. Implements the formula:
+     *
+     * <pre>
+     *  <code>
+     *   cosh(a + bi) = cosh(a)cos(b) + sinh(a)sin(b)i
+     *  </code>
+     * </pre>
+     *
+     * where the (real) functions on the right-hand side are {@link FastMath#sin}, {@link
+     * FastMath#cos}, {@link FastMath#cosh} and {@link FastMath#sinh}.
+     *
+     * <p>Returns {@link Complex#NaN} if either real or imaginary part of the input argument is
+     * {@code NaN}. Infinite values in real or imaginary parts of the input may result in infinite
+     * or NaN values returned in parts of the result.
+     *
+     * <pre>
+     *  Examples:
+     *  <code>
+     *   cosh(1 &plusmn; INFINITY i) = NaN + NaN i
+     *   cosh(&plusmn;INFINITY + i) = INFINITY &plusmn; INFINITY i
+     *   cosh(&plusmn;INFINITY &plusmn; INFINITY i) = NaN + NaN i
+     *  </code>
+     * </pre>
+     *
+     * @return the hyperbolic cosine of this complex number.
+     * @since 1.2
+     */
+    public Complex cosh() {
+        if (isNaN) {
+            return NaN;
+        }
+
+        return createComplex(
+                FastMath.cosh(real) * FastMath.cos(imaginary),
+                FastMath.sinh(real) * FastMath.sin(imaginary));
+    }
+
+    /**
+     * Compute the <a href="http://mathworld.wolfram.com/ExponentialFunction.html" TARGET="_top">
+     * exponential function</a> of this complex number. Implements the formula:
+     *
+     * <pre>
+     *  <code>
+     *   exp(a + bi) = exp(a)cos(b) + exp(a)sin(b)i
+     *  </code>
+     * </pre>
+     *
+     * where the (real) functions on the right-hand side are {@link FastMath#exp}, {@link
+     * FastMath#cos}, and {@link FastMath#sin}.
+     *
+     * <p>Returns {@link Complex#NaN} if either real or imaginary part of the input argument is
+     * {@code NaN}. Infinite values in real or imaginary parts of the input may result in infinite
+     * or NaN values returned in parts of the result.
+     *
+     * <pre>
+     *  Examples:
+     *  <code>
+     *   exp(1 &plusmn; INFINITY i) = NaN + NaN i
+     *   exp(INFINITY + i) = INFINITY + INFINITY i
+     *   exp(-INFINITY + i) = 0 + 0i
+     *   exp(&plusmn;INFINITY &plusmn; INFINITY i) = NaN + NaN i
+     *  </code>
+     * </pre>
+     *
+     * @return <code><i>e</i><sup>this</sup></code>.
+     * @since 1.2
+     */
+    public Complex exp() {
+        if (isNaN) {
+            return NaN;
+        }
+
+        double expReal = FastMath.exp(real);
+        return createComplex(expReal * FastMath.cos(imaginary), expReal * FastMath.sin(imaginary));
+    }
+
+    /**
+     * Compute the <a href="http://mathworld.wolfram.com/NaturalLogarithm.html" TARGET="_top">
+     * natural logarithm</a> of this complex number. Implements the formula:
+     *
+     * <pre>
+     *  <code>
+     *   log(a + bi) = ln(|a + bi|) + arg(a + bi)i
+     *  </code>
+     * </pre>
+     *
+     * where ln on the right hand side is {@link FastMath#log}, {@code |a + bi|} is the modulus,
+     * {@link Complex#abs}, and {@code arg(a + bi) = }{@link FastMath#atan2}(b, a).
+     *
+     * <p>Returns {@link Complex#NaN} if either real or imaginary part of the input argument is
+     * {@code NaN}. Infinite (or critical) values in real or imaginary parts of the input may result
+     * in infinite or NaN values returned in parts of the result.
+     *
+     * <pre>
+     *  Examples:
+     *  <code>
+     *   log(1 &plusmn; INFINITY i) = INFINITY &plusmn; (&pi;/2)i
+     *   log(INFINITY + i) = INFINITY + 0i
+     *   log(-INFINITY + i) = INFINITY + &pi;i
+     *   log(INFINITY &plusmn; INFINITY i) = INFINITY &plusmn; (&pi;/4)i
+     *   log(-INFINITY &plusmn; INFINITY i) = INFINITY &plusmn; (3&pi;/4)i
+     *   log(0 + 0i) = -INFINITY + 0i
+     *  </code>
+     * </pre>
+     *
+     * @return the value <code>ln &nbsp; this</code>, the natural logarithm of {@code this}.
+     * @since 1.2
+     */
+    public Complex log() {
+        if (isNaN) {
+            return NaN;
+        }
+
+        return createComplex(FastMath.log(abs()), FastMath.atan2(imaginary, real));
+    }
+
+    /**
+     * Returns of value of this complex number raised to the power of {@code x}. Implements the
+     * formula:
+     *
+     * <pre>
+     *  <code>
+     *   y<sup>x</sup> = exp(x&middot;log(y))
+     *  </code>
+     * </pre>
+     *
+     * where {@code exp} and {@code log} are {@link #exp} and {@link #log}, respectively.
+     *
+     * <p>Returns {@link Complex#NaN} if either real or imaginary part of the input argument is
+     * {@code NaN} or infinite, or if {@code y} equals {@link Complex#ZERO}.
+     *
+     * @param x exponent to which this {@code Complex} is to be raised.
+     * @return <code> this<sup>x</sup></code>.
+     * @throws NullArgumentException if x is {@code null}.
+     * @since 1.2
+     */
+    public Complex pow(Complex x) throws NullArgumentException {
+        MathUtils.checkNotNull(x);
+        return this.log().multiply(x).exp();
+    }
+
+    /**
+     * Returns of value of this complex number raised to the power of {@code x}.
+     *
+     * @param x exponent to which this {@code Complex} is to be raised.
+     * @return <code>this<sup>x</sup></code>.
+     * @see #pow(Complex)
+     */
+    public Complex pow(double x) {
+        return this.log().multiply(x).exp();
+    }
+
+    /**
+     * Compute the <a href="http://mathworld.wolfram.com/Sine.html" TARGET="_top"> sine</a> of this
+     * complex number. Implements the formula:
+     *
+     * <pre>
+     *  <code>
+     *   sin(a + bi) = sin(a)cosh(b) - cos(a)sinh(b)i
+     *  </code>
+     * </pre>
+     *
+     * where the (real) functions on the right-hand side are {@link FastMath#sin}, {@link
+     * FastMath#cos}, {@link FastMath#cosh} and {@link FastMath#sinh}.
+     *
+     * <p>Returns {@link Complex#NaN} if either real or imaginary part of the input argument is
+     * {@code NaN}.
+     *
+     * <p>Infinite values in real or imaginary parts of the input may result in infinite or {@code
+     * NaN} values returned in parts of the result.
+     *
+     * <pre>
+     *  Examples:
+     *  <code>
+     *   sin(1 &plusmn; INFINITY i) = 1 &plusmn; INFINITY i
+     *   sin(&plusmn;INFINITY + i) = NaN + NaN i
+     *   sin(&plusmn;INFINITY &plusmn; INFINITY i) = NaN + NaN i
+     *  </code>
+     * </pre>
+     *
+     * @return the sine of this complex number.
+     * @since 1.2
+     */
+    public Complex sin() {
+        if (isNaN) {
+            return NaN;
+        }
+
+        return createComplex(
+                FastMath.sin(real) * FastMath.cosh(imaginary),
+                FastMath.cos(real) * FastMath.sinh(imaginary));
+    }
+
+    /**
+     * Compute the <a href="http://mathworld.wolfram.com/HyperbolicSine.html" TARGET="_top">
+     * hyperbolic sine</a> of this complex number. Implements the formula:
+     *
+     * <pre>
+     *  <code>
+     *   sinh(a + bi) = sinh(a)cos(b)) + cosh(a)sin(b)i
+     *  </code>
+     * </pre>
+     *
+     * where the (real) functions on the right-hand side are {@link FastMath#sin}, {@link
+     * FastMath#cos}, {@link FastMath#cosh} and {@link FastMath#sinh}.
+     *
+     * <p>Returns {@link Complex#NaN} if either real or imaginary part of the input argument is
+     * {@code NaN}.
+     *
+     * <p>Infinite values in real or imaginary parts of the input may result in infinite or NaN
+     * values returned in parts of the result.
+     *
+     * <pre>
+     *  Examples:
+     *  <code>
+     *   sinh(1 &plusmn; INFINITY i) = NaN + NaN i
+     *   sinh(&plusmn;INFINITY + i) = &plusmn; INFINITY + INFINITY i
+     *   sinh(&plusmn;INFINITY &plusmn; INFINITY i) = NaN + NaN i
+     *  </code>
+     * </pre>
+     *
+     * @return the hyperbolic sine of {@code this}.
+     * @since 1.2
+     */
+    public Complex sinh() {
+        if (isNaN) {
+            return NaN;
+        }
+
+        return createComplex(
+                FastMath.sinh(real) * FastMath.cos(imaginary),
+                FastMath.cosh(real) * FastMath.sin(imaginary));
+    }
+
+    /**
+     * Compute the <a href="http://mathworld.wolfram.com/SquareRoot.html" TARGET="_top"> square
+     * root</a> of this complex number. Implements the following algorithm to compute {@code sqrt(a
+     * + bi)}:
+     *
+     * <ol>
+     *   <li>Let {@code t = sqrt((|a| + |a + bi|) / 2)}
+     *   <li>
+     *       <pre>if {@code  a &#8805; 0} return {@code t + (b/2t)i}
+     *  else return {@code |b|/2t + sign(b)t i }</pre>
+     * </ol>
+     *
+     * where
+     *
+     * <ul>
+     *   <li>{@code |a| = }{@link FastMath#abs}(a)
+     *   <li>{@code |a + bi| = }{@link Complex#abs}(a + bi)
+     *   <li>{@code sign(b) = }{@link FastMath#copySign(double,double) copySign(1d, b)}
+     * </ul>
+     *
+     * <p>Returns {@link Complex#NaN} if either real or imaginary part of the input argument is
+     * {@code NaN}. Infinite values in real or imaginary parts of the input may result in infinite
+     * or NaN values returned in parts of the result.
+     *
+     * <pre>
+     *  Examples:
+     *  <code>
+     *   sqrt(1 &plusmn; INFINITY i) = INFINITY + NaN i
+     *   sqrt(INFINITY + i) = INFINITY + 0i
+     *   sqrt(-INFINITY + i) = 0 + INFINITY i
+     *   sqrt(INFINITY &plusmn; INFINITY i) = INFINITY + NaN i
+     *   sqrt(-INFINITY &plusmn; INFINITY i) = NaN &plusmn; INFINITY i
+     *  </code>
+     * </pre>
+     *
+     * @return the square root of {@code this}.
+     * @since 1.2
+     */
+    public Complex sqrt() {
+        if (isNaN) {
+            return NaN;
+        }
+
+        if (real == 0.0 && imaginary == 0.0) {
+            return createComplex(0.0, 0.0);
+        }
+
+        double t = FastMath.sqrt((FastMath.abs(real) + abs()) / 2.0);
+        if (real >= 0.0) {
+            return createComplex(t, imaginary / (2.0 * t));
+        } else {
+            return createComplex(
+                    FastMath.abs(imaginary) / (2.0 * t), FastMath.copySign(1d, imaginary) * t);
+        }
+    }
+
+    /**
+     * Compute the <a href="http://mathworld.wolfram.com/SquareRoot.html" TARGET="_top"> square
+     * root</a> of <code>1 - this<sup>2</sup></code> for this complex number. Computes the result
+     * directly as {@code sqrt(ONE.subtract(z.multiply(z)))}.
+     *
+     * <p>Returns {@link Complex#NaN} if either real or imaginary part of the input argument is
+     * {@code NaN}. Infinite values in real or imaginary parts of the input may result in infinite
+     * or NaN values returned in parts of the result.
+     *
+     * @return the square root of <code>1 - this<sup>2</sup></code>.
+     * @since 1.2
+     */
+    public Complex sqrt1z() {
+        return createComplex(1.0, 0.0).subtract(this.multiply(this)).sqrt();
+    }
+
+    /**
+     * Compute the <a href="http://mathworld.wolfram.com/Tangent.html" TARGET="_top"> tangent</a> of
+     * this complex number. Implements the formula:
+     *
+     * <pre>
+     *  <code>
+     *   tan(a + bi) = sin(2a)/(cos(2a)+cosh(2b)) + [sinh(2b)/(cos(2a)+cosh(2b))]i
+     *  </code>
+     * </pre>
+     *
+     * where the (real) functions on the right-hand side are {@link FastMath#sin}, {@link
+     * FastMath#cos}, {@link FastMath#cosh} and {@link FastMath#sinh}.
+     *
+     * <p>Returns {@link Complex#NaN} if either real or imaginary part of the input argument is
+     * {@code NaN}. Infinite (or critical) values in real or imaginary parts of the input may result
+     * in infinite or NaN values returned in parts of the result.
+     *
+     * <pre>
+     *  Examples:
+     *  <code>
+     *   tan(a &plusmn; INFINITY i) = 0 &plusmn; i
+     *   tan(&plusmn;INFINITY + bi) = NaN + NaN i
+     *   tan(&plusmn;INFINITY &plusmn; INFINITY i) = NaN + NaN i
+     *   tan(&plusmn;&pi;/2 + 0 i) = &plusmn;INFINITY + NaN i
+     *  </code>
+     * </pre>
+     *
+     * @return the tangent of {@code this}.
+     * @since 1.2
+     */
+    public Complex tan() {
+        if (isNaN || Double.isInfinite(real)) {
+            return NaN;
+        }
+        if (imaginary > 20.0) {
+            return createComplex(0.0, 1.0);
+        }
+        if (imaginary < -20.0) {
+            return createComplex(0.0, -1.0);
+        }
+
+        double real2 = 2.0 * real;
+        double imaginary2 = 2.0 * imaginary;
+        double d = FastMath.cos(real2) + FastMath.cosh(imaginary2);
+
+        return createComplex(FastMath.sin(real2) / d, FastMath.sinh(imaginary2) / d);
+    }
+
+    /**
+     * Compute the <a href="http://mathworld.wolfram.com/HyperbolicTangent.html" TARGET="_top">
+     * hyperbolic tangent</a> of this complex number. Implements the formula:
+     *
+     * <pre>
+     *  <code>
+     *   tan(a + bi) = sinh(2a)/(cosh(2a)+cos(2b)) + [sin(2b)/(cosh(2a)+cos(2b))]i
+     *  </code>
+     * </pre>
+     *
+     * where the (real) functions on the right-hand side are {@link FastMath#sin}, {@link
+     * FastMath#cos}, {@link FastMath#cosh} and {@link FastMath#sinh}.
+     *
+     * <p>Returns {@link Complex#NaN} if either real or imaginary part of the input argument is
+     * {@code NaN}. Infinite values in real or imaginary parts of the input may result in infinite
+     * or NaN values returned in parts of the result.
+     *
+     * <pre>
+     *  Examples:
+     *  <code>
+     *   tanh(a &plusmn; INFINITY i) = NaN + NaN i
+     *   tanh(&plusmn;INFINITY + bi) = &plusmn;1 + 0 i
+     *   tanh(&plusmn;INFINITY &plusmn; INFINITY i) = NaN + NaN i
+     *   tanh(0 + (&pi;/2)i) = NaN + INFINITY i
+     *  </code>
+     * </pre>
+     *
+     * @return the hyperbolic tangent of {@code this}.
+     * @since 1.2
+     */
+    public Complex tanh() {
+        if (isNaN || Double.isInfinite(imaginary)) {
+            return NaN;
+        }
+        if (real > 20.0) {
+            return createComplex(1.0, 0.0);
+        }
+        if (real < -20.0) {
+            return createComplex(-1.0, 0.0);
+        }
+        double real2 = 2.0 * real;
+        double imaginary2 = 2.0 * imaginary;
+        double d = FastMath.cosh(real2) + FastMath.cos(imaginary2);
+
+        return createComplex(FastMath.sinh(real2) / d, FastMath.sin(imaginary2) / d);
+    }
+
+    /**
+     * Compute the argument of this complex number. The argument is the angle phi between the
+     * positive real axis and the point representing this number in the complex plane. The value
+     * returned is between -PI (not inclusive) and PI (inclusive), with negative values returned for
+     * numbers with negative imaginary parts.
+     *
+     * <p>If either real or imaginary part (or both) is NaN, NaN is returned. Infinite parts are
+     * handled as {@code Math.atan2} handles them, essentially treating finite parts as zero in the
+     * presence of an infinite coordinate and returning a multiple of pi/4 depending on the signs of
+     * the infinite parts. See the javadoc for {@code Math.atan2} for full details.
+     *
+     * @return the argument of {@code this}.
+     */
+    public double getArgument() {
+        return FastMath.atan2(getImaginary(), getReal());
+    }
+
+    /**
+     * Computes the n-th roots of this complex number. The nth roots are defined by the formula:
+     *
+     * <pre>
+     *  <code>
+     *   z<sub>k</sub> = abs<sup>1/n</sup> (cos(phi + 2&pi;k/n) + i (sin(phi + 2&pi;k/n))
+     *  </code>
+     * </pre>
+     *
+     * for <i>{@code k=0, 1, ..., n-1}</i>, where {@code abs} and {@code phi} are respectively the
+     * {@link #abs() modulus} and {@link #getArgument() argument} of this complex number.
+     *
+     * <p>If one or both parts of this complex number is NaN, a list with just one element, {@link
+     * #NaN} is returned. if neither part is NaN, but at least one part is infinite, the result is a
+     * one-element list containing {@link #INF}.
+     *
+     * @param n Degree of root.
+     * @return a List of all {@code n}-th roots of {@code this}.
+     * @throws NotPositiveException if {@code n <= 0}.
+     * @since 2.0
+     */
+    public List<Complex> nthRoot(int n) throws NotPositiveException {
+
+        if (n <= 0) {
+            throw new NotPositiveException(
+                    LocalizedFormats.CANNOT_COMPUTE_NTH_ROOT_FOR_NEGATIVE_N, n);
+        }
+
+        final List<Complex> result = new ArrayList<Complex>();
+
+        if (isNaN) {
+            result.add(NaN);
+            return result;
+        }
+        if (isInfinite()) {
+            result.add(INF);
+            return result;
+        }
+
+        // nth root of abs -- faster / more accurate to use a solver here?
+        final double nthRootOfAbs = FastMath.pow(abs(), 1.0 / n);
+
+        // Compute nth roots of complex number with k = 0, 1, ... n-1
+        final double nthPhi = getArgument() / n;
+        final double slice = 2 * FastMath.PI / n;
+        double innerPart = nthPhi;
+        for (int k = 0; k < n; k++) {
+            // inner part
+            final double realPart = nthRootOfAbs * FastMath.cos(innerPart);
+            final double imaginaryPart = nthRootOfAbs * FastMath.sin(innerPart);
+            result.add(createComplex(realPart, imaginaryPart));
+            innerPart += slice;
+        }
+
+        return result;
+    }
+
+    /**
+     * Create a complex number given the real and imaginary parts.
+     *
+     * @param realPart Real part.
+     * @param imaginaryPart Imaginary part.
+     * @return a new complex number instance.
+     * @since 1.2
+     * @see #valueOf(double, double)
+     */
+    protected Complex createComplex(double realPart, double imaginaryPart) {
+        return new Complex(realPart, imaginaryPart);
+    }
+
+    /**
+     * Create a complex number given the real and imaginary parts.
+     *
+     * @param realPart Real part.
+     * @param imaginaryPart Imaginary part.
+     * @return a Complex instance.
+     */
+    public static Complex valueOf(double realPart, double imaginaryPart) {
+        if (Double.isNaN(realPart) || Double.isNaN(imaginaryPart)) {
+            return NaN;
+        }
+        return new Complex(realPart, imaginaryPart);
+    }
+
+    /**
+     * Create a complex number given only the real part.
+     *
+     * @param realPart Real part.
+     * @return a Complex instance.
+     */
+    public static Complex valueOf(double realPart) {
+        if (Double.isNaN(realPart)) {
+            return NaN;
+        }
+        return new Complex(realPart);
+    }
+
+    /**
+     * Resolve the transient fields in a deserialized Complex Object. Subclasses will need to
+     * override {@link #createComplex} to deserialize properly.
+     *
+     * @return A Complex instance with all fields resolved.
+     * @since 2.0
+     */
+    protected final Object readResolve() {
+        return createComplex(real, imaginary);
+    }
+
+    /** {@inheritDoc} */
+    public ComplexField getField() {
+        return ComplexField.getInstance();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return "(" + real + ", " + imaginary + ")";
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/complex/ComplexField.java b/src/main/java/org/apache/commons/math3/complex/ComplexField.java
new file mode 100644
index 0000000..4baff6f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/complex/ComplexField.java
@@ -0,0 +1,87 @@
+/*
+ * 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.math3.complex;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.FieldElement;
+
+import java.io.Serializable;
+
+/**
+ * Representation of the complex numbers field.
+ *
+ * <p>This class is a singleton.
+ *
+ * @see Complex
+ * @since 2.0
+ */
+public class ComplexField implements Field<Complex>, Serializable {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -6130362688700788798L;
+
+    /** Private constructor for the singleton. */
+    private ComplexField() {}
+
+    /**
+     * Get the unique instance.
+     *
+     * @return the unique instance
+     */
+    public static ComplexField getInstance() {
+        return LazyHolder.INSTANCE;
+    }
+
+    /** {@inheritDoc} */
+    public Complex getOne() {
+        return Complex.ONE;
+    }
+
+    /** {@inheritDoc} */
+    public Complex getZero() {
+        return Complex.ZERO;
+    }
+
+    /** {@inheritDoc} */
+    public Class<? extends FieldElement<Complex>> getRuntimeClass() {
+        return Complex.class;
+    }
+
+    // CHECKSTYLE: stop HideUtilityClassConstructor
+    /**
+     * Holder for the instance.
+     *
+     * <p>We use here the Initialization On Demand Holder Idiom.
+     */
+    private static class LazyHolder {
+        /** Cached field instance. */
+        private static final ComplexField INSTANCE = new ComplexField();
+    }
+
+    // CHECKSTYLE: resume HideUtilityClassConstructor
+
+    /**
+     * Handle deserialization of the singleton.
+     *
+     * @return the singleton instance
+     */
+    private Object readResolve() {
+        // return the singleton instance
+        return LazyHolder.INSTANCE;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/complex/ComplexFormat.java b/src/main/java/org/apache/commons/math3/complex/ComplexFormat.java
new file mode 100644
index 0000000..309326a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/complex/ComplexFormat.java
@@ -0,0 +1,420 @@
+/*
+ * 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.math3.complex;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.MathParseException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.CompositeFormat;
+
+import java.text.FieldPosition;
+import java.text.NumberFormat;
+import java.text.ParsePosition;
+import java.util.Locale;
+
+/**
+ * Formats a Complex number in cartesian format "Re(c) + Im(c)i". 'i' can be replaced with 'j' (or
+ * anything else), and the number format for both real and imaginary parts can be configured.
+ */
+public class ComplexFormat {
+
+    /** The default imaginary character. */
+    private static final String DEFAULT_IMAGINARY_CHARACTER = "i";
+
+    /** The notation used to signify the imaginary part of the complex number. */
+    private final String imaginaryCharacter;
+
+    /** The format used for the imaginary part. */
+    private final NumberFormat imaginaryFormat;
+
+    /** The format used for the real part. */
+    private final NumberFormat realFormat;
+
+    /**
+     * Create an instance with the default imaginary character, 'i', and the default number format
+     * for both real and imaginary parts.
+     */
+    public ComplexFormat() {
+        this.imaginaryCharacter = DEFAULT_IMAGINARY_CHARACTER;
+        this.imaginaryFormat = CompositeFormat.getDefaultNumberFormat();
+        this.realFormat = imaginaryFormat;
+    }
+
+    /**
+     * Create an instance with a custom number format for both real and imaginary parts.
+     *
+     * @param format the custom format for both real and imaginary parts.
+     * @throws NullArgumentException if {@code realFormat} is {@code null}.
+     */
+    public ComplexFormat(NumberFormat format) throws NullArgumentException {
+        if (format == null) {
+            throw new NullArgumentException(LocalizedFormats.IMAGINARY_FORMAT);
+        }
+        this.imaginaryCharacter = DEFAULT_IMAGINARY_CHARACTER;
+        this.imaginaryFormat = format;
+        this.realFormat = format;
+    }
+
+    /**
+     * Create an instance with a custom number format for the real part and a custom number format
+     * for the imaginary part.
+     *
+     * @param realFormat the custom format for the real part.
+     * @param imaginaryFormat the custom format for the imaginary part.
+     * @throws NullArgumentException if {@code imaginaryFormat} is {@code null}.
+     * @throws NullArgumentException if {@code realFormat} is {@code null}.
+     */
+    public ComplexFormat(NumberFormat realFormat, NumberFormat imaginaryFormat)
+            throws NullArgumentException {
+        if (imaginaryFormat == null) {
+            throw new NullArgumentException(LocalizedFormats.IMAGINARY_FORMAT);
+        }
+        if (realFormat == null) {
+            throw new NullArgumentException(LocalizedFormats.REAL_FORMAT);
+        }
+
+        this.imaginaryCharacter = DEFAULT_IMAGINARY_CHARACTER;
+        this.imaginaryFormat = imaginaryFormat;
+        this.realFormat = realFormat;
+    }
+
+    /**
+     * Create an instance with a custom imaginary character, and the default number format for both
+     * real and imaginary parts.
+     *
+     * @param imaginaryCharacter The custom imaginary character.
+     * @throws NullArgumentException if {@code imaginaryCharacter} is {@code null}.
+     * @throws NoDataException if {@code imaginaryCharacter} is an empty string.
+     */
+    public ComplexFormat(String imaginaryCharacter) throws NullArgumentException, NoDataException {
+        this(imaginaryCharacter, CompositeFormat.getDefaultNumberFormat());
+    }
+
+    /**
+     * Create an instance with a custom imaginary character, and a custom number format for both
+     * real and imaginary parts.
+     *
+     * @param imaginaryCharacter The custom imaginary character.
+     * @param format the custom format for both real and imaginary parts.
+     * @throws NullArgumentException if {@code imaginaryCharacter} is {@code null}.
+     * @throws NoDataException if {@code imaginaryCharacter} is an empty string.
+     * @throws NullArgumentException if {@code format} is {@code null}.
+     */
+    public ComplexFormat(String imaginaryCharacter, NumberFormat format)
+            throws NullArgumentException, NoDataException {
+        this(imaginaryCharacter, format, format);
+    }
+
+    /**
+     * Create an instance with a custom imaginary character, a custom number format for the real
+     * part, and a custom number format for the imaginary part.
+     *
+     * @param imaginaryCharacter The custom imaginary character.
+     * @param realFormat the custom format for the real part.
+     * @param imaginaryFormat the custom format for the imaginary part.
+     * @throws NullArgumentException if {@code imaginaryCharacter} is {@code null}.
+     * @throws NoDataException if {@code imaginaryCharacter} is an empty string.
+     * @throws NullArgumentException if {@code imaginaryFormat} is {@code null}.
+     * @throws NullArgumentException if {@code realFormat} is {@code null}.
+     */
+    public ComplexFormat(
+            String imaginaryCharacter, NumberFormat realFormat, NumberFormat imaginaryFormat)
+            throws NullArgumentException, NoDataException {
+        if (imaginaryCharacter == null) {
+            throw new NullArgumentException();
+        }
+        if (imaginaryCharacter.length() == 0) {
+            throw new NoDataException();
+        }
+        if (imaginaryFormat == null) {
+            throw new NullArgumentException(LocalizedFormats.IMAGINARY_FORMAT);
+        }
+        if (realFormat == null) {
+            throw new NullArgumentException(LocalizedFormats.REAL_FORMAT);
+        }
+
+        this.imaginaryCharacter = imaginaryCharacter;
+        this.imaginaryFormat = imaginaryFormat;
+        this.realFormat = realFormat;
+    }
+
+    /**
+     * Get the set of locales for which complex formats are available.
+     *
+     * <p>This is the same set as the {@link NumberFormat} set.
+     *
+     * @return available complex format locales.
+     */
+    public static Locale[] getAvailableLocales() {
+        return NumberFormat.getAvailableLocales();
+    }
+
+    /**
+     * This method calls {@link #format(Object,StringBuffer,FieldPosition)}.
+     *
+     * @param c Complex object to format.
+     * @return A formatted number in the form "Re(c) + Im(c)i".
+     */
+    public String format(Complex c) {
+        return format(c, new StringBuffer(), new FieldPosition(0)).toString();
+    }
+
+    /**
+     * This method calls {@link #format(Object,StringBuffer,FieldPosition)}.
+     *
+     * @param c Double object to format.
+     * @return A formatted number.
+     */
+    public String format(Double c) {
+        return format(new Complex(c, 0), new StringBuffer(), new FieldPosition(0)).toString();
+    }
+
+    /**
+     * Formats a {@link Complex} object to produce a string.
+     *
+     * @param complex the object to format.
+     * @param toAppendTo where the text is to be appended
+     * @param pos On input: an alignment field, if desired. On output: the offsets of the alignment
+     *     field
+     * @return the value passed in as toAppendTo.
+     */
+    public StringBuffer format(Complex complex, StringBuffer toAppendTo, FieldPosition pos) {
+        pos.setBeginIndex(0);
+        pos.setEndIndex(0);
+
+        // format real
+        double re = complex.getReal();
+        CompositeFormat.formatDouble(re, getRealFormat(), toAppendTo, pos);
+
+        // format sign and imaginary
+        double im = complex.getImaginary();
+        StringBuffer imAppendTo;
+        if (im < 0.0) {
+            toAppendTo.append(" - ");
+            imAppendTo = formatImaginary(-im, new StringBuffer(), pos);
+            toAppendTo.append(imAppendTo);
+            toAppendTo.append(getImaginaryCharacter());
+        } else if (im > 0.0 || Double.isNaN(im)) {
+            toAppendTo.append(" + ");
+            imAppendTo = formatImaginary(im, new StringBuffer(), pos);
+            toAppendTo.append(imAppendTo);
+            toAppendTo.append(getImaginaryCharacter());
+        }
+
+        return toAppendTo;
+    }
+
+    /**
+     * Format the absolute value of the imaginary part.
+     *
+     * @param absIm Absolute value of the imaginary part of a complex number.
+     * @param toAppendTo where the text is to be appended.
+     * @param pos On input: an alignment field, if desired. On output: the offsets of the alignment
+     *     field.
+     * @return the value passed in as toAppendTo.
+     */
+    private StringBuffer formatImaginary(double absIm, StringBuffer toAppendTo, FieldPosition pos) {
+        pos.setBeginIndex(0);
+        pos.setEndIndex(0);
+
+        CompositeFormat.formatDouble(absIm, getImaginaryFormat(), toAppendTo, pos);
+        if (toAppendTo.toString().equals("1")) {
+            // Remove the character "1" if it is the only one.
+            toAppendTo.setLength(0);
+        }
+
+        return toAppendTo;
+    }
+
+    /**
+     * Formats a object to produce a string. {@code obj} must be either a {@link Complex} object or
+     * a {@link Number} object. Any other type of object will result in an {@link
+     * IllegalArgumentException} being thrown.
+     *
+     * @param obj the object to format.
+     * @param toAppendTo where the text is to be appended
+     * @param pos On input: an alignment field, if desired. On output: the offsets of the alignment
+     *     field
+     * @return the value passed in as toAppendTo.
+     * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer,
+     *     java.text.FieldPosition)
+     * @throws MathIllegalArgumentException is {@code obj} is not a valid type.
+     */
+    public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos)
+            throws MathIllegalArgumentException {
+
+        StringBuffer ret = null;
+
+        if (obj instanceof Complex) {
+            ret = format((Complex) obj, toAppendTo, pos);
+        } else if (obj instanceof Number) {
+            ret = format(new Complex(((Number) obj).doubleValue(), 0.0), toAppendTo, pos);
+        } else {
+            throw new MathIllegalArgumentException(
+                    LocalizedFormats.CANNOT_FORMAT_INSTANCE_AS_COMPLEX, obj.getClass().getName());
+        }
+
+        return ret;
+    }
+
+    /**
+     * Access the imaginaryCharacter.
+     *
+     * @return the imaginaryCharacter.
+     */
+    public String getImaginaryCharacter() {
+        return imaginaryCharacter;
+    }
+
+    /**
+     * Access the imaginaryFormat.
+     *
+     * @return the imaginaryFormat.
+     */
+    public NumberFormat getImaginaryFormat() {
+        return imaginaryFormat;
+    }
+
+    /**
+     * Returns the default complex format for the current locale.
+     *
+     * @return the default complex format.
+     */
+    public static ComplexFormat getInstance() {
+        return getInstance(Locale.getDefault());
+    }
+
+    /**
+     * Returns the default complex format for the given locale.
+     *
+     * @param locale the specific locale used by the format.
+     * @return the complex format specific to the given locale.
+     */
+    public static ComplexFormat getInstance(Locale locale) {
+        NumberFormat f = CompositeFormat.getDefaultNumberFormat(locale);
+        return new ComplexFormat(f);
+    }
+
+    /**
+     * Returns the default complex format for the given locale.
+     *
+     * @param locale the specific locale used by the format.
+     * @param imaginaryCharacter Imaginary character.
+     * @return the complex format specific to the given locale.
+     * @throws NullArgumentException if {@code imaginaryCharacter} is {@code null}.
+     * @throws NoDataException if {@code imaginaryCharacter} is an empty string.
+     */
+    public static ComplexFormat getInstance(String imaginaryCharacter, Locale locale)
+            throws NullArgumentException, NoDataException {
+        NumberFormat f = CompositeFormat.getDefaultNumberFormat(locale);
+        return new ComplexFormat(imaginaryCharacter, f);
+    }
+
+    /**
+     * Access the realFormat.
+     *
+     * @return the realFormat.
+     */
+    public NumberFormat getRealFormat() {
+        return realFormat;
+    }
+
+    /**
+     * Parses a string to produce a {@link Complex} object.
+     *
+     * @param source the string to parse.
+     * @return the parsed {@link Complex} object.
+     * @throws MathParseException if the beginning of the specified string cannot be parsed.
+     */
+    public Complex parse(String source) throws MathParseException {
+        ParsePosition parsePosition = new ParsePosition(0);
+        Complex result = parse(source, parsePosition);
+        if (parsePosition.getIndex() == 0) {
+            throw new MathParseException(source, parsePosition.getErrorIndex(), Complex.class);
+        }
+        return result;
+    }
+
+    /**
+     * Parses a string to produce a {@link Complex} object.
+     *
+     * @param source the string to parse
+     * @param pos input/ouput parsing parameter.
+     * @return the parsed {@link Complex} object.
+     */
+    public Complex parse(String source, ParsePosition pos) {
+        int initialIndex = pos.getIndex();
+
+        // parse whitespace
+        CompositeFormat.parseAndIgnoreWhitespace(source, pos);
+
+        // parse real
+        Number re = CompositeFormat.parseNumber(source, getRealFormat(), pos);
+        if (re == null) {
+            // invalid real number
+            // set index back to initial, error index should already be set
+            pos.setIndex(initialIndex);
+            return null;
+        }
+
+        // parse sign
+        int startIndex = pos.getIndex();
+        char c = CompositeFormat.parseNextCharacter(source, pos);
+        int sign = 0;
+        switch (c) {
+            case 0:
+                // no sign
+                // return real only complex number
+                return new Complex(re.doubleValue(), 0.0);
+            case '-':
+                sign = -1;
+                break;
+            case '+':
+                sign = 1;
+                break;
+            default:
+                // invalid sign
+                // set index back to initial, error index should be the last
+                // character examined.
+                pos.setIndex(initialIndex);
+                pos.setErrorIndex(startIndex);
+                return null;
+        }
+
+        // parse whitespace
+        CompositeFormat.parseAndIgnoreWhitespace(source, pos);
+
+        // parse imaginary
+        Number im = CompositeFormat.parseNumber(source, getRealFormat(), pos);
+        if (im == null) {
+            // invalid imaginary number
+            // set index back to initial, error index should already be set
+            pos.setIndex(initialIndex);
+            return null;
+        }
+
+        // parse imaginary character
+        if (!CompositeFormat.parseFixedstring(source, getImaginaryCharacter(), pos)) {
+            return null;
+        }
+
+        return new Complex(re.doubleValue(), im.doubleValue() * sign);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/complex/ComplexUtils.java b/src/main/java/org/apache/commons/math3/complex/ComplexUtils.java
new file mode 100644
index 0000000..6fca857
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/complex/ComplexUtils.java
@@ -0,0 +1,82 @@
+/*
+ * 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.math3.complex;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Static implementations of common {@link org.apache.commons.math3.complex.Complex} utilities
+ * functions.
+ */
+public class ComplexUtils {
+
+    /** Default constructor. */
+    private ComplexUtils() {}
+
+    /**
+     * Creates a complex number from the given polar representation.
+     *
+     * <p>The value returned is <code>r&middot;e<sup>i&middot;theta</sup></code>, computed as <code>
+     * r&middot;cos(theta) + r&middot;sin(theta)i</code>
+     *
+     * <p>If either <code>r</code> or <code>theta</code> is NaN, or <code>theta</code> is infinite,
+     * {@link Complex#NaN} is returned.
+     *
+     * <p>If <code>r</code> is infinite and <code>theta</code> is finite, infinite or NaN values may
+     * be returned in parts of the result, following the rules for double arithmetic.
+     *
+     * <pre>
+     * Examples:
+     * <code>
+     * polar2Complex(INFINITY, &pi;/4) = INFINITY + INFINITY i
+     * polar2Complex(INFINITY, 0) = INFINITY + NaN i
+     * polar2Complex(INFINITY, -&pi;/4) = INFINITY - INFINITY i
+     * polar2Complex(INFINITY, 5&pi;/4) = -INFINITY - INFINITY i </code></pre>
+     *
+     * @param r the modulus of the complex number to create
+     * @param theta the argument of the complex number to create
+     * @return <code>r&middot;e<sup>i&middot;theta</sup></code>
+     * @throws MathIllegalArgumentException if {@code r} is negative.
+     * @since 1.1
+     */
+    public static Complex polar2Complex(double r, double theta)
+            throws MathIllegalArgumentException {
+        if (r < 0) {
+            throw new MathIllegalArgumentException(LocalizedFormats.NEGATIVE_COMPLEX_MODULE, r);
+        }
+        return new Complex(r * FastMath.cos(theta), r * FastMath.sin(theta));
+    }
+
+    /**
+     * Convert an array of primitive doubles to an array of {@code Complex} objects.
+     *
+     * @param real Array of numbers to be converted to their {@code Complex} equivalent.
+     * @return an array of {@code Complex} objects.
+     * @since 3.1
+     */
+    public static Complex[] convertToComplex(double[] real) {
+        final Complex c[] = new Complex[real.length];
+        for (int i = 0; i < real.length; i++) {
+            c[i] = new Complex(real[i], 0);
+        }
+
+        return c;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/complex/Quaternion.java b/src/main/java/org/apache/commons/math3/complex/Quaternion.java
new file mode 100644
index 0000000..94d23cd
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/complex/Quaternion.java
@@ -0,0 +1,434 @@
+/*
+ * 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.math3.complex;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.ZeroException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+import org.apache.commons.math3.util.Precision;
+
+import java.io.Serializable;
+
+/**
+ * This class implements <a href="http://mathworld.wolfram.com/Quaternion.html">quaternions</a>
+ * (Hamilton's hypercomplex numbers). <br>
+ * Instance of this class are guaranteed to be immutable.
+ *
+ * @since 3.1
+ */
+public final class Quaternion implements Serializable {
+    /** Identity quaternion. */
+    public static final Quaternion IDENTITY = new Quaternion(1, 0, 0, 0);
+
+    /** Zero quaternion. */
+    public static final Quaternion ZERO = new Quaternion(0, 0, 0, 0);
+
+    /** i */
+    public static final Quaternion I = new Quaternion(0, 1, 0, 0);
+
+    /** j */
+    public static final Quaternion J = new Quaternion(0, 0, 1, 0);
+
+    /** k */
+    public static final Quaternion K = new Quaternion(0, 0, 0, 1);
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 20092012L;
+
+    /** First component (scalar part). */
+    private final double q0;
+
+    /** Second component (first vector part). */
+    private final double q1;
+
+    /** Third component (second vector part). */
+    private final double q2;
+
+    /** Fourth component (third vector part). */
+    private final double q3;
+
+    /**
+     * Builds a quaternion from its components.
+     *
+     * @param a Scalar component.
+     * @param b First vector component.
+     * @param c Second vector component.
+     * @param d Third vector component.
+     */
+    public Quaternion(final double a, final double b, final double c, final double d) {
+        this.q0 = a;
+        this.q1 = b;
+        this.q2 = c;
+        this.q3 = d;
+    }
+
+    /**
+     * Builds a quaternion from scalar and vector parts.
+     *
+     * @param scalar Scalar part of the quaternion.
+     * @param v Components of the vector part of the quaternion.
+     * @throws DimensionMismatchException if the array length is not 3.
+     */
+    public Quaternion(final double scalar, final double[] v) throws DimensionMismatchException {
+        if (v.length != 3) {
+            throw new DimensionMismatchException(v.length, 3);
+        }
+        this.q0 = scalar;
+        this.q1 = v[0];
+        this.q2 = v[1];
+        this.q3 = v[2];
+    }
+
+    /**
+     * Builds a pure quaternion from a vector (assuming that the scalar part is zero).
+     *
+     * @param v Components of the vector part of the pure quaternion.
+     */
+    public Quaternion(final double[] v) {
+        this(0, v);
+    }
+
+    /**
+     * Returns the conjugate quaternion of the instance.
+     *
+     * @return the conjugate quaternion
+     */
+    public Quaternion getConjugate() {
+        return new Quaternion(q0, -q1, -q2, -q3);
+    }
+
+    /**
+     * Returns the Hamilton product of two quaternions.
+     *
+     * @param q1 First quaternion.
+     * @param q2 Second quaternion.
+     * @return the product {@code q1} and {@code q2}, in that order.
+     */
+    public static Quaternion multiply(final Quaternion q1, final Quaternion q2) {
+        // Components of the first quaternion.
+        final double q1a = q1.getQ0();
+        final double q1b = q1.getQ1();
+        final double q1c = q1.getQ2();
+        final double q1d = q1.getQ3();
+
+        // Components of the second quaternion.
+        final double q2a = q2.getQ0();
+        final double q2b = q2.getQ1();
+        final double q2c = q2.getQ2();
+        final double q2d = q2.getQ3();
+
+        // Components of the product.
+        final double w = q1a * q2a - q1b * q2b - q1c * q2c - q1d * q2d;
+        final double x = q1a * q2b + q1b * q2a + q1c * q2d - q1d * q2c;
+        final double y = q1a * q2c - q1b * q2d + q1c * q2a + q1d * q2b;
+        final double z = q1a * q2d + q1b * q2c - q1c * q2b + q1d * q2a;
+
+        return new Quaternion(w, x, y, z);
+    }
+
+    /**
+     * Returns the Hamilton product of the instance by a quaternion.
+     *
+     * @param q Quaternion.
+     * @return the product of this instance with {@code q}, in that order.
+     */
+    public Quaternion multiply(final Quaternion q) {
+        return multiply(this, q);
+    }
+
+    /**
+     * Computes the sum of two quaternions.
+     *
+     * @param q1 Quaternion.
+     * @param q2 Quaternion.
+     * @return the sum of {@code q1} and {@code q2}.
+     */
+    public static Quaternion add(final Quaternion q1, final Quaternion q2) {
+        return new Quaternion(
+                q1.getQ0() + q2.getQ0(),
+                q1.getQ1() + q2.getQ1(),
+                q1.getQ2() + q2.getQ2(),
+                q1.getQ3() + q2.getQ3());
+    }
+
+    /**
+     * Computes the sum of the instance and another quaternion.
+     *
+     * @param q Quaternion.
+     * @return the sum of this instance and {@code q}
+     */
+    public Quaternion add(final Quaternion q) {
+        return add(this, q);
+    }
+
+    /**
+     * Subtracts two quaternions.
+     *
+     * @param q1 First Quaternion.
+     * @param q2 Second quaternion.
+     * @return the difference between {@code q1} and {@code q2}.
+     */
+    public static Quaternion subtract(final Quaternion q1, final Quaternion q2) {
+        return new Quaternion(
+                q1.getQ0() - q2.getQ0(),
+                q1.getQ1() - q2.getQ1(),
+                q1.getQ2() - q2.getQ2(),
+                q1.getQ3() - q2.getQ3());
+    }
+
+    /**
+     * Subtracts a quaternion from the instance.
+     *
+     * @param q Quaternion.
+     * @return the difference between this instance and {@code q}.
+     */
+    public Quaternion subtract(final Quaternion q) {
+        return subtract(this, q);
+    }
+
+    /**
+     * Computes the dot-product of two quaternions.
+     *
+     * @param q1 Quaternion.
+     * @param q2 Quaternion.
+     * @return the dot product of {@code q1} and {@code q2}.
+     */
+    public static double dotProduct(final Quaternion q1, final Quaternion q2) {
+        return q1.getQ0() * q2.getQ0()
+                + q1.getQ1() * q2.getQ1()
+                + q1.getQ2() * q2.getQ2()
+                + q1.getQ3() * q2.getQ3();
+    }
+
+    /**
+     * Computes the dot-product of the instance by a quaternion.
+     *
+     * @param q Quaternion.
+     * @return the dot product of this instance and {@code q}.
+     */
+    public double dotProduct(final Quaternion q) {
+        return dotProduct(this, q);
+    }
+
+    /**
+     * Computes the norm of the quaternion.
+     *
+     * @return the norm.
+     */
+    public double getNorm() {
+        return FastMath.sqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3);
+    }
+
+    /**
+     * Computes the normalized quaternion (the versor of the instance). The norm of the quaternion
+     * must not be zero.
+     *
+     * @return a normalized quaternion.
+     * @throws ZeroException if the norm of the quaternion is zero.
+     */
+    public Quaternion normalize() {
+        final double norm = getNorm();
+
+        if (norm < Precision.SAFE_MIN) {
+            throw new ZeroException(LocalizedFormats.NORM, norm);
+        }
+
+        return new Quaternion(q0 / norm, q1 / norm, q2 / norm, q3 / norm);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+        if (other instanceof Quaternion) {
+            final Quaternion q = (Quaternion) other;
+            return q0 == q.getQ0() && q1 == q.getQ1() && q2 == q.getQ2() && q3 == q.getQ3();
+        }
+
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        // "Effective Java" (second edition, p. 47).
+        int result = 17;
+        for (double comp : new double[] {q0, q1, q2, q3}) {
+            final int c = MathUtils.hash(comp);
+            result = 31 * result + c;
+        }
+        return result;
+    }
+
+    /**
+     * Checks whether this instance is equal to another quaternion within a given tolerance.
+     *
+     * @param q Quaternion with which to compare the current quaternion.
+     * @param eps Tolerance.
+     * @return {@code true} if the each of the components are equal within the allowed absolute
+     *     error.
+     */
+    public boolean equals(final Quaternion q, final double eps) {
+        return Precision.equals(q0, q.getQ0(), eps)
+                && Precision.equals(q1, q.getQ1(), eps)
+                && Precision.equals(q2, q.getQ2(), eps)
+                && Precision.equals(q3, q.getQ3(), eps);
+    }
+
+    /**
+     * Checks whether the instance is a unit quaternion within a given tolerance.
+     *
+     * @param eps Tolerance (absolute error).
+     * @return {@code true} if the norm is 1 within the given tolerance, {@code false} otherwise
+     */
+    public boolean isUnitQuaternion(double eps) {
+        return Precision.equals(getNorm(), 1d, eps);
+    }
+
+    /**
+     * Checks whether the instance is a pure quaternion within a given tolerance.
+     *
+     * @param eps Tolerance (absolute error).
+     * @return {@code true} if the scalar part of the quaternion is zero.
+     */
+    public boolean isPureQuaternion(double eps) {
+        return FastMath.abs(getQ0()) <= eps;
+    }
+
+    /**
+     * Returns the polar form of the quaternion.
+     *
+     * @return the unit quaternion with positive scalar part.
+     */
+    public Quaternion getPositivePolarForm() {
+        if (getQ0() < 0) {
+            final Quaternion unitQ = normalize();
+            // The quaternion of rotation (normalized quaternion) q and -q
+            // are equivalent (i.e. represent the same rotation).
+            return new Quaternion(-unitQ.getQ0(), -unitQ.getQ1(), -unitQ.getQ2(), -unitQ.getQ3());
+        } else {
+            return this.normalize();
+        }
+    }
+
+    /**
+     * Returns the inverse of this instance. The norm of the quaternion must not be zero.
+     *
+     * @return the inverse.
+     * @throws ZeroException if the norm (squared) of the quaternion is zero.
+     */
+    public Quaternion getInverse() {
+        final double squareNorm = q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3;
+        if (squareNorm < Precision.SAFE_MIN) {
+            throw new ZeroException(LocalizedFormats.NORM, squareNorm);
+        }
+
+        return new Quaternion(
+                q0 / squareNorm, -q1 / squareNorm, -q2 / squareNorm, -q3 / squareNorm);
+    }
+
+    /**
+     * Gets the first component of the quaternion (scalar part).
+     *
+     * @return the scalar part.
+     */
+    public double getQ0() {
+        return q0;
+    }
+
+    /**
+     * Gets the second component of the quaternion (first component of the vector part).
+     *
+     * @return the first component of the vector part.
+     */
+    public double getQ1() {
+        return q1;
+    }
+
+    /**
+     * Gets the third component of the quaternion (second component of the vector part).
+     *
+     * @return the second component of the vector part.
+     */
+    public double getQ2() {
+        return q2;
+    }
+
+    /**
+     * Gets the fourth component of the quaternion (third component of the vector part).
+     *
+     * @return the third component of the vector part.
+     */
+    public double getQ3() {
+        return q3;
+    }
+
+    /**
+     * Gets the scalar part of the quaternion.
+     *
+     * @return the scalar part.
+     * @see #getQ0()
+     */
+    public double getScalarPart() {
+        return getQ0();
+    }
+
+    /**
+     * Gets the three components of the vector part of the quaternion.
+     *
+     * @return the vector part.
+     * @see #getQ1()
+     * @see #getQ2()
+     * @see #getQ3()
+     */
+    public double[] getVectorPart() {
+        return new double[] {getQ1(), getQ2(), getQ3()};
+    }
+
+    /**
+     * Multiplies the instance by a scalar.
+     *
+     * @param alpha Scalar factor.
+     * @return a scaled quaternion.
+     */
+    public Quaternion multiply(final double alpha) {
+        return new Quaternion(alpha * q0, alpha * q1, alpha * q2, alpha * q3);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        final String sp = " ";
+        final StringBuilder s = new StringBuilder();
+        s.append("[")
+                .append(q0)
+                .append(sp)
+                .append(q1)
+                .append(sp)
+                .append(q2)
+                .append(sp)
+                .append(q3)
+                .append("]");
+
+        return s.toString();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/complex/RootsOfUnity.java b/src/main/java/org/apache/commons/math3/complex/RootsOfUnity.java
new file mode 100644
index 0000000..9e0cab7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/complex/RootsOfUnity.java
@@ -0,0 +1,198 @@
+/*
+ * 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.math3.complex;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.ZeroException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.FastMath;
+
+import java.io.Serializable;
+
+/**
+ * A helper class for the computation and caching of the {@code n}-th roots of unity.
+ *
+ * @since 3.0
+ */
+public class RootsOfUnity implements Serializable {
+
+    /** Serializable version id. */
+    private static final long serialVersionUID = 20120201L;
+
+    /** Number of roots of unity. */
+    private int omegaCount;
+
+    /** Real part of the roots. */
+    private double[] omegaReal;
+
+    /**
+     * Imaginary part of the {@code n}-th roots of unity, for positive values of {@code n}. In this
+     * array, the roots are stored in counter-clockwise order.
+     */
+    private double[] omegaImaginaryCounterClockwise;
+
+    /**
+     * Imaginary part of the {@code n}-th roots of unity, for negative values of {@code n}. In this
+     * array, the roots are stored in clockwise order.
+     */
+    private double[] omegaImaginaryClockwise;
+
+    /**
+     * {@code true} if {@link #computeRoots(int)} was called with a positive value of its argument
+     * {@code n}. In this case, counter-clockwise ordering of the roots of unity should be used.
+     */
+    private boolean isCounterClockWise;
+
+    /** Build an engine for computing the {@code n}-th roots of unity. */
+    public RootsOfUnity() {
+
+        omegaCount = 0;
+        omegaReal = null;
+        omegaImaginaryCounterClockwise = null;
+        omegaImaginaryClockwise = null;
+        isCounterClockWise = true;
+    }
+
+    /**
+     * Returns {@code true} if {@link #computeRoots(int)} was called with a positive value of its
+     * argument {@code n}. If {@code true}, then counter-clockwise ordering of the roots of unity
+     * should be used.
+     *
+     * @return {@code true} if the roots of unity are stored in counter-clockwise order
+     * @throws MathIllegalStateException if no roots of unity have been computed yet
+     */
+    public synchronized boolean isCounterClockWise() throws MathIllegalStateException {
+
+        if (omegaCount == 0) {
+            throw new MathIllegalStateException(LocalizedFormats.ROOTS_OF_UNITY_NOT_COMPUTED_YET);
+        }
+        return isCounterClockWise;
+    }
+
+    /**
+     * Computes the {@code n}-th roots of unity. The roots are stored in {@code omega[]}, such that
+     * {@code omega[k] = w ^ k}, where {@code k = 0, ..., n - 1}, {@code w = exp(2 * pi * i / n)}
+     * and {@code i = sqrt(-1)}.
+     *
+     * <p>Note that {@code n} can be positive of negative
+     *
+     * <ul>
+     *   <li>{@code abs(n)} is always the number of roots of unity.
+     *   <li>If {@code n > 0}, then the roots are stored in counter-clockwise order.
+     *   <li>If {@code n < 0}, then the roots are stored in clockwise order.
+     * </ul>
+     *
+     * @param n the (signed) number of roots of unity to be computed
+     * @throws ZeroException if {@code n = 0}
+     */
+    public synchronized void computeRoots(int n) throws ZeroException {
+
+        if (n == 0) {
+            throw new ZeroException(LocalizedFormats.CANNOT_COMPUTE_0TH_ROOT_OF_UNITY);
+        }
+
+        isCounterClockWise = n > 0;
+
+        // avoid repetitive calculations
+        final int absN = FastMath.abs(n);
+
+        if (absN == omegaCount) {
+            return;
+        }
+
+        // calculate everything from scratch
+        final double t = 2.0 * FastMath.PI / absN;
+        final double cosT = FastMath.cos(t);
+        final double sinT = FastMath.sin(t);
+        omegaReal = new double[absN];
+        omegaImaginaryCounterClockwise = new double[absN];
+        omegaImaginaryClockwise = new double[absN];
+        omegaReal[0] = 1.0;
+        omegaImaginaryCounterClockwise[0] = 0.0;
+        omegaImaginaryClockwise[0] = 0.0;
+        for (int i = 1; i < absN; i++) {
+            omegaReal[i] = omegaReal[i - 1] * cosT - omegaImaginaryCounterClockwise[i - 1] * sinT;
+            omegaImaginaryCounterClockwise[i] =
+                    omegaReal[i - 1] * sinT + omegaImaginaryCounterClockwise[i - 1] * cosT;
+            omegaImaginaryClockwise[i] = -omegaImaginaryCounterClockwise[i];
+        }
+        omegaCount = absN;
+    }
+
+    /**
+     * Get the real part of the {@code k}-th {@code n}-th root of unity.
+     *
+     * @param k index of the {@code n}-th root of unity
+     * @return real part of the {@code k}-th {@code n}-th root of unity
+     * @throws MathIllegalStateException if no roots of unity have been computed yet
+     * @throws MathIllegalArgumentException if {@code k} is out of range
+     */
+    public synchronized double getReal(int k)
+            throws MathIllegalStateException, MathIllegalArgumentException {
+
+        if (omegaCount == 0) {
+            throw new MathIllegalStateException(LocalizedFormats.ROOTS_OF_UNITY_NOT_COMPUTED_YET);
+        }
+        if ((k < 0) || (k >= omegaCount)) {
+            throw new OutOfRangeException(
+                    LocalizedFormats.OUT_OF_RANGE_ROOT_OF_UNITY_INDEX,
+                    Integer.valueOf(k),
+                    Integer.valueOf(0),
+                    Integer.valueOf(omegaCount - 1));
+        }
+
+        return omegaReal[k];
+    }
+
+    /**
+     * Get the imaginary part of the {@code k}-th {@code n}-th root of unity.
+     *
+     * @param k index of the {@code n}-th root of unity
+     * @return imaginary part of the {@code k}-th {@code n}-th root of unity
+     * @throws MathIllegalStateException if no roots of unity have been computed yet
+     * @throws OutOfRangeException if {@code k} is out of range
+     */
+    public synchronized double getImaginary(int k)
+            throws MathIllegalStateException, OutOfRangeException {
+
+        if (omegaCount == 0) {
+            throw new MathIllegalStateException(LocalizedFormats.ROOTS_OF_UNITY_NOT_COMPUTED_YET);
+        }
+        if ((k < 0) || (k >= omegaCount)) {
+            throw new OutOfRangeException(
+                    LocalizedFormats.OUT_OF_RANGE_ROOT_OF_UNITY_INDEX,
+                    Integer.valueOf(k),
+                    Integer.valueOf(0),
+                    Integer.valueOf(omegaCount - 1));
+        }
+
+        return isCounterClockWise ? omegaImaginaryCounterClockwise[k] : omegaImaginaryClockwise[k];
+    }
+
+    /**
+     * Returns the number of roots of unity currently stored. If {@link #computeRoots(int)} was
+     * called with {@code n}, then this method returns {@code abs(n)}. If no roots of unity have
+     * been computed yet, this method returns 0.
+     *
+     * @return the number of roots of unity currently stored
+     */
+    public synchronized int getNumberOfRoots() {
+        return omegaCount;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/complex/package-info.java b/src/main/java/org/apache/commons/math3/complex/package-info.java
new file mode 100644
index 0000000..4528581
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/complex/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+/** Complex number type and implementations of complex transcendental functions. */
+package org.apache.commons.math3.complex;
diff --git a/src/main/java/org/apache/commons/math3/dfp/BracketingNthOrderBrentSolverDFP.java b/src/main/java/org/apache/commons/math3/dfp/BracketingNthOrderBrentSolverDFP.java
new file mode 100644
index 0000000..23d4861
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/dfp/BracketingNthOrderBrentSolverDFP.java
@@ -0,0 +1,161 @@
+/*
+ * 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.math3.dfp;
+
+import org.apache.commons.math3.analysis.RealFieldUnivariateFunction;
+import org.apache.commons.math3.analysis.solvers.AllowedSolution;
+import org.apache.commons.math3.analysis.solvers.FieldBracketingNthOrderBrentSolver;
+import org.apache.commons.math3.exception.NoBracketingException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * This class implements a modification of the <a
+ * href="http://mathworld.wolfram.com/BrentsMethod.html">Brent algorithm</a>.
+ *
+ * <p>The changes with respect to the original Brent algorithm are:
+ *
+ * <ul>
+ *   <li>the returned value is chosen in the current interval according to user specified {@link
+ *       AllowedSolution},
+ *   <li>the maximal order for the invert polynomial root search is user-specified instead of being
+ *       invert quadratic only
+ * </ul>
+ *
+ * The given interval must bracket the root.
+ *
+ * @deprecated as of 3.6 replaced with {@link FieldBracketingNthOrderBrentSolver}
+ */
+@Deprecated
+public class BracketingNthOrderBrentSolverDFP extends FieldBracketingNthOrderBrentSolver<Dfp> {
+
+    /**
+     * Construct a solver.
+     *
+     * @param relativeAccuracy Relative accuracy.
+     * @param absoluteAccuracy Absolute accuracy.
+     * @param functionValueAccuracy Function value accuracy.
+     * @param maximalOrder maximal order.
+     * @exception NumberIsTooSmallException if maximal order is lower than 2
+     */
+    public BracketingNthOrderBrentSolverDFP(
+            final Dfp relativeAccuracy,
+            final Dfp absoluteAccuracy,
+            final Dfp functionValueAccuracy,
+            final int maximalOrder)
+            throws NumberIsTooSmallException {
+        super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy, maximalOrder);
+    }
+
+    /**
+     * Get the absolute accuracy.
+     *
+     * @return absolute accuracy
+     */
+    @Override
+    public Dfp getAbsoluteAccuracy() {
+        return super.getAbsoluteAccuracy();
+    }
+
+    /**
+     * Get the relative accuracy.
+     *
+     * @return relative accuracy
+     */
+    @Override
+    public Dfp getRelativeAccuracy() {
+        return super.getRelativeAccuracy();
+    }
+
+    /**
+     * Get the function accuracy.
+     *
+     * @return function accuracy
+     */
+    @Override
+    public Dfp getFunctionValueAccuracy() {
+        return super.getFunctionValueAccuracy();
+    }
+
+    /**
+     * Solve for a zero in the given interval. A solver may require that the interval brackets a
+     * single zero root. Solvers that do require bracketing should be able to handle the case where
+     * one of the endpoints is itself a root.
+     *
+     * @param maxEval Maximum number of evaluations.
+     * @param f Function to solve.
+     * @param min Lower bound for the interval.
+     * @param max Upper bound for the interval.
+     * @param allowedSolution The kind of solutions that the root-finding algorithm may accept as
+     *     solutions.
+     * @return a value where the function is zero.
+     * @exception NullArgumentException if f is null.
+     * @exception NoBracketingException if root cannot be bracketed
+     */
+    public Dfp solve(
+            final int maxEval,
+            final UnivariateDfpFunction f,
+            final Dfp min,
+            final Dfp max,
+            final AllowedSolution allowedSolution)
+            throws NullArgumentException, NoBracketingException {
+        return solve(maxEval, f, min, max, min.add(max).divide(2), allowedSolution);
+    }
+
+    /**
+     * Solve for a zero in the given interval, start at {@code startValue}. A solver may require
+     * that the interval brackets a single zero root. Solvers that do require bracketing should be
+     * able to handle the case where one of the endpoints is itself a root.
+     *
+     * @param maxEval Maximum number of evaluations.
+     * @param f Function to solve.
+     * @param min Lower bound for the interval.
+     * @param max Upper bound for the interval.
+     * @param startValue Start value to use.
+     * @param allowedSolution The kind of solutions that the root-finding algorithm may accept as
+     *     solutions.
+     * @return a value where the function is zero.
+     * @exception NullArgumentException if f is null.
+     * @exception NoBracketingException if root cannot be bracketed
+     */
+    public Dfp solve(
+            final int maxEval,
+            final UnivariateDfpFunction f,
+            final Dfp min,
+            final Dfp max,
+            final Dfp startValue,
+            final AllowedSolution allowedSolution)
+            throws NullArgumentException, NoBracketingException {
+
+        // checks
+        MathUtils.checkNotNull(f);
+
+        // wrap the function
+        RealFieldUnivariateFunction<Dfp> fieldF =
+                new RealFieldUnivariateFunction<Dfp>() {
+
+                    /** {@inheritDoc} */
+                    public Dfp value(final Dfp x) {
+                        return f.value(x);
+                    }
+                };
+
+        // delegate to general field solver
+        return solve(maxEval, fieldF, min, max, startValue, allowedSolution);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/dfp/Dfp.java b/src/main/java/org/apache/commons/math3/dfp/Dfp.java
new file mode 100644
index 0000000..4f59a19
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/dfp/Dfp.java
@@ -0,0 +1,3082 @@
+/*
+ * 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.math3.dfp;
+
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.util.FastMath;
+
+import java.util.Arrays;
+
+/**
+ * Decimal floating point library for Java
+ *
+ * <p>Another floating point class. This one is built using radix 10000 which is 10<sup>4</sup>, so
+ * its almost decimal.
+ *
+ * <p>The design goals here are:
+ *
+ * <ol>
+ *   <li>Decimal math, or close to it
+ *   <li>Settable precision (but no mix between numbers using different settings)
+ *   <li>Portability. Code should be kept as portable as possible.
+ *   <li>Performance
+ *   <li>Accuracy - Results should always be +/- 1 ULP for basic algebraic operation
+ *   <li>Comply with IEEE 854-1987 as much as possible. (See IEEE 854-1987 notes below)
+ * </ol>
+ *
+ * <p>Trade offs:
+ *
+ * <ol>
+ *   <li>Memory foot print. I'm using more memory than necessary to represent numbers to get better
+ *       performance.
+ *   <li>Digits are bigger, so rounding is a greater loss. So, if you really need 12 decimal digits,
+ *       better use 4 base 10000 digits there can be one partially filled.
+ * </ol>
+ *
+ * <p>Numbers are represented in the following form:
+ *
+ * <pre>
+ *  n  =  sign &times; mant &times; (radix)<sup>exp</sup>;</p>
+ *  </pre>
+ *
+ * where sign is &plusmn;1, mantissa represents a fractional number between zero and one. mant[0] is
+ * the least significant digit. exp is in the range of -32767 to 32768
+ *
+ * <p>IEEE 854-1987 Notes and differences
+ *
+ * <p>IEEE 854 requires the radix to be either 2 or 10. The radix here is 10000, so that requirement
+ * is not met, but it is possible that a subclassed can be made to make it behave as a radix 10
+ * number. It is my opinion that if it looks and behaves as a radix 10 number then it is one and
+ * that requirement would be met.
+ *
+ * <p>The radix of 10000 was chosen because it should be faster to operate on 4 decimal digits at
+ * once instead of one at a time. Radix 10 behavior can be realized by adding an additional rounding
+ * step to ensure that the number of decimal digits represented is constant.
+ *
+ * <p>The IEEE standard specifically leaves out internal data encoding, so it is reasonable to
+ * conclude that such a subclass of this radix 10000 system is merely an encoding of a radix 10
+ * system.
+ *
+ * <p>IEEE 854 also specifies the existence of "sub-normal" numbers. This class does not contain any
+ * such entities. The most significant radix 10000 digit is always non-zero. Instead, we support
+ * "gradual underflow" by raising the underflow flag for numbers less with exponent less than
+ * expMin, but don't flush to zero until the exponent reaches MIN_EXP-digits. Thus the smallest
+ * number we can represent would be: 1E(-(MIN_EXP-digits-1)*4), eg, for digits=5, MIN_EXP=-32767,
+ * that would be 1e-131092.
+ *
+ * <p>IEEE 854 defines that the implied radix point lies just to the right of the most significant
+ * digit and to the left of the remaining digits. This implementation puts the implied radix point
+ * to the left of all digits including the most significant one. The most significant digit here is
+ * the one just to the right of the radix point. This is a fine detail and is really only a matter
+ * of definition. Any side effects of this can be rendered invisible by a subclass.
+ *
+ * @see DfpField
+ * @since 2.2
+ */
+public class Dfp implements RealFieldElement<Dfp> {
+
+    /** The radix, or base of this system. Set to 10000 */
+    public static final int RADIX = 10000;
+
+    /** The minimum exponent before underflow is signaled. Flush to zero occurs at minExp-DIGITS */
+    public static final int MIN_EXP = -32767;
+
+    /** The maximum exponent before overflow is signaled and results flushed to infinity */
+    public static final int MAX_EXP = 32768;
+
+    /** The amount under/overflows are scaled by before going to trap handler */
+    public static final int ERR_SCALE = 32760;
+
+    /** Indicator value for normal finite numbers. */
+    public static final byte FINITE = 0;
+
+    /** Indicator value for Infinity. */
+    public static final byte INFINITE = 1;
+
+    /** Indicator value for signaling NaN. */
+    public static final byte SNAN = 2;
+
+    /** Indicator value for quiet NaN. */
+    public static final byte QNAN = 3;
+
+    /** String for NaN representation. */
+    private static final String NAN_STRING = "NaN";
+
+    /** String for positive infinity representation. */
+    private static final String POS_INFINITY_STRING = "Infinity";
+
+    /** String for negative infinity representation. */
+    private static final String NEG_INFINITY_STRING = "-Infinity";
+
+    /** Name for traps triggered by addition. */
+    private static final String ADD_TRAP = "add";
+
+    /** Name for traps triggered by multiplication. */
+    private static final String MULTIPLY_TRAP = "multiply";
+
+    /** Name for traps triggered by division. */
+    private static final String DIVIDE_TRAP = "divide";
+
+    /** Name for traps triggered by square root. */
+    private static final String SQRT_TRAP = "sqrt";
+
+    /** Name for traps triggered by alignment. */
+    private static final String ALIGN_TRAP = "align";
+
+    /** Name for traps triggered by truncation. */
+    private static final String TRUNC_TRAP = "trunc";
+
+    /** Name for traps triggered by nextAfter. */
+    private static final String NEXT_AFTER_TRAP = "nextAfter";
+
+    /** Name for traps triggered by lessThan. */
+    private static final String LESS_THAN_TRAP = "lessThan";
+
+    /** Name for traps triggered by greaterThan. */
+    private static final String GREATER_THAN_TRAP = "greaterThan";
+
+    /** Name for traps triggered by newInstance. */
+    private static final String NEW_INSTANCE_TRAP = "newInstance";
+
+    /** Mantissa. */
+    protected int[] mant;
+
+    /** Sign bit: 1 for positive, -1 for negative. */
+    protected byte sign;
+
+    /** Exponent. */
+    protected int exp;
+
+    /** Indicator for non-finite / non-number values. */
+    protected byte nans;
+
+    /** Factory building similar Dfp's. */
+    private final DfpField field;
+
+    /**
+     * Makes an instance with a value of zero.
+     *
+     * @param field field to which this instance belongs
+     */
+    protected Dfp(final DfpField field) {
+        mant = new int[field.getRadixDigits()];
+        sign = 1;
+        exp = 0;
+        nans = FINITE;
+        this.field = field;
+    }
+
+    /**
+     * Create an instance from a byte value.
+     *
+     * @param field field to which this instance belongs
+     * @param x value to convert to an instance
+     */
+    protected Dfp(final DfpField field, byte x) {
+        this(field, (long) x);
+    }
+
+    /**
+     * Create an instance from an int value.
+     *
+     * @param field field to which this instance belongs
+     * @param x value to convert to an instance
+     */
+    protected Dfp(final DfpField field, int x) {
+        this(field, (long) x);
+    }
+
+    /**
+     * Create an instance from a long value.
+     *
+     * @param field field to which this instance belongs
+     * @param x value to convert to an instance
+     */
+    protected Dfp(final DfpField field, long x) {
+
+        // initialize as if 0
+        mant = new int[field.getRadixDigits()];
+        nans = FINITE;
+        this.field = field;
+
+        boolean isLongMin = false;
+        if (x == Long.MIN_VALUE) {
+            // special case for Long.MIN_VALUE (-9223372036854775808)
+            // we must shift it before taking its absolute value
+            isLongMin = true;
+            ++x;
+        }
+
+        // set the sign
+        if (x < 0) {
+            sign = -1;
+            x = -x;
+        } else {
+            sign = 1;
+        }
+
+        exp = 0;
+        while (x != 0) {
+            System.arraycopy(mant, mant.length - exp, mant, mant.length - 1 - exp, exp);
+            mant[mant.length - 1] = (int) (x % RADIX);
+            x /= RADIX;
+            exp++;
+        }
+
+        if (isLongMin) {
+            // remove the shift added for Long.MIN_VALUE
+            // we know in this case that fixing the last digit is sufficient
+            for (int i = 0; i < mant.length - 1; i++) {
+                if (mant[i] != 0) {
+                    mant[i]++;
+                    break;
+                }
+            }
+        }
+    }
+
+    /**
+     * Create an instance from a double value.
+     *
+     * @param field field to which this instance belongs
+     * @param x value to convert to an instance
+     */
+    protected Dfp(final DfpField field, double x) {
+
+        // initialize as if 0
+        mant = new int[field.getRadixDigits()];
+        sign = 1;
+        exp = 0;
+        nans = FINITE;
+        this.field = field;
+
+        long bits = Double.doubleToLongBits(x);
+        long mantissa = bits & 0x000fffffffffffffL;
+        int exponent = (int) ((bits & 0x7ff0000000000000L) >> 52) - 1023;
+
+        if (exponent == -1023) {
+            // Zero or sub-normal
+            if (x == 0) {
+                // make sure 0 has the right sign
+                if ((bits & 0x8000000000000000L) != 0) {
+                    sign = -1;
+                }
+                return;
+            }
+
+            exponent++;
+
+            // Normalize the subnormal number
+            while ((mantissa & 0x0010000000000000L) == 0) {
+                exponent--;
+                mantissa <<= 1;
+            }
+            mantissa &= 0x000fffffffffffffL;
+        }
+
+        if (exponent == 1024) {
+            // infinity or NAN
+            if (x != x) {
+                sign = (byte) 1;
+                nans = QNAN;
+            } else if (x < 0) {
+                sign = (byte) -1;
+                nans = INFINITE;
+            } else {
+                sign = (byte) 1;
+                nans = INFINITE;
+            }
+            return;
+        }
+
+        Dfp xdfp = new Dfp(field, mantissa);
+        xdfp =
+                xdfp.divide(new Dfp(field, 4503599627370496l))
+                        .add(field.getOne()); // Divide by 2^52, then add one
+        xdfp = xdfp.multiply(DfpMath.pow(field.getTwo(), exponent));
+
+        if ((bits & 0x8000000000000000L) != 0) {
+            xdfp = xdfp.negate();
+        }
+
+        System.arraycopy(xdfp.mant, 0, mant, 0, mant.length);
+        sign = xdfp.sign;
+        exp = xdfp.exp;
+        nans = xdfp.nans;
+    }
+
+    /**
+     * Copy constructor.
+     *
+     * @param d instance to copy
+     */
+    public Dfp(final Dfp d) {
+        mant = d.mant.clone();
+        sign = d.sign;
+        exp = d.exp;
+        nans = d.nans;
+        field = d.field;
+    }
+
+    /**
+     * Create an instance from a String representation.
+     *
+     * @param field field to which this instance belongs
+     * @param s string representation of the instance
+     */
+    protected Dfp(final DfpField field, final String s) {
+
+        // initialize as if 0
+        mant = new int[field.getRadixDigits()];
+        sign = 1;
+        exp = 0;
+        nans = FINITE;
+        this.field = field;
+
+        boolean decimalFound = false;
+        final int rsize = 4; // size of radix in decimal digits
+        final int offset = 4; // Starting offset into Striped
+        final char[] striped = new char[getRadixDigits() * rsize + offset * 2];
+
+        // Check some special cases
+        if (s.equals(POS_INFINITY_STRING)) {
+            sign = (byte) 1;
+            nans = INFINITE;
+            return;
+        }
+
+        if (s.equals(NEG_INFINITY_STRING)) {
+            sign = (byte) -1;
+            nans = INFINITE;
+            return;
+        }
+
+        if (s.equals(NAN_STRING)) {
+            sign = (byte) 1;
+            nans = QNAN;
+            return;
+        }
+
+        // Check for scientific notation
+        int p = s.indexOf("e");
+        if (p == -1) { // try upper case?
+            p = s.indexOf("E");
+        }
+
+        final String fpdecimal;
+        int sciexp = 0;
+        if (p != -1) {
+            // scientific notation
+            fpdecimal = s.substring(0, p);
+            String fpexp = s.substring(p + 1);
+            boolean negative = false;
+
+            for (int i = 0; i < fpexp.length(); i++) {
+                if (fpexp.charAt(i) == '-') {
+                    negative = true;
+                    continue;
+                }
+                if (fpexp.charAt(i) >= '0' && fpexp.charAt(i) <= '9') {
+                    sciexp = sciexp * 10 + fpexp.charAt(i) - '0';
+                }
+            }
+
+            if (negative) {
+                sciexp = -sciexp;
+            }
+        } else {
+            // normal case
+            fpdecimal = s;
+        }
+
+        // If there is a minus sign in the number then it is negative
+        if (fpdecimal.indexOf("-") != -1) {
+            sign = -1;
+        }
+
+        // First off, find all of the leading zeros, trailing zeros, and significant digits
+        p = 0;
+
+        // Move p to first significant digit
+        int decimalPos = 0;
+        for (; ; ) {
+            if (fpdecimal.charAt(p) >= '1' && fpdecimal.charAt(p) <= '9') {
+                break;
+            }
+
+            if (decimalFound && fpdecimal.charAt(p) == '0') {
+                decimalPos--;
+            }
+
+            if (fpdecimal.charAt(p) == '.') {
+                decimalFound = true;
+            }
+
+            p++;
+
+            if (p == fpdecimal.length()) {
+                break;
+            }
+        }
+
+        // Copy the string onto Stripped
+        int q = offset;
+        striped[0] = '0';
+        striped[1] = '0';
+        striped[2] = '0';
+        striped[3] = '0';
+        int significantDigits = 0;
+        for (; ; ) {
+            if (p == (fpdecimal.length())) {
+                break;
+            }
+
+            // Don't want to run pass the end of the array
+            if (q == mant.length * rsize + offset + 1) {
+                break;
+            }
+
+            if (fpdecimal.charAt(p) == '.') {
+                decimalFound = true;
+                decimalPos = significantDigits;
+                p++;
+                continue;
+            }
+
+            if (fpdecimal.charAt(p) < '0' || fpdecimal.charAt(p) > '9') {
+                p++;
+                continue;
+            }
+
+            striped[q] = fpdecimal.charAt(p);
+            q++;
+            p++;
+            significantDigits++;
+        }
+
+        // If the decimal point has been found then get rid of trailing zeros.
+        if (decimalFound && q != offset) {
+            for (; ; ) {
+                q--;
+                if (q == offset) {
+                    break;
+                }
+                if (striped[q] == '0') {
+                    significantDigits--;
+                } else {
+                    break;
+                }
+            }
+        }
+
+        // special case of numbers like "0.00000"
+        if (decimalFound && significantDigits == 0) {
+            decimalPos = 0;
+        }
+
+        // Implicit decimal point at end of number if not present
+        if (!decimalFound) {
+            decimalPos = q - offset;
+        }
+
+        // Find the number of significant trailing zeros
+        q = offset; // set q to point to first sig digit
+        p = significantDigits - 1 + offset;
+
+        while (p > q) {
+            if (striped[p] != '0') {
+                break;
+            }
+            p--;
+        }
+
+        // Make sure the decimal is on a mod 10000 boundary
+        int i = ((rsize * 100) - decimalPos - sciexp % rsize) % rsize;
+        q -= i;
+        decimalPos += i;
+
+        // Make the mantissa length right by adding zeros at the end if necessary
+        while ((p - q) < (mant.length * rsize)) {
+            for (i = 0; i < rsize; i++) {
+                striped[++p] = '0';
+            }
+        }
+
+        // Ok, now we know how many trailing zeros there are,
+        // and where the least significant digit is
+        for (i = mant.length - 1; i >= 0; i--) {
+            mant[i] =
+                    (striped[q] - '0') * 1000
+                            + (striped[q + 1] - '0') * 100
+                            + (striped[q + 2] - '0') * 10
+                            + (striped[q + 3] - '0');
+            q += 4;
+        }
+
+        exp = (decimalPos + sciexp) / rsize;
+
+        if (q < striped.length) {
+            // Is there possible another digit?
+            round((striped[q] - '0') * 1000);
+        }
+    }
+
+    /**
+     * Creates an instance with a non-finite value.
+     *
+     * @param field field to which this instance belongs
+     * @param sign sign of the Dfp to create
+     * @param nans code of the value, must be one of {@link #INFINITE}, {@link #SNAN}, {@link #QNAN}
+     */
+    protected Dfp(final DfpField field, final byte sign, final byte nans) {
+        this.field = field;
+        this.mant = new int[field.getRadixDigits()];
+        this.sign = sign;
+        this.exp = 0;
+        this.nans = nans;
+    }
+
+    /**
+     * Create an instance with a value of 0. Use this internally in preference to constructors to
+     * facilitate subclasses
+     *
+     * @return a new instance with a value of 0
+     */
+    public Dfp newInstance() {
+        return new Dfp(getField());
+    }
+
+    /**
+     * Create an instance from a byte value.
+     *
+     * @param x value to convert to an instance
+     * @return a new instance with value x
+     */
+    public Dfp newInstance(final byte x) {
+        return new Dfp(getField(), x);
+    }
+
+    /**
+     * Create an instance from an int value.
+     *
+     * @param x value to convert to an instance
+     * @return a new instance with value x
+     */
+    public Dfp newInstance(final int x) {
+        return new Dfp(getField(), x);
+    }
+
+    /**
+     * Create an instance from a long value.
+     *
+     * @param x value to convert to an instance
+     * @return a new instance with value x
+     */
+    public Dfp newInstance(final long x) {
+        return new Dfp(getField(), x);
+    }
+
+    /**
+     * Create an instance from a double value.
+     *
+     * @param x value to convert to an instance
+     * @return a new instance with value x
+     */
+    public Dfp newInstance(final double x) {
+        return new Dfp(getField(), x);
+    }
+
+    /**
+     * Create an instance by copying an existing one. Use this internally in preference to
+     * constructors to facilitate subclasses.
+     *
+     * @param d instance to copy
+     * @return a new instance with the same value as d
+     */
+    public Dfp newInstance(final Dfp d) {
+
+        // make sure we don't mix number with different precision
+        if (field.getRadixDigits() != d.field.getRadixDigits()) {
+            field.setIEEEFlagsBits(DfpField.FLAG_INVALID);
+            final Dfp result = newInstance(getZero());
+            result.nans = QNAN;
+            return dotrap(DfpField.FLAG_INVALID, NEW_INSTANCE_TRAP, d, result);
+        }
+
+        return new Dfp(d);
+    }
+
+    /**
+     * Create an instance from a String representation. Use this internally in preference to
+     * constructors to facilitate subclasses.
+     *
+     * @param s string representation of the instance
+     * @return a new instance parsed from specified string
+     */
+    public Dfp newInstance(final String s) {
+        return new Dfp(field, s);
+    }
+
+    /**
+     * Creates an instance with a non-finite value.
+     *
+     * @param sig sign of the Dfp to create
+     * @param code code of the value, must be one of {@link #INFINITE}, {@link #SNAN}, {@link #QNAN}
+     * @return a new instance with a non-finite value
+     */
+    public Dfp newInstance(final byte sig, final byte code) {
+        return field.newDfp(sig, code);
+    }
+
+    /**
+     * Get the {@link org.apache.commons.math3.Field Field} (really a {@link DfpField}) to which the
+     * instance belongs.
+     *
+     * <p>The field is linked to the number of digits and acts as a factory for {@link Dfp}
+     * instances.
+     *
+     * @return {@link org.apache.commons.math3.Field Field} (really a {@link DfpField}) to which the
+     *     instance belongs
+     */
+    public DfpField getField() {
+        return field;
+    }
+
+    /**
+     * Get the number of radix digits of the instance.
+     *
+     * @return number of radix digits
+     */
+    public int getRadixDigits() {
+        return field.getRadixDigits();
+    }
+
+    /**
+     * Get the constant 0.
+     *
+     * @return a Dfp with value zero
+     */
+    public Dfp getZero() {
+        return field.getZero();
+    }
+
+    /**
+     * Get the constant 1.
+     *
+     * @return a Dfp with value one
+     */
+    public Dfp getOne() {
+        return field.getOne();
+    }
+
+    /**
+     * Get the constant 2.
+     *
+     * @return a Dfp with value two
+     */
+    public Dfp getTwo() {
+        return field.getTwo();
+    }
+
+    /** Shift the mantissa left, and adjust the exponent to compensate. */
+    protected void shiftLeft() {
+        for (int i = mant.length - 1; i > 0; i--) {
+            mant[i] = mant[i - 1];
+        }
+        mant[0] = 0;
+        exp--;
+    }
+
+    /* Note that shiftRight() does not call round() as that round() itself
+    uses shiftRight() */
+    /** Shift the mantissa right, and adjust the exponent to compensate. */
+    protected void shiftRight() {
+        for (int i = 0; i < mant.length - 1; i++) {
+            mant[i] = mant[i + 1];
+        }
+        mant[mant.length - 1] = 0;
+        exp++;
+    }
+
+    /**
+     * Make our exp equal to the supplied one, this may cause rounding. Also causes de-normalized
+     * numbers. These numbers are generally dangerous because most routines assume normalized
+     * numbers. Align doesn't round, so it will return the last digit destroyed by shifting right.
+     *
+     * @param e desired exponent
+     * @return last digit destroyed by shifting right
+     */
+    protected int align(int e) {
+        int lostdigit = 0;
+        boolean inexact = false;
+
+        int diff = exp - e;
+
+        int adiff = diff;
+        if (adiff < 0) {
+            adiff = -adiff;
+        }
+
+        if (diff == 0) {
+            return 0;
+        }
+
+        if (adiff > (mant.length + 1)) {
+            // Special case
+            Arrays.fill(mant, 0);
+            exp = e;
+
+            field.setIEEEFlagsBits(DfpField.FLAG_INEXACT);
+            dotrap(DfpField.FLAG_INEXACT, ALIGN_TRAP, this, this);
+
+            return 0;
+        }
+
+        for (int i = 0; i < adiff; i++) {
+            if (diff < 0) {
+                /* Keep track of loss -- only signal inexact after losing 2 digits.
+                 * the first lost digit is returned to add() and may be incorporated
+                 * into the result.
+                 */
+                if (lostdigit != 0) {
+                    inexact = true;
+                }
+
+                lostdigit = mant[0];
+
+                shiftRight();
+            } else {
+                shiftLeft();
+            }
+        }
+
+        if (inexact) {
+            field.setIEEEFlagsBits(DfpField.FLAG_INEXACT);
+            dotrap(DfpField.FLAG_INEXACT, ALIGN_TRAP, this, this);
+        }
+
+        return lostdigit;
+    }
+
+    /**
+     * Check if instance is less than x.
+     *
+     * @param x number to check instance against
+     * @return true if instance is less than x and neither are NaN, false otherwise
+     */
+    public boolean lessThan(final Dfp x) {
+
+        // make sure we don't mix number with different precision
+        if (field.getRadixDigits() != x.field.getRadixDigits()) {
+            field.setIEEEFlagsBits(DfpField.FLAG_INVALID);
+            final Dfp result = newInstance(getZero());
+            result.nans = QNAN;
+            dotrap(DfpField.FLAG_INVALID, LESS_THAN_TRAP, x, result);
+            return false;
+        }
+
+        /* if a nan is involved, signal invalid and return false */
+        if (isNaN() || x.isNaN()) {
+            field.setIEEEFlagsBits(DfpField.FLAG_INVALID);
+            dotrap(DfpField.FLAG_INVALID, LESS_THAN_TRAP, x, newInstance(getZero()));
+            return false;
+        }
+
+        return compare(this, x) < 0;
+    }
+
+    /**
+     * Check if instance is greater than x.
+     *
+     * @param x number to check instance against
+     * @return true if instance is greater than x and neither are NaN, false otherwise
+     */
+    public boolean greaterThan(final Dfp x) {
+
+        // make sure we don't mix number with different precision
+        if (field.getRadixDigits() != x.field.getRadixDigits()) {
+            field.setIEEEFlagsBits(DfpField.FLAG_INVALID);
+            final Dfp result = newInstance(getZero());
+            result.nans = QNAN;
+            dotrap(DfpField.FLAG_INVALID, GREATER_THAN_TRAP, x, result);
+            return false;
+        }
+
+        /* if a nan is involved, signal invalid and return false */
+        if (isNaN() || x.isNaN()) {
+            field.setIEEEFlagsBits(DfpField.FLAG_INVALID);
+            dotrap(DfpField.FLAG_INVALID, GREATER_THAN_TRAP, x, newInstance(getZero()));
+            return false;
+        }
+
+        return compare(this, x) > 0;
+    }
+
+    /**
+     * Check if instance is less than or equal to 0.
+     *
+     * @return true if instance is not NaN and less than or equal to 0, false otherwise
+     */
+    public boolean negativeOrNull() {
+
+        if (isNaN()) {
+            field.setIEEEFlagsBits(DfpField.FLAG_INVALID);
+            dotrap(DfpField.FLAG_INVALID, LESS_THAN_TRAP, this, newInstance(getZero()));
+            return false;
+        }
+
+        return (sign < 0) || ((mant[mant.length - 1] == 0) && !isInfinite());
+    }
+
+    /**
+     * Check if instance is strictly less than 0.
+     *
+     * @return true if instance is not NaN and less than or equal to 0, false otherwise
+     */
+    public boolean strictlyNegative() {
+
+        if (isNaN()) {
+            field.setIEEEFlagsBits(DfpField.FLAG_INVALID);
+            dotrap(DfpField.FLAG_INVALID, LESS_THAN_TRAP, this, newInstance(getZero()));
+            return false;
+        }
+
+        return (sign < 0) && ((mant[mant.length - 1] != 0) || isInfinite());
+    }
+
+    /**
+     * Check if instance is greater than or equal to 0.
+     *
+     * @return true if instance is not NaN and greater than or equal to 0, false otherwise
+     */
+    public boolean positiveOrNull() {
+
+        if (isNaN()) {
+            field.setIEEEFlagsBits(DfpField.FLAG_INVALID);
+            dotrap(DfpField.FLAG_INVALID, LESS_THAN_TRAP, this, newInstance(getZero()));
+            return false;
+        }
+
+        return (sign > 0) || ((mant[mant.length - 1] == 0) && !isInfinite());
+    }
+
+    /**
+     * Check if instance is strictly greater than 0.
+     *
+     * @return true if instance is not NaN and greater than or equal to 0, false otherwise
+     */
+    public boolean strictlyPositive() {
+
+        if (isNaN()) {
+            field.setIEEEFlagsBits(DfpField.FLAG_INVALID);
+            dotrap(DfpField.FLAG_INVALID, LESS_THAN_TRAP, this, newInstance(getZero()));
+            return false;
+        }
+
+        return (sign > 0) && ((mant[mant.length - 1] != 0) || isInfinite());
+    }
+
+    /**
+     * Get the absolute value of instance.
+     *
+     * @return absolute value of instance
+     * @since 3.2
+     */
+    public Dfp abs() {
+        Dfp result = newInstance(this);
+        result.sign = 1;
+        return result;
+    }
+
+    /**
+     * Check if instance is infinite.
+     *
+     * @return true if instance is infinite
+     */
+    public boolean isInfinite() {
+        return nans == INFINITE;
+    }
+
+    /**
+     * Check if instance is not a number.
+     *
+     * @return true if instance is not a number
+     */
+    public boolean isNaN() {
+        return (nans == QNAN) || (nans == SNAN);
+    }
+
+    /**
+     * Check if instance is equal to zero.
+     *
+     * @return true if instance is equal to zero
+     */
+    public boolean isZero() {
+
+        if (isNaN()) {
+            field.setIEEEFlagsBits(DfpField.FLAG_INVALID);
+            dotrap(DfpField.FLAG_INVALID, LESS_THAN_TRAP, this, newInstance(getZero()));
+            return false;
+        }
+
+        return (mant[mant.length - 1] == 0) && !isInfinite();
+    }
+
+    /**
+     * Check if instance is equal to x.
+     *
+     * @param other object to check instance against
+     * @return true if instance is equal to x and neither are NaN, false otherwise
+     */
+    @Override
+    public boolean equals(final Object other) {
+
+        if (other instanceof Dfp) {
+            final Dfp x = (Dfp) other;
+            if (isNaN() || x.isNaN() || field.getRadixDigits() != x.field.getRadixDigits()) {
+                return false;
+            }
+
+            return compare(this, x) == 0;
+        }
+
+        return false;
+    }
+
+    /**
+     * Gets a hashCode for the instance.
+     *
+     * @return a hash code value for this object
+     */
+    @Override
+    public int hashCode() {
+        return 17 + (isZero() ? 0 : (sign << 8)) + (nans << 16) + exp + Arrays.hashCode(mant);
+    }
+
+    /**
+     * Check if instance is not equal to x.
+     *
+     * @param x number to check instance against
+     * @return true if instance is not equal to x and neither are NaN, false otherwise
+     */
+    public boolean unequal(final Dfp x) {
+        if (isNaN() || x.isNaN() || field.getRadixDigits() != x.field.getRadixDigits()) {
+            return false;
+        }
+
+        return greaterThan(x) || lessThan(x);
+    }
+
+    /**
+     * Compare two instances.
+     *
+     * @param a first instance in comparison
+     * @param b second instance in comparison
+     * @return -1 if a<b, 1 if a>b and 0 if a==b Note this method does not properly handle NaNs or
+     *     numbers with different precision.
+     */
+    private static int compare(final Dfp a, final Dfp b) {
+        // Ignore the sign of zero
+        if (a.mant[a.mant.length - 1] == 0
+                && b.mant[b.mant.length - 1] == 0
+                && a.nans == FINITE
+                && b.nans == FINITE) {
+            return 0;
+        }
+
+        if (a.sign != b.sign) {
+            if (a.sign == -1) {
+                return -1;
+            } else {
+                return 1;
+            }
+        }
+
+        // deal with the infinities
+        if (a.nans == INFINITE && b.nans == FINITE) {
+            return a.sign;
+        }
+
+        if (a.nans == FINITE && b.nans == INFINITE) {
+            return -b.sign;
+        }
+
+        if (a.nans == INFINITE && b.nans == INFINITE) {
+            return 0;
+        }
+
+        // Handle special case when a or b is zero, by ignoring the exponents
+        if (b.mant[b.mant.length - 1] != 0 && a.mant[b.mant.length - 1] != 0) {
+            if (a.exp < b.exp) {
+                return -a.sign;
+            }
+
+            if (a.exp > b.exp) {
+                return a.sign;
+            }
+        }
+
+        // compare the mantissas
+        for (int i = a.mant.length - 1; i >= 0; i--) {
+            if (a.mant[i] > b.mant[i]) {
+                return a.sign;
+            }
+
+            if (a.mant[i] < b.mant[i]) {
+                return -a.sign;
+            }
+        }
+
+        return 0;
+    }
+
+    /**
+     * Round to nearest integer using the round-half-even method. That is round to nearest integer
+     * unless both are equidistant. In which case round to the even one.
+     *
+     * @return rounded value
+     * @since 3.2
+     */
+    public Dfp rint() {
+        return trunc(DfpField.RoundingMode.ROUND_HALF_EVEN);
+    }
+
+    /**
+     * Round to an integer using the round floor mode. That is, round toward -Infinity
+     *
+     * @return rounded value
+     * @since 3.2
+     */
+    public Dfp floor() {
+        return trunc(DfpField.RoundingMode.ROUND_FLOOR);
+    }
+
+    /**
+     * Round to an integer using the round ceil mode. That is, round toward +Infinity
+     *
+     * @return rounded value
+     * @since 3.2
+     */
+    public Dfp ceil() {
+        return trunc(DfpField.RoundingMode.ROUND_CEIL);
+    }
+
+    /**
+     * Returns the IEEE remainder.
+     *
+     * @param d divisor
+     * @return this less n &times; d, where n is the integer closest to this/d
+     * @since 3.2
+     */
+    public Dfp remainder(final Dfp d) {
+
+        final Dfp result = this.subtract(this.divide(d).rint().multiply(d));
+
+        // IEEE 854-1987 says that if the result is zero, then it carries the sign of this
+        if (result.mant[mant.length - 1] == 0) {
+            result.sign = sign;
+        }
+
+        return result;
+    }
+
+    /**
+     * Does the integer conversions with the specified rounding.
+     *
+     * @param rmode rounding mode to use
+     * @return truncated value
+     */
+    protected Dfp trunc(final DfpField.RoundingMode rmode) {
+        boolean changed = false;
+
+        if (isNaN()) {
+            return newInstance(this);
+        }
+
+        if (nans == INFINITE) {
+            return newInstance(this);
+        }
+
+        if (mant[mant.length - 1] == 0) {
+            // a is zero
+            return newInstance(this);
+        }
+
+        /* If the exponent is less than zero then we can certainly
+         * return zero */
+        if (exp < 0) {
+            field.setIEEEFlagsBits(DfpField.FLAG_INEXACT);
+            Dfp result = newInstance(getZero());
+            result = dotrap(DfpField.FLAG_INEXACT, TRUNC_TRAP, this, result);
+            return result;
+        }
+
+        /* If the exponent is greater than or equal to digits, then it
+         * must already be an integer since there is no precision left
+         * for any fractional part */
+
+        if (exp >= mant.length) {
+            return newInstance(this);
+        }
+
+        /* General case:  create another dfp, result, that contains the
+         * a with the fractional part lopped off.  */
+
+        Dfp result = newInstance(this);
+        for (int i = 0; i < mant.length - result.exp; i++) {
+            changed |= result.mant[i] != 0;
+            result.mant[i] = 0;
+        }
+
+        if (changed) {
+            switch (rmode) {
+                case ROUND_FLOOR:
+                    if (result.sign == -1) {
+                        // then we must increment the mantissa by one
+                        result = result.add(newInstance(-1));
+                    }
+                    break;
+
+                case ROUND_CEIL:
+                    if (result.sign == 1) {
+                        // then we must increment the mantissa by one
+                        result = result.add(getOne());
+                    }
+                    break;
+
+                case ROUND_HALF_EVEN:
+                default:
+                    final Dfp half = newInstance("0.5");
+                    Dfp a = subtract(result); // difference between this and result
+                    a.sign = 1; // force positive (take abs)
+                    if (a.greaterThan(half)) {
+                        a = newInstance(getOne());
+                        a.sign = sign;
+                        result = result.add(a);
+                    }
+
+                    /** If exactly equal to 1/2 and odd then increment */
+                    if (a.equals(half)
+                            && result.exp > 0
+                            && (result.mant[mant.length - result.exp] & 1) != 0) {
+                        a = newInstance(getOne());
+                        a.sign = sign;
+                        result = result.add(a);
+                    }
+                    break;
+            }
+
+            field.setIEEEFlagsBits(DfpField.FLAG_INEXACT); // signal inexact
+            result = dotrap(DfpField.FLAG_INEXACT, TRUNC_TRAP, this, result);
+            return result;
+        }
+
+        return result;
+    }
+
+    /**
+     * Convert this to an integer. If greater than 2147483647, it returns 2147483647. If less than
+     * -2147483648 it returns -2147483648.
+     *
+     * @return converted number
+     */
+    public int intValue() {
+        Dfp rounded;
+        int result = 0;
+
+        rounded = rint();
+
+        if (rounded.greaterThan(newInstance(2147483647))) {
+            return 2147483647;
+        }
+
+        if (rounded.lessThan(newInstance(-2147483648))) {
+            return -2147483648;
+        }
+
+        for (int i = mant.length - 1; i >= mant.length - rounded.exp; i--) {
+            result = result * RADIX + rounded.mant[i];
+        }
+
+        if (rounded.sign == -1) {
+            result = -result;
+        }
+
+        return result;
+    }
+
+    /**
+     * Get the exponent of the greatest power of 10000 that is less than or equal to the absolute
+     * value of this. I.E. if this is 10<sup>6</sup> then log10K would return 1.
+     *
+     * @return integer base 10000 logarithm
+     */
+    public int log10K() {
+        return exp - 1;
+    }
+
+    /**
+     * Get the specified power of 10000.
+     *
+     * @param e desired power
+     * @return 10000<sup>e</sup>
+     */
+    public Dfp power10K(final int e) {
+        Dfp d = newInstance(getOne());
+        d.exp = e + 1;
+        return d;
+    }
+
+    /**
+     * Get the exponent of the greatest power of 10 that is less than or equal to abs(this).
+     *
+     * @return integer base 10 logarithm
+     * @since 3.2
+     */
+    public int intLog10() {
+        if (mant[mant.length - 1] > 1000) {
+            return exp * 4 - 1;
+        }
+        if (mant[mant.length - 1] > 100) {
+            return exp * 4 - 2;
+        }
+        if (mant[mant.length - 1] > 10) {
+            return exp * 4 - 3;
+        }
+        return exp * 4 - 4;
+    }
+
+    /**
+     * Return the specified power of 10.
+     *
+     * @param e desired power
+     * @return 10<sup>e</sup>
+     */
+    public Dfp power10(final int e) {
+        Dfp d = newInstance(getOne());
+
+        if (e >= 0) {
+            d.exp = e / 4 + 1;
+        } else {
+            d.exp = (e + 1) / 4;
+        }
+
+        switch ((e % 4 + 4) % 4) {
+            case 0:
+                break;
+            case 1:
+                d = d.multiply(10);
+                break;
+            case 2:
+                d = d.multiply(100);
+                break;
+            default:
+                d = d.multiply(1000);
+        }
+
+        return d;
+    }
+
+    /**
+     * Negate the mantissa of this by computing the complement. Leaves the sign bit unchanged, used
+     * internally by add. Denormalized numbers are handled properly here.
+     *
+     * @param extra ???
+     * @return ???
+     */
+    protected int complement(int extra) {
+
+        extra = RADIX - extra;
+        for (int i = 0; i < mant.length; i++) {
+            mant[i] = RADIX - mant[i] - 1;
+        }
+
+        int rh = extra / RADIX;
+        extra -= rh * RADIX;
+        for (int i = 0; i < mant.length; i++) {
+            final int r = mant[i] + rh;
+            rh = r / RADIX;
+            mant[i] = r - rh * RADIX;
+        }
+
+        return extra;
+    }
+
+    /**
+     * Add x to this.
+     *
+     * @param x number to add
+     * @return sum of this and x
+     */
+    public Dfp add(final Dfp x) {
+
+        // make sure we don't mix number with different precision
+        if (field.getRadixDigits() != x.field.getRadixDigits()) {
+            field.setIEEEFlagsBits(DfpField.FLAG_INVALID);
+            final Dfp result = newInstance(getZero());
+            result.nans = QNAN;
+            return dotrap(DfpField.FLAG_INVALID, ADD_TRAP, x, result);
+        }
+
+        /* handle special cases */
+        if (nans != FINITE || x.nans != FINITE) {
+            if (isNaN()) {
+                return this;
+            }
+
+            if (x.isNaN()) {
+                return x;
+            }
+
+            if (nans == INFINITE && x.nans == FINITE) {
+                return this;
+            }
+
+            if (x.nans == INFINITE && nans == FINITE) {
+                return x;
+            }
+
+            if (x.nans == INFINITE && nans == INFINITE && sign == x.sign) {
+                return x;
+            }
+
+            if (x.nans == INFINITE && nans == INFINITE && sign != x.sign) {
+                field.setIEEEFlagsBits(DfpField.FLAG_INVALID);
+                Dfp result = newInstance(getZero());
+                result.nans = QNAN;
+                result = dotrap(DfpField.FLAG_INVALID, ADD_TRAP, x, result);
+                return result;
+            }
+        }
+
+        /* copy this and the arg */
+        Dfp a = newInstance(this);
+        Dfp b = newInstance(x);
+
+        /* initialize the result object */
+        Dfp result = newInstance(getZero());
+
+        /* Make all numbers positive, but remember their sign */
+        final byte asign = a.sign;
+        final byte bsign = b.sign;
+
+        a.sign = 1;
+        b.sign = 1;
+
+        /* The result will be signed like the arg with greatest magnitude */
+        byte rsign = bsign;
+        if (compare(a, b) > 0) {
+            rsign = asign;
+        }
+
+        /* Handle special case when a or b is zero, by setting the exponent
+        of the zero number equal to the other one.  This avoids an alignment
+        which would cause catastropic loss of precision */
+        if (b.mant[mant.length - 1] == 0) {
+            b.exp = a.exp;
+        }
+
+        if (a.mant[mant.length - 1] == 0) {
+            a.exp = b.exp;
+        }
+
+        /* align number with the smaller exponent */
+        int aextradigit = 0;
+        int bextradigit = 0;
+        if (a.exp < b.exp) {
+            aextradigit = a.align(b.exp);
+        } else {
+            bextradigit = b.align(a.exp);
+        }
+
+        /* complement the smaller of the two if the signs are different */
+        if (asign != bsign) {
+            if (asign == rsign) {
+                bextradigit = b.complement(bextradigit);
+            } else {
+                aextradigit = a.complement(aextradigit);
+            }
+        }
+
+        /* add the mantissas */
+        int rh = 0; /* acts as a carry */
+        for (int i = 0; i < mant.length; i++) {
+            final int r = a.mant[i] + b.mant[i] + rh;
+            rh = r / RADIX;
+            result.mant[i] = r - rh * RADIX;
+        }
+        result.exp = a.exp;
+        result.sign = rsign;
+
+        /* handle overflow -- note, when asign!=bsign an overflow is
+         * normal and should be ignored.  */
+
+        if (rh != 0 && (asign == bsign)) {
+            final int lostdigit = result.mant[0];
+            result.shiftRight();
+            result.mant[mant.length - 1] = rh;
+            final int excp = result.round(lostdigit);
+            if (excp != 0) {
+                result = dotrap(excp, ADD_TRAP, x, result);
+            }
+        }
+
+        /* normalize the result */
+        for (int i = 0; i < mant.length; i++) {
+            if (result.mant[mant.length - 1] != 0) {
+                break;
+            }
+            result.shiftLeft();
+            if (i == 0) {
+                result.mant[0] = aextradigit + bextradigit;
+                aextradigit = 0;
+                bextradigit = 0;
+            }
+        }
+
+        /* result is zero if after normalization the most sig. digit is zero */
+        if (result.mant[mant.length - 1] == 0) {
+            result.exp = 0;
+
+            if (asign != bsign) {
+                // Unless adding 2 negative zeros, sign is positive
+                result.sign = 1; // Per IEEE 854-1987 Section 6.3
+            }
+        }
+
+        /* Call round to test for over/under flows */
+        final int excp = result.round(aextradigit + bextradigit);
+        if (excp != 0) {
+            result = dotrap(excp, ADD_TRAP, x, result);
+        }
+
+        return result;
+    }
+
+    /**
+     * Returns a number that is this number with the sign bit reversed.
+     *
+     * @return the opposite of this
+     */
+    public Dfp negate() {
+        Dfp result = newInstance(this);
+        result.sign = (byte) -result.sign;
+        return result;
+    }
+
+    /**
+     * Subtract x from this.
+     *
+     * @param x number to subtract
+     * @return difference of this and a
+     */
+    public Dfp subtract(final Dfp x) {
+        return add(x.negate());
+    }
+
+    /**
+     * Round this given the next digit n using the current rounding mode.
+     *
+     * @param n ???
+     * @return the IEEE flag if an exception occurred
+     */
+    protected int round(int n) {
+        boolean inc = false;
+        switch (field.getRoundingMode()) {
+            case ROUND_DOWN:
+                inc = false;
+                break;
+
+            case ROUND_UP:
+                inc = n != 0; // round up if n!=0
+                break;
+
+            case ROUND_HALF_UP:
+                inc = n >= 5000; // round half up
+                break;
+
+            case ROUND_HALF_DOWN:
+                inc = n > 5000; // round half down
+                break;
+
+            case ROUND_HALF_EVEN:
+                inc = n > 5000 || (n == 5000 && (mant[0] & 1) == 1); // round half-even
+                break;
+
+            case ROUND_HALF_ODD:
+                inc = n > 5000 || (n == 5000 && (mant[0] & 1) == 0); // round half-odd
+                break;
+
+            case ROUND_CEIL:
+                inc = sign == 1 && n != 0; // round ceil
+                break;
+
+            case ROUND_FLOOR:
+            default:
+                inc = sign == -1 && n != 0; // round floor
+                break;
+        }
+
+        if (inc) {
+            // increment if necessary
+            int rh = 1;
+            for (int i = 0; i < mant.length; i++) {
+                final int r = mant[i] + rh;
+                rh = r / RADIX;
+                mant[i] = r - rh * RADIX;
+            }
+
+            if (rh != 0) {
+                shiftRight();
+                mant[mant.length - 1] = rh;
+            }
+        }
+
+        // check for exceptional cases and raise signals if necessary
+        if (exp < MIN_EXP) {
+            // Gradual Underflow
+            field.setIEEEFlagsBits(DfpField.FLAG_UNDERFLOW);
+            return DfpField.FLAG_UNDERFLOW;
+        }
+
+        if (exp > MAX_EXP) {
+            // Overflow
+            field.setIEEEFlagsBits(DfpField.FLAG_OVERFLOW);
+            return DfpField.FLAG_OVERFLOW;
+        }
+
+        if (n != 0) {
+            // Inexact
+            field.setIEEEFlagsBits(DfpField.FLAG_INEXACT);
+            return DfpField.FLAG_INEXACT;
+        }
+
+        return 0;
+    }
+
+    /**
+     * Multiply this by x.
+     *
+     * @param x multiplicand
+     * @return product of this and x
+     */
+    public Dfp multiply(final Dfp x) {
+
+        // make sure we don't mix number with different precision
+        if (field.getRadixDigits() != x.field.getRadixDigits()) {
+            field.setIEEEFlagsBits(DfpField.FLAG_INVALID);
+            final Dfp result = newInstance(getZero());
+            result.nans = QNAN;
+            return dotrap(DfpField.FLAG_INVALID, MULTIPLY_TRAP, x, result);
+        }
+
+        Dfp result = newInstance(getZero());
+
+        /* handle special cases */
+        if (nans != FINITE || x.nans != FINITE) {
+            if (isNaN()) {
+                return this;
+            }
+
+            if (x.isNaN()) {
+                return x;
+            }
+
+            if (nans == INFINITE && x.nans == FINITE && x.mant[mant.length - 1] != 0) {
+                result = newInstance(this);
+                result.sign = (byte) (sign * x.sign);
+                return result;
+            }
+
+            if (x.nans == INFINITE && nans == FINITE && mant[mant.length - 1] != 0) {
+                result = newInstance(x);
+                result.sign = (byte) (sign * x.sign);
+                return result;
+            }
+
+            if (x.nans == INFINITE && nans == INFINITE) {
+                result = newInstance(this);
+                result.sign = (byte) (sign * x.sign);
+                return result;
+            }
+
+            if ((x.nans == INFINITE && nans == FINITE && mant[mant.length - 1] == 0)
+                    || (nans == INFINITE && x.nans == FINITE && x.mant[mant.length - 1] == 0)) {
+                field.setIEEEFlagsBits(DfpField.FLAG_INVALID);
+                result = newInstance(getZero());
+                result.nans = QNAN;
+                result = dotrap(DfpField.FLAG_INVALID, MULTIPLY_TRAP, x, result);
+                return result;
+            }
+        }
+
+        int[] product = new int[mant.length * 2]; // Big enough to hold even the largest result
+
+        for (int i = 0; i < mant.length; i++) {
+            int rh = 0; // acts as a carry
+            for (int j = 0; j < mant.length; j++) {
+                int r = mant[i] * x.mant[j]; // multiply the 2 digits
+                r += product[i + j] + rh; // add to the product digit with carry in
+
+                rh = r / RADIX;
+                product[i + j] = r - rh * RADIX;
+            }
+            product[i + mant.length] = rh;
+        }
+
+        // Find the most sig digit
+        int md = mant.length * 2 - 1; // default, in case result is zero
+        for (int i = mant.length * 2 - 1; i >= 0; i--) {
+            if (product[i] != 0) {
+                md = i;
+                break;
+            }
+        }
+
+        // Copy the digits into the result
+        for (int i = 0; i < mant.length; i++) {
+            result.mant[mant.length - i - 1] = product[md - i];
+        }
+
+        // Fixup the exponent.
+        result.exp = exp + x.exp + md - 2 * mant.length + 1;
+        result.sign = (byte) ((sign == x.sign) ? 1 : -1);
+
+        if (result.mant[mant.length - 1] == 0) {
+            // if result is zero, set exp to zero
+            result.exp = 0;
+        }
+
+        final int excp;
+        if (md > (mant.length - 1)) {
+            excp = result.round(product[md - mant.length]);
+        } else {
+            excp = result.round(0); // has no effect except to check status
+        }
+
+        if (excp != 0) {
+            result = dotrap(excp, MULTIPLY_TRAP, x, result);
+        }
+
+        return result;
+    }
+
+    /**
+     * Multiply this by a single digit x.
+     *
+     * @param x multiplicand
+     * @return product of this and x
+     */
+    public Dfp multiply(final int x) {
+        if (x >= 0 && x < RADIX) {
+            return multiplyFast(x);
+        } else {
+            return multiply(newInstance(x));
+        }
+    }
+
+    /**
+     * Multiply this by a single digit 0&lt;=x&lt;radix. There are speed advantages in this special
+     * case.
+     *
+     * @param x multiplicand
+     * @return product of this and x
+     */
+    private Dfp multiplyFast(final int x) {
+        Dfp result = newInstance(this);
+
+        /* handle special cases */
+        if (nans != FINITE) {
+            if (isNaN()) {
+                return this;
+            }
+
+            if (nans == INFINITE && x != 0) {
+                result = newInstance(this);
+                return result;
+            }
+
+            if (nans == INFINITE && x == 0) {
+                field.setIEEEFlagsBits(DfpField.FLAG_INVALID);
+                result = newInstance(getZero());
+                result.nans = QNAN;
+                result =
+                        dotrap(
+                                DfpField.FLAG_INVALID,
+                                MULTIPLY_TRAP,
+                                newInstance(getZero()),
+                                result);
+                return result;
+            }
+        }
+
+        /* range check x */
+        if (x < 0 || x >= RADIX) {
+            field.setIEEEFlagsBits(DfpField.FLAG_INVALID);
+            result = newInstance(getZero());
+            result.nans = QNAN;
+            result = dotrap(DfpField.FLAG_INVALID, MULTIPLY_TRAP, result, result);
+            return result;
+        }
+
+        int rh = 0;
+        for (int i = 0; i < mant.length; i++) {
+            final int r = mant[i] * x + rh;
+            rh = r / RADIX;
+            result.mant[i] = r - rh * RADIX;
+        }
+
+        int lostdigit = 0;
+        if (rh != 0) {
+            lostdigit = result.mant[0];
+            result.shiftRight();
+            result.mant[mant.length - 1] = rh;
+        }
+
+        if (result.mant[mant.length - 1] == 0) { // if result is zero, set exp to zero
+            result.exp = 0;
+        }
+
+        final int excp = result.round(lostdigit);
+        if (excp != 0) {
+            result = dotrap(excp, MULTIPLY_TRAP, result, result);
+        }
+
+        return result;
+    }
+
+    /**
+     * Divide this by divisor.
+     *
+     * @param divisor divisor
+     * @return quotient of this by divisor
+     */
+    public Dfp divide(Dfp divisor) {
+        int dividend[]; // current status of the dividend
+        int quotient[]; // quotient
+        int remainder[]; // remainder
+        int qd; // current quotient digit we're working with
+        int nsqd; // number of significant quotient digits we have
+        int trial = 0; // trial quotient digit
+        int minadj; // minimum adjustment
+        boolean trialgood; // Flag to indicate a good trail digit
+        int md = 0; // most sig digit in result
+        int excp; // exceptions
+
+        // make sure we don't mix number with different precision
+        if (field.getRadixDigits() != divisor.field.getRadixDigits()) {
+            field.setIEEEFlagsBits(DfpField.FLAG_INVALID);
+            final Dfp result = newInstance(getZero());
+            result.nans = QNAN;
+            return dotrap(DfpField.FLAG_INVALID, DIVIDE_TRAP, divisor, result);
+        }
+
+        Dfp result = newInstance(getZero());
+
+        /* handle special cases */
+        if (nans != FINITE || divisor.nans != FINITE) {
+            if (isNaN()) {
+                return this;
+            }
+
+            if (divisor.isNaN()) {
+                return divisor;
+            }
+
+            if (nans == INFINITE && divisor.nans == FINITE) {
+                result = newInstance(this);
+                result.sign = (byte) (sign * divisor.sign);
+                return result;
+            }
+
+            if (divisor.nans == INFINITE && nans == FINITE) {
+                result = newInstance(getZero());
+                result.sign = (byte) (sign * divisor.sign);
+                return result;
+            }
+
+            if (divisor.nans == INFINITE && nans == INFINITE) {
+                field.setIEEEFlagsBits(DfpField.FLAG_INVALID);
+                result = newInstance(getZero());
+                result.nans = QNAN;
+                result = dotrap(DfpField.FLAG_INVALID, DIVIDE_TRAP, divisor, result);
+                return result;
+            }
+        }
+
+        /* Test for divide by zero */
+        if (divisor.mant[mant.length - 1] == 0) {
+            field.setIEEEFlagsBits(DfpField.FLAG_DIV_ZERO);
+            result = newInstance(getZero());
+            result.sign = (byte) (sign * divisor.sign);
+            result.nans = INFINITE;
+            result = dotrap(DfpField.FLAG_DIV_ZERO, DIVIDE_TRAP, divisor, result);
+            return result;
+        }
+
+        dividend = new int[mant.length + 1]; // one extra digit needed
+        quotient =
+                new int[mant.length + 2]; // two extra digits needed 1 for overflow, 1 for rounding
+        remainder = new int[mant.length + 1]; // one extra digit needed
+
+        /* Initialize our most significant digits to zero */
+
+        dividend[mant.length] = 0;
+        quotient[mant.length] = 0;
+        quotient[mant.length + 1] = 0;
+        remainder[mant.length] = 0;
+
+        /* copy our mantissa into the dividend, initialize the
+        quotient while we are at it */
+
+        for (int i = 0; i < mant.length; i++) {
+            dividend[i] = mant[i];
+            quotient[i] = 0;
+            remainder[i] = 0;
+        }
+
+        /* outer loop.  Once per quotient digit */
+        nsqd = 0;
+        for (qd = mant.length + 1; qd >= 0; qd--) {
+            /* Determine outer limits of our quotient digit */
+
+            // r =  most sig 2 digits of dividend
+            final int divMsb = dividend[mant.length] * RADIX + dividend[mant.length - 1];
+            int min = divMsb / (divisor.mant[mant.length - 1] + 1);
+            int max = (divMsb + 1) / divisor.mant[mant.length - 1];
+
+            trialgood = false;
+            while (!trialgood) {
+                // try the mean
+                trial = (min + max) / 2;
+
+                /* Multiply by divisor and store as remainder */
+                int rh = 0;
+                for (int i = 0; i < mant.length + 1; i++) {
+                    int dm = (i < mant.length) ? divisor.mant[i] : 0;
+                    final int r = (dm * trial) + rh;
+                    rh = r / RADIX;
+                    remainder[i] = r - rh * RADIX;
+                }
+
+                /* subtract the remainder from the dividend */
+                rh = 1; // carry in to aid the subtraction
+                for (int i = 0; i < mant.length + 1; i++) {
+                    final int r = ((RADIX - 1) - remainder[i]) + dividend[i] + rh;
+                    rh = r / RADIX;
+                    remainder[i] = r - rh * RADIX;
+                }
+
+                /* Lets analyze what we have here */
+                if (rh == 0) {
+                    // trial is too big -- negative remainder
+                    max = trial - 1;
+                    continue;
+                }
+
+                /* find out how far off the remainder is telling us we are */
+                minadj = (remainder[mant.length] * RADIX) + remainder[mant.length - 1];
+                minadj /= divisor.mant[mant.length - 1] + 1;
+
+                if (minadj >= 2) {
+                    min = trial + minadj; // update the minimum
+                    continue;
+                }
+
+                /* May have a good one here, check more thoroughly.  Basically
+                its a good one if it is less than the divisor */
+                trialgood = false; // assume false
+                for (int i = mant.length - 1; i >= 0; i--) {
+                    if (divisor.mant[i] > remainder[i]) {
+                        trialgood = true;
+                    }
+                    if (divisor.mant[i] < remainder[i]) {
+                        break;
+                    }
+                }
+
+                if (remainder[mant.length] != 0) {
+                    trialgood = false;
+                }
+
+                if (trialgood == false) {
+                    min = trial + 1;
+                }
+            }
+
+            /* Great we have a digit! */
+            quotient[qd] = trial;
+            if (trial != 0 || nsqd != 0) {
+                nsqd++;
+            }
+
+            if (field.getRoundingMode() == DfpField.RoundingMode.ROUND_DOWN
+                    && nsqd == mant.length) {
+                // We have enough for this mode
+                break;
+            }
+
+            if (nsqd > mant.length) {
+                // We have enough digits
+                break;
+            }
+
+            /* move the remainder into the dividend while left shifting */
+            dividend[0] = 0;
+            for (int i = 0; i < mant.length; i++) {
+                dividend[i + 1] = remainder[i];
+            }
+        }
+
+        /* Find the most sig digit */
+        md = mant.length; // default
+        for (int i = mant.length + 1; i >= 0; i--) {
+            if (quotient[i] != 0) {
+                md = i;
+                break;
+            }
+        }
+
+        /* Copy the digits into the result */
+        for (int i = 0; i < mant.length; i++) {
+            result.mant[mant.length - i - 1] = quotient[md - i];
+        }
+
+        /* Fixup the exponent. */
+        result.exp = exp - divisor.exp + md - mant.length;
+        result.sign = (byte) ((sign == divisor.sign) ? 1 : -1);
+
+        if (result.mant[mant.length - 1] == 0) { // if result is zero, set exp to zero
+            result.exp = 0;
+        }
+
+        if (md > (mant.length - 1)) {
+            excp = result.round(quotient[md - mant.length]);
+        } else {
+            excp = result.round(0);
+        }
+
+        if (excp != 0) {
+            result = dotrap(excp, DIVIDE_TRAP, divisor, result);
+        }
+
+        return result;
+    }
+
+    /**
+     * Divide by a single digit less than radix. Special case, so there are speed advantages. 0
+     * &lt;= divisor &lt; radix
+     *
+     * @param divisor divisor
+     * @return quotient of this by divisor
+     */
+    public Dfp divide(int divisor) {
+
+        // Handle special cases
+        if (nans != FINITE) {
+            if (isNaN()) {
+                return this;
+            }
+
+            if (nans == INFINITE) {
+                return newInstance(this);
+            }
+        }
+
+        // Test for divide by zero
+        if (divisor == 0) {
+            field.setIEEEFlagsBits(DfpField.FLAG_DIV_ZERO);
+            Dfp result = newInstance(getZero());
+            result.sign = sign;
+            result.nans = INFINITE;
+            result = dotrap(DfpField.FLAG_DIV_ZERO, DIVIDE_TRAP, getZero(), result);
+            return result;
+        }
+
+        // range check divisor
+        if (divisor < 0 || divisor >= RADIX) {
+            field.setIEEEFlagsBits(DfpField.FLAG_INVALID);
+            Dfp result = newInstance(getZero());
+            result.nans = QNAN;
+            result = dotrap(DfpField.FLAG_INVALID, DIVIDE_TRAP, result, result);
+            return result;
+        }
+
+        Dfp result = newInstance(this);
+
+        int rl = 0;
+        for (int i = mant.length - 1; i >= 0; i--) {
+            final int r = rl * RADIX + result.mant[i];
+            final int rh = r / divisor;
+            rl = r - rh * divisor;
+            result.mant[i] = rh;
+        }
+
+        if (result.mant[mant.length - 1] == 0) {
+            // normalize
+            result.shiftLeft();
+            final int r = rl * RADIX; // compute the next digit and put it in
+            final int rh = r / divisor;
+            rl = r - rh * divisor;
+            result.mant[0] = rh;
+        }
+
+        final int excp = result.round(rl * RADIX / divisor); // do the rounding
+        if (excp != 0) {
+            result = dotrap(excp, DIVIDE_TRAP, result, result);
+        }
+
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    public Dfp reciprocal() {
+        return field.getOne().divide(this);
+    }
+
+    /**
+     * Compute the square root.
+     *
+     * @return square root of the instance
+     * @since 3.2
+     */
+    public Dfp sqrt() {
+
+        // check for unusual cases
+        if (nans == FINITE && mant[mant.length - 1] == 0) {
+            // if zero
+            return newInstance(this);
+        }
+
+        if (nans != FINITE) {
+            if (nans == INFINITE && sign == 1) {
+                // if positive infinity
+                return newInstance(this);
+            }
+
+            if (nans == QNAN) {
+                return newInstance(this);
+            }
+
+            if (nans == SNAN) {
+                Dfp result;
+
+                field.setIEEEFlagsBits(DfpField.FLAG_INVALID);
+                result = newInstance(this);
+                result = dotrap(DfpField.FLAG_INVALID, SQRT_TRAP, null, result);
+                return result;
+            }
+        }
+
+        if (sign == -1) {
+            // if negative
+            Dfp result;
+
+            field.setIEEEFlagsBits(DfpField.FLAG_INVALID);
+            result = newInstance(this);
+            result.nans = QNAN;
+            result = dotrap(DfpField.FLAG_INVALID, SQRT_TRAP, null, result);
+            return result;
+        }
+
+        Dfp x = newInstance(this);
+
+        /* Lets make a reasonable guess as to the size of the square root */
+        if (x.exp < -1 || x.exp > 1) {
+            x.exp = this.exp / 2;
+        }
+
+        /* Coarsely estimate the mantissa */
+        switch (x.mant[mant.length - 1] / 2000) {
+            case 0:
+                x.mant[mant.length - 1] = x.mant[mant.length - 1] / 2 + 1;
+                break;
+            case 2:
+                x.mant[mant.length - 1] = 1500;
+                break;
+            case 3:
+                x.mant[mant.length - 1] = 2200;
+                break;
+            default:
+                x.mant[mant.length - 1] = 3000;
+        }
+
+        Dfp dx = newInstance(x);
+
+        /* Now that we have the first pass estimate, compute the rest
+        by the formula dx = (y - x*x) / (2x); */
+
+        Dfp px = getZero();
+        Dfp ppx = getZero();
+        while (x.unequal(px)) {
+            dx = newInstance(x);
+            dx.sign = -1;
+            dx = dx.add(this.divide(x));
+            dx = dx.divide(2);
+            ppx = px;
+            px = x;
+            x = x.add(dx);
+
+            if (x.equals(ppx)) {
+                // alternating between two values
+                break;
+            }
+
+            // if dx is zero, break.  Note testing the most sig digit
+            // is a sufficient test since dx is normalized
+            if (dx.mant[mant.length - 1] == 0) {
+                break;
+            }
+        }
+
+        return x;
+    }
+
+    /**
+     * Get a string representation of the instance.
+     *
+     * @return string representation of the instance
+     */
+    @Override
+    public String toString() {
+        if (nans != FINITE) {
+            // if non-finite exceptional cases
+            if (nans == INFINITE) {
+                return (sign < 0) ? NEG_INFINITY_STRING : POS_INFINITY_STRING;
+            } else {
+                return NAN_STRING;
+            }
+        }
+
+        if (exp > mant.length || exp < -1) {
+            return dfp2sci();
+        }
+
+        return dfp2string();
+    }
+
+    /**
+     * Convert an instance to a string using scientific notation.
+     *
+     * @return string representation of the instance in scientific notation
+     */
+    protected String dfp2sci() {
+        char rawdigits[] = new char[mant.length * 4];
+        char outputbuffer[] = new char[mant.length * 4 + 20];
+        int p;
+        int q;
+        int e;
+        int ae;
+        int shf;
+
+        // Get all the digits
+        p = 0;
+        for (int i = mant.length - 1; i >= 0; i--) {
+            rawdigits[p++] = (char) ((mant[i] / 1000) + '0');
+            rawdigits[p++] = (char) (((mant[i] / 100) % 10) + '0');
+            rawdigits[p++] = (char) (((mant[i] / 10) % 10) + '0');
+            rawdigits[p++] = (char) (((mant[i]) % 10) + '0');
+        }
+
+        // Find the first non-zero one
+        for (p = 0; p < rawdigits.length; p++) {
+            if (rawdigits[p] != '0') {
+                break;
+            }
+        }
+        shf = p;
+
+        // Now do the conversion
+        q = 0;
+        if (sign == -1) {
+            outputbuffer[q++] = '-';
+        }
+
+        if (p != rawdigits.length) {
+            // there are non zero digits...
+            outputbuffer[q++] = rawdigits[p++];
+            outputbuffer[q++] = '.';
+
+            while (p < rawdigits.length) {
+                outputbuffer[q++] = rawdigits[p++];
+            }
+        } else {
+            outputbuffer[q++] = '0';
+            outputbuffer[q++] = '.';
+            outputbuffer[q++] = '0';
+            outputbuffer[q++] = 'e';
+            outputbuffer[q++] = '0';
+            return new String(outputbuffer, 0, 5);
+        }
+
+        outputbuffer[q++] = 'e';
+
+        // Find the msd of the exponent
+
+        e = exp * 4 - shf - 1;
+        ae = e;
+        if (e < 0) {
+            ae = -e;
+        }
+
+        // Find the largest p such that p < e
+        for (p = 1000000000; p > ae; p /= 10) {
+            // nothing to do
+        }
+
+        if (e < 0) {
+            outputbuffer[q++] = '-';
+        }
+
+        while (p > 0) {
+            outputbuffer[q++] = (char) (ae / p + '0');
+            ae %= p;
+            p /= 10;
+        }
+
+        return new String(outputbuffer, 0, q);
+    }
+
+    /**
+     * Convert an instance to a string using normal notation.
+     *
+     * @return string representation of the instance in normal notation
+     */
+    protected String dfp2string() {
+        char buffer[] = new char[mant.length * 4 + 20];
+        int p = 1;
+        int q;
+        int e = exp;
+        boolean pointInserted = false;
+
+        buffer[0] = ' ';
+
+        if (e <= 0) {
+            buffer[p++] = '0';
+            buffer[p++] = '.';
+            pointInserted = true;
+        }
+
+        while (e < 0) {
+            buffer[p++] = '0';
+            buffer[p++] = '0';
+            buffer[p++] = '0';
+            buffer[p++] = '0';
+            e++;
+        }
+
+        for (int i = mant.length - 1; i >= 0; i--) {
+            buffer[p++] = (char) ((mant[i] / 1000) + '0');
+            buffer[p++] = (char) (((mant[i] / 100) % 10) + '0');
+            buffer[p++] = (char) (((mant[i] / 10) % 10) + '0');
+            buffer[p++] = (char) (((mant[i]) % 10) + '0');
+            if (--e == 0) {
+                buffer[p++] = '.';
+                pointInserted = true;
+            }
+        }
+
+        while (e > 0) {
+            buffer[p++] = '0';
+            buffer[p++] = '0';
+            buffer[p++] = '0';
+            buffer[p++] = '0';
+            e--;
+        }
+
+        if (!pointInserted) {
+            // Ensure we have a radix point!
+            buffer[p++] = '.';
+        }
+
+        // Suppress leading zeros
+        q = 1;
+        while (buffer[q] == '0') {
+            q++;
+        }
+        if (buffer[q] == '.') {
+            q--;
+        }
+
+        // Suppress trailing zeros
+        while (buffer[p - 1] == '0') {
+            p--;
+        }
+
+        // Insert sign
+        if (sign < 0) {
+            buffer[--q] = '-';
+        }
+
+        return new String(buffer, q, p - q);
+    }
+
+    /**
+     * Raises a trap. This does not set the corresponding flag however.
+     *
+     * @param type the trap type
+     * @param what - name of routine trap occurred in
+     * @param oper - input operator to function
+     * @param result - the result computed prior to the trap
+     * @return The suggested return value from the trap handler
+     */
+    public Dfp dotrap(int type, String what, Dfp oper, Dfp result) {
+        Dfp def = result;
+
+        switch (type) {
+            case DfpField.FLAG_INVALID:
+                def = newInstance(getZero());
+                def.sign = result.sign;
+                def.nans = QNAN;
+                break;
+
+            case DfpField.FLAG_DIV_ZERO:
+                if (nans == FINITE && mant[mant.length - 1] != 0) {
+                    // normal case, we are finite, non-zero
+                    def = newInstance(getZero());
+                    def.sign = (byte) (sign * oper.sign);
+                    def.nans = INFINITE;
+                }
+
+                if (nans == FINITE && mant[mant.length - 1] == 0) {
+                    //  0/0
+                    def = newInstance(getZero());
+                    def.nans = QNAN;
+                }
+
+                if (nans == INFINITE || nans == QNAN) {
+                    def = newInstance(getZero());
+                    def.nans = QNAN;
+                }
+
+                if (nans == INFINITE || nans == SNAN) {
+                    def = newInstance(getZero());
+                    def.nans = QNAN;
+                }
+                break;
+
+            case DfpField.FLAG_UNDERFLOW:
+                if ((result.exp + mant.length) < MIN_EXP) {
+                    def = newInstance(getZero());
+                    def.sign = result.sign;
+                } else {
+                    def = newInstance(result); // gradual underflow
+                }
+                result.exp += ERR_SCALE;
+                break;
+
+            case DfpField.FLAG_OVERFLOW:
+                result.exp -= ERR_SCALE;
+                def = newInstance(getZero());
+                def.sign = result.sign;
+                def.nans = INFINITE;
+                break;
+
+            default:
+                def = result;
+                break;
+        }
+
+        return trap(type, what, oper, def, result);
+    }
+
+    /**
+     * Trap handler. Subclasses may override this to provide trap functionality per IEEE 854-1987.
+     *
+     * @param type The exception type - e.g. FLAG_OVERFLOW
+     * @param what The name of the routine we were in e.g. divide()
+     * @param oper An operand to this function if any
+     * @param def The default return value if trap not enabled
+     * @param result The result that is specified to be delivered per IEEE 854, if any
+     * @return the value that should be return by the operation triggering the trap
+     */
+    protected Dfp trap(int type, String what, Dfp oper, Dfp def, Dfp result) {
+        return def;
+    }
+
+    /**
+     * Returns the type - one of FINITE, INFINITE, SNAN, QNAN.
+     *
+     * @return type of the number
+     */
+    public int classify() {
+        return nans;
+    }
+
+    /**
+     * Creates an instance that is the same as x except that it has the sign of y. abs(x) =
+     * dfp.copysign(x, dfp.one)
+     *
+     * @param x number to get the value from
+     * @param y number to get the sign from
+     * @return a number with the value of x and the sign of y
+     */
+    public static Dfp copysign(final Dfp x, final Dfp y) {
+        Dfp result = x.newInstance(x);
+        result.sign = y.sign;
+        return result;
+    }
+
+    /**
+     * Returns the next number greater than this one in the direction of x. If this==x then simply
+     * returns this.
+     *
+     * @param x direction where to look at
+     * @return closest number next to instance in the direction of x
+     */
+    public Dfp nextAfter(final Dfp x) {
+
+        // make sure we don't mix number with different precision
+        if (field.getRadixDigits() != x.field.getRadixDigits()) {
+            field.setIEEEFlagsBits(DfpField.FLAG_INVALID);
+            final Dfp result = newInstance(getZero());
+            result.nans = QNAN;
+            return dotrap(DfpField.FLAG_INVALID, NEXT_AFTER_TRAP, x, result);
+        }
+
+        // if this is greater than x
+        boolean up = false;
+        if (this.lessThan(x)) {
+            up = true;
+        }
+
+        if (compare(this, x) == 0) {
+            return newInstance(x);
+        }
+
+        if (lessThan(getZero())) {
+            up = !up;
+        }
+
+        final Dfp inc;
+        Dfp result;
+        if (up) {
+            inc = newInstance(getOne());
+            inc.exp = this.exp - mant.length + 1;
+            inc.sign = this.sign;
+
+            if (this.equals(getZero())) {
+                inc.exp = MIN_EXP - mant.length;
+            }
+
+            result = add(inc);
+        } else {
+            inc = newInstance(getOne());
+            inc.exp = this.exp;
+            inc.sign = this.sign;
+
+            if (this.equals(inc)) {
+                inc.exp = this.exp - mant.length;
+            } else {
+                inc.exp = this.exp - mant.length + 1;
+            }
+
+            if (this.equals(getZero())) {
+                inc.exp = MIN_EXP - mant.length;
+            }
+
+            result = this.subtract(inc);
+        }
+
+        if (result.classify() == INFINITE && this.classify() != INFINITE) {
+            field.setIEEEFlagsBits(DfpField.FLAG_INEXACT);
+            result = dotrap(DfpField.FLAG_INEXACT, NEXT_AFTER_TRAP, x, result);
+        }
+
+        if (result.equals(getZero()) && this.equals(getZero()) == false) {
+            field.setIEEEFlagsBits(DfpField.FLAG_INEXACT);
+            result = dotrap(DfpField.FLAG_INEXACT, NEXT_AFTER_TRAP, x, result);
+        }
+
+        return result;
+    }
+
+    /**
+     * Convert the instance into a double.
+     *
+     * @return a double approximating the instance
+     * @see #toSplitDouble()
+     */
+    public double toDouble() {
+
+        if (isInfinite()) {
+            if (lessThan(getZero())) {
+                return Double.NEGATIVE_INFINITY;
+            } else {
+                return Double.POSITIVE_INFINITY;
+            }
+        }
+
+        if (isNaN()) {
+            return Double.NaN;
+        }
+
+        Dfp y = this;
+        boolean negate = false;
+        int cmp0 = compare(this, getZero());
+        if (cmp0 == 0) {
+            return sign < 0 ? -0.0 : +0.0;
+        } else if (cmp0 < 0) {
+            y = negate();
+            negate = true;
+        }
+
+        /* Find the exponent, first estimate by integer log10, then adjust.
+        Should be faster than doing a natural logarithm.  */
+        int exponent = (int) (y.intLog10() * 3.32);
+        if (exponent < 0) {
+            exponent--;
+        }
+
+        Dfp tempDfp = DfpMath.pow(getTwo(), exponent);
+        while (tempDfp.lessThan(y) || tempDfp.equals(y)) {
+            tempDfp = tempDfp.multiply(2);
+            exponent++;
+        }
+        exponent--;
+
+        /* We have the exponent, now work on the mantissa */
+
+        y = y.divide(DfpMath.pow(getTwo(), exponent));
+        if (exponent > -1023) {
+            y = y.subtract(getOne());
+        }
+
+        if (exponent < -1074) {
+            return 0;
+        }
+
+        if (exponent > 1023) {
+            return negate ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY;
+        }
+
+        y = y.multiply(newInstance(4503599627370496l)).rint();
+        String str = y.toString();
+        str = str.substring(0, str.length() - 1);
+        long mantissa = Long.parseLong(str);
+
+        if (mantissa == 4503599627370496L) {
+            // Handle special case where we round up to next power of two
+            mantissa = 0;
+            exponent++;
+        }
+
+        /* Its going to be subnormal, so make adjustments */
+        if (exponent <= -1023) {
+            exponent--;
+        }
+
+        while (exponent < -1023) {
+            exponent++;
+            mantissa >>>= 1;
+        }
+
+        long bits = mantissa | ((exponent + 1023L) << 52);
+        double x = Double.longBitsToDouble(bits);
+
+        if (negate) {
+            x = -x;
+        }
+
+        return x;
+    }
+
+    /**
+     * Convert the instance into a split double.
+     *
+     * @return an array of two doubles which sum represent the instance
+     * @see #toDouble()
+     */
+    public double[] toSplitDouble() {
+        double split[] = new double[2];
+        long mask = 0xffffffffc0000000L;
+
+        split[0] = Double.longBitsToDouble(Double.doubleToLongBits(toDouble()) & mask);
+        split[1] = subtract(newInstance(split[0])).toDouble();
+
+        return split;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public double getReal() {
+        return toDouble();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp add(final double a) {
+        return add(newInstance(a));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp subtract(final double a) {
+        return subtract(newInstance(a));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp multiply(final double a) {
+        return multiply(newInstance(a));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp divide(final double a) {
+        return divide(newInstance(a));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp remainder(final double a) {
+        return remainder(newInstance(a));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public long round() {
+        return FastMath.round(toDouble());
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp signum() {
+        if (isNaN() || isZero()) {
+            return this;
+        } else {
+            return newInstance(sign > 0 ? +1 : -1);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp copySign(final Dfp s) {
+        if ((sign >= 0 && s.sign >= 0) || (sign < 0 && s.sign < 0)) { // Sign is currently OK
+            return this;
+        }
+        return negate(); // flip sign
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp copySign(final double s) {
+        long sb = Double.doubleToLongBits(s);
+        if ((sign >= 0 && sb >= 0) || (sign < 0 && sb < 0)) { // Sign is currently OK
+            return this;
+        }
+        return negate(); // flip sign
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp scalb(final int n) {
+        return multiply(DfpMath.pow(getTwo(), n));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp hypot(final Dfp y) {
+        return multiply(this).add(y.multiply(y)).sqrt();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp cbrt() {
+        return rootN(3);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp rootN(final int n) {
+        return (sign >= 0)
+                ? DfpMath.pow(this, getOne().divide(n))
+                : DfpMath.pow(negate(), getOne().divide(n)).negate();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp pow(final double p) {
+        return DfpMath.pow(this, newInstance(p));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp pow(final int n) {
+        return DfpMath.pow(this, n);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp pow(final Dfp e) {
+        return DfpMath.pow(this, e);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp exp() {
+        return DfpMath.exp(this);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp expm1() {
+        return DfpMath.exp(this).subtract(getOne());
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp log() {
+        return DfpMath.log(this);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp log1p() {
+        return DfpMath.log(this.add(getOne()));
+    }
+
+    //  TODO: deactivate this implementation (and return type) in 4.0
+    /**
+     * Get the exponent of the greatest power of 10 that is less than or equal to abs(this).
+     *
+     * @return integer base 10 logarithm
+     * @deprecated as of 3.2, replaced by {@link #intLog10()}, in 4.0 the return type will be
+     *     changed to Dfp
+     */
+    @Deprecated
+    public int log10() {
+        return intLog10();
+    }
+
+    //    TODO: activate this implementation (and return type) in 4.0
+    //    /** {@inheritDoc}
+    //     * @since 3.2
+    //     */
+    //    public Dfp log10() {
+    //        return DfpMath.log(this).divide(DfpMath.log(newInstance(10)));
+    //    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp cos() {
+        return DfpMath.cos(this);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp sin() {
+        return DfpMath.sin(this);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp tan() {
+        return DfpMath.tan(this);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp acos() {
+        return DfpMath.acos(this);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp asin() {
+        return DfpMath.asin(this);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp atan() {
+        return DfpMath.atan(this);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp atan2(final Dfp x) throws DimensionMismatchException {
+
+        // compute r = sqrt(x^2+y^2)
+        final Dfp r = x.multiply(x).add(multiply(this)).sqrt();
+
+        if (x.sign >= 0) {
+
+            // compute atan2(y, x) = 2 atan(y / (r + x))
+            return getTwo().multiply(divide(r.add(x)).atan());
+
+        } else {
+
+            // compute atan2(y, x) = +/- pi - 2 atan(y / (r - x))
+            final Dfp tmp = getTwo().multiply(divide(r.subtract(x)).atan());
+            final Dfp pmPi = newInstance((tmp.sign <= 0) ? -FastMath.PI : FastMath.PI);
+            return pmPi.subtract(tmp);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp cosh() {
+        return DfpMath.exp(this).add(DfpMath.exp(negate())).divide(2);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp sinh() {
+        return DfpMath.exp(this).subtract(DfpMath.exp(negate())).divide(2);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp tanh() {
+        final Dfp ePlus = DfpMath.exp(this);
+        final Dfp eMinus = DfpMath.exp(negate());
+        return ePlus.subtract(eMinus).divide(ePlus.add(eMinus));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp acosh() {
+        return multiply(this).subtract(getOne()).sqrt().add(this).log();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp asinh() {
+        return multiply(this).add(getOne()).sqrt().add(this).log();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp atanh() {
+        return getOne().add(this).divide(getOne().subtract(this)).log().divide(2);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp linearCombination(final Dfp[] a, final Dfp[] b) throws DimensionMismatchException {
+        if (a.length != b.length) {
+            throw new DimensionMismatchException(a.length, b.length);
+        }
+        Dfp r = getZero();
+        for (int i = 0; i < a.length; ++i) {
+            r = r.add(a[i].multiply(b[i]));
+        }
+        return r;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp linearCombination(final double[] a, final Dfp[] b)
+            throws DimensionMismatchException {
+        if (a.length != b.length) {
+            throw new DimensionMismatchException(a.length, b.length);
+        }
+        Dfp r = getZero();
+        for (int i = 0; i < a.length; ++i) {
+            r = r.add(b[i].multiply(a[i]));
+        }
+        return r;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp linearCombination(final Dfp a1, final Dfp b1, final Dfp a2, final Dfp b2) {
+        return a1.multiply(b1).add(a2.multiply(b2));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp linearCombination(final double a1, final Dfp b1, final double a2, final Dfp b2) {
+        return b1.multiply(a1).add(b2.multiply(a2));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp linearCombination(
+            final Dfp a1, final Dfp b1, final Dfp a2, final Dfp b2, final Dfp a3, final Dfp b3) {
+        return a1.multiply(b1).add(a2.multiply(b2)).add(a3.multiply(b3));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp linearCombination(
+            final double a1,
+            final Dfp b1,
+            final double a2,
+            final Dfp b2,
+            final double a3,
+            final Dfp b3) {
+        return b1.multiply(a1).add(b2.multiply(a2)).add(b3.multiply(a3));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp linearCombination(
+            final Dfp a1,
+            final Dfp b1,
+            final Dfp a2,
+            final Dfp b2,
+            final Dfp a3,
+            final Dfp b3,
+            final Dfp a4,
+            final Dfp b4) {
+        return a1.multiply(b1).add(a2.multiply(b2)).add(a3.multiply(b3)).add(a4.multiply(b4));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Dfp linearCombination(
+            final double a1,
+            final Dfp b1,
+            final double a2,
+            final Dfp b2,
+            final double a3,
+            final Dfp b3,
+            final double a4,
+            final Dfp b4) {
+        return b1.multiply(a1).add(b2.multiply(a2)).add(b3.multiply(a3)).add(b4.multiply(a4));
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/dfp/DfpDec.java b/src/main/java/org/apache/commons/math3/dfp/DfpDec.java
new file mode 100644
index 0000000..5571c2d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/dfp/DfpDec.java
@@ -0,0 +1,388 @@
+/*
+ * 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.math3.dfp;
+
+/**
+ * Subclass of {@link Dfp} which hides the radix-10000 artifacts of the superclass. This should give
+ * outward appearances of being a decimal number with DIGITS*4-3 decimal digits. This class can be
+ * subclassed to appear to be an arbitrary number of decimal digits less than DIGITS*4-3.
+ *
+ * @since 2.2
+ */
+public class DfpDec extends Dfp {
+
+    /**
+     * Makes an instance with a value of zero.
+     *
+     * @param factory factory linked to this instance
+     */
+    protected DfpDec(final DfpField factory) {
+        super(factory);
+    }
+
+    /**
+     * Create an instance from a byte value.
+     *
+     * @param factory factory linked to this instance
+     * @param x value to convert to an instance
+     */
+    protected DfpDec(final DfpField factory, byte x) {
+        super(factory, x);
+    }
+
+    /**
+     * Create an instance from an int value.
+     *
+     * @param factory factory linked to this instance
+     * @param x value to convert to an instance
+     */
+    protected DfpDec(final DfpField factory, int x) {
+        super(factory, x);
+    }
+
+    /**
+     * Create an instance from a long value.
+     *
+     * @param factory factory linked to this instance
+     * @param x value to convert to an instance
+     */
+    protected DfpDec(final DfpField factory, long x) {
+        super(factory, x);
+    }
+
+    /**
+     * Create an instance from a double value.
+     *
+     * @param factory factory linked to this instance
+     * @param x value to convert to an instance
+     */
+    protected DfpDec(final DfpField factory, double x) {
+        super(factory, x);
+        round(0);
+    }
+
+    /**
+     * Copy constructor.
+     *
+     * @param d instance to copy
+     */
+    public DfpDec(final Dfp d) {
+        super(d);
+        round(0);
+    }
+
+    /**
+     * Create an instance from a String representation.
+     *
+     * @param factory factory linked to this instance
+     * @param s string representation of the instance
+     */
+    protected DfpDec(final DfpField factory, final String s) {
+        super(factory, s);
+        round(0);
+    }
+
+    /**
+     * Creates an instance with a non-finite value.
+     *
+     * @param factory factory linked to this instance
+     * @param sign sign of the Dfp to create
+     * @param nans code of the value, must be one of {@link #INFINITE}, {@link #SNAN}, {@link #QNAN}
+     */
+    protected DfpDec(final DfpField factory, final byte sign, final byte nans) {
+        super(factory, sign, nans);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Dfp newInstance() {
+        return new DfpDec(getField());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Dfp newInstance(final byte x) {
+        return new DfpDec(getField(), x);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Dfp newInstance(final int x) {
+        return new DfpDec(getField(), x);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Dfp newInstance(final long x) {
+        return new DfpDec(getField(), x);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Dfp newInstance(final double x) {
+        return new DfpDec(getField(), x);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Dfp newInstance(final Dfp d) {
+
+        // make sure we don't mix number with different precision
+        if (getField().getRadixDigits() != d.getField().getRadixDigits()) {
+            getField().setIEEEFlagsBits(DfpField.FLAG_INVALID);
+            final Dfp result = newInstance(getZero());
+            result.nans = QNAN;
+            return dotrap(DfpField.FLAG_INVALID, "newInstance", d, result);
+        }
+
+        return new DfpDec(d);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Dfp newInstance(final String s) {
+        return new DfpDec(getField(), s);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Dfp newInstance(final byte sign, final byte nans) {
+        return new DfpDec(getField(), sign, nans);
+    }
+
+    /**
+     * Get the number of decimal digits this class is going to represent. Default implementation
+     * returns {@link #getRadixDigits()}*4-3. Subclasses can override this to return something less.
+     *
+     * @return number of decimal digits this class is going to represent
+     */
+    protected int getDecimalDigits() {
+        return getRadixDigits() * 4 - 3;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected int round(int in) {
+
+        int msb = mant[mant.length - 1];
+        if (msb == 0) {
+            // special case -- this == zero
+            return 0;
+        }
+
+        int cmaxdigits = mant.length * 4;
+        int lsbthreshold = 1000;
+        while (lsbthreshold > msb) {
+            lsbthreshold /= 10;
+            cmaxdigits--;
+        }
+
+        final int digits = getDecimalDigits();
+        final int lsbshift = cmaxdigits - digits;
+        final int lsd = lsbshift / 4;
+
+        lsbthreshold = 1;
+        for (int i = 0; i < lsbshift % 4; i++) {
+            lsbthreshold *= 10;
+        }
+
+        final int lsb = mant[lsd];
+
+        if (lsbthreshold <= 1 && digits == 4 * mant.length - 3) {
+            return super.round(in);
+        }
+
+        int discarded = in; // not looking at this after this point
+        final int n;
+        if (lsbthreshold == 1) {
+            // look to the next digit for rounding
+            n = (mant[lsd - 1] / 1000) % 10;
+            mant[lsd - 1] %= 1000;
+            discarded |= mant[lsd - 1];
+        } else {
+            n = (lsb * 10 / lsbthreshold) % 10;
+            discarded |= lsb % (lsbthreshold / 10);
+        }
+
+        for (int i = 0; i < lsd; i++) {
+            discarded |= mant[i]; // need to know if there are any discarded bits
+            mant[i] = 0;
+        }
+
+        mant[lsd] = lsb / lsbthreshold * lsbthreshold;
+
+        final boolean inc;
+        switch (getField().getRoundingMode()) {
+            case ROUND_DOWN:
+                inc = false;
+                break;
+
+            case ROUND_UP:
+                inc = (n != 0) || (discarded != 0); // round up if n!=0
+                break;
+
+            case ROUND_HALF_UP:
+                inc = n >= 5; // round half up
+                break;
+
+            case ROUND_HALF_DOWN:
+                inc = n > 5; // round half down
+                break;
+
+            case ROUND_HALF_EVEN:
+                inc =
+                        (n > 5)
+                                || (n == 5 && discarded != 0)
+                                || (n == 5
+                                        && discarded == 0
+                                        && ((lsb / lsbthreshold) & 1) == 1); // round half-even
+                break;
+
+            case ROUND_HALF_ODD:
+                inc =
+                        (n > 5)
+                                || (n == 5 && discarded != 0)
+                                || (n == 5
+                                        && discarded == 0
+                                        && ((lsb / lsbthreshold) & 1) == 0); // round half-odd
+                break;
+
+            case ROUND_CEIL:
+                inc = (sign == 1) && (n != 0 || discarded != 0); // round ceil
+                break;
+
+            case ROUND_FLOOR:
+            default:
+                inc = (sign == -1) && (n != 0 || discarded != 0); // round floor
+                break;
+        }
+
+        if (inc) {
+            // increment if necessary
+            int rh = lsbthreshold;
+            for (int i = lsd; i < mant.length; i++) {
+                final int r = mant[i] + rh;
+                rh = r / RADIX;
+                mant[i] = r % RADIX;
+            }
+
+            if (rh != 0) {
+                shiftRight();
+                mant[mant.length - 1] = rh;
+            }
+        }
+
+        // Check for exceptional cases and raise signals if necessary
+        if (exp < MIN_EXP) {
+            // Gradual Underflow
+            getField().setIEEEFlagsBits(DfpField.FLAG_UNDERFLOW);
+            return DfpField.FLAG_UNDERFLOW;
+        }
+
+        if (exp > MAX_EXP) {
+            // Overflow
+            getField().setIEEEFlagsBits(DfpField.FLAG_OVERFLOW);
+            return DfpField.FLAG_OVERFLOW;
+        }
+
+        if (n != 0 || discarded != 0) {
+            // Inexact
+            getField().setIEEEFlagsBits(DfpField.FLAG_INEXACT);
+            return DfpField.FLAG_INEXACT;
+        }
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Dfp nextAfter(Dfp x) {
+
+        final String trapName = "nextAfter";
+
+        // make sure we don't mix number with different precision
+        if (getField().getRadixDigits() != x.getField().getRadixDigits()) {
+            getField().setIEEEFlagsBits(DfpField.FLAG_INVALID);
+            final Dfp result = newInstance(getZero());
+            result.nans = QNAN;
+            return dotrap(DfpField.FLAG_INVALID, trapName, x, result);
+        }
+
+        boolean up = false;
+        Dfp result;
+        Dfp inc;
+
+        // if this is greater than x
+        if (this.lessThan(x)) {
+            up = true;
+        }
+
+        if (equals(x)) {
+            return newInstance(x);
+        }
+
+        if (lessThan(getZero())) {
+            up = !up;
+        }
+
+        if (up) {
+            inc = power10(intLog10() - getDecimalDigits() + 1);
+            inc = copysign(inc, this);
+
+            if (this.equals(getZero())) {
+                inc = power10K(MIN_EXP - mant.length - 1);
+            }
+
+            if (inc.equals(getZero())) {
+                result = copysign(newInstance(getZero()), this);
+            } else {
+                result = add(inc);
+            }
+        } else {
+            inc = power10(intLog10());
+            inc = copysign(inc, this);
+
+            if (this.equals(inc)) {
+                inc = inc.divide(power10(getDecimalDigits()));
+            } else {
+                inc = inc.divide(power10(getDecimalDigits() - 1));
+            }
+
+            if (this.equals(getZero())) {
+                inc = power10K(MIN_EXP - mant.length - 1);
+            }
+
+            if (inc.equals(getZero())) {
+                result = copysign(newInstance(getZero()), this);
+            } else {
+                result = subtract(inc);
+            }
+        }
+
+        if (result.classify() == INFINITE && this.classify() != INFINITE) {
+            getField().setIEEEFlagsBits(DfpField.FLAG_INEXACT);
+            result = dotrap(DfpField.FLAG_INEXACT, trapName, x, result);
+        }
+
+        if (result.equals(getZero()) && this.equals(getZero()) == false) {
+            getField().setIEEEFlagsBits(DfpField.FLAG_INEXACT);
+            result = dotrap(DfpField.FLAG_INEXACT, trapName, x, result);
+        }
+
+        return result;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/dfp/DfpField.java b/src/main/java/org/apache/commons/math3/dfp/DfpField.java
new file mode 100644
index 0000000..bd5a7eb
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/dfp/DfpField.java
@@ -0,0 +1,826 @@
+/*
+ * 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.math3.dfp;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.FieldElement;
+
+/**
+ * Field for Decimal floating point instances.
+ *
+ * @since 2.2
+ */
+public class DfpField implements Field<Dfp> {
+
+    /** Enumerate for rounding modes. */
+    public enum RoundingMode {
+
+        /** Rounds toward zero (truncation). */
+        ROUND_DOWN,
+
+        /** Rounds away from zero if discarded digit is non-zero. */
+        ROUND_UP,
+
+        /**
+         * Rounds towards nearest unless both are equidistant in which case it rounds away from
+         * zero.
+         */
+        ROUND_HALF_UP,
+
+        /**
+         * Rounds towards nearest unless both are equidistant in which case it rounds toward zero.
+         */
+        ROUND_HALF_DOWN,
+
+        /**
+         * Rounds towards nearest unless both are equidistant in which case it rounds toward the
+         * even neighbor. This is the default as specified by IEEE 854-1987
+         */
+        ROUND_HALF_EVEN,
+
+        /**
+         * Rounds towards nearest unless both are equidistant in which case it rounds toward the odd
+         * neighbor.
+         */
+        ROUND_HALF_ODD,
+
+        /** Rounds towards positive infinity. */
+        ROUND_CEIL,
+
+        /** Rounds towards negative infinity. */
+        ROUND_FLOOR;
+    }
+
+    /** IEEE 854-1987 flag for invalid operation. */
+    public static final int FLAG_INVALID = 1;
+
+    /** IEEE 854-1987 flag for division by zero. */
+    public static final int FLAG_DIV_ZERO = 2;
+
+    /** IEEE 854-1987 flag for overflow. */
+    public static final int FLAG_OVERFLOW = 4;
+
+    /** IEEE 854-1987 flag for underflow. */
+    public static final int FLAG_UNDERFLOW = 8;
+
+    /** IEEE 854-1987 flag for inexact result. */
+    public static final int FLAG_INEXACT = 16;
+
+    /** High precision string representation of &radic;2. */
+    private static String sqr2String;
+
+    // Note: the static strings are set up (once) by the ctor and @GuardedBy("DfpField.class")
+
+    /** High precision string representation of &radic;2 / 2. */
+    private static String sqr2ReciprocalString;
+
+    /** High precision string representation of &radic;3. */
+    private static String sqr3String;
+
+    /** High precision string representation of &radic;3 / 3. */
+    private static String sqr3ReciprocalString;
+
+    /** High precision string representation of &pi;. */
+    private static String piString;
+
+    /** High precision string representation of e. */
+    private static String eString;
+
+    /** High precision string representation of ln(2). */
+    private static String ln2String;
+
+    /** High precision string representation of ln(5). */
+    private static String ln5String;
+
+    /** High precision string representation of ln(10). */
+    private static String ln10String;
+
+    /**
+     * The number of radix digits. Note these depend on the radix which is 10000 digits, so each one
+     * is equivalent to 4 decimal digits.
+     */
+    private final int radixDigits;
+
+    /** A {@link Dfp} with value 0. */
+    private final Dfp zero;
+
+    /** A {@link Dfp} with value 1. */
+    private final Dfp one;
+
+    /** A {@link Dfp} with value 2. */
+    private final Dfp two;
+
+    /** A {@link Dfp} with value &radic;2. */
+    private final Dfp sqr2;
+
+    /** A two elements {@link Dfp} array with value &radic;2 split in two pieces. */
+    private final Dfp[] sqr2Split;
+
+    /** A {@link Dfp} with value &radic;2 / 2. */
+    private final Dfp sqr2Reciprocal;
+
+    /** A {@link Dfp} with value &radic;3. */
+    private final Dfp sqr3;
+
+    /** A {@link Dfp} with value &radic;3 / 3. */
+    private final Dfp sqr3Reciprocal;
+
+    /** A {@link Dfp} with value &pi;. */
+    private final Dfp pi;
+
+    /** A two elements {@link Dfp} array with value &pi; split in two pieces. */
+    private final Dfp[] piSplit;
+
+    /** A {@link Dfp} with value e. */
+    private final Dfp e;
+
+    /** A two elements {@link Dfp} array with value e split in two pieces. */
+    private final Dfp[] eSplit;
+
+    /** A {@link Dfp} with value ln(2). */
+    private final Dfp ln2;
+
+    /** A two elements {@link Dfp} array with value ln(2) split in two pieces. */
+    private final Dfp[] ln2Split;
+
+    /** A {@link Dfp} with value ln(5). */
+    private final Dfp ln5;
+
+    /** A two elements {@link Dfp} array with value ln(5) split in two pieces. */
+    private final Dfp[] ln5Split;
+
+    /** A {@link Dfp} with value ln(10). */
+    private final Dfp ln10;
+
+    /** Current rounding mode. */
+    private RoundingMode rMode;
+
+    /** IEEE 854-1987 signals. */
+    private int ieeeFlags;
+
+    /**
+     * Create a factory for the specified number of radix digits.
+     *
+     * <p>Note that since the {@link Dfp} class uses 10000 as its radix, each radix digit is
+     * equivalent to 4 decimal digits. This implies that asking for 13, 14, 15 or 16 decimal digits
+     * will really lead to a 4 radix 10000 digits in all cases.
+     *
+     * @param decimalDigits minimal number of decimal digits.
+     */
+    public DfpField(final int decimalDigits) {
+        this(decimalDigits, true);
+    }
+
+    /**
+     * Create a factory for the specified number of radix digits.
+     *
+     * <p>Note that since the {@link Dfp} class uses 10000 as its radix, each radix digit is
+     * equivalent to 4 decimal digits. This implies that asking for 13, 14, 15 or 16 decimal digits
+     * will really lead to a 4 radix 10000 digits in all cases.
+     *
+     * @param decimalDigits minimal number of decimal digits
+     * @param computeConstants if true, the transcendental constants for the given precision must be
+     *     computed (setting this flag to false is RESERVED for the internal recursive call)
+     */
+    private DfpField(final int decimalDigits, final boolean computeConstants) {
+
+        this.radixDigits = (decimalDigits < 13) ? 4 : (decimalDigits + 3) / 4;
+        this.rMode = RoundingMode.ROUND_HALF_EVEN;
+        this.ieeeFlags = 0;
+        this.zero = new Dfp(this, 0);
+        this.one = new Dfp(this, 1);
+        this.two = new Dfp(this, 2);
+
+        if (computeConstants) {
+            // set up transcendental constants
+            synchronized (DfpField.class) {
+
+                // as a heuristic to circumvent Table-Maker's Dilemma, we set the string
+                // representation of the constants to be at least 3 times larger than the
+                // number of decimal digits, also as an attempt to really compute these
+                // constants only once, we set a minimum number of digits
+                computeStringConstants((decimalDigits < 67) ? 200 : (3 * decimalDigits));
+
+                // set up the constants at current field accuracy
+                sqr2 = new Dfp(this, sqr2String);
+                sqr2Split = split(sqr2String);
+                sqr2Reciprocal = new Dfp(this, sqr2ReciprocalString);
+                sqr3 = new Dfp(this, sqr3String);
+                sqr3Reciprocal = new Dfp(this, sqr3ReciprocalString);
+                pi = new Dfp(this, piString);
+                piSplit = split(piString);
+                e = new Dfp(this, eString);
+                eSplit = split(eString);
+                ln2 = new Dfp(this, ln2String);
+                ln2Split = split(ln2String);
+                ln5 = new Dfp(this, ln5String);
+                ln5Split = split(ln5String);
+                ln10 = new Dfp(this, ln10String);
+            }
+        } else {
+            // dummy settings for unused constants
+            sqr2 = null;
+            sqr2Split = null;
+            sqr2Reciprocal = null;
+            sqr3 = null;
+            sqr3Reciprocal = null;
+            pi = null;
+            piSplit = null;
+            e = null;
+            eSplit = null;
+            ln2 = null;
+            ln2Split = null;
+            ln5 = null;
+            ln5Split = null;
+            ln10 = null;
+        }
+    }
+
+    /**
+     * Get the number of radix digits of the {@link Dfp} instances built by this factory.
+     *
+     * @return number of radix digits
+     */
+    public int getRadixDigits() {
+        return radixDigits;
+    }
+
+    /**
+     * Set the rounding mode. If not set, the default value is {@link RoundingMode#ROUND_HALF_EVEN}.
+     *
+     * @param mode desired rounding mode Note that the rounding mode is common to all {@link Dfp}
+     *     instances belonging to the current {@link DfpField} in the system and will affect all
+     *     future calculations.
+     */
+    public void setRoundingMode(final RoundingMode mode) {
+        rMode = mode;
+    }
+
+    /**
+     * Get the current rounding mode.
+     *
+     * @return current rounding mode
+     */
+    public RoundingMode getRoundingMode() {
+        return rMode;
+    }
+
+    /**
+     * Get the IEEE 854 status flags.
+     *
+     * @return IEEE 854 status flags
+     * @see #clearIEEEFlags()
+     * @see #setIEEEFlags(int)
+     * @see #setIEEEFlagsBits(int)
+     * @see #FLAG_INVALID
+     * @see #FLAG_DIV_ZERO
+     * @see #FLAG_OVERFLOW
+     * @see #FLAG_UNDERFLOW
+     * @see #FLAG_INEXACT
+     */
+    public int getIEEEFlags() {
+        return ieeeFlags;
+    }
+
+    /**
+     * Clears the IEEE 854 status flags.
+     *
+     * @see #getIEEEFlags()
+     * @see #setIEEEFlags(int)
+     * @see #setIEEEFlagsBits(int)
+     * @see #FLAG_INVALID
+     * @see #FLAG_DIV_ZERO
+     * @see #FLAG_OVERFLOW
+     * @see #FLAG_UNDERFLOW
+     * @see #FLAG_INEXACT
+     */
+    public void clearIEEEFlags() {
+        ieeeFlags = 0;
+    }
+
+    /**
+     * Sets the IEEE 854 status flags.
+     *
+     * @param flags desired value for the flags
+     * @see #getIEEEFlags()
+     * @see #clearIEEEFlags()
+     * @see #setIEEEFlagsBits(int)
+     * @see #FLAG_INVALID
+     * @see #FLAG_DIV_ZERO
+     * @see #FLAG_OVERFLOW
+     * @see #FLAG_UNDERFLOW
+     * @see #FLAG_INEXACT
+     */
+    public void setIEEEFlags(final int flags) {
+        ieeeFlags =
+                flags
+                        & (FLAG_INVALID
+                                | FLAG_DIV_ZERO
+                                | FLAG_OVERFLOW
+                                | FLAG_UNDERFLOW
+                                | FLAG_INEXACT);
+    }
+
+    /**
+     * Sets some bits in the IEEE 854 status flags, without changing the already set bits.
+     *
+     * <p>Calling this method is equivalent to call {@code setIEEEFlags(getIEEEFlags() | bits)}
+     *
+     * @param bits bits to set
+     * @see #getIEEEFlags()
+     * @see #clearIEEEFlags()
+     * @see #setIEEEFlags(int)
+     * @see #FLAG_INVALID
+     * @see #FLAG_DIV_ZERO
+     * @see #FLAG_OVERFLOW
+     * @see #FLAG_UNDERFLOW
+     * @see #FLAG_INEXACT
+     */
+    public void setIEEEFlagsBits(final int bits) {
+        ieeeFlags |=
+                bits
+                        & (FLAG_INVALID
+                                | FLAG_DIV_ZERO
+                                | FLAG_OVERFLOW
+                                | FLAG_UNDERFLOW
+                                | FLAG_INEXACT);
+    }
+
+    /**
+     * Makes a {@link Dfp} with a value of 0.
+     *
+     * @return a new {@link Dfp} with a value of 0
+     */
+    public Dfp newDfp() {
+        return new Dfp(this);
+    }
+
+    /**
+     * Create an instance from a byte value.
+     *
+     * @param x value to convert to an instance
+     * @return a new {@link Dfp} with the same value as x
+     */
+    public Dfp newDfp(final byte x) {
+        return new Dfp(this, x);
+    }
+
+    /**
+     * Create an instance from an int value.
+     *
+     * @param x value to convert to an instance
+     * @return a new {@link Dfp} with the same value as x
+     */
+    public Dfp newDfp(final int x) {
+        return new Dfp(this, x);
+    }
+
+    /**
+     * Create an instance from a long value.
+     *
+     * @param x value to convert to an instance
+     * @return a new {@link Dfp} with the same value as x
+     */
+    public Dfp newDfp(final long x) {
+        return new Dfp(this, x);
+    }
+
+    /**
+     * Create an instance from a double value.
+     *
+     * @param x value to convert to an instance
+     * @return a new {@link Dfp} with the same value as x
+     */
+    public Dfp newDfp(final double x) {
+        return new Dfp(this, x);
+    }
+
+    /**
+     * Copy constructor.
+     *
+     * @param d instance to copy
+     * @return a new {@link Dfp} with the same value as d
+     */
+    public Dfp newDfp(Dfp d) {
+        return new Dfp(d);
+    }
+
+    /**
+     * Create a {@link Dfp} given a String representation.
+     *
+     * @param s string representation of the instance
+     * @return a new {@link Dfp} parsed from specified string
+     */
+    public Dfp newDfp(final String s) {
+        return new Dfp(this, s);
+    }
+
+    /**
+     * Creates a {@link Dfp} with a non-finite value.
+     *
+     * @param sign sign of the Dfp to create
+     * @param nans code of the value, must be one of {@link Dfp#INFINITE}, {@link Dfp#SNAN}, {@link
+     *     Dfp#QNAN}
+     * @return a new {@link Dfp} with a non-finite value
+     */
+    public Dfp newDfp(final byte sign, final byte nans) {
+        return new Dfp(this, sign, nans);
+    }
+
+    /**
+     * Get the constant 0.
+     *
+     * @return a {@link Dfp} with value 0
+     */
+    public Dfp getZero() {
+        return zero;
+    }
+
+    /**
+     * Get the constant 1.
+     *
+     * @return a {@link Dfp} with value 1
+     */
+    public Dfp getOne() {
+        return one;
+    }
+
+    /** {@inheritDoc} */
+    public Class<? extends FieldElement<Dfp>> getRuntimeClass() {
+        return Dfp.class;
+    }
+
+    /**
+     * Get the constant 2.
+     *
+     * @return a {@link Dfp} with value 2
+     */
+    public Dfp getTwo() {
+        return two;
+    }
+
+    /**
+     * Get the constant &radic;2.
+     *
+     * @return a {@link Dfp} with value &radic;2
+     */
+    public Dfp getSqr2() {
+        return sqr2;
+    }
+
+    /**
+     * Get the constant &radic;2 split in two pieces.
+     *
+     * @return a {@link Dfp} with value &radic;2 split in two pieces
+     */
+    public Dfp[] getSqr2Split() {
+        return sqr2Split.clone();
+    }
+
+    /**
+     * Get the constant &radic;2 / 2.
+     *
+     * @return a {@link Dfp} with value &radic;2 / 2
+     */
+    public Dfp getSqr2Reciprocal() {
+        return sqr2Reciprocal;
+    }
+
+    /**
+     * Get the constant &radic;3.
+     *
+     * @return a {@link Dfp} with value &radic;3
+     */
+    public Dfp getSqr3() {
+        return sqr3;
+    }
+
+    /**
+     * Get the constant &radic;3 / 3.
+     *
+     * @return a {@link Dfp} with value &radic;3 / 3
+     */
+    public Dfp getSqr3Reciprocal() {
+        return sqr3Reciprocal;
+    }
+
+    /**
+     * Get the constant &pi;.
+     *
+     * @return a {@link Dfp} with value &pi;
+     */
+    public Dfp getPi() {
+        return pi;
+    }
+
+    /**
+     * Get the constant &pi; split in two pieces.
+     *
+     * @return a {@link Dfp} with value &pi; split in two pieces
+     */
+    public Dfp[] getPiSplit() {
+        return piSplit.clone();
+    }
+
+    /**
+     * Get the constant e.
+     *
+     * @return a {@link Dfp} with value e
+     */
+    public Dfp getE() {
+        return e;
+    }
+
+    /**
+     * Get the constant e split in two pieces.
+     *
+     * @return a {@link Dfp} with value e split in two pieces
+     */
+    public Dfp[] getESplit() {
+        return eSplit.clone();
+    }
+
+    /**
+     * Get the constant ln(2).
+     *
+     * @return a {@link Dfp} with value ln(2)
+     */
+    public Dfp getLn2() {
+        return ln2;
+    }
+
+    /**
+     * Get the constant ln(2) split in two pieces.
+     *
+     * @return a {@link Dfp} with value ln(2) split in two pieces
+     */
+    public Dfp[] getLn2Split() {
+        return ln2Split.clone();
+    }
+
+    /**
+     * Get the constant ln(5).
+     *
+     * @return a {@link Dfp} with value ln(5)
+     */
+    public Dfp getLn5() {
+        return ln5;
+    }
+
+    /**
+     * Get the constant ln(5) split in two pieces.
+     *
+     * @return a {@link Dfp} with value ln(5) split in two pieces
+     */
+    public Dfp[] getLn5Split() {
+        return ln5Split.clone();
+    }
+
+    /**
+     * Get the constant ln(10).
+     *
+     * @return a {@link Dfp} with value ln(10)
+     */
+    public Dfp getLn10() {
+        return ln10;
+    }
+
+    /**
+     * Breaks a string representation up into two {@link Dfp}'s. The split is such that the sum of
+     * them is equivalent to the input string, but has higher precision than using a single Dfp.
+     *
+     * @param a string representation of the number to split
+     * @return an array of two {@link Dfp Dfp} instances which sum equals a
+     */
+    private Dfp[] split(final String a) {
+        Dfp result[] = new Dfp[2];
+        boolean leading = true;
+        int sp = 0;
+        int sig = 0;
+
+        char[] buf = new char[a.length()];
+
+        for (int i = 0; i < buf.length; i++) {
+            buf[i] = a.charAt(i);
+
+            if (buf[i] >= '1' && buf[i] <= '9') {
+                leading = false;
+            }
+
+            if (buf[i] == '.') {
+                sig += (400 - sig) % 4;
+                leading = false;
+            }
+
+            if (sig == (radixDigits / 2) * 4) {
+                sp = i;
+                break;
+            }
+
+            if (buf[i] >= '0' && buf[i] <= '9' && !leading) {
+                sig++;
+            }
+        }
+
+        result[0] = new Dfp(this, new String(buf, 0, sp));
+
+        for (int i = 0; i < buf.length; i++) {
+            buf[i] = a.charAt(i);
+            if (buf[i] >= '0' && buf[i] <= '9' && i < sp) {
+                buf[i] = '0';
+            }
+        }
+
+        result[1] = new Dfp(this, new String(buf));
+
+        return result;
+    }
+
+    /**
+     * Recompute the high precision string constants.
+     *
+     * @param highPrecisionDecimalDigits precision at which the string constants mus be computed
+     */
+    private static void computeStringConstants(final int highPrecisionDecimalDigits) {
+        if (sqr2String == null || sqr2String.length() < highPrecisionDecimalDigits - 3) {
+
+            // recompute the string representation of the transcendental constants
+            final DfpField highPrecisionField = new DfpField(highPrecisionDecimalDigits, false);
+            final Dfp highPrecisionOne = new Dfp(highPrecisionField, 1);
+            final Dfp highPrecisionTwo = new Dfp(highPrecisionField, 2);
+            final Dfp highPrecisionThree = new Dfp(highPrecisionField, 3);
+
+            final Dfp highPrecisionSqr2 = highPrecisionTwo.sqrt();
+            sqr2String = highPrecisionSqr2.toString();
+            sqr2ReciprocalString = highPrecisionOne.divide(highPrecisionSqr2).toString();
+
+            final Dfp highPrecisionSqr3 = highPrecisionThree.sqrt();
+            sqr3String = highPrecisionSqr3.toString();
+            sqr3ReciprocalString = highPrecisionOne.divide(highPrecisionSqr3).toString();
+
+            piString = computePi(highPrecisionOne, highPrecisionTwo, highPrecisionThree).toString();
+            eString = computeExp(highPrecisionOne, highPrecisionOne).toString();
+            ln2String = computeLn(highPrecisionTwo, highPrecisionOne, highPrecisionTwo).toString();
+            ln5String =
+                    computeLn(new Dfp(highPrecisionField, 5), highPrecisionOne, highPrecisionTwo)
+                            .toString();
+            ln10String =
+                    computeLn(new Dfp(highPrecisionField, 10), highPrecisionOne, highPrecisionTwo)
+                            .toString();
+        }
+    }
+
+    /**
+     * Compute &pi; using Jonathan and Peter Borwein quartic formula.
+     *
+     * @param one constant with value 1 at desired precision
+     * @param two constant with value 2 at desired precision
+     * @param three constant with value 3 at desired precision
+     * @return &pi;
+     */
+    private static Dfp computePi(final Dfp one, final Dfp two, final Dfp three) {
+
+        Dfp sqrt2 = two.sqrt();
+        Dfp yk = sqrt2.subtract(one);
+        Dfp four = two.add(two);
+        Dfp two2kp3 = two;
+        Dfp ak = two.multiply(three.subtract(two.multiply(sqrt2)));
+
+        // The formula converges quartically. This means the number of correct
+        // digits is multiplied by 4 at each iteration! Five iterations are
+        // sufficient for about 160 digits, eight iterations give about
+        // 10000 digits (this has been checked) and 20 iterations more than
+        // 160 billions of digits (this has NOT been checked).
+        // So the limit here is considered sufficient for most purposes ...
+        for (int i = 1; i < 20; i++) {
+            final Dfp ykM1 = yk;
+
+            final Dfp y2 = yk.multiply(yk);
+            final Dfp oneMinusY4 = one.subtract(y2.multiply(y2));
+            final Dfp s = oneMinusY4.sqrt().sqrt();
+            yk = one.subtract(s).divide(one.add(s));
+
+            two2kp3 = two2kp3.multiply(four);
+
+            final Dfp p = one.add(yk);
+            final Dfp p2 = p.multiply(p);
+            ak =
+                    ak.multiply(p2.multiply(p2))
+                            .subtract(
+                                    two2kp3.multiply(yk)
+                                            .multiply(one.add(yk).add(yk.multiply(yk))));
+
+            if (yk.equals(ykM1)) {
+                break;
+            }
+        }
+
+        return one.divide(ak);
+    }
+
+    /**
+     * Compute exp(a).
+     *
+     * @param a number for which we want the exponential
+     * @param one constant with value 1 at desired precision
+     * @return exp(a)
+     */
+    public static Dfp computeExp(final Dfp a, final Dfp one) {
+
+        Dfp y = new Dfp(one);
+        Dfp py = new Dfp(one);
+        Dfp f = new Dfp(one);
+        Dfp fi = new Dfp(one);
+        Dfp x = new Dfp(one);
+
+        for (int i = 0; i < 10000; i++) {
+            x = x.multiply(a);
+            y = y.add(x.divide(f));
+            fi = fi.add(one);
+            f = f.multiply(fi);
+            if (y.equals(py)) {
+                break;
+            }
+            py = new Dfp(y);
+        }
+
+        return y;
+    }
+
+    /**
+     * Compute ln(a).
+     *
+     * <p>Let f(x) = ln(x),
+     *
+     * <p>We know that f'(x) = 1/x, thus from Taylor's theorem we have:
+     *
+     * <p>----- n+1 n f(x) = \ (-1) (x - 1) / ---------------- for 1 <= n <= infinity ----- n
+     *
+     * <p>or 2 3 4 (x-1) (x-1) (x-1) ln(x) = (x-1) - ----- + ------ - ------ + ... 2 3 4
+     *
+     * <p>alternatively,
+     *
+     * <p>2 3 4 x x x ln(x+1) = x - - + - - - + ... 2 3 4
+     *
+     * <p>This series can be used to compute ln(x), but it converges too slowly.
+     *
+     * <p>If we substitute -x for x above, we get
+     *
+     * <p>2 3 4 x x x ln(1-x) = -x - - - - - - + ... 2 3 4
+     *
+     * <p>Note that all terms are now negative. Because the even powered ones absorbed the sign.
+     * Now, subtract the series above from the previous one to get ln(x+1) - ln(1-x). Note the even
+     * terms cancel out leaving only the odd ones
+     *
+     * <p>3 5 7 2x 2x 2x ln(x+1) - ln(x-1) = 2x + --- + --- + ---- + ... 3 5 7
+     *
+     * <p>By the property of logarithms that ln(a) - ln(b) = ln (a/b) we have:
+     *
+     * <p>3 5 7 x+1 / x x x \ ln ----- = 2 * | x + ---- + ---- + ---- + ... | x-1 \ 3 5 7 /
+     *
+     * <p>But now we want to find ln(a), so we need to find the value of x such that a =
+     * (x+1)/(x-1). This is easily solved to find that x = (a-1)/(a+1).
+     *
+     * @param a number for which we want the exponential
+     * @param one constant with value 1 at desired precision
+     * @param two constant with value 2 at desired precision
+     * @return ln(a)
+     */
+    public static Dfp computeLn(final Dfp a, final Dfp one, final Dfp two) {
+
+        int den = 1;
+        Dfp x = a.add(new Dfp(a.getField(), -1)).divide(a.add(one));
+
+        Dfp y = new Dfp(x);
+        Dfp num = new Dfp(x);
+        Dfp py = new Dfp(y);
+        for (int i = 0; i < 10000; i++) {
+            num = num.multiply(x);
+            num = num.multiply(x);
+            den += 2;
+            Dfp t = num.divide(den);
+            y = y.add(t);
+            if (y.equals(py)) {
+                break;
+            }
+            py = new Dfp(y);
+        }
+
+        return y.multiply(two);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/dfp/DfpMath.java b/src/main/java/org/apache/commons/math3/dfp/DfpMath.java
new file mode 100644
index 0000000..ba50d05
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/dfp/DfpMath.java
@@ -0,0 +1,970 @@
+/*
+ * 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.math3.dfp;
+
+/**
+ * Mathematical routines for use with {@link Dfp}. The constants are defined in {@link DfpField}
+ *
+ * @since 2.2
+ */
+public class DfpMath {
+
+    /** Name for traps triggered by pow. */
+    private static final String POW_TRAP = "pow";
+
+    /** Private Constructor. */
+    private DfpMath() {}
+
+    /**
+     * Breaks a string representation up into two dfp's.
+     *
+     * <p>The two dfp are such that the sum of them is equivalent to the input string, but has
+     * higher precision than using a single dfp. This is useful for improving accuracy of
+     * exponentiation and critical multiplies.
+     *
+     * @param field field to which the Dfp must belong
+     * @param a string representation to split
+     * @return an array of two {@link Dfp} which sum is a
+     */
+    protected static Dfp[] split(final DfpField field, final String a) {
+        Dfp result[] = new Dfp[2];
+        char[] buf;
+        boolean leading = true;
+        int sp = 0;
+        int sig = 0;
+
+        buf = new char[a.length()];
+
+        for (int i = 0; i < buf.length; i++) {
+            buf[i] = a.charAt(i);
+
+            if (buf[i] >= '1' && buf[i] <= '9') {
+                leading = false;
+            }
+
+            if (buf[i] == '.') {
+                sig += (400 - sig) % 4;
+                leading = false;
+            }
+
+            if (sig == (field.getRadixDigits() / 2) * 4) {
+                sp = i;
+                break;
+            }
+
+            if (buf[i] >= '0' && buf[i] <= '9' && !leading) {
+                sig++;
+            }
+        }
+
+        result[0] = field.newDfp(new String(buf, 0, sp));
+
+        for (int i = 0; i < buf.length; i++) {
+            buf[i] = a.charAt(i);
+            if (buf[i] >= '0' && buf[i] <= '9' && i < sp) {
+                buf[i] = '0';
+            }
+        }
+
+        result[1] = field.newDfp(new String(buf));
+
+        return result;
+    }
+
+    /**
+     * Splits a {@link Dfp} into 2 {@link Dfp}'s such that their sum is equal to the input {@link
+     * Dfp}.
+     *
+     * @param a number to split
+     * @return two elements array containing the split number
+     */
+    protected static Dfp[] split(final Dfp a) {
+        final Dfp[] result = new Dfp[2];
+        final Dfp shift = a.multiply(a.power10K(a.getRadixDigits() / 2));
+        result[0] = a.add(shift).subtract(shift);
+        result[1] = a.subtract(result[0]);
+        return result;
+    }
+
+    /**
+     * Multiply two numbers that are split in to two pieces that are meant to be added together. Use
+     * binomial multiplication so ab = a0 b0 + a0 b1 + a1 b0 + a1 b1 Store the first term in
+     * result0, the rest in result1
+     *
+     * @param a first factor of the multiplication, in split form
+     * @param b second factor of the multiplication, in split form
+     * @return a &times; b, in split form
+     */
+    protected static Dfp[] splitMult(final Dfp[] a, final Dfp[] b) {
+        final Dfp[] result = new Dfp[2];
+
+        result[1] = a[0].getZero();
+        result[0] = a[0].multiply(b[0]);
+
+        /* If result[0] is infinite or zero, don't compute result[1].
+         * Attempting to do so may produce NaNs.
+         */
+
+        if (result[0].classify() == Dfp.INFINITE || result[0].equals(result[1])) {
+            return result;
+        }
+
+        result[1] = a[0].multiply(b[1]).add(a[1].multiply(b[0])).add(a[1].multiply(b[1]));
+
+        return result;
+    }
+
+    /**
+     * Divide two numbers that are split in to two pieces that are meant to be added together.
+     * Inverse of split multiply above: (a+b) / (c+d) = (a/c) + ( (bc-ad)/(c**2+cd) )
+     *
+     * @param a dividend, in split form
+     * @param b divisor, in split form
+     * @return a / b, in split form
+     */
+    protected static Dfp[] splitDiv(final Dfp[] a, final Dfp[] b) {
+        final Dfp[] result;
+
+        result = new Dfp[2];
+
+        result[0] = a[0].divide(b[0]);
+        result[1] = a[1].multiply(b[0]).subtract(a[0].multiply(b[1]));
+        result[1] = result[1].divide(b[0].multiply(b[0]).add(b[0].multiply(b[1])));
+
+        return result;
+    }
+
+    /**
+     * Raise a split base to the a power.
+     *
+     * @param base number to raise
+     * @param a power
+     * @return base<sup>a</sup>
+     */
+    protected static Dfp splitPow(final Dfp[] base, int a) {
+        boolean invert = false;
+
+        Dfp[] r = new Dfp[2];
+
+        Dfp[] result = new Dfp[2];
+        result[0] = base[0].getOne();
+        result[1] = base[0].getZero();
+
+        if (a == 0) {
+            // Special case a = 0
+            return result[0].add(result[1]);
+        }
+
+        if (a < 0) {
+            // If a is less than zero
+            invert = true;
+            a = -a;
+        }
+
+        // Exponentiate by successive squaring
+        do {
+            r[0] = new Dfp(base[0]);
+            r[1] = new Dfp(base[1]);
+            int trial = 1;
+
+            int prevtrial;
+            while (true) {
+                prevtrial = trial;
+                trial *= 2;
+                if (trial > a) {
+                    break;
+                }
+                r = splitMult(r, r);
+            }
+
+            trial = prevtrial;
+
+            a -= trial;
+            result = splitMult(result, r);
+
+        } while (a >= 1);
+
+        result[0] = result[0].add(result[1]);
+
+        if (invert) {
+            result[0] = base[0].getOne().divide(result[0]);
+        }
+
+        return result[0];
+    }
+
+    /**
+     * Raises base to the power a by successive squaring.
+     *
+     * @param base number to raise
+     * @param a power
+     * @return base<sup>a</sup>
+     */
+    public static Dfp pow(Dfp base, int a) {
+        boolean invert = false;
+
+        Dfp result = base.getOne();
+
+        if (a == 0) {
+            // Special case
+            return result;
+        }
+
+        if (a < 0) {
+            invert = true;
+            a = -a;
+        }
+
+        // Exponentiate by successive squaring
+        do {
+            Dfp r = new Dfp(base);
+            Dfp prevr;
+            int trial = 1;
+            int prevtrial;
+
+            do {
+                prevr = new Dfp(r);
+                prevtrial = trial;
+                r = r.multiply(r);
+                trial *= 2;
+            } while (a > trial);
+
+            r = prevr;
+            trial = prevtrial;
+
+            a -= trial;
+            result = result.multiply(r);
+
+        } while (a >= 1);
+
+        if (invert) {
+            result = base.getOne().divide(result);
+        }
+
+        return base.newInstance(result);
+    }
+
+    /**
+     * Computes e to the given power. a is broken into two parts, such that a = n+m where n is an
+     * integer. We use pow() to compute e<sup>n</sup> and a Taylor series to compute e<sup>m</sup>.
+     * We return e*<sup>n</sup> &times; e<sup>m</sup>
+     *
+     * @param a power at which e should be raised
+     * @return e<sup>a</sup>
+     */
+    public static Dfp exp(final Dfp a) {
+
+        final Dfp inta = a.rint();
+        final Dfp fraca = a.subtract(inta);
+
+        final int ia = inta.intValue();
+        if (ia > 2147483646) {
+            // return +Infinity
+            return a.newInstance((byte) 1, Dfp.INFINITE);
+        }
+
+        if (ia < -2147483646) {
+            // return 0;
+            return a.newInstance();
+        }
+
+        final Dfp einta = splitPow(a.getField().getESplit(), ia);
+        final Dfp efraca = expInternal(fraca);
+
+        return einta.multiply(efraca);
+    }
+
+    /**
+     * Computes e to the given power. Where -1 < a < 1. Use the classic Taylor series. 1 + x**2/2! +
+     * x**3/3! + x**4/4! ...
+     *
+     * @param a power at which e should be raised
+     * @return e<sup>a</sup>
+     */
+    protected static Dfp expInternal(final Dfp a) {
+        Dfp y = a.getOne();
+        Dfp x = a.getOne();
+        Dfp fact = a.getOne();
+        Dfp py = new Dfp(y);
+
+        for (int i = 1; i < 90; i++) {
+            x = x.multiply(a);
+            fact = fact.divide(i);
+            y = y.add(x.multiply(fact));
+            if (y.equals(py)) {
+                break;
+            }
+            py = new Dfp(y);
+        }
+
+        return y;
+    }
+
+    /**
+     * Returns the natural logarithm of a. a is first split into three parts such that a =
+     * (10000^h)(2^j)k. ln(a) is computed by ln(a) = ln(5)*h + ln(2)*(h+j) + ln(k) k is in the range
+     * 2/3 < k <4/3 and is passed on to a series expansion.
+     *
+     * @param a number from which logarithm is requested
+     * @return log(a)
+     */
+    public static Dfp log(Dfp a) {
+        int lr;
+        Dfp x;
+        int ix;
+        int p2 = 0;
+
+        // Check the arguments somewhat here
+        if (a.equals(a.getZero()) || a.lessThan(a.getZero()) || a.isNaN()) {
+            // negative, zero or NaN
+            a.getField().setIEEEFlagsBits(DfpField.FLAG_INVALID);
+            return a.dotrap(DfpField.FLAG_INVALID, "ln", a, a.newInstance((byte) 1, Dfp.QNAN));
+        }
+
+        if (a.classify() == Dfp.INFINITE) {
+            return a;
+        }
+
+        x = new Dfp(a);
+        lr = x.log10K();
+
+        x = x.divide(pow(a.newInstance(10000), lr)); /* This puts x in the range 0-10000 */
+        ix = x.floor().intValue();
+
+        while (ix > 2) {
+            ix >>= 1;
+            p2++;
+        }
+
+        Dfp[] spx = split(x);
+        Dfp[] spy = new Dfp[2];
+        spy[0] = pow(a.getTwo(), p2); // use spy[0] temporarily as a divisor
+        spx[0] = spx[0].divide(spy[0]);
+        spx[1] = spx[1].divide(spy[0]);
+
+        spy[0] = a.newInstance("1.33333"); // Use spy[0] for comparison
+        while (spx[0].add(spx[1]).greaterThan(spy[0])) {
+            spx[0] = spx[0].divide(2);
+            spx[1] = spx[1].divide(2);
+            p2++;
+        }
+
+        // X is now in the range of 2/3 < x < 4/3
+        Dfp[] spz = logInternal(spx);
+
+        spx[0] = a.newInstance(new StringBuilder().append(p2 + 4 * lr).toString());
+        spx[1] = a.getZero();
+        spy = splitMult(a.getField().getLn2Split(), spx);
+
+        spz[0] = spz[0].add(spy[0]);
+        spz[1] = spz[1].add(spy[1]);
+
+        spx[0] = a.newInstance(new StringBuilder().append(4 * lr).toString());
+        spx[1] = a.getZero();
+        spy = splitMult(a.getField().getLn5Split(), spx);
+
+        spz[0] = spz[0].add(spy[0]);
+        spz[1] = spz[1].add(spy[1]);
+
+        return a.newInstance(spz[0].add(spz[1]));
+    }
+
+    /**
+     * Computes the natural log of a number between 0 and 2. Let f(x) = ln(x),
+     *
+     * <p>We know that f'(x) = 1/x, thus from Taylor's theorum we have:
+     *
+     * <p>----- n+1 n f(x) = \ (-1) (x - 1) / ---------------- for 1 <= n <= infinity ----- n
+     *
+     * <p>or 2 3 4 (x-1) (x-1) (x-1) ln(x) = (x-1) - ----- + ------ - ------ + ... 2 3 4
+     *
+     * <p>alternatively,
+     *
+     * <p>2 3 4 x x x ln(x+1) = x - - + - - - + ... 2 3 4
+     *
+     * <p>This series can be used to compute ln(x), but it converges too slowly.
+     *
+     * <p>If we substitute -x for x above, we get
+     *
+     * <p>2 3 4 x x x ln(1-x) = -x - - - - - - + ... 2 3 4
+     *
+     * <p>Note that all terms are now negative. Because the even powered ones absorbed the sign.
+     * Now, subtract the series above from the previous one to get ln(x+1) - ln(1-x). Note the even
+     * terms cancel out leaving only the odd ones
+     *
+     * <p>3 5 7 2x 2x 2x ln(x+1) - ln(x-1) = 2x + --- + --- + ---- + ... 3 5 7
+     *
+     * <p>By the property of logarithms that ln(a) - ln(b) = ln (a/b) we have:
+     *
+     * <p>3 5 7 x+1 / x x x \ ln ----- = 2 * | x + ---- + ---- + ---- + ... | x-1 \ 3 5 7 /
+     *
+     * <p>But now we want to find ln(a), so we need to find the value of x such that a =
+     * (x+1)/(x-1). This is easily solved to find that x = (a-1)/(a+1).
+     *
+     * @param a number from which logarithm is requested, in split form
+     * @return log(a)
+     */
+    protected static Dfp[] logInternal(final Dfp a[]) {
+
+        /* Now we want to compute x = (a-1)/(a+1) but this is prone to
+         * loss of precision.  So instead, compute x = (a/4 - 1/4) / (a/4 + 1/4)
+         */
+        Dfp t = a[0].divide(4).add(a[1].divide(4));
+        Dfp x = t.add(a[0].newInstance("-0.25")).divide(t.add(a[0].newInstance("0.25")));
+
+        Dfp y = new Dfp(x);
+        Dfp num = new Dfp(x);
+        Dfp py = new Dfp(y);
+        int den = 1;
+        for (int i = 0; i < 10000; i++) {
+            num = num.multiply(x);
+            num = num.multiply(x);
+            den += 2;
+            t = num.divide(den);
+            y = y.add(t);
+            if (y.equals(py)) {
+                break;
+            }
+            py = new Dfp(y);
+        }
+
+        y = y.multiply(a[0].getTwo());
+
+        return split(y);
+    }
+
+    /**
+     * Computes x to the y power.
+     *
+     * <p>Uses the following method:
+     *
+     * <p>
+     *
+     * <ol>
+     *   <li>Set u = rint(y), v = y-u
+     *   <li>Compute a = v * ln(x)
+     *   <li>Compute b = rint( a/ln(2) )
+     *   <li>Compute c = a - b*ln(2)
+     *   <li>x<sup>y</sup> = x<sup>u</sup> * 2<sup>b</sup> * e<sup>c</sup>
+     * </ol>
+     *
+     * if |y| > 1e8, then we compute by exp(y*ln(x))
+     *
+     * <p><b>Special Cases</b>
+     *
+     * <p>
+     *
+     * <ul>
+     *   <li>if y is 0.0 or -0.0 then result is 1.0
+     *   <li>if y is 1.0 then result is x
+     *   <li>if y is NaN then result is NaN
+     *   <li>if x is NaN and y is not zero then result is NaN
+     *   <li>if |x| > 1.0 and y is +Infinity then result is +Infinity
+     *   <li>if |x| < 1.0 and y is -Infinity then result is +Infinity
+     *   <li>if |x| > 1.0 and y is -Infinity then result is +0
+     *   <li>if |x| < 1.0 and y is +Infinity then result is +0
+     *   <li>if |x| = 1.0 and y is +/-Infinity then result is NaN
+     *   <li>if x = +0 and y > 0 then result is +0
+     *   <li>if x = +Inf and y < 0 then result is +0
+     *   <li>if x = +0 and y < 0 then result is +Inf
+     *   <li>if x = +Inf and y > 0 then result is +Inf
+     *   <li>if x = -0 and y > 0, finite, not odd integer then result is +0
+     *   <li>if x = -0 and y < 0, finite, and odd integer then result is -Inf
+     *   <li>if x = -Inf and y > 0, finite, and odd integer then result is -Inf
+     *   <li>if x = -0 and y < 0, not finite odd integer then result is +Inf
+     *   <li>if x = -Inf and y > 0, not finite odd integer then result is +Inf
+     *   <li>if x < 0 and y > 0, finite, and odd integer then result is -(|x|<sup>y</sup>)
+     *   <li>if x < 0 and y > 0, finite, and not integer then result is NaN
+     * </ul>
+     *
+     * @param x base to be raised
+     * @param y power to which base should be raised
+     * @return x<sup>y</sup>
+     */
+    public static Dfp pow(Dfp x, final Dfp y) {
+
+        // make sure we don't mix number with different precision
+        if (x.getField().getRadixDigits() != y.getField().getRadixDigits()) {
+            x.getField().setIEEEFlagsBits(DfpField.FLAG_INVALID);
+            final Dfp result = x.newInstance(x.getZero());
+            result.nans = Dfp.QNAN;
+            return x.dotrap(DfpField.FLAG_INVALID, POW_TRAP, x, result);
+        }
+
+        final Dfp zero = x.getZero();
+        final Dfp one = x.getOne();
+        final Dfp two = x.getTwo();
+        boolean invert = false;
+        int ui;
+
+        /* Check for special cases */
+        if (y.equals(zero)) {
+            return x.newInstance(one);
+        }
+
+        if (y.equals(one)) {
+            if (x.isNaN()) {
+                // Test for NaNs
+                x.getField().setIEEEFlagsBits(DfpField.FLAG_INVALID);
+                return x.dotrap(DfpField.FLAG_INVALID, POW_TRAP, x, x);
+            }
+            return x;
+        }
+
+        if (x.isNaN() || y.isNaN()) {
+            // Test for NaNs
+            x.getField().setIEEEFlagsBits(DfpField.FLAG_INVALID);
+            return x.dotrap(DfpField.FLAG_INVALID, POW_TRAP, x, x.newInstance((byte) 1, Dfp.QNAN));
+        }
+
+        // X == 0
+        if (x.equals(zero)) {
+            if (Dfp.copysign(one, x).greaterThan(zero)) {
+                // X == +0
+                if (y.greaterThan(zero)) {
+                    return x.newInstance(zero);
+                } else {
+                    return x.newInstance(x.newInstance((byte) 1, Dfp.INFINITE));
+                }
+            } else {
+                // X == -0
+                if (y.classify() == Dfp.FINITE
+                        && y.rint().equals(y)
+                        && !y.remainder(two).equals(zero)) {
+                    // If y is odd integer
+                    if (y.greaterThan(zero)) {
+                        return x.newInstance(zero.negate());
+                    } else {
+                        return x.newInstance(x.newInstance((byte) -1, Dfp.INFINITE));
+                    }
+                } else {
+                    // Y is not odd integer
+                    if (y.greaterThan(zero)) {
+                        return x.newInstance(zero);
+                    } else {
+                        return x.newInstance(x.newInstance((byte) 1, Dfp.INFINITE));
+                    }
+                }
+            }
+        }
+
+        if (x.lessThan(zero)) {
+            // Make x positive, but keep track of it
+            x = x.negate();
+            invert = true;
+        }
+
+        if (x.greaterThan(one) && y.classify() == Dfp.INFINITE) {
+            if (y.greaterThan(zero)) {
+                return y;
+            } else {
+                return x.newInstance(zero);
+            }
+        }
+
+        if (x.lessThan(one) && y.classify() == Dfp.INFINITE) {
+            if (y.greaterThan(zero)) {
+                return x.newInstance(zero);
+            } else {
+                return x.newInstance(Dfp.copysign(y, one));
+            }
+        }
+
+        if (x.equals(one) && y.classify() == Dfp.INFINITE) {
+            x.getField().setIEEEFlagsBits(DfpField.FLAG_INVALID);
+            return x.dotrap(DfpField.FLAG_INVALID, POW_TRAP, x, x.newInstance((byte) 1, Dfp.QNAN));
+        }
+
+        if (x.classify() == Dfp.INFINITE) {
+            // x = +/- inf
+            if (invert) {
+                // negative infinity
+                if (y.classify() == Dfp.FINITE
+                        && y.rint().equals(y)
+                        && !y.remainder(two).equals(zero)) {
+                    // If y is odd integer
+                    if (y.greaterThan(zero)) {
+                        return x.newInstance(x.newInstance((byte) -1, Dfp.INFINITE));
+                    } else {
+                        return x.newInstance(zero.negate());
+                    }
+                } else {
+                    // Y is not odd integer
+                    if (y.greaterThan(zero)) {
+                        return x.newInstance(x.newInstance((byte) 1, Dfp.INFINITE));
+                    } else {
+                        return x.newInstance(zero);
+                    }
+                }
+            } else {
+                // positive infinity
+                if (y.greaterThan(zero)) {
+                    return x;
+                } else {
+                    return x.newInstance(zero);
+                }
+            }
+        }
+
+        if (invert && !y.rint().equals(y)) {
+            x.getField().setIEEEFlagsBits(DfpField.FLAG_INVALID);
+            return x.dotrap(DfpField.FLAG_INVALID, POW_TRAP, x, x.newInstance((byte) 1, Dfp.QNAN));
+        }
+
+        // End special cases
+
+        Dfp r;
+        if (y.lessThan(x.newInstance(100000000)) && y.greaterThan(x.newInstance(-100000000))) {
+            final Dfp u = y.rint();
+            ui = u.intValue();
+
+            final Dfp v = y.subtract(u);
+
+            if (v.unequal(zero)) {
+                final Dfp a = v.multiply(log(x));
+                final Dfp b = a.divide(x.getField().getLn2()).rint();
+
+                final Dfp c = a.subtract(b.multiply(x.getField().getLn2()));
+                r = splitPow(split(x), ui);
+                r = r.multiply(pow(two, b.intValue()));
+                r = r.multiply(exp(c));
+            } else {
+                r = splitPow(split(x), ui);
+            }
+        } else {
+            // very large exponent.  |y| > 1e8
+            r = exp(log(x).multiply(y));
+        }
+
+        if (invert && y.rint().equals(y) && !y.remainder(two).equals(zero)) {
+            // if y is odd integer
+            r = r.negate();
+        }
+
+        return x.newInstance(r);
+    }
+
+    /**
+     * Computes sin(a) Used when 0 < a < pi/4. Uses the classic Taylor series. x - x**3/3! + x**5/5!
+     * ...
+     *
+     * @param a number from which sine is desired, in split form
+     * @return sin(a)
+     */
+    protected static Dfp sinInternal(Dfp a[]) {
+
+        Dfp c = a[0].add(a[1]);
+        Dfp y = c;
+        c = c.multiply(c);
+        Dfp x = y;
+        Dfp fact = a[0].getOne();
+        Dfp py = new Dfp(y);
+
+        for (int i = 3; i < 90; i += 2) {
+            x = x.multiply(c);
+            x = x.negate();
+
+            fact = fact.divide((i - 1) * i); // 1 over fact
+            y = y.add(x.multiply(fact));
+            if (y.equals(py)) {
+                break;
+            }
+            py = new Dfp(y);
+        }
+
+        return y;
+    }
+
+    /**
+     * Computes cos(a) Used when 0 < a < pi/4. Uses the classic Taylor series for cosine. 1 -
+     * x**2/2! + x**4/4! ...
+     *
+     * @param a number from which cosine is desired, in split form
+     * @return cos(a)
+     */
+    protected static Dfp cosInternal(Dfp a[]) {
+        final Dfp one = a[0].getOne();
+
+        Dfp x = one;
+        Dfp y = one;
+        Dfp c = a[0].add(a[1]);
+        c = c.multiply(c);
+
+        Dfp fact = one;
+        Dfp py = new Dfp(y);
+
+        for (int i = 2; i < 90; i += 2) {
+            x = x.multiply(c);
+            x = x.negate();
+
+            fact = fact.divide((i - 1) * i); // 1 over fact
+
+            y = y.add(x.multiply(fact));
+            if (y.equals(py)) {
+                break;
+            }
+            py = new Dfp(y);
+        }
+
+        return y;
+    }
+
+    /**
+     * computes the sine of the argument.
+     *
+     * @param a number from which sine is desired
+     * @return sin(a)
+     */
+    public static Dfp sin(final Dfp a) {
+        final Dfp pi = a.getField().getPi();
+        final Dfp zero = a.getField().getZero();
+        boolean neg = false;
+
+        /* First reduce the argument to the range of +/- PI */
+        Dfp x = a.remainder(pi.multiply(2));
+
+        /* if x < 0 then apply identity sin(-x) = -sin(x) */
+        /* This puts x in the range 0 < x < PI            */
+        if (x.lessThan(zero)) {
+            x = x.negate();
+            neg = true;
+        }
+
+        /* Since sine(x) = sine(pi - x) we can reduce the range to
+         * 0 < x < pi/2
+         */
+
+        if (x.greaterThan(pi.divide(2))) {
+            x = pi.subtract(x);
+        }
+
+        Dfp y;
+        if (x.lessThan(pi.divide(4))) {
+            y = sinInternal(split(x));
+        } else {
+            final Dfp c[] = new Dfp[2];
+            final Dfp[] piSplit = a.getField().getPiSplit();
+            c[0] = piSplit[0].divide(2).subtract(x);
+            c[1] = piSplit[1].divide(2);
+            y = cosInternal(c);
+        }
+
+        if (neg) {
+            y = y.negate();
+        }
+
+        return a.newInstance(y);
+    }
+
+    /**
+     * computes the cosine of the argument.
+     *
+     * @param a number from which cosine is desired
+     * @return cos(a)
+     */
+    public static Dfp cos(Dfp a) {
+        final Dfp pi = a.getField().getPi();
+        final Dfp zero = a.getField().getZero();
+        boolean neg = false;
+
+        /* First reduce the argument to the range of +/- PI */
+        Dfp x = a.remainder(pi.multiply(2));
+
+        /* if x < 0 then apply identity cos(-x) = cos(x) */
+        /* This puts x in the range 0 < x < PI           */
+        if (x.lessThan(zero)) {
+            x = x.negate();
+        }
+
+        /* Since cos(x) = -cos(pi - x) we can reduce the range to
+         * 0 < x < pi/2
+         */
+
+        if (x.greaterThan(pi.divide(2))) {
+            x = pi.subtract(x);
+            neg = true;
+        }
+
+        Dfp y;
+        if (x.lessThan(pi.divide(4))) {
+            Dfp c[] = new Dfp[2];
+            c[0] = x;
+            c[1] = zero;
+
+            y = cosInternal(c);
+        } else {
+            final Dfp c[] = new Dfp[2];
+            final Dfp[] piSplit = a.getField().getPiSplit();
+            c[0] = piSplit[0].divide(2).subtract(x);
+            c[1] = piSplit[1].divide(2);
+            y = sinInternal(c);
+        }
+
+        if (neg) {
+            y = y.negate();
+        }
+
+        return a.newInstance(y);
+    }
+
+    /**
+     * computes the tangent of the argument.
+     *
+     * @param a number from which tangent is desired
+     * @return tan(a)
+     */
+    public static Dfp tan(final Dfp a) {
+        return sin(a).divide(cos(a));
+    }
+
+    /**
+     * computes the arc-tangent of the argument.
+     *
+     * @param a number from which arc-tangent is desired
+     * @return atan(a)
+     */
+    protected static Dfp atanInternal(final Dfp a) {
+
+        Dfp y = new Dfp(a);
+        Dfp x = new Dfp(y);
+        Dfp py = new Dfp(y);
+
+        for (int i = 3; i < 90; i += 2) {
+            x = x.multiply(a);
+            x = x.multiply(a);
+            x = x.negate();
+            y = y.add(x.divide(i));
+            if (y.equals(py)) {
+                break;
+            }
+            py = new Dfp(y);
+        }
+
+        return y;
+    }
+
+    /**
+     * computes the arc tangent of the argument
+     *
+     * <p>Uses the typical taylor series
+     *
+     * <p>but may reduce arguments using the following identity tan(x+y) = (tan(x) + tan(y)) / (1 -
+     * tan(x)*tan(y))
+     *
+     * <p>since tan(PI/8) = sqrt(2)-1,
+     *
+     * <p>atan(x) = atan( (x - sqrt(2) + 1) / (1+x*sqrt(2) - x) + PI/8.0
+     *
+     * @param a number from which arc-tangent is desired
+     * @return atan(a)
+     */
+    public static Dfp atan(final Dfp a) {
+        final Dfp zero = a.getField().getZero();
+        final Dfp one = a.getField().getOne();
+        final Dfp[] sqr2Split = a.getField().getSqr2Split();
+        final Dfp[] piSplit = a.getField().getPiSplit();
+        boolean recp = false;
+        boolean neg = false;
+        boolean sub = false;
+
+        final Dfp ty = sqr2Split[0].subtract(one).add(sqr2Split[1]);
+
+        Dfp x = new Dfp(a);
+        if (x.lessThan(zero)) {
+            neg = true;
+            x = x.negate();
+        }
+
+        if (x.greaterThan(one)) {
+            recp = true;
+            x = one.divide(x);
+        }
+
+        if (x.greaterThan(ty)) {
+            Dfp sty[] = new Dfp[2];
+            sub = true;
+
+            sty[0] = sqr2Split[0].subtract(one);
+            sty[1] = sqr2Split[1];
+
+            Dfp[] xs = split(x);
+
+            Dfp[] ds = splitMult(xs, sty);
+            ds[0] = ds[0].add(one);
+
+            xs[0] = xs[0].subtract(sty[0]);
+            xs[1] = xs[1].subtract(sty[1]);
+
+            xs = splitDiv(xs, ds);
+            x = xs[0].add(xs[1]);
+
+            // x = x.subtract(ty).divide(dfp.one.add(x.multiply(ty)));
+        }
+
+        Dfp y = atanInternal(x);
+
+        if (sub) {
+            y = y.add(piSplit[0].divide(8)).add(piSplit[1].divide(8));
+        }
+
+        if (recp) {
+            y = piSplit[0].divide(2).subtract(y).add(piSplit[1].divide(2));
+        }
+
+        if (neg) {
+            y = y.negate();
+        }
+
+        return a.newInstance(y);
+    }
+
+    /**
+     * computes the arc-sine of the argument.
+     *
+     * @param a number from which arc-sine is desired
+     * @return asin(a)
+     */
+    public static Dfp asin(final Dfp a) {
+        return atan(a.divide(a.getOne().subtract(a.multiply(a)).sqrt()));
+    }
+
+    /**
+     * computes the arc-cosine of the argument.
+     *
+     * @param a number from which arc-cosine is desired
+     * @return acos(a)
+     */
+    public static Dfp acos(Dfp a) {
+        Dfp result;
+        boolean negative = false;
+
+        if (a.lessThan(a.getZero())) {
+            negative = true;
+        }
+
+        a = Dfp.copysign(a, a.getOne()); // absolute value
+
+        result = atan(a.getOne().subtract(a.multiply(a)).sqrt().divide(a));
+
+        if (negative) {
+            result = a.getField().getPi().subtract(result);
+        }
+
+        return a.newInstance(result);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/dfp/UnivariateDfpFunction.java b/src/main/java/org/apache/commons/math3/dfp/UnivariateDfpFunction.java
new file mode 100644
index 0000000..4507b41
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/dfp/UnivariateDfpFunction.java
@@ -0,0 +1,40 @@
+/*
+ * 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.math3.dfp;
+
+/**
+ * An interface representing a univariate {@link Dfp} function.
+ *
+ * @deprecated as of 3.6, replaced with {@link
+ *     org.apache.commons.math3.analysis.RealFieldUnivariateFunction}
+ */
+@Deprecated
+public interface UnivariateDfpFunction {
+
+    /**
+     * Compute the value of the function.
+     *
+     * @param x Point at which the function value should be computed.
+     * @return the value.
+     * @throws IllegalArgumentException when the activated method itself can ascertain that
+     *     preconditions, specified in the API expressed at the level of the activated method, have
+     *     been violated. In the vast majority of cases where Commons-Math throws
+     *     IllegalArgumentException, it is the result of argument checking of actual parameters
+     *     immediately passed to a method.
+     */
+    Dfp value(Dfp x);
+}
diff --git a/src/main/java/org/apache/commons/math3/dfp/package-info.java b/src/main/java/org/apache/commons/math3/dfp/package-info.java
new file mode 100644
index 0000000..f4a7ca8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/dfp/package-info.java
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+/**
+ * Decimal floating point library for Java
+ *
+ * <p>Another floating point class. This one is built using radix 10000 which is 10<sup>4</sup>, so
+ * its almost decimal.
+ *
+ * <p>The design goals here are:
+ *
+ * <ol>
+ *   <li>Decimal math, or close to it
+ *   <li>Settable precision (but no mix between numbers using different settings)
+ *   <li>Portability. Code should be keep as portable as possible.
+ *   <li>Performance
+ *   <li>Accuracy - Results should always be +/- 1 ULP for basic algebraic operation
+ *   <li>Comply with IEEE 854-1987 as much as possible. (See IEEE 854-1987 notes below)
+ * </ol>
+ *
+ * <p>Trade offs:
+ *
+ * <ol>
+ *   <li>Memory foot print. I'm using more memory than necessary to represent numbers to get better
+ *       performance.
+ *   <li>Digits are bigger, so rounding is a greater loss. So, if you really need 12 decimal digits,
+ *       better use 4 base 10000 digits there can be one partially filled.
+ * </ol>
+ *
+ * <p>Numbers are represented in the following form:
+ *
+ * <pre>
+ * n  =  sign &times; mant &times; (radix)<sup>exp</sup>;</p>
+ * </pre>
+ *
+ * where sign is &plusmn;1, mantissa represents a fractional number between zero and one. mant[0] is
+ * the least significant digit. exp is in the range of -32767 to 32768
+ *
+ * <p>IEEE 854-1987 Notes and differences
+ *
+ * <p>IEEE 854 requires the radix to be either 2 or 10. The radix here is 10000, so that requirement
+ * is not met, but it is possible that a subclassed can be made to make it behave as a radix 10
+ * number. It is my opinion that if it looks and behaves as a radix 10 number then it is one and
+ * that requirement would be met.
+ *
+ * <p>The radix of 10000 was chosen because it should be faster to operate on 4 decimal digits at
+ * once instead of one at a time. Radix 10 behavior can be realized by add an additional rounding
+ * step to ensure that the number of decimal digits represented is constant.
+ *
+ * <p>The IEEE standard specifically leaves out internal data encoding, so it is reasonable to
+ * conclude that such a subclass of this radix 10000 system is merely an encoding of a radix 10
+ * system.
+ *
+ * <p>IEEE 854 also specifies the existence of "sub-normal" numbers. This class does not contain any
+ * such entities. The most significant radix 10000 digit is always non-zero. Instead, we support
+ * "gradual underflow" by raising the underflow flag for numbers less with exponent less than
+ * expMin, but don't flush to zero until the exponent reaches MIN_EXP-digits. Thus the smallest
+ * number we can represent would be: 1E(-(MIN_EXP-digits-1)&lowast;4), eg, for digits=5,
+ * MIN_EXP=-32767, that would be 1e-131092.
+ *
+ * <p>IEEE 854 defines that the implied radix point lies just to the right of the most significant
+ * digit and to the left of the remaining digits. This implementation puts the implied radix point
+ * to the left of all digits including the most significant one. The most significant digit here is
+ * the one just to the right of the radix point. This is a fine detail and is really only a matter
+ * of definition. Any side effects of this can be rendered invisible by a subclass.
+ */
+package org.apache.commons.math3.dfp;
diff --git a/src/main/java/org/apache/commons/math3/distribution/AbstractIntegerDistribution.java b/src/main/java/org/apache/commons/math3/distribution/AbstractIntegerDistribution.java
new file mode 100644
index 0000000..102700e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/AbstractIntegerDistribution.java
@@ -0,0 +1,250 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.MathInternalError;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.util.FastMath;
+
+import java.io.Serializable;
+
+/**
+ * Base class for integer-valued discrete distributions. Default implementations are provided for
+ * some of the methods that do not vary from distribution to distribution.
+ */
+public abstract class AbstractIntegerDistribution implements IntegerDistribution, Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -1146319659338487221L;
+
+    /**
+     * RandomData instance used to generate samples from the distribution.
+     *
+     * @deprecated As of 3.1, to be removed in 4.0. Please use the {@link #random} instance variable
+     *     instead.
+     */
+    @Deprecated
+    protected final org.apache.commons.math3.random.RandomDataImpl randomData =
+            new org.apache.commons.math3.random.RandomDataImpl();
+
+    /**
+     * RNG instance used to generate samples from the distribution.
+     *
+     * @since 3.1
+     */
+    protected final RandomGenerator random;
+
+    /**
+     * @deprecated As of 3.1, to be removed in 4.0. Please use {@link
+     *     #AbstractIntegerDistribution(RandomGenerator)} instead.
+     */
+    @Deprecated
+    protected AbstractIntegerDistribution() {
+        // Legacy users are only allowed to access the deprecated "randomData".
+        // New users are forbidden to use this constructor.
+        random = null;
+    }
+
+    /**
+     * @param rng Random number generator.
+     * @since 3.1
+     */
+    protected AbstractIntegerDistribution(RandomGenerator rng) {
+        random = rng;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The default implementation uses the identity
+     *
+     * <p>{@code P(x0 < X <= x1) = P(X <= x1) - P(X <= x0)}
+     */
+    public double cumulativeProbability(int x0, int x1) throws NumberIsTooLargeException {
+        if (x1 < x0) {
+            throw new NumberIsTooLargeException(
+                    LocalizedFormats.LOWER_ENDPOINT_ABOVE_UPPER_ENDPOINT, x0, x1, true);
+        }
+        return cumulativeProbability(x1) - cumulativeProbability(x0);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The default implementation returns
+     *
+     * <ul>
+     *   <li>{@link #getSupportLowerBound()} for {@code p = 0},
+     *   <li>{@link #getSupportUpperBound()} for {@code p = 1}, and
+     *   <li>{@link #solveInverseCumulativeProbability(double, int, int)} for {@code 0 < p < 1}.
+     * </ul>
+     */
+    public int inverseCumulativeProbability(final double p) throws OutOfRangeException {
+        if (p < 0.0 || p > 1.0) {
+            throw new OutOfRangeException(p, 0, 1);
+        }
+
+        int lower = getSupportLowerBound();
+        if (p == 0.0) {
+            return lower;
+        }
+        if (lower == Integer.MIN_VALUE) {
+            if (checkedCumulativeProbability(lower) >= p) {
+                return lower;
+            }
+        } else {
+            lower -= 1; // this ensures cumulativeProbability(lower) < p, which
+            // is important for the solving step
+        }
+
+        int upper = getSupportUpperBound();
+        if (p == 1.0) {
+            return upper;
+        }
+
+        // use the one-sided Chebyshev inequality to narrow the bracket
+        // cf. AbstractRealDistribution.inverseCumulativeProbability(double)
+        final double mu = getNumericalMean();
+        final double sigma = FastMath.sqrt(getNumericalVariance());
+        final boolean chebyshevApplies =
+                !(Double.isInfinite(mu)
+                        || Double.isNaN(mu)
+                        || Double.isInfinite(sigma)
+                        || Double.isNaN(sigma)
+                        || sigma == 0.0);
+        if (chebyshevApplies) {
+            double k = FastMath.sqrt((1.0 - p) / p);
+            double tmp = mu - k * sigma;
+            if (tmp > lower) {
+                lower = ((int) FastMath.ceil(tmp)) - 1;
+            }
+            k = 1.0 / k;
+            tmp = mu + k * sigma;
+            if (tmp < upper) {
+                upper = ((int) FastMath.ceil(tmp)) - 1;
+            }
+        }
+
+        return solveInverseCumulativeProbability(p, lower, upper);
+    }
+
+    /**
+     * This is a utility function used by {@link #inverseCumulativeProbability(double)}. It assumes
+     * {@code 0 < p < 1} and that the inverse cumulative probability lies in the bracket {@code
+     * (lower, upper]}. The implementation does simple bisection to find the smallest {@code
+     * p}-quantile <code>inf{x in Z | P(X<=x) >= p}</code>.
+     *
+     * @param p the cumulative probability
+     * @param lower a value satisfying {@code cumulativeProbability(lower) < p}
+     * @param upper a value satisfying {@code p <= cumulativeProbability(upper)}
+     * @return the smallest {@code p}-quantile of this distribution
+     */
+    protected int solveInverseCumulativeProbability(final double p, int lower, int upper) {
+        while (lower + 1 < upper) {
+            int xm = (lower + upper) / 2;
+            if (xm < lower || xm > upper) {
+                /*
+                 * Overflow.
+                 * There will never be an overflow in both calculation methods
+                 * for xm at the same time
+                 */
+                xm = lower + (upper - lower) / 2;
+            }
+
+            double pm = checkedCumulativeProbability(xm);
+            if (pm >= p) {
+                upper = xm;
+            } else {
+                lower = xm;
+            }
+        }
+        return upper;
+    }
+
+    /** {@inheritDoc} */
+    public void reseedRandomGenerator(long seed) {
+        random.setSeed(seed);
+        randomData.reSeed(seed);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The default implementation uses the <a
+     * href="http://en.wikipedia.org/wiki/Inverse_transform_sampling">inversion method</a>.
+     */
+    public int sample() {
+        return inverseCumulativeProbability(random.nextDouble());
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The default implementation generates the sample by calling {@link #sample()} in a loop.
+     */
+    public int[] sample(int sampleSize) {
+        if (sampleSize <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.NUMBER_OF_SAMPLES, sampleSize);
+        }
+        int[] out = new int[sampleSize];
+        for (int i = 0; i < sampleSize; i++) {
+            out[i] = sample();
+        }
+        return out;
+    }
+
+    /**
+     * Computes the cumulative probability function and checks for {@code NaN} values returned.
+     * Throws {@code MathInternalError} if the value is {@code NaN}. Rethrows any exception
+     * encountered evaluating the cumulative probability function. Throws {@code MathInternalError}
+     * if the cumulative probability function returns {@code NaN}.
+     *
+     * @param argument input value
+     * @return the cumulative probability
+     * @throws MathInternalError if the cumulative probability is {@code NaN}
+     */
+    private double checkedCumulativeProbability(int argument) throws MathInternalError {
+        double result = Double.NaN;
+        result = cumulativeProbability(argument);
+        if (Double.isNaN(result)) {
+            throw new MathInternalError(
+                    LocalizedFormats.DISCRETE_CUMULATIVE_PROBABILITY_RETURNED_NAN, argument);
+        }
+        return result;
+    }
+
+    /**
+     * For a random variable {@code X} whose values are distributed according to this distribution,
+     * this method returns {@code log(P(X = x))}, where {@code log} is the natural logarithm. In
+     * other words, this method represents the logarithm of the probability mass function (PMF) for
+     * the distribution. Note that due to the floating point precision and under/overflow issues,
+     * this method will for some distributions be more precise and faster than computing the
+     * logarithm of {@link #probability(int)}.
+     *
+     * <p>The default implementation simply computes the logarithm of {@code probability(x)}.
+     *
+     * @param x the point at which the PMF is evaluated
+     * @return the logarithm of the value of the probability mass function at {@code x}
+     */
+    public double logProbability(int x) {
+        return FastMath.log(probability(x));
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/AbstractMultivariateRealDistribution.java b/src/main/java/org/apache/commons/math3/distribution/AbstractMultivariateRealDistribution.java
new file mode 100644
index 0000000..98e9348
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/AbstractMultivariateRealDistribution.java
@@ -0,0 +1,68 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+
+/**
+ * Base class for multivariate probability distributions.
+ *
+ * @since 3.1
+ */
+public abstract class AbstractMultivariateRealDistribution implements MultivariateRealDistribution {
+    /** RNG instance used to generate samples from the distribution. */
+    protected final RandomGenerator random;
+
+    /** The number of dimensions or columns in the multivariate distribution. */
+    private final int dimension;
+
+    /**
+     * @param rng Random number generator.
+     * @param n Number of dimensions.
+     */
+    protected AbstractMultivariateRealDistribution(RandomGenerator rng, int n) {
+        random = rng;
+        dimension = n;
+    }
+
+    /** {@inheritDoc} */
+    public void reseedRandomGenerator(long seed) {
+        random.setSeed(seed);
+    }
+
+    /** {@inheritDoc} */
+    public int getDimension() {
+        return dimension;
+    }
+
+    /** {@inheritDoc} */
+    public abstract double[] sample();
+
+    /** {@inheritDoc} */
+    public double[][] sample(final int sampleSize) {
+        if (sampleSize <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.NUMBER_OF_SAMPLES, sampleSize);
+        }
+        final double[][] out = new double[sampleSize][dimension];
+        for (int i = 0; i < sampleSize; i++) {
+            out[i] = sample();
+        }
+        return out;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/AbstractRealDistribution.java b/src/main/java/org/apache/commons/math3/distribution/AbstractRealDistribution.java
new file mode 100644
index 0000000..b9e5bca
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/AbstractRealDistribution.java
@@ -0,0 +1,306 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.solvers.UnivariateSolverUtils;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.util.FastMath;
+
+import java.io.Serializable;
+
+/**
+ * Base class for probability distributions on the reals. Default implementations are provided for
+ * some of the methods that do not vary from distribution to distribution.
+ *
+ * @since 3.0
+ */
+public abstract class AbstractRealDistribution implements RealDistribution, Serializable {
+    /** Default accuracy. */
+    public static final double SOLVER_DEFAULT_ABSOLUTE_ACCURACY = 1e-6;
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -38038050983108802L;
+
+    /**
+     * RandomData instance used to generate samples from the distribution.
+     *
+     * @deprecated As of 3.1, to be removed in 4.0. Please use the {@link #random} instance variable
+     *     instead.
+     */
+    @Deprecated
+    protected org.apache.commons.math3.random.RandomDataImpl randomData =
+            new org.apache.commons.math3.random.RandomDataImpl();
+
+    /**
+     * RNG instance used to generate samples from the distribution.
+     *
+     * @since 3.1
+     */
+    protected final RandomGenerator random;
+
+    /** Solver absolute accuracy for inverse cumulative computation */
+    private double solverAbsoluteAccuracy = SOLVER_DEFAULT_ABSOLUTE_ACCURACY;
+
+    /**
+     * @deprecated As of 3.1, to be removed in 4.0. Please use {@link
+     *     #AbstractRealDistribution(RandomGenerator)} instead.
+     */
+    @Deprecated
+    protected AbstractRealDistribution() {
+        // Legacy users are only allowed to access the deprecated "randomData".
+        // New users are forbidden to use this constructor.
+        random = null;
+    }
+
+    /**
+     * @param rng Random number generator.
+     * @since 3.1
+     */
+    protected AbstractRealDistribution(RandomGenerator rng) {
+        random = rng;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The default implementation uses the identity
+     *
+     * <p>{@code P(x0 < X <= x1) = P(X <= x1) - P(X <= x0)}
+     *
+     * @deprecated As of 3.1 (to be removed in 4.0). Please use {@link #probability(double,double)}
+     *     instead.
+     */
+    @Deprecated
+    public double cumulativeProbability(double x0, double x1) throws NumberIsTooLargeException {
+        return probability(x0, x1);
+    }
+
+    /**
+     * For a random variable {@code X} whose values are distributed according to this distribution,
+     * this method returns {@code P(x0 < X <= x1)}.
+     *
+     * @param x0 Lower bound (excluded).
+     * @param x1 Upper bound (included).
+     * @return the probability that a random variable with this distribution takes a value between
+     *     {@code x0} and {@code x1}, excluding the lower and including the upper endpoint.
+     * @throws NumberIsTooLargeException if {@code x0 > x1}.
+     *     <p>The default implementation uses the identity {@code P(x0 < X <= x1) = P(X <= x1) - P(X
+     *     <= x0)}
+     * @since 3.1
+     */
+    public double probability(double x0, double x1) {
+        if (x0 > x1) {
+            throw new NumberIsTooLargeException(
+                    LocalizedFormats.LOWER_ENDPOINT_ABOVE_UPPER_ENDPOINT, x0, x1, true);
+        }
+        return cumulativeProbability(x1) - cumulativeProbability(x0);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The default implementation returns
+     *
+     * <ul>
+     *   <li>{@link #getSupportLowerBound()} for {@code p = 0},
+     *   <li>{@link #getSupportUpperBound()} for {@code p = 1}.
+     * </ul>
+     */
+    public double inverseCumulativeProbability(final double p) throws OutOfRangeException {
+        /*
+         * IMPLEMENTATION NOTES
+         * --------------------
+         * Where applicable, use is made of the one-sided Chebyshev inequality
+         * to bracket the root. This inequality states that
+         * P(X - mu >= k * sig) <= 1 / (1 + k^2),
+         * mu: mean, sig: standard deviation. Equivalently
+         * 1 - P(X < mu + k * sig) <= 1 / (1 + k^2),
+         * F(mu + k * sig) >= k^2 / (1 + k^2).
+         *
+         * For k = sqrt(p / (1 - p)), we find
+         * F(mu + k * sig) >= p,
+         * and (mu + k * sig) is an upper-bound for the root.
+         *
+         * Then, introducing Y = -X, mean(Y) = -mu, sd(Y) = sig, and
+         * P(Y >= -mu + k * sig) <= 1 / (1 + k^2),
+         * P(-X >= -mu + k * sig) <= 1 / (1 + k^2),
+         * P(X <= mu - k * sig) <= 1 / (1 + k^2),
+         * F(mu - k * sig) <= 1 / (1 + k^2).
+         *
+         * For k = sqrt((1 - p) / p), we find
+         * F(mu - k * sig) <= p,
+         * and (mu - k * sig) is a lower-bound for the root.
+         *
+         * In cases where the Chebyshev inequality does not apply, geometric
+         * progressions 1, 2, 4, ... and -1, -2, -4, ... are used to bracket
+         * the root.
+         */
+        if (p < 0.0 || p > 1.0) {
+            throw new OutOfRangeException(p, 0, 1);
+        }
+
+        double lowerBound = getSupportLowerBound();
+        if (p == 0.0) {
+            return lowerBound;
+        }
+
+        double upperBound = getSupportUpperBound();
+        if (p == 1.0) {
+            return upperBound;
+        }
+
+        final double mu = getNumericalMean();
+        final double sig = FastMath.sqrt(getNumericalVariance());
+        final boolean chebyshevApplies;
+        chebyshevApplies =
+                !(Double.isInfinite(mu)
+                        || Double.isNaN(mu)
+                        || Double.isInfinite(sig)
+                        || Double.isNaN(sig));
+
+        if (lowerBound == Double.NEGATIVE_INFINITY) {
+            if (chebyshevApplies) {
+                lowerBound = mu - sig * FastMath.sqrt((1. - p) / p);
+            } else {
+                lowerBound = -1.0;
+                while (cumulativeProbability(lowerBound) >= p) {
+                    lowerBound *= 2.0;
+                }
+            }
+        }
+
+        if (upperBound == Double.POSITIVE_INFINITY) {
+            if (chebyshevApplies) {
+                upperBound = mu + sig * FastMath.sqrt(p / (1. - p));
+            } else {
+                upperBound = 1.0;
+                while (cumulativeProbability(upperBound) < p) {
+                    upperBound *= 2.0;
+                }
+            }
+        }
+
+        final UnivariateFunction toSolve =
+                new UnivariateFunction() {
+                    /** {@inheritDoc} */
+                    public double value(final double x) {
+                        return cumulativeProbability(x) - p;
+                    }
+                };
+
+        double x =
+                UnivariateSolverUtils.solve(
+                        toSolve, lowerBound, upperBound, getSolverAbsoluteAccuracy());
+
+        if (!isSupportConnected()) {
+            /* Test for plateau. */
+            final double dx = getSolverAbsoluteAccuracy();
+            if (x - dx >= getSupportLowerBound()) {
+                double px = cumulativeProbability(x);
+                if (cumulativeProbability(x - dx) == px) {
+                    upperBound = x;
+                    while (upperBound - lowerBound > dx) {
+                        final double midPoint = 0.5 * (lowerBound + upperBound);
+                        if (cumulativeProbability(midPoint) < px) {
+                            lowerBound = midPoint;
+                        } else {
+                            upperBound = midPoint;
+                        }
+                    }
+                    return upperBound;
+                }
+            }
+        }
+        return x;
+    }
+
+    /**
+     * Returns the solver absolute accuracy for inverse cumulative computation. You can override
+     * this method in order to use a Brent solver with an absolute accuracy different from the
+     * default.
+     *
+     * @return the maximum absolute error in inverse cumulative probability estimates
+     */
+    protected double getSolverAbsoluteAccuracy() {
+        return solverAbsoluteAccuracy;
+    }
+
+    /** {@inheritDoc} */
+    public void reseedRandomGenerator(long seed) {
+        random.setSeed(seed);
+        randomData.reSeed(seed);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The default implementation uses the <a
+     * href="http://en.wikipedia.org/wiki/Inverse_transform_sampling">inversion method. </a>
+     */
+    public double sample() {
+        return inverseCumulativeProbability(random.nextDouble());
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The default implementation generates the sample by calling {@link #sample()} in a loop.
+     */
+    public double[] sample(int sampleSize) {
+        if (sampleSize <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.NUMBER_OF_SAMPLES, sampleSize);
+        }
+        double[] out = new double[sampleSize];
+        for (int i = 0; i < sampleSize; i++) {
+            out[i] = sample();
+        }
+        return out;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @return zero.
+     * @since 3.1
+     */
+    public double probability(double x) {
+        return 0d;
+    }
+
+    /**
+     * Returns the natural logarithm of the probability density function (PDF) of this distribution
+     * evaluated at the specified point {@code x}. In general, the PDF is the derivative of the
+     * {@link #cumulativeProbability(double) CDF}. If the derivative does not exist at {@code x},
+     * then an appropriate replacement should be returned, e.g. {@code Double.POSITIVE_INFINITY},
+     * {@code Double.NaN}, or the limit inferior or limit superior of the difference quotient. Note
+     * that due to the floating point precision and under/overflow issues, this method will for some
+     * distributions be more precise and faster than computing the logarithm of {@link
+     * #density(double)}. The default implementation simply computes the logarithm of {@code
+     * density(x)}.
+     *
+     * @param x the point at which the PDF is evaluated
+     * @return the logarithm of the value of the probability density function at point {@code x}
+     */
+    public double logDensity(double x) {
+        return FastMath.log(density(x));
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/BetaDistribution.java b/src/main/java/org/apache/commons/math3/distribution/BetaDistribution.java
new file mode 100644
index 0000000..c7c2663
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/BetaDistribution.java
@@ -0,0 +1,417 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well19937c;
+import org.apache.commons.math3.special.Beta;
+import org.apache.commons.math3.special.Gamma;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.Precision;
+
+/**
+ * Implements the Beta distribution.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Beta_distribution">Beta distribution</a>
+ * @since 2.0 (changed to concrete class in 3.0)
+ */
+public class BetaDistribution extends AbstractRealDistribution {
+    /**
+     * Default inverse cumulative probability accuracy.
+     *
+     * @since 2.1
+     */
+    public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9;
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -1221965979403477668L;
+
+    /** First shape parameter. */
+    private final double alpha;
+
+    /** Second shape parameter. */
+    private final double beta;
+
+    /**
+     * Normalizing factor used in density computations. updated whenever alpha or beta are changed.
+     */
+    private double z;
+
+    /** Inverse cumulative probability accuracy. */
+    private final double solverAbsoluteAccuracy;
+
+    /**
+     * Build a new instance.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param alpha First shape parameter (must be positive).
+     * @param beta Second shape parameter (must be positive).
+     */
+    public BetaDistribution(double alpha, double beta) {
+        this(alpha, beta, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Build a new instance.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param alpha First shape parameter (must be positive).
+     * @param beta Second shape parameter (must be positive).
+     * @param inverseCumAccuracy Maximum absolute error in inverse cumulative probability estimates
+     *     (defaults to {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}).
+     * @since 2.1
+     */
+    public BetaDistribution(double alpha, double beta, double inverseCumAccuracy) {
+        this(new Well19937c(), alpha, beta, inverseCumAccuracy);
+    }
+
+    /**
+     * Creates a &beta; distribution.
+     *
+     * @param rng Random number generator.
+     * @param alpha First shape parameter (must be positive).
+     * @param beta Second shape parameter (must be positive).
+     * @since 3.3
+     */
+    public BetaDistribution(RandomGenerator rng, double alpha, double beta) {
+        this(rng, alpha, beta, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Creates a &beta; distribution.
+     *
+     * @param rng Random number generator.
+     * @param alpha First shape parameter (must be positive).
+     * @param beta Second shape parameter (must be positive).
+     * @param inverseCumAccuracy Maximum absolute error in inverse cumulative probability estimates
+     *     (defaults to {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}).
+     * @since 3.1
+     */
+    public BetaDistribution(
+            RandomGenerator rng, double alpha, double beta, double inverseCumAccuracy) {
+        super(rng);
+
+        this.alpha = alpha;
+        this.beta = beta;
+        z = Double.NaN;
+        solverAbsoluteAccuracy = inverseCumAccuracy;
+    }
+
+    /**
+     * Access the first shape parameter, {@code alpha}.
+     *
+     * @return the first shape parameter.
+     */
+    public double getAlpha() {
+        return alpha;
+    }
+
+    /**
+     * Access the second shape parameter, {@code beta}.
+     *
+     * @return the second shape parameter.
+     */
+    public double getBeta() {
+        return beta;
+    }
+
+    /** Recompute the normalization factor. */
+    private void recomputeZ() {
+        if (Double.isNaN(z)) {
+            z = Gamma.logGamma(alpha) + Gamma.logGamma(beta) - Gamma.logGamma(alpha + beta);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public double density(double x) {
+        final double logDensity = logDensity(x);
+        return logDensity == Double.NEGATIVE_INFINITY ? 0 : FastMath.exp(logDensity);
+    }
+
+    /** {@inheritDoc} * */
+    @Override
+    public double logDensity(double x) {
+        recomputeZ();
+        if (x < 0 || x > 1) {
+            return Double.NEGATIVE_INFINITY;
+        } else if (x == 0) {
+            if (alpha < 1) {
+                throw new NumberIsTooSmallException(
+                        LocalizedFormats.CANNOT_COMPUTE_BETA_DENSITY_AT_0_FOR_SOME_ALPHA,
+                        alpha,
+                        1,
+                        false);
+            }
+            return Double.NEGATIVE_INFINITY;
+        } else if (x == 1) {
+            if (beta < 1) {
+                throw new NumberIsTooSmallException(
+                        LocalizedFormats.CANNOT_COMPUTE_BETA_DENSITY_AT_1_FOR_SOME_BETA,
+                        beta,
+                        1,
+                        false);
+            }
+            return Double.NEGATIVE_INFINITY;
+        } else {
+            double logX = FastMath.log(x);
+            double log1mX = FastMath.log1p(-x);
+            return (alpha - 1) * logX + (beta - 1) * log1mX - z;
+        }
+    }
+
+    /** {@inheritDoc} */
+    public double cumulativeProbability(double x) {
+        if (x <= 0) {
+            return 0;
+        } else if (x >= 1) {
+            return 1;
+        } else {
+            return Beta.regularizedBeta(x, alpha, beta);
+        }
+    }
+
+    /**
+     * Return the absolute accuracy setting of the solver used to estimate inverse cumulative
+     * probabilities.
+     *
+     * @return the solver absolute accuracy.
+     * @since 2.1
+     */
+    @Override
+    protected double getSolverAbsoluteAccuracy() {
+        return solverAbsoluteAccuracy;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For first shape parameter {@code alpha} and second shape parameter {@code beta}, the mean
+     * is {@code alpha / (alpha + beta)}.
+     */
+    public double getNumericalMean() {
+        final double a = getAlpha();
+        return a / (a + getBeta());
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For first shape parameter {@code alpha} and second shape parameter {@code beta}, the
+     * variance is {@code (alpha * beta) / [(alpha + beta)^2 * (alpha + beta + 1)]}.
+     */
+    public double getNumericalVariance() {
+        final double a = getAlpha();
+        final double b = getBeta();
+        final double alphabetasum = a + b;
+        return (a * b) / ((alphabetasum * alphabetasum) * (alphabetasum + 1));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The lower bound of the support is always 0 no matter the parameters.
+     *
+     * @return lower bound of the support (always 0)
+     */
+    public double getSupportLowerBound() {
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The upper bound of the support is always 1 no matter the parameters.
+     *
+     * @return upper bound of the support (always 1)
+     */
+    public double getSupportUpperBound() {
+        return 1;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportLowerBoundInclusive() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportUpperBoundInclusive() {
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The support of this distribution is connected.
+     *
+     * @return {@code true}
+     */
+    public boolean isSupportConnected() {
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>Sampling is performed using Cheng algorithms:
+     *
+     * <p>R. C. H. Cheng, "Generating beta variates with nonintegral shape parameters.".
+     * Communications of the ACM, 21, 317–322, 1978.
+     */
+    @Override
+    public double sample() {
+        return ChengBetaSampler.sample(random, alpha, beta);
+    }
+
+    /**
+     * Utility class implementing Cheng's algorithms for beta distribution sampling.
+     *
+     * <p>R. C. H. Cheng, "Generating beta variates with nonintegral shape parameters.".
+     * Communications of the ACM, 21, 317–322, 1978.
+     *
+     * @since 3.6
+     */
+    private static final class ChengBetaSampler {
+
+        /**
+         * Returns one sample using Cheng's sampling algorithm.
+         *
+         * @param random random generator to use
+         * @param alpha distribution first shape parameter
+         * @param beta distribution second shape parameter
+         * @return sampled value
+         */
+        static double sample(RandomGenerator random, final double alpha, final double beta) {
+            final double a = FastMath.min(alpha, beta);
+            final double b = FastMath.max(alpha, beta);
+
+            if (a > 1) {
+                return algorithmBB(random, alpha, a, b);
+            } else {
+                return algorithmBC(random, alpha, b, a);
+            }
+        }
+
+        /**
+         * Returns one sample using Cheng's BB algorithm, when both &alpha; and &beta; are greater
+         * than 1.
+         *
+         * @param random random generator to use
+         * @param a0 distribution first shape parameter (&alpha;)
+         * @param a min(&alpha;, &beta;) where &alpha;, &beta; are the two distribution shape
+         *     parameters
+         * @param b max(&alpha;, &beta;) where &alpha;, &beta; are the two distribution shape
+         *     parameters
+         * @return sampled value
+         */
+        private static double algorithmBB(
+                RandomGenerator random, final double a0, final double a, final double b) {
+            final double alpha = a + b;
+            final double beta = FastMath.sqrt((alpha - 2.) / (2. * a * b - alpha));
+            final double gamma = a + 1. / beta;
+
+            double r;
+            double w;
+            double t;
+            do {
+                final double u1 = random.nextDouble();
+                final double u2 = random.nextDouble();
+                final double v = beta * (FastMath.log(u1) - FastMath.log1p(-u1));
+                w = a * FastMath.exp(v);
+                final double z = u1 * u1 * u2;
+                r = gamma * v - 1.3862944;
+                final double s = a + r - w;
+                if (s + 2.609438 >= 5 * z) {
+                    break;
+                }
+
+                t = FastMath.log(z);
+                if (s >= t) {
+                    break;
+                }
+            } while (r + alpha * (FastMath.log(alpha) - FastMath.log(b + w)) < t);
+
+            w = FastMath.min(w, Double.MAX_VALUE);
+            return Precision.equals(a, a0) ? w / (b + w) : b / (b + w);
+        }
+
+        /**
+         * Returns one sample using Cheng's BC algorithm, when at least one of &alpha; and &beta; is
+         * smaller than 1.
+         *
+         * @param random random generator to use
+         * @param a0 distribution first shape parameter (&alpha;)
+         * @param a max(&alpha;, &beta;) where &alpha;, &beta; are the two distribution shape
+         *     parameters
+         * @param b min(&alpha;, &beta;) where &alpha;, &beta; are the two distribution shape
+         *     parameters
+         * @return sampled value
+         */
+        private static double algorithmBC(
+                RandomGenerator random, final double a0, final double a, final double b) {
+            final double alpha = a + b;
+            final double beta = 1. / b;
+            final double delta = 1. + a - b;
+            final double k1 = delta * (0.0138889 + 0.0416667 * b) / (a * beta - 0.777778);
+            final double k2 = 0.25 + (0.5 + 0.25 / delta) * b;
+
+            double w;
+            for (; ; ) {
+                final double u1 = random.nextDouble();
+                final double u2 = random.nextDouble();
+                final double y = u1 * u2;
+                final double z = u1 * y;
+                if (u1 < 0.5) {
+                    if (0.25 * u2 + z - y >= k1) {
+                        continue;
+                    }
+                } else {
+                    if (z <= 0.25) {
+                        final double v = beta * (FastMath.log(u1) - FastMath.log1p(-u1));
+                        w = a * FastMath.exp(v);
+                        break;
+                    }
+
+                    if (z >= k2) {
+                        continue;
+                    }
+                }
+
+                final double v = beta * (FastMath.log(u1) - FastMath.log1p(-u1));
+                w = a * FastMath.exp(v);
+                if (alpha * (FastMath.log(alpha) - FastMath.log(b + w) + v) - 1.3862944
+                        >= FastMath.log(z)) {
+                    break;
+                }
+            }
+
+            w = FastMath.min(w, Double.MAX_VALUE);
+            return Precision.equals(a, a0) ? w / (b + w) : b / (b + w);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/BinomialDistribution.java b/src/main/java/org/apache/commons/math3/distribution/BinomialDistribution.java
new file mode 100644
index 0000000..611666a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/BinomialDistribution.java
@@ -0,0 +1,195 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well19937c;
+import org.apache.commons.math3.special.Beta;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Implementation of the binomial distribution.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Binomial_distribution">Binomial distribution
+ *     (Wikipedia)</a>
+ * @see <a href="http://mathworld.wolfram.com/BinomialDistribution.html">Binomial Distribution
+ *     (MathWorld)</a>
+ */
+public class BinomialDistribution extends AbstractIntegerDistribution {
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 6751309484392813623L;
+
+    /** The number of trials. */
+    private final int numberOfTrials;
+
+    /** The probability of success. */
+    private final double probabilityOfSuccess;
+
+    /**
+     * Create a binomial distribution with the given number of trials and probability of success.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param trials Number of trials.
+     * @param p Probability of success.
+     * @throws NotPositiveException if {@code trials < 0}.
+     * @throws OutOfRangeException if {@code p < 0} or {@code p > 1}.
+     */
+    public BinomialDistribution(int trials, double p) {
+        this(new Well19937c(), trials, p);
+    }
+
+    /**
+     * Creates a binomial distribution.
+     *
+     * @param rng Random number generator.
+     * @param trials Number of trials.
+     * @param p Probability of success.
+     * @throws NotPositiveException if {@code trials < 0}.
+     * @throws OutOfRangeException if {@code p < 0} or {@code p > 1}.
+     * @since 3.1
+     */
+    public BinomialDistribution(RandomGenerator rng, int trials, double p) {
+        super(rng);
+
+        if (trials < 0) {
+            throw new NotPositiveException(LocalizedFormats.NUMBER_OF_TRIALS, trials);
+        }
+        if (p < 0 || p > 1) {
+            throw new OutOfRangeException(p, 0, 1);
+        }
+
+        probabilityOfSuccess = p;
+        numberOfTrials = trials;
+    }
+
+    /**
+     * Access the number of trials for this distribution.
+     *
+     * @return the number of trials.
+     */
+    public int getNumberOfTrials() {
+        return numberOfTrials;
+    }
+
+    /**
+     * Access the probability of success for this distribution.
+     *
+     * @return the probability of success.
+     */
+    public double getProbabilityOfSuccess() {
+        return probabilityOfSuccess;
+    }
+
+    /** {@inheritDoc} */
+    public double probability(int x) {
+        final double logProbability = logProbability(x);
+        return logProbability == Double.NEGATIVE_INFINITY ? 0 : FastMath.exp(logProbability);
+    }
+
+    /** {@inheritDoc} * */
+    @Override
+    public double logProbability(int x) {
+        if (numberOfTrials == 0) {
+            return (x == 0) ? 0. : Double.NEGATIVE_INFINITY;
+        }
+        double ret;
+        if (x < 0 || x > numberOfTrials) {
+            ret = Double.NEGATIVE_INFINITY;
+        } else {
+            ret =
+                    SaddlePointExpansion.logBinomialProbability(
+                            x, numberOfTrials, probabilityOfSuccess, 1.0 - probabilityOfSuccess);
+        }
+        return ret;
+    }
+
+    /** {@inheritDoc} */
+    public double cumulativeProbability(int x) {
+        double ret;
+        if (x < 0) {
+            ret = 0.0;
+        } else if (x >= numberOfTrials) {
+            ret = 1.0;
+        } else {
+            ret = 1.0 - Beta.regularizedBeta(probabilityOfSuccess, x + 1.0, numberOfTrials - x);
+        }
+        return ret;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For {@code n} trials and probability parameter {@code p}, the mean is {@code n * p}.
+     */
+    public double getNumericalMean() {
+        return numberOfTrials * probabilityOfSuccess;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For {@code n} trials and probability parameter {@code p}, the variance is {@code n * p *
+     * (1 - p)}.
+     */
+    public double getNumericalVariance() {
+        final double p = probabilityOfSuccess;
+        return numberOfTrials * p * (1 - p);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The lower bound of the support is always 0 except for the probability parameter {@code p =
+     * 1}.
+     *
+     * @return lower bound of the support (0 or the number of trials)
+     */
+    public int getSupportLowerBound() {
+        return probabilityOfSuccess < 1.0 ? 0 : numberOfTrials;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The upper bound of the support is the number of trials except for the probability
+     * parameter {@code p = 0}.
+     *
+     * @return upper bound of the support (number of trials or 0)
+     */
+    public int getSupportUpperBound() {
+        return probabilityOfSuccess > 0.0 ? numberOfTrials : 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The support of this distribution is connected.
+     *
+     * @return {@code true}
+     */
+    public boolean isSupportConnected() {
+        return true;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/CauchyDistribution.java b/src/main/java/org/apache/commons/math3/distribution/CauchyDistribution.java
new file mode 100644
index 0000000..8c235ea
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/CauchyDistribution.java
@@ -0,0 +1,251 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well19937c;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Implementation of the Cauchy distribution.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Cauchy_distribution">Cauchy distribution
+ *     (Wikipedia)</a>
+ * @see <a href="http://mathworld.wolfram.com/CauchyDistribution.html">Cauchy Distribution
+ *     (MathWorld)</a>
+ * @since 1.1 (changed to concrete class in 3.0)
+ */
+public class CauchyDistribution extends AbstractRealDistribution {
+    /**
+     * Default inverse cumulative probability accuracy.
+     *
+     * @since 2.1
+     */
+    public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9;
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 8589540077390120676L;
+
+    /** The median of this distribution. */
+    private final double median;
+
+    /** The scale of this distribution. */
+    private final double scale;
+
+    /** Inverse cumulative probability accuracy */
+    private final double solverAbsoluteAccuracy;
+
+    /** Creates a Cauchy distribution with the median equal to zero and scale equal to one. */
+    public CauchyDistribution() {
+        this(0, 1);
+    }
+
+    /**
+     * Creates a Cauchy distribution using the given median and scale.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param median Median for this distribution.
+     * @param scale Scale parameter for this distribution.
+     */
+    public CauchyDistribution(double median, double scale) {
+        this(median, scale, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Creates a Cauchy distribution using the given median and scale.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param median Median for this distribution.
+     * @param scale Scale parameter for this distribution.
+     * @param inverseCumAccuracy Maximum absolute error in inverse cumulative probability estimates
+     *     (defaults to {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}).
+     * @throws NotStrictlyPositiveException if {@code scale <= 0}.
+     * @since 2.1
+     */
+    public CauchyDistribution(double median, double scale, double inverseCumAccuracy) {
+        this(new Well19937c(), median, scale, inverseCumAccuracy);
+    }
+
+    /**
+     * Creates a Cauchy distribution.
+     *
+     * @param rng Random number generator.
+     * @param median Median for this distribution.
+     * @param scale Scale parameter for this distribution.
+     * @throws NotStrictlyPositiveException if {@code scale <= 0}.
+     * @since 3.3
+     */
+    public CauchyDistribution(RandomGenerator rng, double median, double scale) {
+        this(rng, median, scale, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Creates a Cauchy distribution.
+     *
+     * @param rng Random number generator.
+     * @param median Median for this distribution.
+     * @param scale Scale parameter for this distribution.
+     * @param inverseCumAccuracy Maximum absolute error in inverse cumulative probability estimates
+     *     (defaults to {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}).
+     * @throws NotStrictlyPositiveException if {@code scale <= 0}.
+     * @since 3.1
+     */
+    public CauchyDistribution(
+            RandomGenerator rng, double median, double scale, double inverseCumAccuracy) {
+        super(rng);
+        if (scale <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.SCALE, scale);
+        }
+        this.scale = scale;
+        this.median = median;
+        solverAbsoluteAccuracy = inverseCumAccuracy;
+    }
+
+    /** {@inheritDoc} */
+    public double cumulativeProbability(double x) {
+        return 0.5 + (FastMath.atan((x - median) / scale) / FastMath.PI);
+    }
+
+    /**
+     * Access the median.
+     *
+     * @return the median for this distribution.
+     */
+    public double getMedian() {
+        return median;
+    }
+
+    /**
+     * Access the scale parameter.
+     *
+     * @return the scale parameter for this distribution.
+     */
+    public double getScale() {
+        return scale;
+    }
+
+    /** {@inheritDoc} */
+    public double density(double x) {
+        final double dev = x - median;
+        return (1 / FastMath.PI) * (scale / (dev * dev + scale * scale));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>Returns {@code Double.NEGATIVE_INFINITY} when {@code p == 0} and {@code
+     * Double.POSITIVE_INFINITY} when {@code p == 1}.
+     */
+    @Override
+    public double inverseCumulativeProbability(double p) throws OutOfRangeException {
+        double ret;
+        if (p < 0 || p > 1) {
+            throw new OutOfRangeException(p, 0, 1);
+        } else if (p == 0) {
+            ret = Double.NEGATIVE_INFINITY;
+        } else if (p == 1) {
+            ret = Double.POSITIVE_INFINITY;
+        } else {
+            ret = median + scale * FastMath.tan(FastMath.PI * (p - .5));
+        }
+        return ret;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected double getSolverAbsoluteAccuracy() {
+        return solverAbsoluteAccuracy;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The mean is always undefined no matter the parameters.
+     *
+     * @return mean (always Double.NaN)
+     */
+    public double getNumericalMean() {
+        return Double.NaN;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The variance is always undefined no matter the parameters.
+     *
+     * @return variance (always Double.NaN)
+     */
+    public double getNumericalVariance() {
+        return Double.NaN;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The lower bound of the support is always negative infinity no matter the parameters.
+     *
+     * @return lower bound of the support (always Double.NEGATIVE_INFINITY)
+     */
+    public double getSupportLowerBound() {
+        return Double.NEGATIVE_INFINITY;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The upper bound of the support is always positive infinity no matter the parameters.
+     *
+     * @return upper bound of the support (always Double.POSITIVE_INFINITY)
+     */
+    public double getSupportUpperBound() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportLowerBoundInclusive() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportUpperBoundInclusive() {
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The support of this distribution is connected.
+     *
+     * @return {@code true}
+     */
+    public boolean isSupportConnected() {
+        return true;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/ChiSquaredDistribution.java b/src/main/java/org/apache/commons/math3/distribution/ChiSquaredDistribution.java
new file mode 100644
index 0000000..06af167
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/ChiSquaredDistribution.java
@@ -0,0 +1,196 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well19937c;
+
+/**
+ * Implementation of the chi-squared distribution.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Chi-squared_distribution">Chi-squared distribution
+ *     (Wikipedia)</a>
+ * @see <a href="http://mathworld.wolfram.com/Chi-SquaredDistribution.html">Chi-squared Distribution
+ *     (MathWorld)</a>
+ */
+public class ChiSquaredDistribution extends AbstractRealDistribution {
+    /**
+     * Default inverse cumulative probability accuracy
+     *
+     * @since 2.1
+     */
+    public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9;
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -8352658048349159782L;
+
+    /** Internal Gamma distribution. */
+    private final GammaDistribution gamma;
+
+    /** Inverse cumulative probability accuracy */
+    private final double solverAbsoluteAccuracy;
+
+    /**
+     * Create a Chi-Squared distribution with the given degrees of freedom.
+     *
+     * @param degreesOfFreedom Degrees of freedom.
+     */
+    public ChiSquaredDistribution(double degreesOfFreedom) {
+        this(degreesOfFreedom, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Create a Chi-Squared distribution with the given degrees of freedom and inverse cumulative
+     * probability accuracy.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param degreesOfFreedom Degrees of freedom.
+     * @param inverseCumAccuracy the maximum absolute error in inverse cumulative probability
+     *     estimates (defaults to {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}).
+     * @since 2.1
+     */
+    public ChiSquaredDistribution(double degreesOfFreedom, double inverseCumAccuracy) {
+        this(new Well19937c(), degreesOfFreedom, inverseCumAccuracy);
+    }
+
+    /**
+     * Create a Chi-Squared distribution with the given degrees of freedom.
+     *
+     * @param rng Random number generator.
+     * @param degreesOfFreedom Degrees of freedom.
+     * @since 3.3
+     */
+    public ChiSquaredDistribution(RandomGenerator rng, double degreesOfFreedom) {
+        this(rng, degreesOfFreedom, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Create a Chi-Squared distribution with the given degrees of freedom and inverse cumulative
+     * probability accuracy.
+     *
+     * @param rng Random number generator.
+     * @param degreesOfFreedom Degrees of freedom.
+     * @param inverseCumAccuracy the maximum absolute error in inverse cumulative probability
+     *     estimates (defaults to {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}).
+     * @since 3.1
+     */
+    public ChiSquaredDistribution(
+            RandomGenerator rng, double degreesOfFreedom, double inverseCumAccuracy) {
+        super(rng);
+
+        gamma = new GammaDistribution(degreesOfFreedom / 2, 2);
+        solverAbsoluteAccuracy = inverseCumAccuracy;
+    }
+
+    /**
+     * Access the number of degrees of freedom.
+     *
+     * @return the degrees of freedom.
+     */
+    public double getDegreesOfFreedom() {
+        return gamma.getShape() * 2.0;
+    }
+
+    /** {@inheritDoc} */
+    public double density(double x) {
+        return gamma.density(x);
+    }
+
+    /** {@inheritDoc} * */
+    @Override
+    public double logDensity(double x) {
+        return gamma.logDensity(x);
+    }
+
+    /** {@inheritDoc} */
+    public double cumulativeProbability(double x) {
+        return gamma.cumulativeProbability(x);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected double getSolverAbsoluteAccuracy() {
+        return solverAbsoluteAccuracy;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For {@code k} degrees of freedom, the mean is {@code k}.
+     */
+    public double getNumericalMean() {
+        return getDegreesOfFreedom();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @return {@code 2 * k}, where {@code k} is the number of degrees of freedom.
+     */
+    public double getNumericalVariance() {
+        return 2 * getDegreesOfFreedom();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The lower bound of the support is always 0 no matter the degrees of freedom.
+     *
+     * @return zero.
+     */
+    public double getSupportLowerBound() {
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The upper bound of the support is always positive infinity no matter the degrees of
+     * freedom.
+     *
+     * @return {@code Double.POSITIVE_INFINITY}.
+     */
+    public double getSupportUpperBound() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportLowerBoundInclusive() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportUpperBoundInclusive() {
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The support of this distribution is connected.
+     *
+     * @return {@code true}
+     */
+    public boolean isSupportConnected() {
+        return true;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/ConstantRealDistribution.java b/src/main/java/org/apache/commons/math3/distribution/ConstantRealDistribution.java
new file mode 100644
index 0000000..93ba255
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/ConstantRealDistribution.java
@@ -0,0 +1,112 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.OutOfRangeException;
+
+/**
+ * Implementation of the constant real distribution.
+ *
+ * @since 3.4
+ */
+public class ConstantRealDistribution extends AbstractRealDistribution {
+
+    /** Serialization ID */
+    private static final long serialVersionUID = -4157745166772046273L;
+
+    /** Constant value of the distribution */
+    private final double value;
+
+    /**
+     * Create a constant real distribution with the given value.
+     *
+     * @param value the constant value of this distribution
+     */
+    public ConstantRealDistribution(double value) {
+        super(null); // Avoid creating RandomGenerator
+        this.value = value;
+    }
+
+    /** {@inheritDoc} */
+    public double density(double x) {
+        return x == value ? 1 : 0;
+    }
+
+    /** {@inheritDoc} */
+    public double cumulativeProbability(double x) {
+        return x < value ? 0 : 1;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double inverseCumulativeProbability(final double p) throws OutOfRangeException {
+        if (p < 0.0 || p > 1.0) {
+            throw new OutOfRangeException(p, 0, 1);
+        }
+        return value;
+    }
+
+    /** {@inheritDoc} */
+    public double getNumericalMean() {
+        return value;
+    }
+
+    /** {@inheritDoc} */
+    public double getNumericalVariance() {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    public double getSupportLowerBound() {
+        return value;
+    }
+
+    /** {@inheritDoc} */
+    public double getSupportUpperBound() {
+        return value;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportLowerBoundInclusive() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportUpperBoundInclusive() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportConnected() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double sample() {
+        return value;
+    }
+
+    /**
+     * Override with no-op (there is no generator).
+     *
+     * @param seed (ignored)
+     */
+    @Override
+    public void reseedRandomGenerator(long seed) {}
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/EnumeratedDistribution.java b/src/main/java/org/apache/commons/math3/distribution/EnumeratedDistribution.java
new file mode 100644
index 0000000..991a9f8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/EnumeratedDistribution.java
@@ -0,0 +1,283 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.NotANumberException;
+import org.apache.commons.math3.exception.NotFiniteNumberException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well19937c;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.util.Pair;
+
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A generic implementation of a <a
+ * href="http://en.wikipedia.org/wiki/Probability_distribution#Discrete_probability_distribution">
+ * discrete probability distribution (Wikipedia)</a> over a finite sample space, based on an
+ * enumerated list of &lt;value, probability&gt; pairs. Input probabilities must all be
+ * non-negative, but zero values are allowed and their sum does not have to equal one. Constructors
+ * will normalize input probabilities to make them sum to one.
+ *
+ * <p>The list of <value, probability> pairs does not, strictly speaking, have to be a function and
+ * it can contain null values. The pmf created by the constructor will combine probabilities of
+ * equal values and will treat null values as equal. For example, if the list of pairs &lt;"dog",
+ * 0.2&gt;, &lt;null, 0.1&gt;, &lt;"pig", 0.2&gt;, &lt;"dog", 0.1&gt;, &lt;null, 0.4&gt; is provided
+ * to the constructor, the resulting pmf will assign mass of 0.5 to null, 0.3 to "dog" and 0.2 to
+ * null.
+ *
+ * @param <T> type of the elements in the sample space.
+ * @since 3.2
+ */
+public class EnumeratedDistribution<T> implements Serializable {
+
+    /** Serializable UID. */
+    private static final long serialVersionUID = 20123308L;
+
+    /** RNG instance used to generate samples from the distribution. */
+    protected final RandomGenerator random;
+
+    /** List of random variable values. */
+    private final List<T> singletons;
+
+    /**
+     * Probabilities of respective random variable values. For i = 0, ..., singletons.size() - 1,
+     * probability[i] is the probability that a random variable following this distribution takes
+     * the value singletons[i].
+     */
+    private final double[] probabilities;
+
+    /** Cumulative probabilities, cached to speed up sampling. */
+    private final double[] cumulativeProbabilities;
+
+    /**
+     * Create an enumerated distribution using the given probability mass function enumeration.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param pmf probability mass function enumerated as a list of <T, probability> pairs.
+     * @throws NotPositiveException if any of the probabilities are negative.
+     * @throws NotFiniteNumberException if any of the probabilities are infinite.
+     * @throws NotANumberException if any of the probabilities are NaN.
+     * @throws MathArithmeticException all of the probabilities are 0.
+     */
+    public EnumeratedDistribution(final List<Pair<T, Double>> pmf)
+            throws NotPositiveException,
+                    MathArithmeticException,
+                    NotFiniteNumberException,
+                    NotANumberException {
+        this(new Well19937c(), pmf);
+    }
+
+    /**
+     * Create an enumerated distribution using the given random number generator and probability
+     * mass function enumeration.
+     *
+     * @param rng random number generator.
+     * @param pmf probability mass function enumerated as a list of <T, probability> pairs.
+     * @throws NotPositiveException if any of the probabilities are negative.
+     * @throws NotFiniteNumberException if any of the probabilities are infinite.
+     * @throws NotANumberException if any of the probabilities are NaN.
+     * @throws MathArithmeticException all of the probabilities are 0.
+     */
+    public EnumeratedDistribution(final RandomGenerator rng, final List<Pair<T, Double>> pmf)
+            throws NotPositiveException,
+                    MathArithmeticException,
+                    NotFiniteNumberException,
+                    NotANumberException {
+        random = rng;
+
+        singletons = new ArrayList<T>(pmf.size());
+        final double[] probs = new double[pmf.size()];
+
+        for (int i = 0; i < pmf.size(); i++) {
+            final Pair<T, Double> sample = pmf.get(i);
+            singletons.add(sample.getKey());
+            final double p = sample.getValue();
+            if (p < 0) {
+                throw new NotPositiveException(sample.getValue());
+            }
+            if (Double.isInfinite(p)) {
+                throw new NotFiniteNumberException(p);
+            }
+            if (Double.isNaN(p)) {
+                throw new NotANumberException();
+            }
+            probs[i] = p;
+        }
+
+        probabilities = MathArrays.normalizeArray(probs, 1.0);
+
+        cumulativeProbabilities = new double[probabilities.length];
+        double sum = 0;
+        for (int i = 0; i < probabilities.length; i++) {
+            sum += probabilities[i];
+            cumulativeProbabilities[i] = sum;
+        }
+    }
+
+    /**
+     * Reseed the random generator used to generate samples.
+     *
+     * @param seed the new seed
+     */
+    public void reseedRandomGenerator(long seed) {
+        random.setSeed(seed);
+    }
+
+    /**
+     * For a random variable {@code X} whose values are distributed according to this distribution,
+     * this method returns {@code P(X = x)}. In other words, this method represents the probability
+     * mass function (PMF) for the distribution.
+     *
+     * <p>Note that if {@code x1} and {@code x2} satisfy {@code x1.equals(x2)}, or both are null,
+     * then {@code probability(x1) = probability(x2)}.
+     *
+     * @param x the point at which the PMF is evaluated
+     * @return the value of the probability mass function at {@code x}
+     */
+    double probability(final T x) {
+        double probability = 0;
+
+        for (int i = 0; i < probabilities.length; i++) {
+            if ((x == null && singletons.get(i) == null)
+                    || (x != null && x.equals(singletons.get(i)))) {
+                probability += probabilities[i];
+            }
+        }
+
+        return probability;
+    }
+
+    /**
+     * Return the probability mass function as a list of <value, probability> pairs.
+     *
+     * <p>Note that if duplicate and / or null values were provided to the constructor when creating
+     * this EnumeratedDistribution, the returned list will contain these values. If duplicates
+     * values exist, what is returned will not represent a pmf (i.e., it is up to the caller to
+     * consolidate duplicate mass points).
+     *
+     * @return the probability mass function.
+     */
+    public List<Pair<T, Double>> getPmf() {
+        final List<Pair<T, Double>> samples = new ArrayList<Pair<T, Double>>(probabilities.length);
+
+        for (int i = 0; i < probabilities.length; i++) {
+            samples.add(new Pair<T, Double>(singletons.get(i), probabilities[i]));
+        }
+
+        return samples;
+    }
+
+    /**
+     * Generate a random value sampled from this distribution.
+     *
+     * @return a random value.
+     */
+    public T sample() {
+        final double randomValue = random.nextDouble();
+
+        int index = Arrays.binarySearch(cumulativeProbabilities, randomValue);
+        if (index < 0) {
+            index = -index - 1;
+        }
+
+        if (index >= 0
+                && index < probabilities.length
+                && randomValue < cumulativeProbabilities[index]) {
+            return singletons.get(index);
+        }
+
+        /* This should never happen, but it ensures we will return a correct
+         * object in case there is some floating point inequality problem
+         * wrt the cumulative probabilities. */
+        return singletons.get(singletons.size() - 1);
+    }
+
+    /**
+     * Generate a random sample from the distribution.
+     *
+     * @param sampleSize the number of random values to generate.
+     * @return an array representing the random sample.
+     * @throws NotStrictlyPositiveException if {@code sampleSize} is not positive.
+     */
+    public Object[] sample(int sampleSize) throws NotStrictlyPositiveException {
+        if (sampleSize <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.NUMBER_OF_SAMPLES, sampleSize);
+        }
+
+        final Object[] out = new Object[sampleSize];
+
+        for (int i = 0; i < sampleSize; i++) {
+            out[i] = sample();
+        }
+
+        return out;
+    }
+
+    /**
+     * Generate a random sample from the distribution.
+     *
+     * <p>If the requested samples fit in the specified array, it is returned therein. Otherwise, a
+     * new array is allocated with the runtime type of the specified array and the size of this
+     * collection.
+     *
+     * @param sampleSize the number of random values to generate.
+     * @param array the array to populate.
+     * @return an array representing the random sample.
+     * @throws NotStrictlyPositiveException if {@code sampleSize} is not positive.
+     * @throws NullArgumentException if {@code array} is null
+     */
+    public T[] sample(int sampleSize, final T[] array) throws NotStrictlyPositiveException {
+        if (sampleSize <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.NUMBER_OF_SAMPLES, sampleSize);
+        }
+
+        if (array == null) {
+            throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY);
+        }
+
+        T[] out;
+        if (array.length < sampleSize) {
+            @SuppressWarnings("unchecked") // safe as both are of type T
+            final T[] unchecked =
+                    (T[]) Array.newInstance(array.getClass().getComponentType(), sampleSize);
+            out = unchecked;
+        } else {
+            out = array;
+        }
+
+        for (int i = 0; i < sampleSize; i++) {
+            out[i] = sample();
+        }
+
+        return out;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/EnumeratedIntegerDistribution.java b/src/main/java/org/apache/commons/math3/distribution/EnumeratedIntegerDistribution.java
new file mode 100644
index 0000000..37daf57
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/EnumeratedIntegerDistribution.java
@@ -0,0 +1,274 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.NotANumberException;
+import org.apache.commons.math3.exception.NotFiniteNumberException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well19937c;
+import org.apache.commons.math3.util.Pair;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Implementation of an integer-valued {@link EnumeratedDistribution}.
+ *
+ * <p>Values with zero-probability are allowed but they do not extend the support.<br>
+ * Duplicate values are allowed. Probabilities of duplicate values are combined when computing
+ * cumulative probabilities and statistics.
+ *
+ * @since 3.2
+ */
+public class EnumeratedIntegerDistribution extends AbstractIntegerDistribution {
+
+    /** Serializable UID. */
+    private static final long serialVersionUID = 20130308L;
+
+    /**
+     * {@link EnumeratedDistribution} instance (using the {@link Integer} wrapper) used to generate
+     * the pmf.
+     */
+    protected final EnumeratedDistribution<Integer> innerDistribution;
+
+    /**
+     * Create a discrete distribution using the given probability mass function definition.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param singletons array of random variable values.
+     * @param probabilities array of probabilities.
+     * @throws DimensionMismatchException if {@code singletons.length != probabilities.length}
+     * @throws NotPositiveException if any of the probabilities are negative.
+     * @throws NotFiniteNumberException if any of the probabilities are infinite.
+     * @throws NotANumberException if any of the probabilities are NaN.
+     * @throws MathArithmeticException all of the probabilities are 0.
+     */
+    public EnumeratedIntegerDistribution(final int[] singletons, final double[] probabilities)
+            throws DimensionMismatchException,
+                    NotPositiveException,
+                    MathArithmeticException,
+                    NotFiniteNumberException,
+                    NotANumberException {
+        this(new Well19937c(), singletons, probabilities);
+    }
+
+    /**
+     * Create a discrete distribution using the given random number generator and probability mass
+     * function definition.
+     *
+     * @param rng random number generator.
+     * @param singletons array of random variable values.
+     * @param probabilities array of probabilities.
+     * @throws DimensionMismatchException if {@code singletons.length != probabilities.length}
+     * @throws NotPositiveException if any of the probabilities are negative.
+     * @throws NotFiniteNumberException if any of the probabilities are infinite.
+     * @throws NotANumberException if any of the probabilities are NaN.
+     * @throws MathArithmeticException all of the probabilities are 0.
+     */
+    public EnumeratedIntegerDistribution(
+            final RandomGenerator rng, final int[] singletons, final double[] probabilities)
+            throws DimensionMismatchException,
+                    NotPositiveException,
+                    MathArithmeticException,
+                    NotFiniteNumberException,
+                    NotANumberException {
+        super(rng);
+        innerDistribution =
+                new EnumeratedDistribution<Integer>(
+                        rng, createDistribution(singletons, probabilities));
+    }
+
+    /**
+     * Create a discrete integer-valued distribution from the input data. Values are assigned mass
+     * based on their frequency.
+     *
+     * @param rng random number generator used for sampling
+     * @param data input dataset
+     * @since 3.6
+     */
+    public EnumeratedIntegerDistribution(final RandomGenerator rng, final int[] data) {
+        super(rng);
+        final Map<Integer, Integer> dataMap = new HashMap<Integer, Integer>();
+        for (int value : data) {
+            Integer count = dataMap.get(value);
+            if (count == null) {
+                count = 0;
+            }
+            dataMap.put(value, ++count);
+        }
+        final int massPoints = dataMap.size();
+        final double denom = data.length;
+        final int[] values = new int[massPoints];
+        final double[] probabilities = new double[massPoints];
+        int index = 0;
+        for (Entry<Integer, Integer> entry : dataMap.entrySet()) {
+            values[index] = entry.getKey();
+            probabilities[index] = entry.getValue().intValue() / denom;
+            index++;
+        }
+        innerDistribution =
+                new EnumeratedDistribution<Integer>(rng, createDistribution(values, probabilities));
+    }
+
+    /**
+     * Create a discrete integer-valued distribution from the input data. Values are assigned mass
+     * based on their frequency. For example, [0,1,1,2] as input creates a distribution with values
+     * 0, 1 and 2 having probability masses 0.25, 0.5 and 0.25 respectively,
+     *
+     * @param data input dataset
+     * @since 3.6
+     */
+    public EnumeratedIntegerDistribution(final int[] data) {
+        this(new Well19937c(), data);
+    }
+
+    /**
+     * Create the list of Pairs representing the distribution from singletons and probabilities.
+     *
+     * @param singletons values
+     * @param probabilities probabilities
+     * @return list of value/probability pairs
+     */
+    private static List<Pair<Integer, Double>> createDistribution(
+            int[] singletons, double[] probabilities) {
+        if (singletons.length != probabilities.length) {
+            throw new DimensionMismatchException(probabilities.length, singletons.length);
+        }
+
+        final List<Pair<Integer, Double>> samples =
+                new ArrayList<Pair<Integer, Double>>(singletons.length);
+
+        for (int i = 0; i < singletons.length; i++) {
+            samples.add(new Pair<Integer, Double>(singletons[i], probabilities[i]));
+        }
+        return samples;
+    }
+
+    /** {@inheritDoc} */
+    public double probability(final int x) {
+        return innerDistribution.probability(x);
+    }
+
+    /** {@inheritDoc} */
+    public double cumulativeProbability(final int x) {
+        double probability = 0;
+
+        for (final Pair<Integer, Double> sample : innerDistribution.getPmf()) {
+            if (sample.getKey() <= x) {
+                probability += sample.getValue();
+            }
+        }
+
+        return probability;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @return {@code sum(singletons[i] * probabilities[i])}
+     */
+    public double getNumericalMean() {
+        double mean = 0;
+
+        for (final Pair<Integer, Double> sample : innerDistribution.getPmf()) {
+            mean += sample.getValue() * sample.getKey();
+        }
+
+        return mean;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @return {@code sum((singletons[i] - mean) ^ 2 * probabilities[i])}
+     */
+    public double getNumericalVariance() {
+        double mean = 0;
+        double meanOfSquares = 0;
+
+        for (final Pair<Integer, Double> sample : innerDistribution.getPmf()) {
+            mean += sample.getValue() * sample.getKey();
+            meanOfSquares += sample.getValue() * sample.getKey() * sample.getKey();
+        }
+
+        return meanOfSquares - mean * mean;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>Returns the lowest value with non-zero probability.
+     *
+     * @return the lowest value with non-zero probability.
+     */
+    public int getSupportLowerBound() {
+        int min = Integer.MAX_VALUE;
+        for (final Pair<Integer, Double> sample : innerDistribution.getPmf()) {
+            if (sample.getKey() < min && sample.getValue() > 0) {
+                min = sample.getKey();
+            }
+        }
+
+        return min;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>Returns the highest value with non-zero probability.
+     *
+     * @return the highest value with non-zero probability.
+     */
+    public int getSupportUpperBound() {
+        int max = Integer.MIN_VALUE;
+        for (final Pair<Integer, Double> sample : innerDistribution.getPmf()) {
+            if (sample.getKey() > max && sample.getValue() > 0) {
+                max = sample.getKey();
+            }
+        }
+
+        return max;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The support of this distribution is connected.
+     *
+     * @return {@code true}
+     */
+    public boolean isSupportConnected() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int sample() {
+        return innerDistribution.sample();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/EnumeratedRealDistribution.java b/src/main/java/org/apache/commons/math3/distribution/EnumeratedRealDistribution.java
new file mode 100644
index 0000000..2dd35ec
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/EnumeratedRealDistribution.java
@@ -0,0 +1,336 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.NotANumberException;
+import org.apache.commons.math3.exception.NotFiniteNumberException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well19937c;
+import org.apache.commons.math3.util.Pair;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Implementation of a real-valued {@link EnumeratedDistribution}.
+ *
+ * <p>Values with zero-probability are allowed but they do not extend the support.<br>
+ * Duplicate values are allowed. Probabilities of duplicate values are combined when computing
+ * cumulative probabilities and statistics.
+ *
+ * @since 3.2
+ */
+public class EnumeratedRealDistribution extends AbstractRealDistribution {
+
+    /** Serializable UID. */
+    private static final long serialVersionUID = 20130308L;
+
+    /**
+     * {@link EnumeratedDistribution} (using the {@link Double} wrapper) used to generate the pmf.
+     */
+    protected final EnumeratedDistribution<Double> innerDistribution;
+
+    /**
+     * Create a discrete real-valued distribution using the given probability mass function
+     * enumeration.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param singletons array of random variable values.
+     * @param probabilities array of probabilities.
+     * @throws DimensionMismatchException if {@code singletons.length != probabilities.length}
+     * @throws NotPositiveException if any of the probabilities are negative.
+     * @throws NotFiniteNumberException if any of the probabilities are infinite.
+     * @throws NotANumberException if any of the probabilities are NaN.
+     * @throws MathArithmeticException all of the probabilities are 0.
+     */
+    public EnumeratedRealDistribution(final double[] singletons, final double[] probabilities)
+            throws DimensionMismatchException,
+                    NotPositiveException,
+                    MathArithmeticException,
+                    NotFiniteNumberException,
+                    NotANumberException {
+        this(new Well19937c(), singletons, probabilities);
+    }
+
+    /**
+     * Create a discrete real-valued distribution using the given random number generator and
+     * probability mass function enumeration.
+     *
+     * @param rng random number generator.
+     * @param singletons array of random variable values.
+     * @param probabilities array of probabilities.
+     * @throws DimensionMismatchException if {@code singletons.length != probabilities.length}
+     * @throws NotPositiveException if any of the probabilities are negative.
+     * @throws NotFiniteNumberException if any of the probabilities are infinite.
+     * @throws NotANumberException if any of the probabilities are NaN.
+     * @throws MathArithmeticException all of the probabilities are 0.
+     */
+    public EnumeratedRealDistribution(
+            final RandomGenerator rng, final double[] singletons, final double[] probabilities)
+            throws DimensionMismatchException,
+                    NotPositiveException,
+                    MathArithmeticException,
+                    NotFiniteNumberException,
+                    NotANumberException {
+        super(rng);
+
+        innerDistribution =
+                new EnumeratedDistribution<Double>(
+                        rng, createDistribution(singletons, probabilities));
+    }
+
+    /**
+     * Create a discrete real-valued distribution from the input data. Values are assigned mass
+     * based on their frequency.
+     *
+     * @param rng random number generator used for sampling
+     * @param data input dataset
+     * @since 3.6
+     */
+    public EnumeratedRealDistribution(final RandomGenerator rng, final double[] data) {
+        super(rng);
+        final Map<Double, Integer> dataMap = new HashMap<Double, Integer>();
+        for (double value : data) {
+            Integer count = dataMap.get(value);
+            if (count == null) {
+                count = 0;
+            }
+            dataMap.put(value, ++count);
+        }
+        final int massPoints = dataMap.size();
+        final double denom = data.length;
+        final double[] values = new double[massPoints];
+        final double[] probabilities = new double[massPoints];
+        int index = 0;
+        for (Entry<Double, Integer> entry : dataMap.entrySet()) {
+            values[index] = entry.getKey();
+            probabilities[index] = entry.getValue().intValue() / denom;
+            index++;
+        }
+        innerDistribution =
+                new EnumeratedDistribution<Double>(rng, createDistribution(values, probabilities));
+    }
+
+    /**
+     * Create a discrete real-valued distribution from the input data. Values are assigned mass
+     * based on their frequency. For example, [0,1,1,2] as input creates a distribution with values
+     * 0, 1 and 2 having probability masses 0.25, 0.5 and 0.25 respectively,
+     *
+     * @param data input dataset
+     * @since 3.6
+     */
+    public EnumeratedRealDistribution(final double[] data) {
+        this(new Well19937c(), data);
+    }
+
+    /**
+     * Create the list of Pairs representing the distribution from singletons and probabilities.
+     *
+     * @param singletons values
+     * @param probabilities probabilities
+     * @return list of value/probability pairs
+     */
+    private static List<Pair<Double, Double>> createDistribution(
+            double[] singletons, double[] probabilities) {
+        if (singletons.length != probabilities.length) {
+            throw new DimensionMismatchException(probabilities.length, singletons.length);
+        }
+
+        final List<Pair<Double, Double>> samples =
+                new ArrayList<Pair<Double, Double>>(singletons.length);
+
+        for (int i = 0; i < singletons.length; i++) {
+            samples.add(new Pair<Double, Double>(singletons[i], probabilities[i]));
+        }
+        return samples;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double probability(final double x) {
+        return innerDistribution.probability(x);
+    }
+
+    /**
+     * For a random variable {@code X} whose values are distributed according to this distribution,
+     * this method returns {@code P(X = x)}. In other words, this method represents the probability
+     * mass function (PMF) for the distribution.
+     *
+     * @param x the point at which the PMF is evaluated
+     * @return the value of the probability mass function at point {@code x}
+     */
+    public double density(final double x) {
+        return probability(x);
+    }
+
+    /** {@inheritDoc} */
+    public double cumulativeProbability(final double x) {
+        double probability = 0;
+
+        for (final Pair<Double, Double> sample : innerDistribution.getPmf()) {
+            if (sample.getKey() <= x) {
+                probability += sample.getValue();
+            }
+        }
+
+        return probability;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double inverseCumulativeProbability(final double p) throws OutOfRangeException {
+        if (p < 0.0 || p > 1.0) {
+            throw new OutOfRangeException(p, 0, 1);
+        }
+
+        double probability = 0;
+        double x = getSupportLowerBound();
+        for (final Pair<Double, Double> sample : innerDistribution.getPmf()) {
+            if (sample.getValue() == 0.0) {
+                continue;
+            }
+
+            probability += sample.getValue();
+            x = sample.getKey();
+
+            if (probability >= p) {
+                break;
+            }
+        }
+
+        return x;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @return {@code sum(singletons[i] * probabilities[i])}
+     */
+    public double getNumericalMean() {
+        double mean = 0;
+
+        for (final Pair<Double, Double> sample : innerDistribution.getPmf()) {
+            mean += sample.getValue() * sample.getKey();
+        }
+
+        return mean;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @return {@code sum((singletons[i] - mean) ^ 2 * probabilities[i])}
+     */
+    public double getNumericalVariance() {
+        double mean = 0;
+        double meanOfSquares = 0;
+
+        for (final Pair<Double, Double> sample : innerDistribution.getPmf()) {
+            mean += sample.getValue() * sample.getKey();
+            meanOfSquares += sample.getValue() * sample.getKey() * sample.getKey();
+        }
+
+        return meanOfSquares - mean * mean;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>Returns the lowest value with non-zero probability.
+     *
+     * @return the lowest value with non-zero probability.
+     */
+    public double getSupportLowerBound() {
+        double min = Double.POSITIVE_INFINITY;
+        for (final Pair<Double, Double> sample : innerDistribution.getPmf()) {
+            if (sample.getKey() < min && sample.getValue() > 0) {
+                min = sample.getKey();
+            }
+        }
+
+        return min;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>Returns the highest value with non-zero probability.
+     *
+     * @return the highest value with non-zero probability.
+     */
+    public double getSupportUpperBound() {
+        double max = Double.NEGATIVE_INFINITY;
+        for (final Pair<Double, Double> sample : innerDistribution.getPmf()) {
+            if (sample.getKey() > max && sample.getValue() > 0) {
+                max = sample.getKey();
+            }
+        }
+
+        return max;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The support of this distribution includes the lower bound.
+     *
+     * @return {@code true}
+     */
+    public boolean isSupportLowerBoundInclusive() {
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The support of this distribution includes the upper bound.
+     *
+     * @return {@code true}
+     */
+    public boolean isSupportUpperBoundInclusive() {
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The support of this distribution is connected.
+     *
+     * @return {@code true}
+     */
+    public boolean isSupportConnected() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double sample() {
+        return innerDistribution.sample();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/ExponentialDistribution.java b/src/main/java/org/apache/commons/math3/distribution/ExponentialDistribution.java
new file mode 100644
index 0000000..6ca5f1c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/ExponentialDistribution.java
@@ -0,0 +1,342 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well19937c;
+import org.apache.commons.math3.util.CombinatoricsUtils;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.ResizableDoubleArray;
+
+/**
+ * Implementation of the exponential distribution.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Exponential_distribution">Exponential distribution
+ *     (Wikipedia)</a>
+ * @see <a href="http://mathworld.wolfram.com/ExponentialDistribution.html">Exponential distribution
+ *     (MathWorld)</a>
+ */
+public class ExponentialDistribution extends AbstractRealDistribution {
+    /**
+     * Default inverse cumulative probability accuracy.
+     *
+     * @since 2.1
+     */
+    public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9;
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 2401296428283614780L;
+
+    /**
+     * Used when generating Exponential samples. Table containing the constants q_i = sum_{j=1}^i
+     * (ln 2)^j/j! = ln 2 + (ln 2)^2/2 + ... + (ln 2)^i/i! until the largest representable fraction
+     * below 1 is exceeded.
+     *
+     * <p>Note that 1 = 2 - 1 = exp(ln 2) - 1 = sum_{n=1}^infty (ln 2)^n / n! thus q_i -> 1 as i ->
+     * +inf, so the higher i, the closer to one we get (the series is not alternating).
+     *
+     * <p>By trying, n = 16 in Java is enough to reach 1.0.
+     */
+    private static final double[] EXPONENTIAL_SA_QI;
+
+    /** The mean of this distribution. */
+    private final double mean;
+
+    /** The logarithm of the mean, stored to reduce computing time. * */
+    private final double logMean;
+
+    /** Inverse cumulative probability accuracy. */
+    private final double solverAbsoluteAccuracy;
+
+    /** Initialize tables. */
+    static {
+        /** Filling EXPONENTIAL_SA_QI table. Note that we don't want qi = 0 in the table. */
+        final double LN2 = FastMath.log(2);
+        double qi = 0;
+        int i = 1;
+
+        /**
+         * ArithmeticUtils provides factorials up to 20, so let's use that limit together with
+         * Precision.EPSILON to generate the following code (a priori, we know that there will be 16
+         * elements, but it is better to not hardcode it).
+         */
+        final ResizableDoubleArray ra = new ResizableDoubleArray(20);
+
+        while (qi < 1) {
+            qi += FastMath.pow(LN2, i) / CombinatoricsUtils.factorial(i);
+            ra.addElement(qi);
+            ++i;
+        }
+
+        EXPONENTIAL_SA_QI = ra.getElements();
+    }
+
+    /**
+     * Create an exponential distribution with the given mean.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param mean mean of this distribution.
+     */
+    public ExponentialDistribution(double mean) {
+        this(mean, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Create an exponential distribution with the given mean.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param mean Mean of this distribution.
+     * @param inverseCumAccuracy Maximum absolute error in inverse cumulative probability estimates
+     *     (defaults to {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}).
+     * @throws NotStrictlyPositiveException if {@code mean <= 0}.
+     * @since 2.1
+     */
+    public ExponentialDistribution(double mean, double inverseCumAccuracy) {
+        this(new Well19937c(), mean, inverseCumAccuracy);
+    }
+
+    /**
+     * Creates an exponential distribution.
+     *
+     * @param rng Random number generator.
+     * @param mean Mean of this distribution.
+     * @throws NotStrictlyPositiveException if {@code mean <= 0}.
+     * @since 3.3
+     */
+    public ExponentialDistribution(RandomGenerator rng, double mean)
+            throws NotStrictlyPositiveException {
+        this(rng, mean, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Creates an exponential distribution.
+     *
+     * @param rng Random number generator.
+     * @param mean Mean of this distribution.
+     * @param inverseCumAccuracy Maximum absolute error in inverse cumulative probability estimates
+     *     (defaults to {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}).
+     * @throws NotStrictlyPositiveException if {@code mean <= 0}.
+     * @since 3.1
+     */
+    public ExponentialDistribution(RandomGenerator rng, double mean, double inverseCumAccuracy)
+            throws NotStrictlyPositiveException {
+        super(rng);
+
+        if (mean <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.MEAN, mean);
+        }
+        this.mean = mean;
+        logMean = FastMath.log(mean);
+        solverAbsoluteAccuracy = inverseCumAccuracy;
+    }
+
+    /**
+     * Access the mean.
+     *
+     * @return the mean.
+     */
+    public double getMean() {
+        return mean;
+    }
+
+    /** {@inheritDoc} */
+    public double density(double x) {
+        final double logDensity = logDensity(x);
+        return logDensity == Double.NEGATIVE_INFINITY ? 0 : FastMath.exp(logDensity);
+    }
+
+    /** {@inheritDoc} * */
+    @Override
+    public double logDensity(double x) {
+        if (x < 0) {
+            return Double.NEGATIVE_INFINITY;
+        }
+        return -x / mean - logMean;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The implementation of this method is based on:
+     *
+     * <ul>
+     *   <li><a href="http://mathworld.wolfram.com/ExponentialDistribution.html">Exponential
+     *       Distribution</a>, equation (1).
+     * </ul>
+     */
+    public double cumulativeProbability(double x) {
+        double ret;
+        if (x <= 0.0) {
+            ret = 0.0;
+        } else {
+            ret = 1.0 - FastMath.exp(-x / mean);
+        }
+        return ret;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>Returns {@code 0} when {@code p= = 0} and {@code Double.POSITIVE_INFINITY} when {@code p
+     * == 1}.
+     */
+    @Override
+    public double inverseCumulativeProbability(double p) throws OutOfRangeException {
+        double ret;
+
+        if (p < 0.0 || p > 1.0) {
+            throw new OutOfRangeException(p, 0.0, 1.0);
+        } else if (p == 1.0) {
+            ret = Double.POSITIVE_INFINITY;
+        } else {
+            ret = -mean * FastMath.log(1.0 - p);
+        }
+
+        return ret;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p><strong>Algorithm Description</strong>: this implementation uses the <a
+     * href="http://www.jesus.ox.ac.uk/~clifford/a5/chap1/node5.html">Inversion Method</a> to
+     * generate exponentially distributed random values from uniform deviates.
+     *
+     * @return a random value.
+     * @since 2.2
+     */
+    @Override
+    public double sample() {
+        // Step 1:
+        double a = 0;
+        double u = random.nextDouble();
+
+        // Step 2 and 3:
+        while (u < 0.5) {
+            a += EXPONENTIAL_SA_QI[0];
+            u *= 2;
+        }
+
+        // Step 4 (now u >= 0.5):
+        u += u - 1;
+
+        // Step 5:
+        if (u <= EXPONENTIAL_SA_QI[0]) {
+            return mean * (a + u);
+        }
+
+        // Step 6:
+        int i = 0; // Should be 1, be we iterate before it in while using 0
+        double u2 = random.nextDouble();
+        double umin = u2;
+
+        // Step 7 and 8:
+        do {
+            ++i;
+            u2 = random.nextDouble();
+
+            if (u2 < umin) {
+                umin = u2;
+            }
+
+            // Step 8:
+        } while (u > EXPONENTIAL_SA_QI[i]); // Ensured to exit since EXPONENTIAL_SA_QI[MAX] = 1
+
+        return mean * (a + umin * EXPONENTIAL_SA_QI[0]);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected double getSolverAbsoluteAccuracy() {
+        return solverAbsoluteAccuracy;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For mean parameter {@code k}, the mean is {@code k}.
+     */
+    public double getNumericalMean() {
+        return getMean();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For mean parameter {@code k}, the variance is {@code k^2}.
+     */
+    public double getNumericalVariance() {
+        final double m = getMean();
+        return m * m;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The lower bound of the support is always 0 no matter the mean parameter.
+     *
+     * @return lower bound of the support (always 0)
+     */
+    public double getSupportLowerBound() {
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The upper bound of the support is always positive infinity no matter the mean parameter.
+     *
+     * @return upper bound of the support (always Double.POSITIVE_INFINITY)
+     */
+    public double getSupportUpperBound() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportLowerBoundInclusive() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportUpperBoundInclusive() {
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The support of this distribution is connected.
+     *
+     * @return {@code true}
+     */
+    public boolean isSupportConnected() {
+        return true;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/FDistribution.java b/src/main/java/org/apache/commons/math3/distribution/FDistribution.java
new file mode 100644
index 0000000..3269f8d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/FDistribution.java
@@ -0,0 +1,341 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well19937c;
+import org.apache.commons.math3.special.Beta;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Implementation of the F-distribution.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/F-distribution">F-distribution (Wikipedia)</a>
+ * @see <a href="http://mathworld.wolfram.com/F-Distribution.html">F-distribution (MathWorld)</a>
+ */
+public class FDistribution extends AbstractRealDistribution {
+    /**
+     * Default inverse cumulative probability accuracy.
+     *
+     * @since 2.1
+     */
+    public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9;
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -8516354193418641566L;
+
+    /** The numerator degrees of freedom. */
+    private final double numeratorDegreesOfFreedom;
+
+    /** The numerator degrees of freedom. */
+    private final double denominatorDegreesOfFreedom;
+
+    /** Inverse cumulative probability accuracy. */
+    private final double solverAbsoluteAccuracy;
+
+    /** Cached numerical variance */
+    private double numericalVariance = Double.NaN;
+
+    /** Whether or not the numerical variance has been calculated */
+    private boolean numericalVarianceIsCalculated = false;
+
+    /**
+     * Creates an F distribution using the given degrees of freedom.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param numeratorDegreesOfFreedom Numerator degrees of freedom.
+     * @param denominatorDegreesOfFreedom Denominator degrees of freedom.
+     * @throws NotStrictlyPositiveException if {@code numeratorDegreesOfFreedom <= 0} or {@code
+     *     denominatorDegreesOfFreedom <= 0}.
+     */
+    public FDistribution(double numeratorDegreesOfFreedom, double denominatorDegreesOfFreedom)
+            throws NotStrictlyPositiveException {
+        this(
+                numeratorDegreesOfFreedom,
+                denominatorDegreesOfFreedom,
+                DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Creates an F distribution using the given degrees of freedom and inverse cumulative
+     * probability accuracy.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param numeratorDegreesOfFreedom Numerator degrees of freedom.
+     * @param denominatorDegreesOfFreedom Denominator degrees of freedom.
+     * @param inverseCumAccuracy the maximum absolute error in inverse cumulative probability
+     *     estimates.
+     * @throws NotStrictlyPositiveException if {@code numeratorDegreesOfFreedom <= 0} or {@code
+     *     denominatorDegreesOfFreedom <= 0}.
+     * @since 2.1
+     */
+    public FDistribution(
+            double numeratorDegreesOfFreedom,
+            double denominatorDegreesOfFreedom,
+            double inverseCumAccuracy)
+            throws NotStrictlyPositiveException {
+        this(
+                new Well19937c(),
+                numeratorDegreesOfFreedom,
+                denominatorDegreesOfFreedom,
+                inverseCumAccuracy);
+    }
+
+    /**
+     * Creates an F distribution.
+     *
+     * @param rng Random number generator.
+     * @param numeratorDegreesOfFreedom Numerator degrees of freedom.
+     * @param denominatorDegreesOfFreedom Denominator degrees of freedom.
+     * @throws NotStrictlyPositiveException if {@code numeratorDegreesOfFreedom <= 0} or {@code
+     *     denominatorDegreesOfFreedom <= 0}.
+     * @since 3.3
+     */
+    public FDistribution(
+            RandomGenerator rng,
+            double numeratorDegreesOfFreedom,
+            double denominatorDegreesOfFreedom)
+            throws NotStrictlyPositiveException {
+        this(
+                rng,
+                numeratorDegreesOfFreedom,
+                denominatorDegreesOfFreedom,
+                DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Creates an F distribution.
+     *
+     * @param rng Random number generator.
+     * @param numeratorDegreesOfFreedom Numerator degrees of freedom.
+     * @param denominatorDegreesOfFreedom Denominator degrees of freedom.
+     * @param inverseCumAccuracy the maximum absolute error in inverse cumulative probability
+     *     estimates.
+     * @throws NotStrictlyPositiveException if {@code numeratorDegreesOfFreedom <= 0} or {@code
+     *     denominatorDegreesOfFreedom <= 0}.
+     * @since 3.1
+     */
+    public FDistribution(
+            RandomGenerator rng,
+            double numeratorDegreesOfFreedom,
+            double denominatorDegreesOfFreedom,
+            double inverseCumAccuracy)
+            throws NotStrictlyPositiveException {
+        super(rng);
+
+        if (numeratorDegreesOfFreedom <= 0) {
+            throw new NotStrictlyPositiveException(
+                    LocalizedFormats.DEGREES_OF_FREEDOM, numeratorDegreesOfFreedom);
+        }
+        if (denominatorDegreesOfFreedom <= 0) {
+            throw new NotStrictlyPositiveException(
+                    LocalizedFormats.DEGREES_OF_FREEDOM, denominatorDegreesOfFreedom);
+        }
+        this.numeratorDegreesOfFreedom = numeratorDegreesOfFreedom;
+        this.denominatorDegreesOfFreedom = denominatorDegreesOfFreedom;
+        solverAbsoluteAccuracy = inverseCumAccuracy;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 2.1
+     */
+    public double density(double x) {
+        return FastMath.exp(logDensity(x));
+    }
+
+    /** {@inheritDoc} * */
+    @Override
+    public double logDensity(double x) {
+        final double nhalf = numeratorDegreesOfFreedom / 2;
+        final double mhalf = denominatorDegreesOfFreedom / 2;
+        final double logx = FastMath.log(x);
+        final double logn = FastMath.log(numeratorDegreesOfFreedom);
+        final double logm = FastMath.log(denominatorDegreesOfFreedom);
+        final double lognxm =
+                FastMath.log(numeratorDegreesOfFreedom * x + denominatorDegreesOfFreedom);
+        return nhalf * logn
+                + nhalf * logx
+                - logx
+                + mhalf * logm
+                - nhalf * lognxm
+                - mhalf * lognxm
+                - Beta.logBeta(nhalf, mhalf);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The implementation of this method is based on
+     *
+     * <ul>
+     *   <li><a href="http://mathworld.wolfram.com/F-Distribution.html">F-Distribution</a>, equation
+     *       (4).
+     * </ul>
+     */
+    public double cumulativeProbability(double x) {
+        double ret;
+        if (x <= 0) {
+            ret = 0;
+        } else {
+            double n = numeratorDegreesOfFreedom;
+            double m = denominatorDegreesOfFreedom;
+
+            ret = Beta.regularizedBeta((n * x) / (m + n * x), 0.5 * n, 0.5 * m);
+        }
+        return ret;
+    }
+
+    /**
+     * Access the numerator degrees of freedom.
+     *
+     * @return the numerator degrees of freedom.
+     */
+    public double getNumeratorDegreesOfFreedom() {
+        return numeratorDegreesOfFreedom;
+    }
+
+    /**
+     * Access the denominator degrees of freedom.
+     *
+     * @return the denominator degrees of freedom.
+     */
+    public double getDenominatorDegreesOfFreedom() {
+        return denominatorDegreesOfFreedom;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected double getSolverAbsoluteAccuracy() {
+        return solverAbsoluteAccuracy;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For denominator degrees of freedom parameter {@code b}, the mean is
+     *
+     * <ul>
+     *   <li>if {@code b > 2} then {@code b / (b - 2)},
+     *   <li>else undefined ({@code Double.NaN}).
+     * </ul>
+     */
+    public double getNumericalMean() {
+        final double denominatorDF = getDenominatorDegreesOfFreedom();
+
+        if (denominatorDF > 2) {
+            return denominatorDF / (denominatorDF - 2);
+        }
+
+        return Double.NaN;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For numerator degrees of freedom parameter {@code a} and denominator degrees of freedom
+     * parameter {@code b}, the variance is
+     *
+     * <ul>
+     *   <li>if {@code b > 4} then {@code [2 * b^2 * (a + b - 2)] / [a * (b - 2)^2 * (b - 4)]},
+     *   <li>else undefined ({@code Double.NaN}).
+     * </ul>
+     */
+    public double getNumericalVariance() {
+        if (!numericalVarianceIsCalculated) {
+            numericalVariance = calculateNumericalVariance();
+            numericalVarianceIsCalculated = true;
+        }
+        return numericalVariance;
+    }
+
+    /**
+     * used by {@link #getNumericalVariance()}
+     *
+     * @return the variance of this distribution
+     */
+    protected double calculateNumericalVariance() {
+        final double denominatorDF = getDenominatorDegreesOfFreedom();
+
+        if (denominatorDF > 4) {
+            final double numeratorDF = getNumeratorDegreesOfFreedom();
+            final double denomDFMinusTwo = denominatorDF - 2;
+
+            return (2 * (denominatorDF * denominatorDF) * (numeratorDF + denominatorDF - 2))
+                    / ((numeratorDF * (denomDFMinusTwo * denomDFMinusTwo) * (denominatorDF - 4)));
+        }
+
+        return Double.NaN;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The lower bound of the support is always 0 no matter the parameters.
+     *
+     * @return lower bound of the support (always 0)
+     */
+    public double getSupportLowerBound() {
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The upper bound of the support is always positive infinity no matter the parameters.
+     *
+     * @return upper bound of the support (always Double.POSITIVE_INFINITY)
+     */
+    public double getSupportUpperBound() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportLowerBoundInclusive() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportUpperBoundInclusive() {
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The support of this distribution is connected.
+     *
+     * @return {@code true}
+     */
+    public boolean isSupportConnected() {
+        return true;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/GammaDistribution.java b/src/main/java/org/apache/commons/math3/distribution/GammaDistribution.java
new file mode 100644
index 0000000..f062fd2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/GammaDistribution.java
@@ -0,0 +1,505 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well19937c;
+import org.apache.commons.math3.special.Gamma;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Implementation of the Gamma distribution.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Gamma_distribution">Gamma distribution (Wikipedia)</a>
+ * @see <a href="http://mathworld.wolfram.com/GammaDistribution.html">Gamma distribution
+ *     (MathWorld)</a>
+ */
+public class GammaDistribution extends AbstractRealDistribution {
+    /**
+     * Default inverse cumulative probability accuracy.
+     *
+     * @since 2.1
+     */
+    public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9;
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 20120524L;
+
+    /** The shape parameter. */
+    private final double shape;
+
+    /** The scale parameter. */
+    private final double scale;
+
+    /**
+     * The constant value of {@code shape + g + 0.5}, where {@code g} is the Lanczos constant {@link
+     * Gamma#LANCZOS_G}.
+     */
+    private final double shiftedShape;
+
+    /**
+     * The constant value of {@code shape / scale * sqrt(e / (2 * pi * (shape + g + 0.5))) /
+     * L(shape)}, where {@code L(shape)} is the Lanczos approximation returned by {@link
+     * Gamma#lanczos(double)}. This prefactor is used in {@link #density(double)}, when no overflow
+     * occurs with the natural calculation.
+     */
+    private final double densityPrefactor1;
+
+    /**
+     * The constant value of {@code log(shape / scale * sqrt(e / (2 * pi * (shape + g + 0.5))) /
+     * L(shape))}, where {@code L(shape)} is the Lanczos approximation returned by {@link
+     * Gamma#lanczos(double)}. This prefactor is used in {@link #logDensity(double)}, when no
+     * overflow occurs with the natural calculation.
+     */
+    private final double logDensityPrefactor1;
+
+    /**
+     * The constant value of {@code shape * sqrt(e / (2 * pi * (shape + g + 0.5))) / L(shape)},
+     * where {@code L(shape)} is the Lanczos approximation returned by {@link
+     * Gamma#lanczos(double)}. This prefactor is used in {@link #density(double)}, when overflow
+     * occurs with the natural calculation.
+     */
+    private final double densityPrefactor2;
+
+    /**
+     * The constant value of {@code log(shape * sqrt(e / (2 * pi * (shape + g + 0.5))) / L(shape))},
+     * where {@code L(shape)} is the Lanczos approximation returned by {@link
+     * Gamma#lanczos(double)}. This prefactor is used in {@link #logDensity(double)}, when overflow
+     * occurs with the natural calculation.
+     */
+    private final double logDensityPrefactor2;
+
+    /**
+     * Lower bound on {@code y = x / scale} for the selection of the computation method in {@link
+     * #density(double)}. For {@code y <= minY}, the natural calculation overflows.
+     */
+    private final double minY;
+
+    /**
+     * Upper bound on {@code log(y)} ({@code y = x / scale}) for the selection of the computation
+     * method in {@link #density(double)}. For {@code log(y) >= maxLogY}, the natural calculation
+     * overflows.
+     */
+    private final double maxLogY;
+
+    /** Inverse cumulative probability accuracy. */
+    private final double solverAbsoluteAccuracy;
+
+    /**
+     * Creates a new gamma distribution with specified values of the shape and scale parameters.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param shape the shape parameter
+     * @param scale the scale parameter
+     * @throws NotStrictlyPositiveException if {@code shape <= 0} or {@code scale <= 0}.
+     */
+    public GammaDistribution(double shape, double scale) throws NotStrictlyPositiveException {
+        this(shape, scale, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Creates a new gamma distribution with specified values of the shape and scale parameters.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param shape the shape parameter
+     * @param scale the scale parameter
+     * @param inverseCumAccuracy the maximum absolute error in inverse cumulative probability
+     *     estimates (defaults to {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}).
+     * @throws NotStrictlyPositiveException if {@code shape <= 0} or {@code scale <= 0}.
+     * @since 2.1
+     */
+    public GammaDistribution(double shape, double scale, double inverseCumAccuracy)
+            throws NotStrictlyPositiveException {
+        this(new Well19937c(), shape, scale, inverseCumAccuracy);
+    }
+
+    /**
+     * Creates a Gamma distribution.
+     *
+     * @param rng Random number generator.
+     * @param shape the shape parameter
+     * @param scale the scale parameter
+     * @throws NotStrictlyPositiveException if {@code shape <= 0} or {@code scale <= 0}.
+     * @since 3.3
+     */
+    public GammaDistribution(RandomGenerator rng, double shape, double scale)
+            throws NotStrictlyPositiveException {
+        this(rng, shape, scale, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Creates a Gamma distribution.
+     *
+     * @param rng Random number generator.
+     * @param shape the shape parameter
+     * @param scale the scale parameter
+     * @param inverseCumAccuracy the maximum absolute error in inverse cumulative probability
+     *     estimates (defaults to {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}).
+     * @throws NotStrictlyPositiveException if {@code shape <= 0} or {@code scale <= 0}.
+     * @since 3.1
+     */
+    public GammaDistribution(
+            RandomGenerator rng, double shape, double scale, double inverseCumAccuracy)
+            throws NotStrictlyPositiveException {
+        super(rng);
+
+        if (shape <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.SHAPE, shape);
+        }
+        if (scale <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.SCALE, scale);
+        }
+
+        this.shape = shape;
+        this.scale = scale;
+        this.solverAbsoluteAccuracy = inverseCumAccuracy;
+        this.shiftedShape = shape + Gamma.LANCZOS_G + 0.5;
+        final double aux = FastMath.E / (2.0 * FastMath.PI * shiftedShape);
+        this.densityPrefactor2 = shape * FastMath.sqrt(aux) / Gamma.lanczos(shape);
+        this.logDensityPrefactor2 =
+                FastMath.log(shape) + 0.5 * FastMath.log(aux) - FastMath.log(Gamma.lanczos(shape));
+        this.densityPrefactor1 =
+                this.densityPrefactor2
+                        / scale
+                        * FastMath.pow(shiftedShape, -shape)
+                        * FastMath.exp(shape + Gamma.LANCZOS_G);
+        this.logDensityPrefactor1 =
+                this.logDensityPrefactor2
+                        - FastMath.log(scale)
+                        - FastMath.log(shiftedShape) * shape
+                        + shape
+                        + Gamma.LANCZOS_G;
+        this.minY = shape + Gamma.LANCZOS_G - FastMath.log(Double.MAX_VALUE);
+        this.maxLogY = FastMath.log(Double.MAX_VALUE) / (shape - 1.0);
+    }
+
+    /**
+     * Returns the shape parameter of {@code this} distribution.
+     *
+     * @return the shape parameter
+     * @deprecated as of version 3.1, {@link #getShape()} should be preferred. This method will be
+     *     removed in version 4.0.
+     */
+    @Deprecated
+    public double getAlpha() {
+        return shape;
+    }
+
+    /**
+     * Returns the shape parameter of {@code this} distribution.
+     *
+     * @return the shape parameter
+     * @since 3.1
+     */
+    public double getShape() {
+        return shape;
+    }
+
+    /**
+     * Returns the scale parameter of {@code this} distribution.
+     *
+     * @return the scale parameter
+     * @deprecated as of version 3.1, {@link #getScale()} should be preferred. This method will be
+     *     removed in version 4.0.
+     */
+    @Deprecated
+    public double getBeta() {
+        return scale;
+    }
+
+    /**
+     * Returns the scale parameter of {@code this} distribution.
+     *
+     * @return the scale parameter
+     * @since 3.1
+     */
+    public double getScale() {
+        return scale;
+    }
+
+    /** {@inheritDoc} */
+    public double density(double x) {
+        /* The present method must return the value of
+         *
+         *     1       x a     - x
+         * ---------- (-)  exp(---)
+         * x Gamma(a)  b        b
+         *
+         * where a is the shape parameter, and b the scale parameter.
+         * Substituting the Lanczos approximation of Gamma(a) leads to the
+         * following expression of the density
+         *
+         * a              e            1         y      a
+         * - sqrt(------------------) ---- (-----------)  exp(a - y + g),
+         * x      2 pi (a + g + 0.5)  L(a)  a + g + 0.5
+         *
+         * where y = x / b. The above formula is the "natural" computation, which
+         * is implemented when no overflow is likely to occur. If overflow occurs
+         * with the natural computation, the following identity is used. It is
+         * based on the BOOST library
+         * http://www.boost.org/doc/libs/1_35_0/libs/math/doc/sf_and_dist/html/math_toolkit/special/sf_gamma/igamma.html
+         * Formula (15) needs adaptations, which are detailed below.
+         *
+         *       y      a
+         * (-----------)  exp(a - y + g)
+         *  a + g + 0.5
+         *                              y - a - g - 0.5    y (g + 0.5)
+         *               = exp(a log1pm(---------------) - ----------- + g),
+         *                                a + g + 0.5      a + g + 0.5
+         *
+         *  where log1pm(z) = log(1 + z) - z. Therefore, the value to be
+         *  returned is
+         *
+         * a              e            1
+         * - sqrt(------------------) ----
+         * x      2 pi (a + g + 0.5)  L(a)
+         *                              y - a - g - 0.5    y (g + 0.5)
+         *               * exp(a log1pm(---------------) - ----------- + g).
+         *                                a + g + 0.5      a + g + 0.5
+         */
+        if (x < 0) {
+            return 0;
+        }
+        final double y = x / scale;
+        if ((y <= minY) || (FastMath.log(y) >= maxLogY)) {
+            /*
+             * Overflow.
+             */
+            final double aux1 = (y - shiftedShape) / shiftedShape;
+            final double aux2 = shape * (FastMath.log1p(aux1) - aux1);
+            final double aux3 =
+                    -y * (Gamma.LANCZOS_G + 0.5) / shiftedShape + Gamma.LANCZOS_G + aux2;
+            return densityPrefactor2 / x * FastMath.exp(aux3);
+        }
+        /*
+         * Natural calculation.
+         */
+        return densityPrefactor1 * FastMath.exp(-y) * FastMath.pow(y, shape - 1);
+    }
+
+    /** {@inheritDoc} * */
+    @Override
+    public double logDensity(double x) {
+        /*
+         * see the comment in {@link #density(double)} for computation details
+         */
+        if (x < 0) {
+            return Double.NEGATIVE_INFINITY;
+        }
+        final double y = x / scale;
+        if ((y <= minY) || (FastMath.log(y) >= maxLogY)) {
+            /*
+             * Overflow.
+             */
+            final double aux1 = (y - shiftedShape) / shiftedShape;
+            final double aux2 = shape * (FastMath.log1p(aux1) - aux1);
+            final double aux3 =
+                    -y * (Gamma.LANCZOS_G + 0.5) / shiftedShape + Gamma.LANCZOS_G + aux2;
+            return logDensityPrefactor2 - FastMath.log(x) + aux3;
+        }
+        /*
+         * Natural calculation.
+         */
+        return logDensityPrefactor1 - y + FastMath.log(y) * (shape - 1);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The implementation of this method is based on:
+     *
+     * <ul>
+     *   <li><a href="http://mathworld.wolfram.com/Chi-SquaredDistribution.html">Chi-Squared
+     *       Distribution</a>, equation (9).
+     *   <li>Casella, G., & Berger, R. (1990). <i>Statistical Inference</i>. Belmont, CA: Duxbury
+     *       Press.
+     * </ul>
+     */
+    public double cumulativeProbability(double x) {
+        double ret;
+
+        if (x <= 0) {
+            ret = 0;
+        } else {
+            ret = Gamma.regularizedGammaP(shape, x / scale);
+        }
+
+        return ret;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected double getSolverAbsoluteAccuracy() {
+        return solverAbsoluteAccuracy;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For shape parameter {@code alpha} and scale parameter {@code beta}, the mean is {@code
+     * alpha * beta}.
+     */
+    public double getNumericalMean() {
+        return shape * scale;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For shape parameter {@code alpha} and scale parameter {@code beta}, the variance is {@code
+     * alpha * beta^2}.
+     *
+     * @return {@inheritDoc}
+     */
+    public double getNumericalVariance() {
+        return shape * scale * scale;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The lower bound of the support is always 0 no matter the parameters.
+     *
+     * @return lower bound of the support (always 0)
+     */
+    public double getSupportLowerBound() {
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The upper bound of the support is always positive infinity no matter the parameters.
+     *
+     * @return upper bound of the support (always Double.POSITIVE_INFINITY)
+     */
+    public double getSupportUpperBound() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportLowerBoundInclusive() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportUpperBoundInclusive() {
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The support of this distribution is connected.
+     *
+     * @return {@code true}
+     */
+    public boolean isSupportConnected() {
+        return true;
+    }
+
+    /**
+     * This implementation uses the following algorithms:
+     *
+     * <p>For 0 < shape < 1: <br>
+     * Ahrens, J. H. and Dieter, U., <i>Computer methods for sampling from gamma, beta, Poisson and
+     * binomial distributions.</i> Computing, 12, 223-246, 1974.
+     *
+     * <p>For shape >= 1: <br>
+     * Marsaglia and Tsang, <i>A Simple Method for Generating Gamma Variables.</i> ACM Transactions
+     * on Mathematical Software, Volume 26 Issue 3, September, 2000.
+     *
+     * @return random value sampled from the Gamma(shape, scale) distribution
+     */
+    @Override
+    public double sample() {
+        if (shape < 1) {
+            // [1]: p. 228, Algorithm GS
+
+            while (true) {
+                // Step 1:
+                final double u = random.nextDouble();
+                final double bGS = 1 + shape / FastMath.E;
+                final double p = bGS * u;
+
+                if (p <= 1) {
+                    // Step 2:
+
+                    final double x = FastMath.pow(p, 1 / shape);
+                    final double u2 = random.nextDouble();
+
+                    if (u2 > FastMath.exp(-x)) {
+                        // Reject
+                        continue;
+                    } else {
+                        return scale * x;
+                    }
+                } else {
+                    // Step 3:
+
+                    final double x = -1 * FastMath.log((bGS - p) / shape);
+                    final double u2 = random.nextDouble();
+
+                    if (u2 > FastMath.pow(x, shape - 1)) {
+                        // Reject
+                        continue;
+                    } else {
+                        return scale * x;
+                    }
+                }
+            }
+        }
+
+        // Now shape >= 1
+
+        final double d = shape - 0.333333333333333333;
+        final double c = 1 / (3 * FastMath.sqrt(d));
+
+        while (true) {
+            final double x = random.nextGaussian();
+            final double v = (1 + c * x) * (1 + c * x) * (1 + c * x);
+
+            if (v <= 0) {
+                continue;
+            }
+
+            final double x2 = x * x;
+            final double u = random.nextDouble();
+
+            // Squeeze
+            if (u < 1 - 0.0331 * x2 * x2) {
+                return scale * d * v;
+            }
+
+            if (FastMath.log(u) < 0.5 * x2 + d * (1 - v + FastMath.log(v))) {
+                return scale * d * v;
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/GeometricDistribution.java b/src/main/java/org/apache/commons/math3/distribution/GeometricDistribution.java
new file mode 100644
index 0000000..89c0a59
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/GeometricDistribution.java
@@ -0,0 +1,186 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well19937c;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Implementation of the geometric distribution.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Geometric_distribution">Geometric distribution
+ *     (Wikipedia)</a>
+ * @see <a href="http://mathworld.wolfram.com/GeometricDistribution.html">Geometric Distribution
+ *     (MathWorld)</a>
+ * @since 3.3
+ */
+public class GeometricDistribution extends AbstractIntegerDistribution {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 20130507L;
+
+    /** The probability of success. */
+    private final double probabilityOfSuccess;
+
+    /** {@code log(p)} where p is the probability of success. */
+    private final double logProbabilityOfSuccess;
+
+    /** {@code log(1 - p)} where p is the probability of success. */
+    private final double log1mProbabilityOfSuccess;
+
+    /**
+     * Create a geometric distribution with the given probability of success.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param p probability of success.
+     * @throws OutOfRangeException if {@code p <= 0} or {@code p > 1}.
+     */
+    public GeometricDistribution(double p) {
+        this(new Well19937c(), p);
+    }
+
+    /**
+     * Creates a geometric distribution.
+     *
+     * @param rng Random number generator.
+     * @param p Probability of success.
+     * @throws OutOfRangeException if {@code p <= 0} or {@code p > 1}.
+     */
+    public GeometricDistribution(RandomGenerator rng, double p) {
+        super(rng);
+
+        if (p <= 0 || p > 1) {
+            throw new OutOfRangeException(LocalizedFormats.OUT_OF_RANGE_LEFT, p, 0, 1);
+        }
+
+        probabilityOfSuccess = p;
+        logProbabilityOfSuccess = FastMath.log(p);
+        log1mProbabilityOfSuccess = FastMath.log1p(-p);
+    }
+
+    /**
+     * Access the probability of success for this distribution.
+     *
+     * @return the probability of success.
+     */
+    public double getProbabilityOfSuccess() {
+        return probabilityOfSuccess;
+    }
+
+    /** {@inheritDoc} */
+    public double probability(int x) {
+        if (x < 0) {
+            return 0.0;
+        } else {
+            return FastMath.exp(log1mProbabilityOfSuccess * x) * probabilityOfSuccess;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double logProbability(int x) {
+        if (x < 0) {
+            return Double.NEGATIVE_INFINITY;
+        } else {
+            return x * log1mProbabilityOfSuccess + logProbabilityOfSuccess;
+        }
+    }
+
+    /** {@inheritDoc} */
+    public double cumulativeProbability(int x) {
+        if (x < 0) {
+            return 0.0;
+        } else {
+            return -FastMath.expm1(log1mProbabilityOfSuccess * (x + 1));
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For probability parameter {@code p}, the mean is {@code (1 - p) / p}.
+     */
+    public double getNumericalMean() {
+        return (1 - probabilityOfSuccess) / probabilityOfSuccess;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For probability parameter {@code p}, the variance is {@code (1 - p) / (p * p)}.
+     */
+    public double getNumericalVariance() {
+        return (1 - probabilityOfSuccess) / (probabilityOfSuccess * probabilityOfSuccess);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The lower bound of the support is always 0.
+     *
+     * @return lower bound of the support (always 0)
+     */
+    public int getSupportLowerBound() {
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The upper bound of the support is infinite (which we approximate as {@code
+     * Integer.MAX_VALUE}).
+     *
+     * @return upper bound of the support (always Integer.MAX_VALUE)
+     */
+    public int getSupportUpperBound() {
+        return Integer.MAX_VALUE;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The support of this distribution is connected.
+     *
+     * @return {@code true}
+     */
+    public boolean isSupportConnected() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int inverseCumulativeProbability(double p) throws OutOfRangeException {
+        if (p < 0 || p > 1) {
+            throw new OutOfRangeException(p, 0, 1);
+        }
+        if (p == 1) {
+            return Integer.MAX_VALUE;
+        }
+        if (p == 0) {
+            return 0;
+        }
+        return Math.max(0, (int) Math.ceil(FastMath.log1p(-p) / log1mProbabilityOfSuccess - 1));
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/GumbelDistribution.java b/src/main/java/org/apache/commons/math3/distribution/GumbelDistribution.java
new file mode 100644
index 0000000..78280a5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/GumbelDistribution.java
@@ -0,0 +1,167 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well19937c;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * This class implements the Gumbel distribution.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Gumbel_distribution">Gumbel Distribution
+ *     (Wikipedia)</a>
+ * @see <a href="http://mathworld.wolfram.com/GumbelDistribution.html">Gumbel Distribution
+ *     (Mathworld)</a>
+ * @since 3.4
+ */
+public class GumbelDistribution extends AbstractRealDistribution {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 20141003;
+
+    /**
+     * Approximation of Euler's constant see
+     * http://mathworld.wolfram.com/Euler-MascheroniConstantApproximations.html
+     */
+    private static final double EULER = FastMath.PI / (2 * FastMath.E);
+
+    /** The location parameter. */
+    private final double mu;
+
+    /** The scale parameter. */
+    private final double beta;
+
+    /**
+     * Build a new instance.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param mu location parameter
+     * @param beta scale parameter (must be positive)
+     * @throws NotStrictlyPositiveException if {@code beta <= 0}
+     */
+    public GumbelDistribution(double mu, double beta) {
+        this(new Well19937c(), mu, beta);
+    }
+
+    /**
+     * Build a new instance.
+     *
+     * @param rng Random number generator
+     * @param mu location parameter
+     * @param beta scale parameter (must be positive)
+     * @throws NotStrictlyPositiveException if {@code beta <= 0}
+     */
+    public GumbelDistribution(RandomGenerator rng, double mu, double beta) {
+        super(rng);
+
+        if (beta <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.SCALE, beta);
+        }
+
+        this.beta = beta;
+        this.mu = mu;
+    }
+
+    /**
+     * Access the location parameter, {@code mu}.
+     *
+     * @return the location parameter.
+     */
+    public double getLocation() {
+        return mu;
+    }
+
+    /**
+     * Access the scale parameter, {@code beta}.
+     *
+     * @return the scale parameter.
+     */
+    public double getScale() {
+        return beta;
+    }
+
+    /** {@inheritDoc} */
+    public double density(double x) {
+        final double z = (x - mu) / beta;
+        final double t = FastMath.exp(-z);
+        return FastMath.exp(-z - t) / beta;
+    }
+
+    /** {@inheritDoc} */
+    public double cumulativeProbability(double x) {
+        final double z = (x - mu) / beta;
+        return FastMath.exp(-FastMath.exp(-z));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double inverseCumulativeProbability(double p) throws OutOfRangeException {
+        if (p < 0.0 || p > 1.0) {
+            throw new OutOfRangeException(p, 0.0, 1.0);
+        } else if (p == 0) {
+            return Double.NEGATIVE_INFINITY;
+        } else if (p == 1) {
+            return Double.POSITIVE_INFINITY;
+        }
+        return mu - FastMath.log(-FastMath.log(p)) * beta;
+    }
+
+    /** {@inheritDoc} */
+    public double getNumericalMean() {
+        return mu + EULER * beta;
+    }
+
+    /** {@inheritDoc} */
+    public double getNumericalVariance() {
+        return (MathUtils.PI_SQUARED) / 6.0 * (beta * beta);
+    }
+
+    /** {@inheritDoc} */
+    public double getSupportLowerBound() {
+        return Double.NEGATIVE_INFINITY;
+    }
+
+    /** {@inheritDoc} */
+    public double getSupportUpperBound() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportLowerBoundInclusive() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportUpperBoundInclusive() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportConnected() {
+        return true;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/HypergeometricDistribution.java b/src/main/java/org/apache/commons/math3/distribution/HypergeometricDistribution.java
new file mode 100644
index 0000000..dece6c8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/HypergeometricDistribution.java
@@ -0,0 +1,347 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well19937c;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Implementation of the hypergeometric distribution.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Hypergeometric_distribution">Hypergeometric
+ *     distribution (Wikipedia)</a>
+ * @see <a href="http://mathworld.wolfram.com/HypergeometricDistribution.html">Hypergeometric
+ *     distribution (MathWorld)</a>
+ */
+public class HypergeometricDistribution extends AbstractIntegerDistribution {
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -436928820673516179L;
+
+    /** The number of successes in the population. */
+    private final int numberOfSuccesses;
+
+    /** The population size. */
+    private final int populationSize;
+
+    /** The sample size. */
+    private final int sampleSize;
+
+    /** Cached numerical variance */
+    private double numericalVariance = Double.NaN;
+
+    /** Whether or not the numerical variance has been calculated */
+    private boolean numericalVarianceIsCalculated = false;
+
+    /**
+     * Construct a new hypergeometric distribution with the specified population size, number of
+     * successes in the population, and sample size.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param populationSize Population size.
+     * @param numberOfSuccesses Number of successes in the population.
+     * @param sampleSize Sample size.
+     * @throws NotPositiveException if {@code numberOfSuccesses < 0}.
+     * @throws NotStrictlyPositiveException if {@code populationSize <= 0}.
+     * @throws NumberIsTooLargeException if {@code numberOfSuccesses > populationSize}, or {@code
+     *     sampleSize > populationSize}.
+     */
+    public HypergeometricDistribution(int populationSize, int numberOfSuccesses, int sampleSize)
+            throws NotPositiveException, NotStrictlyPositiveException, NumberIsTooLargeException {
+        this(new Well19937c(), populationSize, numberOfSuccesses, sampleSize);
+    }
+
+    /**
+     * Creates a new hypergeometric distribution.
+     *
+     * @param rng Random number generator.
+     * @param populationSize Population size.
+     * @param numberOfSuccesses Number of successes in the population.
+     * @param sampleSize Sample size.
+     * @throws NotPositiveException if {@code numberOfSuccesses < 0}.
+     * @throws NotStrictlyPositiveException if {@code populationSize <= 0}.
+     * @throws NumberIsTooLargeException if {@code numberOfSuccesses > populationSize}, or {@code
+     *     sampleSize > populationSize}.
+     * @since 3.1
+     */
+    public HypergeometricDistribution(
+            RandomGenerator rng, int populationSize, int numberOfSuccesses, int sampleSize)
+            throws NotPositiveException, NotStrictlyPositiveException, NumberIsTooLargeException {
+        super(rng);
+
+        if (populationSize <= 0) {
+            throw new NotStrictlyPositiveException(
+                    LocalizedFormats.POPULATION_SIZE, populationSize);
+        }
+        if (numberOfSuccesses < 0) {
+            throw new NotPositiveException(LocalizedFormats.NUMBER_OF_SUCCESSES, numberOfSuccesses);
+        }
+        if (sampleSize < 0) {
+            throw new NotPositiveException(LocalizedFormats.NUMBER_OF_SAMPLES, sampleSize);
+        }
+
+        if (numberOfSuccesses > populationSize) {
+            throw new NumberIsTooLargeException(
+                    LocalizedFormats.NUMBER_OF_SUCCESS_LARGER_THAN_POPULATION_SIZE,
+                    numberOfSuccesses,
+                    populationSize,
+                    true);
+        }
+        if (sampleSize > populationSize) {
+            throw new NumberIsTooLargeException(
+                    LocalizedFormats.SAMPLE_SIZE_LARGER_THAN_POPULATION_SIZE,
+                    sampleSize,
+                    populationSize,
+                    true);
+        }
+
+        this.numberOfSuccesses = numberOfSuccesses;
+        this.populationSize = populationSize;
+        this.sampleSize = sampleSize;
+    }
+
+    /** {@inheritDoc} */
+    public double cumulativeProbability(int x) {
+        double ret;
+
+        int[] domain = getDomain(populationSize, numberOfSuccesses, sampleSize);
+        if (x < domain[0]) {
+            ret = 0.0;
+        } else if (x >= domain[1]) {
+            ret = 1.0;
+        } else {
+            ret = innerCumulativeProbability(domain[0], x, 1);
+        }
+
+        return ret;
+    }
+
+    /**
+     * Return the domain for the given hypergeometric distribution parameters.
+     *
+     * @param n Population size.
+     * @param m Number of successes in the population.
+     * @param k Sample size.
+     * @return a two element array containing the lower and upper bounds of the hypergeometric
+     *     distribution.
+     */
+    private int[] getDomain(int n, int m, int k) {
+        return new int[] {getLowerDomain(n, m, k), getUpperDomain(m, k)};
+    }
+
+    /**
+     * Return the lowest domain value for the given hypergeometric distribution parameters.
+     *
+     * @param n Population size.
+     * @param m Number of successes in the population.
+     * @param k Sample size.
+     * @return the lowest domain value of the hypergeometric distribution.
+     */
+    private int getLowerDomain(int n, int m, int k) {
+        return FastMath.max(0, m - (n - k));
+    }
+
+    /**
+     * Access the number of successes.
+     *
+     * @return the number of successes.
+     */
+    public int getNumberOfSuccesses() {
+        return numberOfSuccesses;
+    }
+
+    /**
+     * Access the population size.
+     *
+     * @return the population size.
+     */
+    public int getPopulationSize() {
+        return populationSize;
+    }
+
+    /**
+     * Access the sample size.
+     *
+     * @return the sample size.
+     */
+    public int getSampleSize() {
+        return sampleSize;
+    }
+
+    /**
+     * Return the highest domain value for the given hypergeometric distribution parameters.
+     *
+     * @param m Number of successes in the population.
+     * @param k Sample size.
+     * @return the highest domain value of the hypergeometric distribution.
+     */
+    private int getUpperDomain(int m, int k) {
+        return FastMath.min(k, m);
+    }
+
+    /** {@inheritDoc} */
+    public double probability(int x) {
+        final double logProbability = logProbability(x);
+        return logProbability == Double.NEGATIVE_INFINITY ? 0 : FastMath.exp(logProbability);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double logProbability(int x) {
+        double ret;
+
+        int[] domain = getDomain(populationSize, numberOfSuccesses, sampleSize);
+        if (x < domain[0] || x > domain[1]) {
+            ret = Double.NEGATIVE_INFINITY;
+        } else {
+            double p = (double) sampleSize / (double) populationSize;
+            double q = (double) (populationSize - sampleSize) / (double) populationSize;
+            double p1 = SaddlePointExpansion.logBinomialProbability(x, numberOfSuccesses, p, q);
+            double p2 =
+                    SaddlePointExpansion.logBinomialProbability(
+                            sampleSize - x, populationSize - numberOfSuccesses, p, q);
+            double p3 =
+                    SaddlePointExpansion.logBinomialProbability(sampleSize, populationSize, p, q);
+            ret = p1 + p2 - p3;
+        }
+
+        return ret;
+    }
+
+    /**
+     * For this distribution, {@code X}, this method returns {@code P(X >= x)}.
+     *
+     * @param x Value at which the CDF is evaluated.
+     * @return the upper tail CDF for this distribution.
+     * @since 1.1
+     */
+    public double upperCumulativeProbability(int x) {
+        double ret;
+
+        final int[] domain = getDomain(populationSize, numberOfSuccesses, sampleSize);
+        if (x <= domain[0]) {
+            ret = 1.0;
+        } else if (x > domain[1]) {
+            ret = 0.0;
+        } else {
+            ret = innerCumulativeProbability(domain[1], x, -1);
+        }
+
+        return ret;
+    }
+
+    /**
+     * For this distribution, {@code X}, this method returns {@code P(x0 <= X <= x1)}. This
+     * probability is computed by summing the point probabilities for the values {@code x0, x0 + 1,
+     * x0 + 2, ..., x1}, in the order directed by {@code dx}.
+     *
+     * @param x0 Inclusive lower bound.
+     * @param x1 Inclusive upper bound.
+     * @param dx Direction of summation (1 indicates summing from x0 to x1, and 0 indicates summing
+     *     from x1 to x0).
+     * @return {@code P(x0 <= X <= x1)}.
+     */
+    private double innerCumulativeProbability(int x0, int x1, int dx) {
+        double ret = probability(x0);
+        while (x0 != x1) {
+            x0 += dx;
+            ret += probability(x0);
+        }
+        return ret;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For population size {@code N}, number of successes {@code m}, and sample size {@code n},
+     * the mean is {@code n * m / N}.
+     */
+    public double getNumericalMean() {
+        return getSampleSize() * (getNumberOfSuccesses() / (double) getPopulationSize());
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For population size {@code N}, number of successes {@code m}, and sample size {@code n},
+     * the variance is {@code [n * m * (N - n) * (N - m)] / [N^2 * (N - 1)]}.
+     */
+    public double getNumericalVariance() {
+        if (!numericalVarianceIsCalculated) {
+            numericalVariance = calculateNumericalVariance();
+            numericalVarianceIsCalculated = true;
+        }
+        return numericalVariance;
+    }
+
+    /**
+     * Used by {@link #getNumericalVariance()}.
+     *
+     * @return the variance of this distribution
+     */
+    protected double calculateNumericalVariance() {
+        final double N = getPopulationSize();
+        final double m = getNumberOfSuccesses();
+        final double n = getSampleSize();
+        return (n * m * (N - n) * (N - m)) / (N * N * (N - 1));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For population size {@code N}, number of successes {@code m}, and sample size {@code n},
+     * the lower bound of the support is {@code max(0, n + m - N)}.
+     *
+     * @return lower bound of the support
+     */
+    public int getSupportLowerBound() {
+        return FastMath.max(0, getSampleSize() + getNumberOfSuccesses() - getPopulationSize());
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For number of successes {@code m} and sample size {@code n}, the upper bound of the
+     * support is {@code min(m, n)}.
+     *
+     * @return upper bound of the support
+     */
+    public int getSupportUpperBound() {
+        return FastMath.min(getNumberOfSuccesses(), getSampleSize());
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The support of this distribution is connected.
+     *
+     * @return {@code true}
+     */
+    public boolean isSupportConnected() {
+        return true;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/IntegerDistribution.java b/src/main/java/org/apache/commons/math3/distribution/IntegerDistribution.java
new file mode 100644
index 0000000..c188a78
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/IntegerDistribution.java
@@ -0,0 +1,145 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+
+/** Interface for distributions on the integers. */
+public interface IntegerDistribution {
+    /**
+     * For a random variable {@code X} whose values are distributed according to this distribution,
+     * this method returns {@code P(X = x)}. In other words, this method represents the probability
+     * mass function (PMF) for the distribution.
+     *
+     * @param x the point at which the PMF is evaluated
+     * @return the value of the probability mass function at {@code x}
+     */
+    double probability(int x);
+
+    /**
+     * For a random variable {@code X} whose values are distributed according to this distribution,
+     * this method returns {@code P(X <= x)}. In other words, this method represents the
+     * (cumulative) distribution function (CDF) for this distribution.
+     *
+     * @param x the point at which the CDF is evaluated
+     * @return the probability that a random variable with this distribution takes a value less than
+     *     or equal to {@code x}
+     */
+    double cumulativeProbability(int x);
+
+    /**
+     * For a random variable {@code X} whose values are distributed according to this distribution,
+     * this method returns {@code P(x0 < X <= x1)}.
+     *
+     * @param x0 the exclusive lower bound
+     * @param x1 the inclusive upper bound
+     * @return the probability that a random variable with this distribution will take a value
+     *     between {@code x0} and {@code x1}, excluding the lower and including the upper endpoint
+     * @throws NumberIsTooLargeException if {@code x0 > x1}
+     */
+    double cumulativeProbability(int x0, int x1) throws NumberIsTooLargeException;
+
+    /**
+     * Computes the quantile function of this distribution. For a random variable {@code X}
+     * distributed according to this distribution, the returned value is
+     *
+     * <ul>
+     *   <li><code>inf{x in Z | P(X<=x) >= p}</code> for {@code 0 < p <= 1},
+     *   <li><code>inf{x in Z | P(X<=x) > 0}</code> for {@code p = 0}.
+     * </ul>
+     *
+     * If the result exceeds the range of the data type {@code int}, then {@code Integer.MIN_VALUE}
+     * or {@code Integer.MAX_VALUE} is returned.
+     *
+     * @param p the cumulative probability
+     * @return the smallest {@code p}-quantile of this distribution (largest 0-quantile for {@code p
+     *     = 0})
+     * @throws OutOfRangeException if {@code p < 0} or {@code p > 1}
+     */
+    int inverseCumulativeProbability(double p) throws OutOfRangeException;
+
+    /**
+     * Use this method to get the numerical value of the mean of this distribution.
+     *
+     * @return the mean or {@code Double.NaN} if it is not defined
+     */
+    double getNumericalMean();
+
+    /**
+     * Use this method to get the numerical value of the variance of this distribution.
+     *
+     * @return the variance (possibly {@code Double.POSITIVE_INFINITY} or {@code Double.NaN} if it
+     *     is not defined)
+     */
+    double getNumericalVariance();
+
+    /**
+     * Access the lower bound of the support. This method must return the same value as {@code
+     * inverseCumulativeProbability(0)}. In other words, this method must return
+     *
+     * <p><code>inf {x in Z | P(X <= x) > 0}</code>.
+     *
+     * @return lower bound of the support ({@code Integer.MIN_VALUE} for negative infinity)
+     */
+    int getSupportLowerBound();
+
+    /**
+     * Access the upper bound of the support. This method must return the same value as {@code
+     * inverseCumulativeProbability(1)}. In other words, this method must return
+     *
+     * <p><code>inf {x in R | P(X <= x) = 1}</code>.
+     *
+     * @return upper bound of the support ({@code Integer.MAX_VALUE} for positive infinity)
+     */
+    int getSupportUpperBound();
+
+    /**
+     * Use this method to get information about whether the support is connected, i.e. whether all
+     * integers between the lower and upper bound of the support are included in the support.
+     *
+     * @return whether the support is connected or not
+     */
+    boolean isSupportConnected();
+
+    /**
+     * Reseed the random generator used to generate samples.
+     *
+     * @param seed the new seed
+     * @since 3.0
+     */
+    void reseedRandomGenerator(long seed);
+
+    /**
+     * Generate a random value sampled from this distribution.
+     *
+     * @return a random value
+     * @since 3.0
+     */
+    int sample();
+
+    /**
+     * Generate a random sample from the distribution.
+     *
+     * @param sampleSize the number of random values to generate
+     * @return an array representing the random sample
+     * @throws org.apache.commons.math3.exception.NotStrictlyPositiveException if {@code sampleSize}
+     *     is not positive
+     * @since 3.0
+     */
+    int[] sample(int sampleSize);
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/KolmogorovSmirnovDistribution.java b/src/main/java/org/apache/commons/math3/distribution/KolmogorovSmirnovDistribution.java
new file mode 100644
index 0000000..3ee007f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/KolmogorovSmirnovDistribution.java
@@ -0,0 +1,338 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.fraction.BigFraction;
+import org.apache.commons.math3.fraction.BigFractionField;
+import org.apache.commons.math3.fraction.FractionConversionException;
+import org.apache.commons.math3.linear.Array2DRowFieldMatrix;
+import org.apache.commons.math3.linear.Array2DRowRealMatrix;
+import org.apache.commons.math3.linear.FieldMatrix;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.util.FastMath;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * Implementation of the Kolmogorov-Smirnov distribution.
+ *
+ * <p>Treats the distribution of the two-sided {@code P(D_n < d)} where {@code D_n = sup_x |G(x) -
+ * G_n (x)|} for the theoretical cdf {@code G} and the empirical cdf {@code G_n}.
+ *
+ * <p>This implementation is based on [1] with certain quick decisions for extreme values given in
+ * [2].
+ *
+ * <p>In short, when wanting to evaluate {@code P(D_n < d)}, the method in [1] is to write {@code d
+ * = (k - h) / n} for positive integer {@code k} and {@code 0 <= h < 1}. Then {@code P(D_n < d) =
+ * (n! / n^n) * t_kk}, where {@code t_kk} is the {@code (k, k)}'th entry in the special matrix
+ * {@code H^n}, i.e. {@code H} to the {@code n}'th power.
+ *
+ * <p>References:
+ *
+ * <ul>
+ *   <li>[1] <a href="http://www.jstatsoft.org/v08/i18/">Evaluating Kolmogorov's Distribution</a> by
+ *       George Marsaglia, Wai Wan Tsang, and Jingbo Wang
+ *   <li>[2] <a href="http://www.jstatsoft.org/v39/i11/">Computing the Two-Sided Kolmogorov-Smirnov
+ *       Distribution</a> by Richard Simard and Pierre L'Ecuyer
+ * </ul>
+ *
+ * Note that [1] contains an error in computing h, refer to <a
+ * href="https://issues.apache.org/jira/browse/MATH-437">MATH-437</a> for details.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Kolmogorov-Smirnov_test">Kolmogorov-Smirnov test
+ *     (Wikipedia)</a>
+ * @deprecated to be removed in version 4.0 - use {@link
+ *     org.apache.commons.math3.stat.inference.KolmogorovSmirnovTest}
+ */
+public class KolmogorovSmirnovDistribution implements Serializable {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -4670676796862967187L;
+
+    /** Number of observations. */
+    private int n;
+
+    /**
+     * @param n Number of observations
+     * @throws NotStrictlyPositiveException if {@code n <= 0}
+     */
+    public KolmogorovSmirnovDistribution(int n) throws NotStrictlyPositiveException {
+        if (n <= 0) {
+            throw new NotStrictlyPositiveException(
+                    LocalizedFormats.NOT_POSITIVE_NUMBER_OF_SAMPLES, n);
+        }
+
+        this.n = n;
+    }
+
+    /**
+     * Calculates {@code P(D_n < d)} using method described in [1] with quick decisions for extreme
+     * values given in [2] (see above). The result is not exact as with {@link
+     * KolmogorovSmirnovDistribution#cdfExact(double)} because calculations are based on {@code
+     * double} rather than {@link org.apache.commons.math3.fraction.BigFraction}.
+     *
+     * @param d statistic
+     * @return the two-sided probability of {@code P(D_n < d)}
+     * @throws MathArithmeticException if algorithm fails to convert {@code h} to a {@link
+     *     org.apache.commons.math3.fraction.BigFraction} in expressing {@code d} as {@code (k - h)
+     *     / m} for integer {@code k, m} and {@code 0 <= h < 1}.
+     */
+    public double cdf(double d) throws MathArithmeticException {
+        return this.cdf(d, false);
+    }
+
+    /**
+     * Calculates {@code P(D_n < d)} using method described in [1] with quick decisions for extreme
+     * values given in [2] (see above). The result is exact in the sense that BigFraction/BigReal is
+     * used everywhere at the expense of very slow execution time. Almost never choose this in real
+     * applications unless you are very sure; this is almost solely for verification purposes.
+     * Normally, you would choose {@link KolmogorovSmirnovDistribution#cdf(double)}
+     *
+     * @param d statistic
+     * @return the two-sided probability of {@code P(D_n < d)}
+     * @throws MathArithmeticException if algorithm fails to convert {@code h} to a {@link
+     *     org.apache.commons.math3.fraction.BigFraction} in expressing {@code d} as {@code (k - h)
+     *     / m} for integer {@code k, m} and {@code 0 <= h < 1}.
+     */
+    public double cdfExact(double d) throws MathArithmeticException {
+        return this.cdf(d, true);
+    }
+
+    /**
+     * Calculates {@code P(D_n < d)} using method described in [1] with quick decisions for extreme
+     * values given in [2] (see above).
+     *
+     * @param d statistic
+     * @param exact whether the probability should be calculated exact using {@link
+     *     org.apache.commons.math3.fraction.BigFraction} everywhere at the expense of very slow
+     *     execution time, or if {@code double} should be used convenient places to gain speed.
+     *     Almost never choose {@code true} in real applications unless you are very sure; {@code
+     *     true} is almost solely for verification purposes.
+     * @return the two-sided probability of {@code P(D_n < d)}
+     * @throws MathArithmeticException if algorithm fails to convert {@code h} to a {@link
+     *     org.apache.commons.math3.fraction.BigFraction} in expressing {@code d} as {@code (k - h)
+     *     / m} for integer {@code k, m} and {@code 0 <= h < 1}.
+     */
+    public double cdf(double d, boolean exact) throws MathArithmeticException {
+
+        final double ninv = 1 / ((double) n);
+        final double ninvhalf = 0.5 * ninv;
+
+        if (d <= ninvhalf) {
+
+            return 0;
+
+        } else if (ninvhalf < d && d <= ninv) {
+
+            double res = 1;
+            double f = 2 * d - ninv;
+
+            // n! f^n = n*f * (n-1)*f * ... * 1*x
+            for (int i = 1; i <= n; ++i) {
+                res *= i * f;
+            }
+
+            return res;
+
+        } else if (1 - ninv <= d && d < 1) {
+
+            return 1 - 2 * FastMath.pow(1 - d, n);
+
+        } else if (1 <= d) {
+
+            return 1;
+        }
+
+        return exact ? exactK(d) : roundedK(d);
+    }
+
+    /**
+     * Calculates the exact value of {@code P(D_n < d)} using method described in [1] and {@link
+     * org.apache.commons.math3.fraction.BigFraction} (see above).
+     *
+     * @param d statistic
+     * @return the two-sided probability of {@code P(D_n < d)}
+     * @throws MathArithmeticException if algorithm fails to convert {@code h} to a {@link
+     *     org.apache.commons.math3.fraction.BigFraction} in expressing {@code d} as {@code (k - h)
+     *     / m} for integer {@code k, m} and {@code 0 <= h < 1}.
+     */
+    private double exactK(double d) throws MathArithmeticException {
+
+        final int k = (int) FastMath.ceil(n * d);
+
+        final FieldMatrix<BigFraction> H = this.createH(d);
+        final FieldMatrix<BigFraction> Hpower = H.power(n);
+
+        BigFraction pFrac = Hpower.getEntry(k - 1, k - 1);
+
+        for (int i = 1; i <= n; ++i) {
+            pFrac = pFrac.multiply(i).divide(n);
+        }
+
+        /*
+         * BigFraction.doubleValue converts numerator to double and the
+         * denominator to double and divides afterwards. That gives NaN quite
+         * easy. This does not (scale is the number of digits):
+         */
+        return pFrac.bigDecimalValue(20, BigDecimal.ROUND_HALF_UP).doubleValue();
+    }
+
+    /**
+     * Calculates {@code P(D_n < d)} using method described in [1] and doubles (see above).
+     *
+     * @param d statistic
+     * @return the two-sided probability of {@code P(D_n < d)}
+     * @throws MathArithmeticException if algorithm fails to convert {@code h} to a {@link
+     *     org.apache.commons.math3.fraction.BigFraction} in expressing {@code d} as {@code (k - h)
+     *     / m} for integer {@code k, m} and {@code 0 <= h < 1}.
+     */
+    private double roundedK(double d) throws MathArithmeticException {
+
+        final int k = (int) FastMath.ceil(n * d);
+        final FieldMatrix<BigFraction> HBigFraction = this.createH(d);
+        final int m = HBigFraction.getRowDimension();
+
+        /*
+         * Here the rounding part comes into play: use
+         * RealMatrix instead of FieldMatrix<BigFraction>
+         */
+        final RealMatrix H = new Array2DRowRealMatrix(m, m);
+
+        for (int i = 0; i < m; ++i) {
+            for (int j = 0; j < m; ++j) {
+                H.setEntry(i, j, HBigFraction.getEntry(i, j).doubleValue());
+            }
+        }
+
+        final RealMatrix Hpower = H.power(n);
+
+        double pFrac = Hpower.getEntry(k - 1, k - 1);
+
+        for (int i = 1; i <= n; ++i) {
+            pFrac *= (double) i / (double) n;
+        }
+
+        return pFrac;
+    }
+
+    /***
+     * Creates {@code H} of size {@code m x m} as described in [1] (see above).
+     *
+     * @param d statistic
+     * @return H matrix
+     * @throws NumberIsTooLargeException if fractional part is greater than 1
+     * @throws FractionConversionException if algorithm fails to convert
+     * {@code h} to a {@link org.apache.commons.math3.fraction.BigFraction} in
+     * expressing {@code d} as {@code (k - h) / m} for integer {@code k, m} and
+     * {@code 0 <= h < 1}.
+     */
+    private FieldMatrix<BigFraction> createH(double d)
+            throws NumberIsTooLargeException, FractionConversionException {
+
+        int k = (int) FastMath.ceil(n * d);
+
+        int m = 2 * k - 1;
+        double hDouble = k - n * d;
+
+        if (hDouble >= 1) {
+            throw new NumberIsTooLargeException(hDouble, 1.0, false);
+        }
+
+        BigFraction h = null;
+
+        try {
+            h = new BigFraction(hDouble, 1.0e-20, 10000);
+        } catch (FractionConversionException e1) {
+            try {
+                h = new BigFraction(hDouble, 1.0e-10, 10000);
+            } catch (FractionConversionException e2) {
+                h = new BigFraction(hDouble, 1.0e-5, 10000);
+            }
+        }
+
+        final BigFraction[][] Hdata = new BigFraction[m][m];
+
+        /*
+         * Start by filling everything with either 0 or 1.
+         */
+        for (int i = 0; i < m; ++i) {
+            for (int j = 0; j < m; ++j) {
+                if (i - j + 1 < 0) {
+                    Hdata[i][j] = BigFraction.ZERO;
+                } else {
+                    Hdata[i][j] = BigFraction.ONE;
+                }
+            }
+        }
+
+        /*
+         * Setting up power-array to avoid calculating the same value twice:
+         * hPowers[0] = h^1 ... hPowers[m-1] = h^m
+         */
+        final BigFraction[] hPowers = new BigFraction[m];
+        hPowers[0] = h;
+        for (int i = 1; i < m; ++i) {
+            hPowers[i] = h.multiply(hPowers[i - 1]);
+        }
+
+        /*
+         * First column and last row has special values (each other reversed).
+         */
+        for (int i = 0; i < m; ++i) {
+            Hdata[i][0] = Hdata[i][0].subtract(hPowers[i]);
+            Hdata[m - 1][i] = Hdata[m - 1][i].subtract(hPowers[m - i - 1]);
+        }
+
+        /*
+         * [1] states: "For 1/2 < h < 1 the bottom left element of the matrix
+         * should be (1 - 2*h^m + (2h - 1)^m )/m!" Since 0 <= h < 1, then if h >
+         * 1/2 is sufficient to check:
+         */
+        if (h.compareTo(BigFraction.ONE_HALF) == 1) {
+            Hdata[m - 1][0] = Hdata[m - 1][0].add(h.multiply(2).subtract(1).pow(m));
+        }
+
+        /*
+         * Aside from the first column and last row, the (i, j)-th element is
+         * 1/(i - j + 1)! if i - j + 1 >= 0, else 0. 1's and 0's are already
+         * put, so only division with (i - j + 1)! is needed in the elements
+         * that have 1's. There is no need to calculate (i - j + 1)! and then
+         * divide - small steps avoid overflows.
+         *
+         * Note that i - j + 1 > 0 <=> i + 1 > j instead of j'ing all the way to
+         * m. Also note that it is started at g = 2 because dividing by 1 isn't
+         * really necessary.
+         */
+        for (int i = 0; i < m; ++i) {
+            for (int j = 0; j < i + 1; ++j) {
+                if (i - j + 1 > 0) {
+                    for (int g = 2; g <= i - j + 1; ++g) {
+                        Hdata[i][j] = Hdata[i][j].divide(g);
+                    }
+                }
+            }
+        }
+
+        return new Array2DRowFieldMatrix<BigFraction>(BigFractionField.getInstance(), Hdata);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/LaplaceDistribution.java b/src/main/java/org/apache/commons/math3/distribution/LaplaceDistribution.java
new file mode 100644
index 0000000..2ce36fc
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/LaplaceDistribution.java
@@ -0,0 +1,160 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well19937c;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * This class implements the Laplace distribution.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Laplace_distribution">Laplace distribution
+ *     (Wikipedia)</a>
+ * @since 3.4
+ */
+public class LaplaceDistribution extends AbstractRealDistribution {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 20141003;
+
+    /** The location parameter. */
+    private final double mu;
+
+    /** The scale parameter. */
+    private final double beta;
+
+    /**
+     * Build a new instance.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param mu location parameter
+     * @param beta scale parameter (must be positive)
+     * @throws NotStrictlyPositiveException if {@code beta <= 0}
+     */
+    public LaplaceDistribution(double mu, double beta) {
+        this(new Well19937c(), mu, beta);
+    }
+
+    /**
+     * Build a new instance.
+     *
+     * @param rng Random number generator
+     * @param mu location parameter
+     * @param beta scale parameter (must be positive)
+     * @throws NotStrictlyPositiveException if {@code beta <= 0}
+     */
+    public LaplaceDistribution(RandomGenerator rng, double mu, double beta) {
+        super(rng);
+
+        if (beta <= 0.0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.NOT_POSITIVE_SCALE, beta);
+        }
+
+        this.mu = mu;
+        this.beta = beta;
+    }
+
+    /**
+     * Access the location parameter, {@code mu}.
+     *
+     * @return the location parameter.
+     */
+    public double getLocation() {
+        return mu;
+    }
+
+    /**
+     * Access the scale parameter, {@code beta}.
+     *
+     * @return the scale parameter.
+     */
+    public double getScale() {
+        return beta;
+    }
+
+    /** {@inheritDoc} */
+    public double density(double x) {
+        return FastMath.exp(-FastMath.abs(x - mu) / beta) / (2.0 * beta);
+    }
+
+    /** {@inheritDoc} */
+    public double cumulativeProbability(double x) {
+        if (x <= mu) {
+            return FastMath.exp((x - mu) / beta) / 2.0;
+        } else {
+            return 1.0 - FastMath.exp((mu - x) / beta) / 2.0;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double inverseCumulativeProbability(double p) throws OutOfRangeException {
+        if (p < 0.0 || p > 1.0) {
+            throw new OutOfRangeException(p, 0.0, 1.0);
+        } else if (p == 0) {
+            return Double.NEGATIVE_INFINITY;
+        } else if (p == 1) {
+            return Double.POSITIVE_INFINITY;
+        }
+        double x = (p > 0.5) ? -Math.log(2.0 - 2.0 * p) : Math.log(2.0 * p);
+        return mu + beta * x;
+    }
+
+    /** {@inheritDoc} */
+    public double getNumericalMean() {
+        return mu;
+    }
+
+    /** {@inheritDoc} */
+    public double getNumericalVariance() {
+        return 2.0 * beta * beta;
+    }
+
+    /** {@inheritDoc} */
+    public double getSupportLowerBound() {
+        return Double.NEGATIVE_INFINITY;
+    }
+
+    /** {@inheritDoc} */
+    public double getSupportUpperBound() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportLowerBoundInclusive() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportUpperBoundInclusive() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportConnected() {
+        return true;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/LevyDistribution.java b/src/main/java/org/apache/commons/math3/distribution/LevyDistribution.java
new file mode 100644
index 0000000..d76e993
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/LevyDistribution.java
@@ -0,0 +1,197 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well19937c;
+import org.apache.commons.math3.special.Erf;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * This class implements the <a href="http://en.wikipedia.org/wiki/L%C3%A9vy_distribution">
+ * L&eacute;vy distribution</a>.
+ *
+ * @since 3.2
+ */
+public class LevyDistribution extends AbstractRealDistribution {
+
+    /** Serializable UID. */
+    private static final long serialVersionUID = 20130314L;
+
+    /** Location parameter. */
+    private final double mu;
+
+    /** Scale parameter. */
+    private final double c; // Setting this to 1 returns a cumProb of 1.0
+
+    /** Half of c (for calculations). */
+    private final double halfC;
+
+    /**
+     * Build a new instance.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param mu location parameter
+     * @param c scale parameter
+     * @since 3.4
+     */
+    public LevyDistribution(final double mu, final double c) {
+        this(new Well19937c(), mu, c);
+    }
+
+    /**
+     * Creates a LevyDistribution.
+     *
+     * @param rng random generator to be used for sampling
+     * @param mu location
+     * @param c scale parameter
+     */
+    public LevyDistribution(final RandomGenerator rng, final double mu, final double c) {
+        super(rng);
+        this.mu = mu;
+        this.c = c;
+        this.halfC = 0.5 * c;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>From Wikipedia: The probability density function of the L&eacute;vy distribution over the
+     * domain is
+     *
+     * <pre>
+     * f(x; &mu;, c) = &radic;(c / 2&pi;) * e<sup>-c / 2 (x - &mu;)</sup> / (x - &mu;)<sup>3/2</sup>
+     * </pre>
+     *
+     * <p>For this distribution, {@code X}, this method returns {@code P(X < x)}. If {@code x} is
+     * less than location parameter &mu;, {@code Double.NaN} is returned, as in these cases the
+     * distribution is not defined.
+     */
+    public double density(final double x) {
+        if (x < mu) {
+            return Double.NaN;
+        }
+
+        final double delta = x - mu;
+        final double f = halfC / delta;
+        return FastMath.sqrt(f / FastMath.PI) * FastMath.exp(-f) / delta;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>See documentation of {@link #density(double)} for computation details.
+     */
+    @Override
+    public double logDensity(double x) {
+        if (x < mu) {
+            return Double.NaN;
+        }
+
+        final double delta = x - mu;
+        final double f = halfC / delta;
+        return 0.5 * FastMath.log(f / FastMath.PI) - f - FastMath.log(delta);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>From Wikipedia: the cumulative distribution function is
+     *
+     * <pre>
+     * f(x; u, c) = erfc (&radic; (c / 2 (x - u )))
+     * </pre>
+     */
+    public double cumulativeProbability(final double x) {
+        if (x < mu) {
+            return Double.NaN;
+        }
+        return Erf.erfc(FastMath.sqrt(halfC / (x - mu)));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double inverseCumulativeProbability(final double p) throws OutOfRangeException {
+        if (p < 0.0 || p > 1.0) {
+            throw new OutOfRangeException(p, 0, 1);
+        }
+        final double t = Erf.erfcInv(p);
+        return mu + halfC / (t * t);
+    }
+
+    /**
+     * Get the scale parameter of the distribution.
+     *
+     * @return scale parameter of the distribution
+     */
+    public double getScale() {
+        return c;
+    }
+
+    /**
+     * Get the location parameter of the distribution.
+     *
+     * @return location parameter of the distribution
+     */
+    public double getLocation() {
+        return mu;
+    }
+
+    /** {@inheritDoc} */
+    public double getNumericalMean() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    /** {@inheritDoc} */
+    public double getNumericalVariance() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    /** {@inheritDoc} */
+    public double getSupportLowerBound() {
+        return mu;
+    }
+
+    /** {@inheritDoc} */
+    public double getSupportUpperBound() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportLowerBoundInclusive() {
+        // there is a division by x-mu in the computation, so density
+        // is not finite at lower bound, bound must be excluded
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportUpperBoundInclusive() {
+        // upper bound is infinite, so it must be excluded
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportConnected() {
+        return true;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/LogNormalDistribution.java b/src/main/java/org/apache/commons/math3/distribution/LogNormalDistribution.java
new file mode 100644
index 0000000..e6a6deb
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/LogNormalDistribution.java
@@ -0,0 +1,349 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well19937c;
+import org.apache.commons.math3.special.Erf;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Implementation of the log-normal (gaussian) distribution.
+ *
+ * <p><strong>Parameters:</strong> {@code X} is log-normally distributed if its natural logarithm
+ * {@code log(X)} is normally distributed. The probability distribution function of {@code X} is
+ * given by (for {@code x > 0})
+ *
+ * <p>{@code exp(-0.5 * ((ln(x) - m) / s)^2) / (s * sqrt(2 * pi) * x)}
+ *
+ * <ul>
+ *   <li>{@code m} is the <em>scale</em> parameter: this is the mean of the normally distributed
+ *       natural logarithm of this distribution,
+ *   <li>{@code s} is the <em>shape</em> parameter: this is the standard deviation of the normally
+ *       distributed natural logarithm of this distribution.
+ * </ul>
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Log-normal_distribution">Log-normal distribution
+ *     (Wikipedia)</a>
+ * @see <a href="http://mathworld.wolfram.com/LogNormalDistribution.html">Log Normal distribution
+ *     (MathWorld)</a>
+ * @since 3.0
+ */
+public class LogNormalDistribution extends AbstractRealDistribution {
+    /** Default inverse cumulative probability accuracy. */
+    public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9;
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 20120112;
+
+    /** &radic;(2 &pi;) */
+    private static final double SQRT2PI = FastMath.sqrt(2 * FastMath.PI);
+
+    /** &radic;(2) */
+    private static final double SQRT2 = FastMath.sqrt(2.0);
+
+    /** The scale parameter of this distribution. */
+    private final double scale;
+
+    /** The shape parameter of this distribution. */
+    private final double shape;
+
+    /** The value of {@code log(shape) + 0.5 * log(2*PI)} stored for faster computation. */
+    private final double logShapePlusHalfLog2Pi;
+
+    /** Inverse cumulative probability accuracy. */
+    private final double solverAbsoluteAccuracy;
+
+    /**
+     * Create a log-normal distribution, where the mean and standard deviation of the {@link
+     * NormalDistribution normally distributed} natural logarithm of the log-normal distribution are
+     * equal to zero and one respectively. In other words, the scale of the returned distribution is
+     * {@code 0}, while its shape is {@code 1}.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     */
+    public LogNormalDistribution() {
+        this(0, 1);
+    }
+
+    /**
+     * Create a log-normal distribution using the specified scale and shape.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param scale the scale parameter of this distribution
+     * @param shape the shape parameter of this distribution
+     * @throws NotStrictlyPositiveException if {@code shape <= 0}.
+     */
+    public LogNormalDistribution(double scale, double shape) throws NotStrictlyPositiveException {
+        this(scale, shape, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Create a log-normal distribution using the specified scale, shape and inverse cumulative
+     * distribution accuracy.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param scale the scale parameter of this distribution
+     * @param shape the shape parameter of this distribution
+     * @param inverseCumAccuracy Inverse cumulative probability accuracy.
+     * @throws NotStrictlyPositiveException if {@code shape <= 0}.
+     */
+    public LogNormalDistribution(double scale, double shape, double inverseCumAccuracy)
+            throws NotStrictlyPositiveException {
+        this(new Well19937c(), scale, shape, inverseCumAccuracy);
+    }
+
+    /**
+     * Creates a log-normal distribution.
+     *
+     * @param rng Random number generator.
+     * @param scale Scale parameter of this distribution.
+     * @param shape Shape parameter of this distribution.
+     * @throws NotStrictlyPositiveException if {@code shape <= 0}.
+     * @since 3.3
+     */
+    public LogNormalDistribution(RandomGenerator rng, double scale, double shape)
+            throws NotStrictlyPositiveException {
+        this(rng, scale, shape, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Creates a log-normal distribution.
+     *
+     * @param rng Random number generator.
+     * @param scale Scale parameter of this distribution.
+     * @param shape Shape parameter of this distribution.
+     * @param inverseCumAccuracy Inverse cumulative probability accuracy.
+     * @throws NotStrictlyPositiveException if {@code shape <= 0}.
+     * @since 3.1
+     */
+    public LogNormalDistribution(
+            RandomGenerator rng, double scale, double shape, double inverseCumAccuracy)
+            throws NotStrictlyPositiveException {
+        super(rng);
+
+        if (shape <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.SHAPE, shape);
+        }
+
+        this.scale = scale;
+        this.shape = shape;
+        this.logShapePlusHalfLog2Pi = FastMath.log(shape) + 0.5 * FastMath.log(2 * FastMath.PI);
+        this.solverAbsoluteAccuracy = inverseCumAccuracy;
+    }
+
+    /**
+     * Returns the scale parameter of this distribution.
+     *
+     * @return the scale parameter
+     */
+    public double getScale() {
+        return scale;
+    }
+
+    /**
+     * Returns the shape parameter of this distribution.
+     *
+     * @return the shape parameter
+     */
+    public double getShape() {
+        return shape;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For scale {@code m}, and shape {@code s} of this distribution, the PDF is given by
+     *
+     * <ul>
+     *   <li>{@code 0} if {@code x <= 0},
+     *   <li>{@code exp(-0.5 * ((ln(x) - m) / s)^2) / (s * sqrt(2 * pi) * x)} otherwise.
+     * </ul>
+     */
+    public double density(double x) {
+        if (x <= 0) {
+            return 0;
+        }
+        final double x0 = FastMath.log(x) - scale;
+        final double x1 = x0 / shape;
+        return FastMath.exp(-0.5 * x1 * x1) / (shape * SQRT2PI * x);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>See documentation of {@link #density(double)} for computation details.
+     */
+    @Override
+    public double logDensity(double x) {
+        if (x <= 0) {
+            return Double.NEGATIVE_INFINITY;
+        }
+        final double logX = FastMath.log(x);
+        final double x0 = logX - scale;
+        final double x1 = x0 / shape;
+        return -0.5 * x1 * x1 - (logShapePlusHalfLog2Pi + logX);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For scale {@code m}, and shape {@code s} of this distribution, the CDF is given by
+     *
+     * <ul>
+     *   <li>{@code 0} if {@code x <= 0},
+     *   <li>{@code 0} if {@code ln(x) - m < 0} and {@code m - ln(x) > 40 * s}, as in these cases
+     *       the actual value is within {@code Double.MIN_VALUE} of 0,
+     *   <li>{@code 1} if {@code ln(x) - m >= 0} and {@code ln(x) - m > 40 * s}, as in these cases
+     *       the actual value is within {@code Double.MIN_VALUE} of 1,
+     *   <li>{@code 0.5 + 0.5 * erf((ln(x) - m) / (s * sqrt(2))} otherwise.
+     * </ul>
+     */
+    public double cumulativeProbability(double x) {
+        if (x <= 0) {
+            return 0;
+        }
+        final double dev = FastMath.log(x) - scale;
+        if (FastMath.abs(dev) > 40 * shape) {
+            return dev < 0 ? 0.0d : 1.0d;
+        }
+        return 0.5 + 0.5 * Erf.erf(dev / (shape * SQRT2));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @deprecated See {@link RealDistribution#cumulativeProbability(double,double)}
+     */
+    @Override
+    @Deprecated
+    public double cumulativeProbability(double x0, double x1) throws NumberIsTooLargeException {
+        return probability(x0, x1);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double probability(double x0, double x1) throws NumberIsTooLargeException {
+        if (x0 > x1) {
+            throw new NumberIsTooLargeException(
+                    LocalizedFormats.LOWER_ENDPOINT_ABOVE_UPPER_ENDPOINT, x0, x1, true);
+        }
+        if (x0 <= 0 || x1 <= 0) {
+            return super.probability(x0, x1);
+        }
+        final double denom = shape * SQRT2;
+        final double v0 = (FastMath.log(x0) - scale) / denom;
+        final double v1 = (FastMath.log(x1) - scale) / denom;
+        return 0.5 * Erf.erf(v0, v1);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected double getSolverAbsoluteAccuracy() {
+        return solverAbsoluteAccuracy;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For scale {@code m} and shape {@code s}, the mean is {@code exp(m + s^2 / 2)}.
+     */
+    public double getNumericalMean() {
+        double s = shape;
+        return FastMath.exp(scale + (s * s / 2));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For scale {@code m} and shape {@code s}, the variance is {@code (exp(s^2) - 1) * exp(2 * m
+     * + s^2)}.
+     */
+    public double getNumericalVariance() {
+        final double s = shape;
+        final double ss = s * s;
+        return (FastMath.expm1(ss)) * FastMath.exp(2 * scale + ss);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The lower bound of the support is always 0 no matter the parameters.
+     *
+     * @return lower bound of the support (always 0)
+     */
+    public double getSupportLowerBound() {
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The upper bound of the support is always positive infinity no matter the parameters.
+     *
+     * @return upper bound of the support (always {@code Double.POSITIVE_INFINITY})
+     */
+    public double getSupportUpperBound() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportLowerBoundInclusive() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportUpperBoundInclusive() {
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The support of this distribution is connected.
+     *
+     * @return {@code true}
+     */
+    public boolean isSupportConnected() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double sample() {
+        final double n = random.nextGaussian();
+        return FastMath.exp(scale + shape * n);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/LogisticDistribution.java b/src/main/java/org/apache/commons/math3/distribution/LogisticDistribution.java
new file mode 100644
index 0000000..d2e7504
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/LogisticDistribution.java
@@ -0,0 +1,161 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well19937c;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * This class implements the Logistic distribution.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Logistic_distribution">Logistic Distribution
+ *     (Wikipedia)</a>
+ * @see <a href="http://mathworld.wolfram.com/LogisticDistribution.html">Logistic Distribution
+ *     (Mathworld)</a>
+ * @since 3.4
+ */
+public class LogisticDistribution extends AbstractRealDistribution {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 20141003;
+
+    /** The location parameter. */
+    private final double mu;
+
+    /** The scale parameter. */
+    private final double s;
+
+    /**
+     * Build a new instance.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param mu location parameter
+     * @param s scale parameter (must be positive)
+     * @throws NotStrictlyPositiveException if {@code beta <= 0}
+     */
+    public LogisticDistribution(double mu, double s) {
+        this(new Well19937c(), mu, s);
+    }
+
+    /**
+     * Build a new instance.
+     *
+     * @param rng Random number generator
+     * @param mu location parameter
+     * @param s scale parameter (must be positive)
+     * @throws NotStrictlyPositiveException if {@code beta <= 0}
+     */
+    public LogisticDistribution(RandomGenerator rng, double mu, double s) {
+        super(rng);
+
+        if (s <= 0.0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.NOT_POSITIVE_SCALE, s);
+        }
+
+        this.mu = mu;
+        this.s = s;
+    }
+
+    /**
+     * Access the location parameter, {@code mu}.
+     *
+     * @return the location parameter.
+     */
+    public double getLocation() {
+        return mu;
+    }
+
+    /**
+     * Access the scale parameter, {@code s}.
+     *
+     * @return the scale parameter.
+     */
+    public double getScale() {
+        return s;
+    }
+
+    /** {@inheritDoc} */
+    public double density(double x) {
+        double z = (x - mu) / s;
+        double v = FastMath.exp(-z);
+        return 1 / s * v / ((1.0 + v) * (1.0 + v));
+    }
+
+    /** {@inheritDoc} */
+    public double cumulativeProbability(double x) {
+        double z = 1 / s * (x - mu);
+        return 1.0 / (1.0 + FastMath.exp(-z));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double inverseCumulativeProbability(double p) throws OutOfRangeException {
+        if (p < 0.0 || p > 1.0) {
+            throw new OutOfRangeException(p, 0.0, 1.0);
+        } else if (p == 0) {
+            return 0.0;
+        } else if (p == 1) {
+            return Double.POSITIVE_INFINITY;
+        }
+        return s * Math.log(p / (1.0 - p)) + mu;
+    }
+
+    /** {@inheritDoc} */
+    public double getNumericalMean() {
+        return mu;
+    }
+
+    /** {@inheritDoc} */
+    public double getNumericalVariance() {
+        return (MathUtils.PI_SQUARED / 3.0) * (1.0 / (s * s));
+    }
+
+    /** {@inheritDoc} */
+    public double getSupportLowerBound() {
+        return Double.NEGATIVE_INFINITY;
+    }
+
+    /** {@inheritDoc} */
+    public double getSupportUpperBound() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportLowerBoundInclusive() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportUpperBoundInclusive() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportConnected() {
+        return true;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/MixtureMultivariateNormalDistribution.java b/src/main/java/org/apache/commons/math3/distribution/MixtureMultivariateNormalDistribution.java
new file mode 100644
index 0000000..547d349
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/MixtureMultivariateNormalDistribution.java
@@ -0,0 +1,104 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.util.Pair;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Multivariate normal mixture distribution. This class is mainly syntactic sugar.
+ *
+ * @see MixtureMultivariateRealDistribution
+ * @since 3.2
+ */
+public class MixtureMultivariateNormalDistribution
+        extends MixtureMultivariateRealDistribution<MultivariateNormalDistribution> {
+
+    /**
+     * Creates a multivariate normal mixture distribution.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link
+     * org.apache.commons.math3.random.Well19937c Well19937c} as random generator to be used for
+     * sampling only (see {@link #sample()} and {@link #sample(int)}). In case no sampling is needed
+     * for the created distribution, it is advised to pass {@code null} as random generator via the
+     * appropriate constructors to avoid the additional initialisation overhead.
+     *
+     * @param weights Weights of each component.
+     * @param means Mean vector for each component.
+     * @param covariances Covariance matrix for each component.
+     */
+    public MixtureMultivariateNormalDistribution(
+            double[] weights, double[][] means, double[][][] covariances) {
+        super(createComponents(weights, means, covariances));
+    }
+
+    /**
+     * Creates a mixture model from a list of distributions and their associated weights.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link
+     * org.apache.commons.math3.random.Well19937c Well19937c} as random generator to be used for
+     * sampling only (see {@link #sample()} and {@link #sample(int)}). In case no sampling is needed
+     * for the created distribution, it is advised to pass {@code null} as random generator via the
+     * appropriate constructors to avoid the additional initialisation overhead.
+     *
+     * @param components List of (weight, distribution) pairs from which to sample.
+     */
+    public MixtureMultivariateNormalDistribution(
+            List<Pair<Double, MultivariateNormalDistribution>> components) {
+        super(components);
+    }
+
+    /**
+     * Creates a mixture model from a list of distributions and their associated weights.
+     *
+     * @param rng Random number generator.
+     * @param components Distributions from which to sample.
+     * @throws NotPositiveException if any of the weights is negative.
+     * @throws DimensionMismatchException if not all components have the same number of variables.
+     */
+    public MixtureMultivariateNormalDistribution(
+            RandomGenerator rng, List<Pair<Double, MultivariateNormalDistribution>> components)
+            throws NotPositiveException, DimensionMismatchException {
+        super(rng, components);
+    }
+
+    /**
+     * @param weights Weights of each component.
+     * @param means Mean vector for each component.
+     * @param covariances Covariance matrix for each component.
+     * @return the list of components.
+     */
+    private static List<Pair<Double, MultivariateNormalDistribution>> createComponents(
+            double[] weights, double[][] means, double[][][] covariances) {
+        final List<Pair<Double, MultivariateNormalDistribution>> mvns =
+                new ArrayList<Pair<Double, MultivariateNormalDistribution>>(weights.length);
+
+        for (int i = 0; i < weights.length; i++) {
+            final MultivariateNormalDistribution dist =
+                    new MultivariateNormalDistribution(means[i], covariances[i]);
+
+            mvns.add(new Pair<Double, MultivariateNormalDistribution>(weights[i], dist));
+        }
+
+        return mvns;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/MixtureMultivariateRealDistribution.java b/src/main/java/org/apache/commons/math3/distribution/MixtureMultivariateRealDistribution.java
new file mode 100644
index 0000000..4c65b75
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/MixtureMultivariateRealDistribution.java
@@ -0,0 +1,167 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well19937c;
+import org.apache.commons.math3.util.Pair;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class for representing <a href="http://en.wikipedia.org/wiki/Mixture_model">mixture model</a>
+ * distributions.
+ *
+ * @param <T> Type of the mixture components.
+ * @since 3.1
+ */
+public class MixtureMultivariateRealDistribution<T extends MultivariateRealDistribution>
+        extends AbstractMultivariateRealDistribution {
+    /** Normalized weight of each mixture component. */
+    private final double[] weight;
+
+    /** Mixture components. */
+    private final List<T> distribution;
+
+    /**
+     * Creates a mixture model from a list of distributions and their associated weights.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param components List of (weight, distribution) pairs from which to sample.
+     */
+    public MixtureMultivariateRealDistribution(List<Pair<Double, T>> components) {
+        this(new Well19937c(), components);
+    }
+
+    /**
+     * Creates a mixture model from a list of distributions and their associated weights.
+     *
+     * @param rng Random number generator.
+     * @param components Distributions from which to sample.
+     * @throws NotPositiveException if any of the weights is negative.
+     * @throws DimensionMismatchException if not all components have the same number of variables.
+     */
+    public MixtureMultivariateRealDistribution(
+            RandomGenerator rng, List<Pair<Double, T>> components) {
+        super(rng, components.get(0).getSecond().getDimension());
+
+        final int numComp = components.size();
+        final int dim = getDimension();
+        double weightSum = 0;
+        for (int i = 0; i < numComp; i++) {
+            final Pair<Double, T> comp = components.get(i);
+            if (comp.getSecond().getDimension() != dim) {
+                throw new DimensionMismatchException(comp.getSecond().getDimension(), dim);
+            }
+            if (comp.getFirst() < 0) {
+                throw new NotPositiveException(comp.getFirst());
+            }
+            weightSum += comp.getFirst();
+        }
+
+        // Check for overflow.
+        if (Double.isInfinite(weightSum)) {
+            throw new MathArithmeticException(LocalizedFormats.OVERFLOW);
+        }
+
+        // Store each distribution and its normalized weight.
+        distribution = new ArrayList<T>();
+        weight = new double[numComp];
+        for (int i = 0; i < numComp; i++) {
+            final Pair<Double, T> comp = components.get(i);
+            weight[i] = comp.getFirst() / weightSum;
+            distribution.add(comp.getSecond());
+        }
+    }
+
+    /** {@inheritDoc} */
+    public double density(final double[] values) {
+        double p = 0;
+        for (int i = 0; i < weight.length; i++) {
+            p += weight[i] * distribution.get(i).density(values);
+        }
+        return p;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double[] sample() {
+        // Sampled values.
+        double[] vals = null;
+
+        // Determine which component to sample from.
+        final double randomValue = random.nextDouble();
+        double sum = 0;
+
+        for (int i = 0; i < weight.length; i++) {
+            sum += weight[i];
+            if (randomValue <= sum) {
+                // pick model i
+                vals = distribution.get(i).sample();
+                break;
+            }
+        }
+
+        if (vals == null) {
+            // This should never happen, but it ensures we won't return a null in
+            // case the loop above has some floating point inequality problem on
+            // the final iteration.
+            vals = distribution.get(weight.length - 1).sample();
+        }
+
+        return vals;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void reseedRandomGenerator(long seed) {
+        // Seed needs to be propagated to underlying components
+        // in order to maintain consistency between runs.
+        super.reseedRandomGenerator(seed);
+
+        for (int i = 0; i < distribution.size(); i++) {
+            // Make each component's seed different in order to avoid
+            // using the same sequence of random numbers.
+            distribution.get(i).reseedRandomGenerator(i + 1 + seed);
+        }
+    }
+
+    /**
+     * Gets the distributions that make up the mixture model.
+     *
+     * @return the component distributions and associated weights.
+     */
+    public List<Pair<Double, T>> getComponents() {
+        final List<Pair<Double, T>> list = new ArrayList<Pair<Double, T>>(weight.length);
+
+        for (int i = 0; i < weight.length; i++) {
+            list.add(new Pair<Double, T>(weight[i], distribution.get(i)));
+        }
+
+        return list;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/MultivariateNormalDistribution.java b/src/main/java/org/apache/commons/math3/distribution/MultivariateNormalDistribution.java
new file mode 100644
index 0000000..388761a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/MultivariateNormalDistribution.java
@@ -0,0 +1,237 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.linear.Array2DRowRealMatrix;
+import org.apache.commons.math3.linear.EigenDecomposition;
+import org.apache.commons.math3.linear.NonPositiveDefiniteMatrixException;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.linear.SingularMatrixException;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well19937c;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * Implementation of the multivariate normal (Gaussian) distribution.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Multivariate_normal_distribution">Multivariate normal
+ *     distribution (Wikipedia)</a>
+ * @see <a href="http://mathworld.wolfram.com/MultivariateNormalDistribution.html">Multivariate
+ *     normal distribution (MathWorld)</a>
+ * @since 3.1
+ */
+public class MultivariateNormalDistribution extends AbstractMultivariateRealDistribution {
+    /** Vector of means. */
+    private final double[] means;
+
+    /** Covariance matrix. */
+    private final RealMatrix covarianceMatrix;
+
+    /** The matrix inverse of the covariance matrix. */
+    private final RealMatrix covarianceMatrixInverse;
+
+    /** The determinant of the covariance matrix. */
+    private final double covarianceMatrixDeterminant;
+
+    /** Matrix used in computation of samples. */
+    private final RealMatrix samplingMatrix;
+
+    /**
+     * Creates a multivariate normal distribution with the given mean vector and covariance matrix.
+     * <br>
+     * The number of dimensions is equal to the length of the mean vector and to the number of rows
+     * and columns of the covariance matrix. It is frequently written as "p" in formulae.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param means Vector of means.
+     * @param covariances Covariance matrix.
+     * @throws DimensionMismatchException if the arrays length are inconsistent.
+     * @throws SingularMatrixException if the eigenvalue decomposition cannot be performed on the
+     *     provided covariance matrix.
+     * @throws NonPositiveDefiniteMatrixException if any of the eigenvalues is negative.
+     */
+    public MultivariateNormalDistribution(final double[] means, final double[][] covariances)
+            throws SingularMatrixException,
+                    DimensionMismatchException,
+                    NonPositiveDefiniteMatrixException {
+        this(new Well19937c(), means, covariances);
+    }
+
+    /**
+     * Creates a multivariate normal distribution with the given mean vector and covariance matrix.
+     * <br>
+     * The number of dimensions is equal to the length of the mean vector and to the number of rows
+     * and columns of the covariance matrix. It is frequently written as "p" in formulae.
+     *
+     * @param rng Random Number Generator.
+     * @param means Vector of means.
+     * @param covariances Covariance matrix.
+     * @throws DimensionMismatchException if the arrays length are inconsistent.
+     * @throws SingularMatrixException if the eigenvalue decomposition cannot be performed on the
+     *     provided covariance matrix.
+     * @throws NonPositiveDefiniteMatrixException if any of the eigenvalues is negative.
+     */
+    public MultivariateNormalDistribution(
+            RandomGenerator rng, final double[] means, final double[][] covariances)
+            throws SingularMatrixException,
+                    DimensionMismatchException,
+                    NonPositiveDefiniteMatrixException {
+        super(rng, means.length);
+
+        final int dim = means.length;
+
+        if (covariances.length != dim) {
+            throw new DimensionMismatchException(covariances.length, dim);
+        }
+
+        for (int i = 0; i < dim; i++) {
+            if (dim != covariances[i].length) {
+                throw new DimensionMismatchException(covariances[i].length, dim);
+            }
+        }
+
+        this.means = MathArrays.copyOf(means);
+
+        covarianceMatrix = new Array2DRowRealMatrix(covariances);
+
+        // Covariance matrix eigen decomposition.
+        final EigenDecomposition covMatDec = new EigenDecomposition(covarianceMatrix);
+
+        // Compute and store the inverse.
+        covarianceMatrixInverse = covMatDec.getSolver().getInverse();
+        // Compute and store the determinant.
+        covarianceMatrixDeterminant = covMatDec.getDeterminant();
+
+        // Eigenvalues of the covariance matrix.
+        final double[] covMatEigenvalues = covMatDec.getRealEigenvalues();
+
+        for (int i = 0; i < covMatEigenvalues.length; i++) {
+            if (covMatEigenvalues[i] < 0) {
+                throw new NonPositiveDefiniteMatrixException(covMatEigenvalues[i], i, 0);
+            }
+        }
+
+        // Matrix where each column is an eigenvector of the covariance matrix.
+        final Array2DRowRealMatrix covMatEigenvectors = new Array2DRowRealMatrix(dim, dim);
+        for (int v = 0; v < dim; v++) {
+            final double[] evec = covMatDec.getEigenvector(v).toArray();
+            covMatEigenvectors.setColumn(v, evec);
+        }
+
+        final RealMatrix tmpMatrix = covMatEigenvectors.transpose();
+
+        // Scale each eigenvector by the square root of its eigenvalue.
+        for (int row = 0; row < dim; row++) {
+            final double factor = FastMath.sqrt(covMatEigenvalues[row]);
+            for (int col = 0; col < dim; col++) {
+                tmpMatrix.multiplyEntry(row, col, factor);
+            }
+        }
+
+        samplingMatrix = covMatEigenvectors.multiply(tmpMatrix);
+    }
+
+    /**
+     * Gets the mean vector.
+     *
+     * @return the mean vector.
+     */
+    public double[] getMeans() {
+        return MathArrays.copyOf(means);
+    }
+
+    /**
+     * Gets the covariance matrix.
+     *
+     * @return the covariance matrix.
+     */
+    public RealMatrix getCovariances() {
+        return covarianceMatrix.copy();
+    }
+
+    /** {@inheritDoc} */
+    public double density(final double[] vals) throws DimensionMismatchException {
+        final int dim = getDimension();
+        if (vals.length != dim) {
+            throw new DimensionMismatchException(vals.length, dim);
+        }
+
+        return FastMath.pow(2 * FastMath.PI, -0.5 * dim)
+                * FastMath.pow(covarianceMatrixDeterminant, -0.5)
+                * getExponentTerm(vals);
+    }
+
+    /**
+     * Gets the square root of each element on the diagonal of the covariance matrix.
+     *
+     * @return the standard deviations.
+     */
+    public double[] getStandardDeviations() {
+        final int dim = getDimension();
+        final double[] std = new double[dim];
+        final double[][] s = covarianceMatrix.getData();
+        for (int i = 0; i < dim; i++) {
+            std[i] = FastMath.sqrt(s[i][i]);
+        }
+        return std;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double[] sample() {
+        final int dim = getDimension();
+        final double[] normalVals = new double[dim];
+
+        for (int i = 0; i < dim; i++) {
+            normalVals[i] = random.nextGaussian();
+        }
+
+        final double[] vals = samplingMatrix.operate(normalVals);
+
+        for (int i = 0; i < dim; i++) {
+            vals[i] += means[i];
+        }
+
+        return vals;
+    }
+
+    /**
+     * Computes the term used in the exponent (see definition of the distribution).
+     *
+     * @param values Values at which to compute density.
+     * @return the multiplication factor of density calculations.
+     */
+    private double getExponentTerm(final double[] values) {
+        final double[] centered = new double[values.length];
+        for (int i = 0; i < centered.length; i++) {
+            centered[i] = values[i] - getMeans()[i];
+        }
+        final double[] preMultiplied = covarianceMatrixInverse.preMultiply(centered);
+        double sum = 0;
+        for (int i = 0; i < preMultiplied.length; i++) {
+            sum += preMultiplied[i] * centered[i];
+        }
+        return FastMath.exp(-0.5 * sum);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/MultivariateRealDistribution.java b/src/main/java/org/apache/commons/math3/distribution/MultivariateRealDistribution.java
new file mode 100644
index 0000000..050cfd5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/MultivariateRealDistribution.java
@@ -0,0 +1,75 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+
+/**
+ * Base interface for multivariate distributions on the reals.
+ *
+ * <p>This is based largely on the RealDistribution interface, but cumulative distribution functions
+ * are not required because they are often quite difficult to compute for multivariate
+ * distributions.
+ *
+ * @since 3.1
+ */
+public interface MultivariateRealDistribution {
+    /**
+     * Returns the probability density function (PDF) of this distribution evaluated at the
+     * specified point {@code x}. In general, the PDF is the derivative of the cumulative
+     * distribution function. If the derivative does not exist at {@code x}, then an appropriate
+     * replacement should be returned, e.g. {@code Double.POSITIVE_INFINITY}, {@code Double.NaN}, or
+     * the limit inferior or limit superior of the difference quotient.
+     *
+     * @param x Point at which the PDF is evaluated.
+     * @return the value of the probability density function at point {@code x}.
+     */
+    double density(double[] x);
+
+    /**
+     * Reseeds the random generator used to generate samples.
+     *
+     * @param seed Seed with which to initialize the random number generator.
+     */
+    void reseedRandomGenerator(long seed);
+
+    /**
+     * Gets the number of random variables of the distribution. It is the size of the array returned
+     * by the {@link #sample() sample} method.
+     *
+     * @return the number of variables.
+     */
+    int getDimension();
+
+    /**
+     * Generates a random value vector sampled from this distribution.
+     *
+     * @return a random value vector.
+     */
+    double[] sample();
+
+    /**
+     * Generates a list of a random value vectors from the distribution.
+     *
+     * @param sampleSize the number of random vectors to generate.
+     * @return an array representing the random samples.
+     * @throws org.apache.commons.math3.exception.NotStrictlyPositiveException if {@code sampleSize}
+     *     is not positive.
+     * @see #sample()
+     */
+    double[][] sample(int sampleSize) throws NotStrictlyPositiveException;
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/NakagamiDistribution.java b/src/main/java/org/apache/commons/math3/distribution/NakagamiDistribution.java
new file mode 100644
index 0000000..298cb30
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/NakagamiDistribution.java
@@ -0,0 +1,192 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well19937c;
+import org.apache.commons.math3.special.Gamma;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * This class implements the Nakagami distribution.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Nakagami_distribution">Nakagami Distribution
+ *     (Wikipedia)</a>
+ * @since 3.4
+ */
+public class NakagamiDistribution extends AbstractRealDistribution {
+
+    /** Default inverse cumulative probability accuracy. */
+    public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9;
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 20141003;
+
+    /** The shape parameter. */
+    private final double mu;
+
+    /** The scale parameter. */
+    private final double omega;
+
+    /** Inverse cumulative probability accuracy. */
+    private final double inverseAbsoluteAccuracy;
+
+    /**
+     * Build a new instance.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param mu shape parameter
+     * @param omega scale parameter (must be positive)
+     * @throws NumberIsTooSmallException if {@code mu < 0.5}
+     * @throws NotStrictlyPositiveException if {@code omega <= 0}
+     */
+    public NakagamiDistribution(double mu, double omega) {
+        this(mu, omega, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Build a new instance.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param mu shape parameter
+     * @param omega scale parameter (must be positive)
+     * @param inverseAbsoluteAccuracy the maximum absolute error in inverse cumulative probability
+     *     estimates (defaults to {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}).
+     * @throws NumberIsTooSmallException if {@code mu < 0.5}
+     * @throws NotStrictlyPositiveException if {@code omega <= 0}
+     */
+    public NakagamiDistribution(double mu, double omega, double inverseAbsoluteAccuracy) {
+        this(new Well19937c(), mu, omega, inverseAbsoluteAccuracy);
+    }
+
+    /**
+     * Build a new instance.
+     *
+     * @param rng Random number generator
+     * @param mu shape parameter
+     * @param omega scale parameter (must be positive)
+     * @param inverseAbsoluteAccuracy the maximum absolute error in inverse cumulative probability
+     *     estimates (defaults to {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}).
+     * @throws NumberIsTooSmallException if {@code mu < 0.5}
+     * @throws NotStrictlyPositiveException if {@code omega <= 0}
+     */
+    public NakagamiDistribution(
+            RandomGenerator rng, double mu, double omega, double inverseAbsoluteAccuracy) {
+        super(rng);
+
+        if (mu < 0.5) {
+            throw new NumberIsTooSmallException(mu, 0.5, true);
+        }
+        if (omega <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.NOT_POSITIVE_SCALE, omega);
+        }
+
+        this.mu = mu;
+        this.omega = omega;
+        this.inverseAbsoluteAccuracy = inverseAbsoluteAccuracy;
+    }
+
+    /**
+     * Access the shape parameter, {@code mu}.
+     *
+     * @return the shape parameter.
+     */
+    public double getShape() {
+        return mu;
+    }
+
+    /**
+     * Access the scale parameter, {@code omega}.
+     *
+     * @return the scale parameter.
+     */
+    public double getScale() {
+        return omega;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected double getSolverAbsoluteAccuracy() {
+        return inverseAbsoluteAccuracy;
+    }
+
+    /** {@inheritDoc} */
+    public double density(double x) {
+        if (x <= 0) {
+            return 0.0;
+        }
+        return 2.0
+                * FastMath.pow(mu, mu)
+                / (Gamma.gamma(mu) * FastMath.pow(omega, mu))
+                * FastMath.pow(x, 2 * mu - 1)
+                * FastMath.exp(-mu * x * x / omega);
+    }
+
+    /** {@inheritDoc} */
+    public double cumulativeProbability(double x) {
+        return Gamma.regularizedGammaP(mu, mu * x * x / omega);
+    }
+
+    /** {@inheritDoc} */
+    public double getNumericalMean() {
+        return Gamma.gamma(mu + 0.5) / Gamma.gamma(mu) * FastMath.sqrt(omega / mu);
+    }
+
+    /** {@inheritDoc} */
+    public double getNumericalVariance() {
+        double v = Gamma.gamma(mu + 0.5) / Gamma.gamma(mu);
+        return omega * (1 - 1 / mu * v * v);
+    }
+
+    /** {@inheritDoc} */
+    public double getSupportLowerBound() {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    public double getSupportUpperBound() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportLowerBoundInclusive() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportUpperBoundInclusive() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportConnected() {
+        return true;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/NormalDistribution.java b/src/main/java/org/apache/commons/math3/distribution/NormalDistribution.java
new file mode 100644
index 0000000..a2bab56
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/NormalDistribution.java
@@ -0,0 +1,308 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well19937c;
+import org.apache.commons.math3.special.Erf;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Implementation of the normal (gaussian) distribution.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Normal_distribution">Normal distribution
+ *     (Wikipedia)</a>
+ * @see <a href="http://mathworld.wolfram.com/NormalDistribution.html">Normal distribution
+ *     (MathWorld)</a>
+ */
+public class NormalDistribution extends AbstractRealDistribution {
+    /**
+     * Default inverse cumulative probability accuracy.
+     *
+     * @since 2.1
+     */
+    public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9;
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 8589540077390120676L;
+
+    /** &radic;(2) */
+    private static final double SQRT2 = FastMath.sqrt(2.0);
+
+    /** Mean of this distribution. */
+    private final double mean;
+
+    /** Standard deviation of this distribution. */
+    private final double standardDeviation;
+
+    /** The value of {@code log(sd) + 0.5*log(2*pi)} stored for faster computation. */
+    private final double logStandardDeviationPlusHalfLog2Pi;
+
+    /** Inverse cumulative probability accuracy. */
+    private final double solverAbsoluteAccuracy;
+
+    /**
+     * Create a normal distribution with mean equal to zero and standard deviation equal to one.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     */
+    public NormalDistribution() {
+        this(0, 1);
+    }
+
+    /**
+     * Create a normal distribution using the given mean and standard deviation.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param mean Mean for this distribution.
+     * @param sd Standard deviation for this distribution.
+     * @throws NotStrictlyPositiveException if {@code sd <= 0}.
+     */
+    public NormalDistribution(double mean, double sd) throws NotStrictlyPositiveException {
+        this(mean, sd, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Create a normal distribution using the given mean, standard deviation and inverse cumulative
+     * distribution accuracy.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param mean Mean for this distribution.
+     * @param sd Standard deviation for this distribution.
+     * @param inverseCumAccuracy Inverse cumulative probability accuracy.
+     * @throws NotStrictlyPositiveException if {@code sd <= 0}.
+     * @since 2.1
+     */
+    public NormalDistribution(double mean, double sd, double inverseCumAccuracy)
+            throws NotStrictlyPositiveException {
+        this(new Well19937c(), mean, sd, inverseCumAccuracy);
+    }
+
+    /**
+     * Creates a normal distribution.
+     *
+     * @param rng Random number generator.
+     * @param mean Mean for this distribution.
+     * @param sd Standard deviation for this distribution.
+     * @throws NotStrictlyPositiveException if {@code sd <= 0}.
+     * @since 3.3
+     */
+    public NormalDistribution(RandomGenerator rng, double mean, double sd)
+            throws NotStrictlyPositiveException {
+        this(rng, mean, sd, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Creates a normal distribution.
+     *
+     * @param rng Random number generator.
+     * @param mean Mean for this distribution.
+     * @param sd Standard deviation for this distribution.
+     * @param inverseCumAccuracy Inverse cumulative probability accuracy.
+     * @throws NotStrictlyPositiveException if {@code sd <= 0}.
+     * @since 3.1
+     */
+    public NormalDistribution(
+            RandomGenerator rng, double mean, double sd, double inverseCumAccuracy)
+            throws NotStrictlyPositiveException {
+        super(rng);
+
+        if (sd <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.STANDARD_DEVIATION, sd);
+        }
+
+        this.mean = mean;
+        standardDeviation = sd;
+        logStandardDeviationPlusHalfLog2Pi = FastMath.log(sd) + 0.5 * FastMath.log(2 * FastMath.PI);
+        solverAbsoluteAccuracy = inverseCumAccuracy;
+    }
+
+    /**
+     * Access the mean.
+     *
+     * @return the mean for this distribution.
+     */
+    public double getMean() {
+        return mean;
+    }
+
+    /**
+     * Access the standard deviation.
+     *
+     * @return the standard deviation for this distribution.
+     */
+    public double getStandardDeviation() {
+        return standardDeviation;
+    }
+
+    /** {@inheritDoc} */
+    public double density(double x) {
+        return FastMath.exp(logDensity(x));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double logDensity(double x) {
+        final double x0 = x - mean;
+        final double x1 = x0 / standardDeviation;
+        return -0.5 * x1 * x1 - logStandardDeviationPlusHalfLog2Pi;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>If {@code x} is more than 40 standard deviations from the mean, 0 or 1 is returned, as in
+     * these cases the actual value is within {@code Double.MIN_VALUE} of 0 or 1.
+     */
+    public double cumulativeProbability(double x) {
+        final double dev = x - mean;
+        if (FastMath.abs(dev) > 40 * standardDeviation) {
+            return dev < 0 ? 0.0d : 1.0d;
+        }
+        return 0.5 * Erf.erfc(-dev / (standardDeviation * SQRT2));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    @Override
+    public double inverseCumulativeProbability(final double p) throws OutOfRangeException {
+        if (p < 0.0 || p > 1.0) {
+            throw new OutOfRangeException(p, 0, 1);
+        }
+        return mean + standardDeviation * SQRT2 * Erf.erfInv(2 * p - 1);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @deprecated See {@link RealDistribution#cumulativeProbability(double,double)}
+     */
+    @Override
+    @Deprecated
+    public double cumulativeProbability(double x0, double x1) throws NumberIsTooLargeException {
+        return probability(x0, x1);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double probability(double x0, double x1) throws NumberIsTooLargeException {
+        if (x0 > x1) {
+            throw new NumberIsTooLargeException(
+                    LocalizedFormats.LOWER_ENDPOINT_ABOVE_UPPER_ENDPOINT, x0, x1, true);
+        }
+        final double denom = standardDeviation * SQRT2;
+        final double v0 = (x0 - mean) / denom;
+        final double v1 = (x1 - mean) / denom;
+        return 0.5 * Erf.erf(v0, v1);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected double getSolverAbsoluteAccuracy() {
+        return solverAbsoluteAccuracy;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For mean parameter {@code mu}, the mean is {@code mu}.
+     */
+    public double getNumericalMean() {
+        return getMean();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For standard deviation parameter {@code s}, the variance is {@code s^2}.
+     */
+    public double getNumericalVariance() {
+        final double s = getStandardDeviation();
+        return s * s;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The lower bound of the support is always negative infinity no matter the parameters.
+     *
+     * @return lower bound of the support (always {@code Double.NEGATIVE_INFINITY})
+     */
+    public double getSupportLowerBound() {
+        return Double.NEGATIVE_INFINITY;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The upper bound of the support is always positive infinity no matter the parameters.
+     *
+     * @return upper bound of the support (always {@code Double.POSITIVE_INFINITY})
+     */
+    public double getSupportUpperBound() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportLowerBoundInclusive() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportUpperBoundInclusive() {
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The support of this distribution is connected.
+     *
+     * @return {@code true}
+     */
+    public boolean isSupportConnected() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double sample() {
+        return standardDeviation * random.nextGaussian() + mean;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/ParetoDistribution.java b/src/main/java/org/apache/commons/math3/distribution/ParetoDistribution.java
new file mode 100644
index 0000000..c4d5d58
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/ParetoDistribution.java
@@ -0,0 +1,315 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well19937c;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Implementation of the Pareto distribution.
+ *
+ * <p><strong>Parameters:</strong> The probability distribution function of {@code X} is given by
+ * (for {@code x >= k}):
+ *
+ * <pre>
+ *  α * k^α / x^(α + 1)
+ * </pre>
+ *
+ * <p>
+ *
+ * <ul>
+ *   <li>{@code k} is the <em>scale</em> parameter: this is the minimum possible value of {@code X},
+ *   <li>{@code α} is the <em>shape</em> parameter: this is the Pareto index
+ * </ul>
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Pareto_distribution">Pareto distribution
+ *     (Wikipedia)</a>
+ * @see <a href="http://mathworld.wolfram.com/ParetoDistribution.html">Pareto distribution
+ *     (MathWorld)</a>
+ * @since 3.3
+ */
+public class ParetoDistribution extends AbstractRealDistribution {
+
+    /** Default inverse cumulative probability accuracy. */
+    public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9;
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 20130424;
+
+    /** The scale parameter of this distribution. */
+    private final double scale;
+
+    /** The shape parameter of this distribution. */
+    private final double shape;
+
+    /** Inverse cumulative probability accuracy. */
+    private final double solverAbsoluteAccuracy;
+
+    /** Create a Pareto distribution with a scale of {@code 1} and a shape of {@code 1}. */
+    public ParetoDistribution() {
+        this(1, 1);
+    }
+
+    /**
+     * Create a Pareto distribution using the specified scale and shape.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param scale the scale parameter of this distribution
+     * @param shape the shape parameter of this distribution
+     * @throws NotStrictlyPositiveException if {@code scale <= 0} or {@code shape <= 0}.
+     */
+    public ParetoDistribution(double scale, double shape) throws NotStrictlyPositiveException {
+        this(scale, shape, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Create a Pareto distribution using the specified scale, shape and inverse cumulative
+     * distribution accuracy.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param scale the scale parameter of this distribution
+     * @param shape the shape parameter of this distribution
+     * @param inverseCumAccuracy Inverse cumulative probability accuracy.
+     * @throws NotStrictlyPositiveException if {@code scale <= 0} or {@code shape <= 0}.
+     */
+    public ParetoDistribution(double scale, double shape, double inverseCumAccuracy)
+            throws NotStrictlyPositiveException {
+        this(new Well19937c(), scale, shape, inverseCumAccuracy);
+    }
+
+    /**
+     * Creates a Pareto distribution.
+     *
+     * @param rng Random number generator.
+     * @param scale Scale parameter of this distribution.
+     * @param shape Shape parameter of this distribution.
+     * @throws NotStrictlyPositiveException if {@code scale <= 0} or {@code shape <= 0}.
+     */
+    public ParetoDistribution(RandomGenerator rng, double scale, double shape)
+            throws NotStrictlyPositiveException {
+        this(rng, scale, shape, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Creates a Pareto distribution.
+     *
+     * @param rng Random number generator.
+     * @param scale Scale parameter of this distribution.
+     * @param shape Shape parameter of this distribution.
+     * @param inverseCumAccuracy Inverse cumulative probability accuracy.
+     * @throws NotStrictlyPositiveException if {@code scale <= 0} or {@code shape <= 0}.
+     */
+    public ParetoDistribution(
+            RandomGenerator rng, double scale, double shape, double inverseCumAccuracy)
+            throws NotStrictlyPositiveException {
+        super(rng);
+
+        if (scale <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.SCALE, scale);
+        }
+
+        if (shape <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.SHAPE, shape);
+        }
+
+        this.scale = scale;
+        this.shape = shape;
+        this.solverAbsoluteAccuracy = inverseCumAccuracy;
+    }
+
+    /**
+     * Returns the scale parameter of this distribution.
+     *
+     * @return the scale parameter
+     */
+    public double getScale() {
+        return scale;
+    }
+
+    /**
+     * Returns the shape parameter of this distribution.
+     *
+     * @return the shape parameter
+     */
+    public double getShape() {
+        return shape;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For scale {@code k}, and shape {@code α} of this distribution, the PDF is given by
+     *
+     * <ul>
+     *   <li>{@code 0} if {@code x < k},
+     *   <li>{@code α * k^α / x^(α + 1)} otherwise.
+     * </ul>
+     */
+    public double density(double x) {
+        if (x < scale) {
+            return 0;
+        }
+        return FastMath.pow(scale, shape) / FastMath.pow(x, shape + 1) * shape;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>See documentation of {@link #density(double)} for computation details.
+     */
+    @Override
+    public double logDensity(double x) {
+        if (x < scale) {
+            return Double.NEGATIVE_INFINITY;
+        }
+        return FastMath.log(scale) * shape - FastMath.log(x) * (shape + 1) + FastMath.log(shape);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For scale {@code k}, and shape {@code α} of this distribution, the CDF is given by
+     *
+     * <ul>
+     *   <li>{@code 0} if {@code x < k},
+     *   <li>{@code 1 - (k / x)^α} otherwise.
+     * </ul>
+     */
+    public double cumulativeProbability(double x) {
+        if (x <= scale) {
+            return 0;
+        }
+        return 1 - FastMath.pow(scale / x, shape);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @deprecated See {@link RealDistribution#cumulativeProbability(double,double)}
+     */
+    @Override
+    @Deprecated
+    public double cumulativeProbability(double x0, double x1) throws NumberIsTooLargeException {
+        return probability(x0, x1);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected double getSolverAbsoluteAccuracy() {
+        return solverAbsoluteAccuracy;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For scale {@code k} and shape {@code α}, the mean is given by
+     *
+     * <ul>
+     *   <li>{@code ∞} if {@code α <= 1},
+     *   <li>{@code α * k / (α - 1)} otherwise.
+     * </ul>
+     */
+    public double getNumericalMean() {
+        if (shape <= 1) {
+            return Double.POSITIVE_INFINITY;
+        }
+        return shape * scale / (shape - 1);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For scale {@code k} and shape {@code α}, the variance is given by
+     *
+     * <ul>
+     *   <li>{@code ∞} if {@code 1 < α <= 2},
+     *   <li>{@code k^2 * α / ((α - 1)^2 * (α - 2))} otherwise.
+     * </ul>
+     */
+    public double getNumericalVariance() {
+        if (shape <= 2) {
+            return Double.POSITIVE_INFINITY;
+        }
+        double s = shape - 1;
+        return scale * scale * shape / (s * s) / (shape - 2);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The lower bound of the support is equal to the scale parameter {@code k}.
+     *
+     * @return lower bound of the support
+     */
+    public double getSupportLowerBound() {
+        return scale;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The upper bound of the support is always positive infinity no matter the parameters.
+     *
+     * @return upper bound of the support (always {@code Double.POSITIVE_INFINITY})
+     */
+    public double getSupportUpperBound() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportLowerBoundInclusive() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportUpperBoundInclusive() {
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The support of this distribution is connected.
+     *
+     * @return {@code true}
+     */
+    public boolean isSupportConnected() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double sample() {
+        final double n = random.nextDouble();
+        return scale / FastMath.pow(n, 1 / shape);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/PascalDistribution.java b/src/main/java/org/apache/commons/math3/distribution/PascalDistribution.java
new file mode 100644
index 0000000..c850f8f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/PascalDistribution.java
@@ -0,0 +1,240 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well19937c;
+import org.apache.commons.math3.special.Beta;
+import org.apache.commons.math3.util.CombinatoricsUtils;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Implementation of the Pascal distribution. The Pascal distribution is a special case of the
+ * Negative Binomial distribution where the number of successes parameter is an integer.
+ *
+ * <p>There are various ways to express the probability mass and distribution functions for the
+ * Pascal distribution. The present implementation represents the distribution of the number of
+ * failures before {@code r} successes occur. This is the convention adopted in e.g. <a
+ * href="http://mathworld.wolfram.com/NegativeBinomialDistribution.html">MathWorld</a>, but
+ * <em>not</em> in <a
+ * href="http://en.wikipedia.org/wiki/Negative_binomial_distribution">Wikipedia</a>.
+ *
+ * <p>For a random variable {@code X} whose values are distributed according to this distribution,
+ * the probability mass function is given by<br>
+ * {@code P(X = k) = C(k + r - 1, r - 1) * p^r * (1 - p)^k,}<br>
+ * where {@code r} is the number of successes, {@code p} is the probability of success, and {@code
+ * X} is the total number of failures. {@code C(n, k)} is the binomial coefficient ({@code n} choose
+ * {@code k}). The mean and variance of {@code X} are<br>
+ * {@code E(X) = (1 - p) * r / p, var(X) = (1 - p) * r / p^2.}<br>
+ * Finally, the cumulative distribution function is given by<br>
+ * {@code P(X <= k) = I(p, r, k + 1)}, where I is the regularized incomplete Beta function.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Negative_binomial_distribution">Negative binomial
+ *     distribution (Wikipedia)</a>
+ * @see <a href="http://mathworld.wolfram.com/NegativeBinomialDistribution.html">Negative binomial
+ *     distribution (MathWorld)</a>
+ * @since 1.2 (changed to concrete class in 3.0)
+ */
+public class PascalDistribution extends AbstractIntegerDistribution {
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 6751309484392813623L;
+
+    /** The number of successes. */
+    private final int numberOfSuccesses;
+
+    /** The probability of success. */
+    private final double probabilityOfSuccess;
+
+    /**
+     * The value of {@code log(p)}, where {@code p} is the probability of success, stored for faster
+     * computation.
+     */
+    private final double logProbabilityOfSuccess;
+
+    /**
+     * The value of {@code log(1-p)}, where {@code p} is the probability of success, stored for
+     * faster computation.
+     */
+    private final double log1mProbabilityOfSuccess;
+
+    /**
+     * Create a Pascal distribution with the given number of successes and probability of success.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param r Number of successes.
+     * @param p Probability of success.
+     * @throws NotStrictlyPositiveException if the number of successes is not positive
+     * @throws OutOfRangeException if the probability of success is not in the range {@code [0, 1]}.
+     */
+    public PascalDistribution(int r, double p)
+            throws NotStrictlyPositiveException, OutOfRangeException {
+        this(new Well19937c(), r, p);
+    }
+
+    /**
+     * Create a Pascal distribution with the given number of successes and probability of success.
+     *
+     * @param rng Random number generator.
+     * @param r Number of successes.
+     * @param p Probability of success.
+     * @throws NotStrictlyPositiveException if the number of successes is not positive
+     * @throws OutOfRangeException if the probability of success is not in the range {@code [0, 1]}.
+     * @since 3.1
+     */
+    public PascalDistribution(RandomGenerator rng, int r, double p)
+            throws NotStrictlyPositiveException, OutOfRangeException {
+        super(rng);
+
+        if (r <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.NUMBER_OF_SUCCESSES, r);
+        }
+        if (p < 0 || p > 1) {
+            throw new OutOfRangeException(p, 0, 1);
+        }
+
+        numberOfSuccesses = r;
+        probabilityOfSuccess = p;
+        logProbabilityOfSuccess = FastMath.log(p);
+        log1mProbabilityOfSuccess = FastMath.log1p(-p);
+    }
+
+    /**
+     * Access the number of successes for this distribution.
+     *
+     * @return the number of successes.
+     */
+    public int getNumberOfSuccesses() {
+        return numberOfSuccesses;
+    }
+
+    /**
+     * Access the probability of success for this distribution.
+     *
+     * @return the probability of success.
+     */
+    public double getProbabilityOfSuccess() {
+        return probabilityOfSuccess;
+    }
+
+    /** {@inheritDoc} */
+    public double probability(int x) {
+        double ret;
+        if (x < 0) {
+            ret = 0.0;
+        } else {
+            ret =
+                    CombinatoricsUtils.binomialCoefficientDouble(
+                                    x + numberOfSuccesses - 1, numberOfSuccesses - 1)
+                            * FastMath.pow(probabilityOfSuccess, numberOfSuccesses)
+                            * FastMath.pow(1.0 - probabilityOfSuccess, x);
+        }
+        return ret;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double logProbability(int x) {
+        double ret;
+        if (x < 0) {
+            ret = Double.NEGATIVE_INFINITY;
+        } else {
+            ret =
+                    CombinatoricsUtils.binomialCoefficientLog(
+                                    x + numberOfSuccesses - 1, numberOfSuccesses - 1)
+                            + logProbabilityOfSuccess * numberOfSuccesses
+                            + log1mProbabilityOfSuccess * x;
+        }
+        return ret;
+    }
+
+    /** {@inheritDoc} */
+    public double cumulativeProbability(int x) {
+        double ret;
+        if (x < 0) {
+            ret = 0.0;
+        } else {
+            ret = Beta.regularizedBeta(probabilityOfSuccess, numberOfSuccesses, x + 1.0);
+        }
+        return ret;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For number of successes {@code r} and probability of success {@code p}, the mean is {@code
+     * r * (1 - p) / p}.
+     */
+    public double getNumericalMean() {
+        final double p = getProbabilityOfSuccess();
+        final double r = getNumberOfSuccesses();
+        return (r * (1 - p)) / p;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For number of successes {@code r} and probability of success {@code p}, the variance is
+     * {@code r * (1 - p) / p^2}.
+     */
+    public double getNumericalVariance() {
+        final double p = getProbabilityOfSuccess();
+        final double r = getNumberOfSuccesses();
+        return r * (1 - p) / (p * p);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The lower bound of the support is always 0 no matter the parameters.
+     *
+     * @return lower bound of the support (always 0)
+     */
+    public int getSupportLowerBound() {
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The upper bound of the support is always positive infinity no matter the parameters.
+     * Positive infinity is symbolized by {@code Integer.MAX_VALUE}.
+     *
+     * @return upper bound of the support (always {@code Integer.MAX_VALUE} for positive infinity)
+     */
+    public int getSupportUpperBound() {
+        return Integer.MAX_VALUE;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The support of this distribution is connected.
+     *
+     * @return {@code true}
+     */
+    public boolean isSupportConnected() {
+        return true;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/PoissonDistribution.java b/src/main/java/org/apache/commons/math3/distribution/PoissonDistribution.java
new file mode 100644
index 0000000..7d9eab3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/PoissonDistribution.java
@@ -0,0 +1,394 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well19937c;
+import org.apache.commons.math3.special.Gamma;
+import org.apache.commons.math3.util.CombinatoricsUtils;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * Implementation of the Poisson distribution.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Poisson_distribution">Poisson distribution
+ *     (Wikipedia)</a>
+ * @see <a href="http://mathworld.wolfram.com/PoissonDistribution.html">Poisson distribution
+ *     (MathWorld)</a>
+ */
+public class PoissonDistribution extends AbstractIntegerDistribution {
+    /**
+     * Default maximum number of iterations for cumulative probability calculations.
+     *
+     * @since 2.1
+     */
+    public static final int DEFAULT_MAX_ITERATIONS = 10000000;
+
+    /**
+     * Default convergence criterion.
+     *
+     * @since 2.1
+     */
+    public static final double DEFAULT_EPSILON = 1e-12;
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -3349935121172596109L;
+
+    /** Distribution used to compute normal approximation. */
+    private final NormalDistribution normal;
+
+    /** Distribution needed for the {@link #sample()} method. */
+    private final ExponentialDistribution exponential;
+
+    /** Mean of the distribution. */
+    private final double mean;
+
+    /**
+     * Maximum number of iterations for cumulative probability. Cumulative probabilities are
+     * estimated using either Lanczos series approximation of {@link Gamma#regularizedGammaP(double,
+     * double, double, int)} or continued fraction approximation of {@link
+     * Gamma#regularizedGammaQ(double, double, double, int)}.
+     */
+    private final int maxIterations;
+
+    /** Convergence criterion for cumulative probability. */
+    private final double epsilon;
+
+    /**
+     * Creates a new Poisson distribution with specified mean.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param p the Poisson mean
+     * @throws NotStrictlyPositiveException if {@code p <= 0}.
+     */
+    public PoissonDistribution(double p) throws NotStrictlyPositiveException {
+        this(p, DEFAULT_EPSILON, DEFAULT_MAX_ITERATIONS);
+    }
+
+    /**
+     * Creates a new Poisson distribution with specified mean, convergence criterion and maximum
+     * number of iterations.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param p Poisson mean.
+     * @param epsilon Convergence criterion for cumulative probabilities.
+     * @param maxIterations the maximum number of iterations for cumulative probabilities.
+     * @throws NotStrictlyPositiveException if {@code p <= 0}.
+     * @since 2.1
+     */
+    public PoissonDistribution(double p, double epsilon, int maxIterations)
+            throws NotStrictlyPositiveException {
+        this(new Well19937c(), p, epsilon, maxIterations);
+    }
+
+    /**
+     * Creates a new Poisson distribution with specified mean, convergence criterion and maximum
+     * number of iterations.
+     *
+     * @param rng Random number generator.
+     * @param p Poisson mean.
+     * @param epsilon Convergence criterion for cumulative probabilities.
+     * @param maxIterations the maximum number of iterations for cumulative probabilities.
+     * @throws NotStrictlyPositiveException if {@code p <= 0}.
+     * @since 3.1
+     */
+    public PoissonDistribution(RandomGenerator rng, double p, double epsilon, int maxIterations)
+            throws NotStrictlyPositiveException {
+        super(rng);
+
+        if (p <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.MEAN, p);
+        }
+        mean = p;
+        this.epsilon = epsilon;
+        this.maxIterations = maxIterations;
+
+        // Use the same RNG instance as the parent class.
+        normal =
+                new NormalDistribution(
+                        rng,
+                        p,
+                        FastMath.sqrt(p),
+                        NormalDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+        exponential =
+                new ExponentialDistribution(
+                        rng, 1, ExponentialDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Creates a new Poisson distribution with the specified mean and convergence criterion.
+     *
+     * @param p Poisson mean.
+     * @param epsilon Convergence criterion for cumulative probabilities.
+     * @throws NotStrictlyPositiveException if {@code p <= 0}.
+     * @since 2.1
+     */
+    public PoissonDistribution(double p, double epsilon) throws NotStrictlyPositiveException {
+        this(p, epsilon, DEFAULT_MAX_ITERATIONS);
+    }
+
+    /**
+     * Creates a new Poisson distribution with the specified mean and maximum number of iterations.
+     *
+     * @param p Poisson mean.
+     * @param maxIterations Maximum number of iterations for cumulative probabilities.
+     * @since 2.1
+     */
+    public PoissonDistribution(double p, int maxIterations) {
+        this(p, DEFAULT_EPSILON, maxIterations);
+    }
+
+    /**
+     * Get the mean for the distribution.
+     *
+     * @return the mean for the distribution.
+     */
+    public double getMean() {
+        return mean;
+    }
+
+    /** {@inheritDoc} */
+    public double probability(int x) {
+        final double logProbability = logProbability(x);
+        return logProbability == Double.NEGATIVE_INFINITY ? 0 : FastMath.exp(logProbability);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double logProbability(int x) {
+        double ret;
+        if (x < 0 || x == Integer.MAX_VALUE) {
+            ret = Double.NEGATIVE_INFINITY;
+        } else if (x == 0) {
+            ret = -mean;
+        } else {
+            ret =
+                    -SaddlePointExpansion.getStirlingError(x)
+                            - SaddlePointExpansion.getDeviancePart(x, mean)
+                            - 0.5 * FastMath.log(MathUtils.TWO_PI)
+                            - 0.5 * FastMath.log(x);
+        }
+        return ret;
+    }
+
+    /** {@inheritDoc} */
+    public double cumulativeProbability(int x) {
+        if (x < 0) {
+            return 0;
+        }
+        if (x == Integer.MAX_VALUE) {
+            return 1;
+        }
+        return Gamma.regularizedGammaQ((double) x + 1, mean, epsilon, maxIterations);
+    }
+
+    /**
+     * Calculates the Poisson distribution function using a normal approximation. The {@code N(mean,
+     * sqrt(mean))} distribution is used to approximate the Poisson distribution. The computation
+     * uses "half-correction" (evaluating the normal distribution function at {@code x + 0.5}).
+     *
+     * @param x Upper bound, inclusive.
+     * @return the distribution function value calculated using a normal approximation.
+     */
+    public double normalApproximateProbability(int x) {
+        // calculate the probability using half-correction
+        return normal.cumulativeProbability(x + 0.5);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For mean parameter {@code p}, the mean is {@code p}.
+     */
+    public double getNumericalMean() {
+        return getMean();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For mean parameter {@code p}, the variance is {@code p}.
+     */
+    public double getNumericalVariance() {
+        return getMean();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The lower bound of the support is always 0 no matter the mean parameter.
+     *
+     * @return lower bound of the support (always 0)
+     */
+    public int getSupportLowerBound() {
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The upper bound of the support is positive infinity, regardless of the parameter values.
+     * There is no integer infinity, so this method returns {@code Integer.MAX_VALUE}.
+     *
+     * @return upper bound of the support (always {@code Integer.MAX_VALUE} for positive infinity)
+     */
+    public int getSupportUpperBound() {
+        return Integer.MAX_VALUE;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The support of this distribution is connected.
+     *
+     * @return {@code true}
+     */
+    public boolean isSupportConnected() {
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p><strong>Algorithm Description</strong>:
+     *
+     * <ul>
+     *   <li>For small means, uses simulation of a Poisson process using Uniform deviates, as
+     *       described <a href="http://mathaa.epfl.ch/cours/PMMI2001/interactive/rng7.htm">here</a>.
+     *       The Poisson process (and hence value returned) is bounded by 1000 * mean.
+     *   <li>For large means, uses the rejection algorithm described in
+     *       <blockquote>
+     *       Devroye, Luc. (1981).<i>The Computer Generation of Poisson Random Variables</i><br>
+     *       <strong>Computing</strong> vol. 26 pp. 197-207.<br>
+     *       </blockquote>
+     * </ul>
+     *
+     * @return a random value.
+     * @since 2.2
+     */
+    @Override
+    public int sample() {
+        return (int) FastMath.min(nextPoisson(mean), Integer.MAX_VALUE);
+    }
+
+    /**
+     * @param meanPoisson Mean of the Poisson distribution.
+     * @return the next sample.
+     */
+    private long nextPoisson(double meanPoisson) {
+        final double pivot = 40.0d;
+        if (meanPoisson < pivot) {
+            double p = FastMath.exp(-meanPoisson);
+            long n = 0;
+            double r = 1.0d;
+            double rnd = 1.0d;
+
+            while (n < 1000 * meanPoisson) {
+                rnd = random.nextDouble();
+                r *= rnd;
+                if (r >= p) {
+                    n++;
+                } else {
+                    return n;
+                }
+            }
+            return n;
+        } else {
+            final double lambda = FastMath.floor(meanPoisson);
+            final double lambdaFractional = meanPoisson - lambda;
+            final double logLambda = FastMath.log(lambda);
+            final double logLambdaFactorial = CombinatoricsUtils.factorialLog((int) lambda);
+            final long y2 = lambdaFractional < Double.MIN_VALUE ? 0 : nextPoisson(lambdaFractional);
+            final double delta =
+                    FastMath.sqrt(lambda * FastMath.log(32 * lambda / FastMath.PI + 1));
+            final double halfDelta = delta / 2;
+            final double twolpd = 2 * lambda + delta;
+            final double a1 = FastMath.sqrt(FastMath.PI * twolpd) * FastMath.exp(1 / (8 * lambda));
+            final double a2 = (twolpd / delta) * FastMath.exp(-delta * (1 + delta) / twolpd);
+            final double aSum = a1 + a2 + 1;
+            final double p1 = a1 / aSum;
+            final double p2 = a2 / aSum;
+            final double c1 = 1 / (8 * lambda);
+
+            double x = 0;
+            double y = 0;
+            double v = 0;
+            int a = 0;
+            double t = 0;
+            double qr = 0;
+            double qa = 0;
+            for (; ; ) {
+                final double u = random.nextDouble();
+                if (u <= p1) {
+                    final double n = random.nextGaussian();
+                    x = n * FastMath.sqrt(lambda + halfDelta) - 0.5d;
+                    if (x > delta || x < -lambda) {
+                        continue;
+                    }
+                    y = x < 0 ? FastMath.floor(x) : FastMath.ceil(x);
+                    final double e = exponential.sample();
+                    v = -e - (n * n / 2) + c1;
+                } else {
+                    if (u > p1 + p2) {
+                        y = lambda;
+                        break;
+                    } else {
+                        x = delta + (twolpd / delta) * exponential.sample();
+                        y = FastMath.ceil(x);
+                        v = -exponential.sample() - delta * (x + 1) / twolpd;
+                    }
+                }
+                a = x < 0 ? 1 : 0;
+                t = y * (y + 1) / (2 * lambda);
+                if (v < -t && a == 0) {
+                    y = lambda + y;
+                    break;
+                }
+                qr = t * ((2 * y + 1) / (6 * lambda) - 1);
+                qa = qr - (t * t) / (3 * (lambda + a * (y + 1)));
+                if (v < qa) {
+                    y = lambda + y;
+                    break;
+                }
+                if (v > qr) {
+                    continue;
+                }
+                if (v
+                        < y * logLambda
+                                - CombinatoricsUtils.factorialLog((int) (y + lambda))
+                                + logLambdaFactorial) {
+                    y = lambda + y;
+                    break;
+                }
+            }
+            return y2 + (long) y;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/RealDistribution.java b/src/main/java/org/apache/commons/math3/distribution/RealDistribution.java
new file mode 100644
index 0000000..bee70a3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/RealDistribution.java
@@ -0,0 +1,182 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+
+/**
+ * Base interface for distributions on the reals.
+ *
+ * @since 3.0
+ */
+public interface RealDistribution {
+    /**
+     * For a random variable {@code X} whose values are distributed according to this distribution,
+     * this method returns {@code P(X = x)}. In other words, this method represents the probability
+     * mass function (PMF) for the distribution.
+     *
+     * @param x the point at which the PMF is evaluated
+     * @return the value of the probability mass function at point {@code x}
+     */
+    double probability(double x);
+
+    /**
+     * Returns the probability density function (PDF) of this distribution evaluated at the
+     * specified point {@code x}. In general, the PDF is the derivative of the {@link
+     * #cumulativeProbability(double) CDF}. If the derivative does not exist at {@code x}, then an
+     * appropriate replacement should be returned, e.g. {@code Double.POSITIVE_INFINITY}, {@code
+     * Double.NaN}, or the limit inferior or limit superior of the difference quotient.
+     *
+     * @param x the point at which the PDF is evaluated
+     * @return the value of the probability density function at point {@code x}
+     */
+    double density(double x);
+
+    /**
+     * For a random variable {@code X} whose values are distributed according to this distribution,
+     * this method returns {@code P(X <= x)}. In other words, this method represents the
+     * (cumulative) distribution function (CDF) for this distribution.
+     *
+     * @param x the point at which the CDF is evaluated
+     * @return the probability that a random variable with this distribution takes a value less than
+     *     or equal to {@code x}
+     */
+    double cumulativeProbability(double x);
+
+    /**
+     * For a random variable {@code X} whose values are distributed according to this distribution,
+     * this method returns {@code P(x0 < X <= x1)}.
+     *
+     * @param x0 the exclusive lower bound
+     * @param x1 the inclusive upper bound
+     * @return the probability that a random variable with this distribution takes a value between
+     *     {@code x0} and {@code x1}, excluding the lower and including the upper endpoint
+     * @throws NumberIsTooLargeException if {@code x0 > x1}
+     * @deprecated As of 3.1. In 4.0, this method will be renamed {@code probability(double x0,
+     *     double x1)}.
+     */
+    @Deprecated
+    double cumulativeProbability(double x0, double x1) throws NumberIsTooLargeException;
+
+    /**
+     * Computes the quantile function of this distribution. For a random variable {@code X}
+     * distributed according to this distribution, the returned value is
+     *
+     * <ul>
+     *   <li><code>inf{x in R | P(X<=x) >= p}</code> for {@code 0 < p <= 1},
+     *   <li><code>inf{x in R | P(X<=x) > 0}</code> for {@code p = 0}.
+     * </ul>
+     *
+     * @param p the cumulative probability
+     * @return the smallest {@code p}-quantile of this distribution (largest 0-quantile for {@code p
+     *     = 0})
+     * @throws OutOfRangeException if {@code p < 0} or {@code p > 1}
+     */
+    double inverseCumulativeProbability(double p) throws OutOfRangeException;
+
+    /**
+     * Use this method to get the numerical value of the mean of this distribution.
+     *
+     * @return the mean or {@code Double.NaN} if it is not defined
+     */
+    double getNumericalMean();
+
+    /**
+     * Use this method to get the numerical value of the variance of this distribution.
+     *
+     * @return the variance (possibly {@code Double.POSITIVE_INFINITY} as for certain cases in
+     *     {@link TDistribution}) or {@code Double.NaN} if it is not defined
+     */
+    double getNumericalVariance();
+
+    /**
+     * Access the lower bound of the support. This method must return the same value as {@code
+     * inverseCumulativeProbability(0)}. In other words, this method must return
+     *
+     * <p><code>inf {x in R | P(X <= x) > 0}</code>.
+     *
+     * @return lower bound of the support (might be {@code Double.NEGATIVE_INFINITY})
+     */
+    double getSupportLowerBound();
+
+    /**
+     * Access the upper bound of the support. This method must return the same value as {@code
+     * inverseCumulativeProbability(1)}. In other words, this method must return
+     *
+     * <p><code>inf {x in R | P(X <= x) = 1}</code>.
+     *
+     * @return upper bound of the support (might be {@code Double.POSITIVE_INFINITY})
+     */
+    double getSupportUpperBound();
+
+    /**
+     * Whether or not the lower bound of support is in the domain of the density function. Returns
+     * true iff {@code getSupporLowerBound()} is finite and {@code density(getSupportLowerBound())}
+     * returns a non-NaN, non-infinite value.
+     *
+     * @return true if the lower bound of support is finite and the density function returns a
+     *     non-NaN, non-infinite value there
+     * @deprecated to be removed in 4.0
+     */
+    @Deprecated
+    boolean isSupportLowerBoundInclusive();
+
+    /**
+     * Whether or not the upper bound of support is in the domain of the density function. Returns
+     * true iff {@code getSupportUpperBound()} is finite and {@code density(getSupportUpperBound())}
+     * returns a non-NaN, non-infinite value.
+     *
+     * @return true if the upper bound of support is finite and the density function returns a
+     *     non-NaN, non-infinite value there
+     * @deprecated to be removed in 4.0
+     */
+    @Deprecated
+    boolean isSupportUpperBoundInclusive();
+
+    /**
+     * Use this method to get information about whether the support is connected, i.e. whether all
+     * values between the lower and upper bound of the support are included in the support.
+     *
+     * @return whether the support is connected or not
+     */
+    boolean isSupportConnected();
+
+    /**
+     * Reseed the random generator used to generate samples.
+     *
+     * @param seed the new seed
+     */
+    void reseedRandomGenerator(long seed);
+
+    /**
+     * Generate a random value sampled from this distribution.
+     *
+     * @return a random value.
+     */
+    double sample();
+
+    /**
+     * Generate a random sample from the distribution.
+     *
+     * @param sampleSize the number of random values to generate
+     * @return an array representing the random sample
+     * @throws org.apache.commons.math3.exception.NotStrictlyPositiveException if {@code sampleSize}
+     *     is not positive
+     */
+    double[] sample(int sampleSize);
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/SaddlePointExpansion.java b/src/main/java/org/apache/commons/math3/distribution/SaddlePointExpansion.java
new file mode 100644
index 0000000..9dbceec
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/SaddlePointExpansion.java
@@ -0,0 +1,199 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.special.Gamma;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * Utility class used by various distributions to accurately compute their respective probability
+ * mass functions. The implementation for this class is based on the Catherine Loader's <a
+ * target="_blank" href="http://www.herine.net/stat/software/dbinom.html">dbinom</a> routines.
+ *
+ * <p>This class is not intended to be called directly.
+ *
+ * <p>References:
+ *
+ * <ol>
+ *   <li>Catherine Loader (2000). "Fast and Accurate Computation of Binomial Probabilities.". <a
+ *       target="_blank" href="http://www.herine.net/stat/papers/dbinom.pdf">
+ *       http://www.herine.net/stat/papers/dbinom.pdf</a>
+ * </ol>
+ *
+ * @since 2.1
+ */
+final class SaddlePointExpansion {
+
+    /** 1/2 * log(2 &#960;). */
+    private static final double HALF_LOG_2_PI = 0.5 * FastMath.log(MathUtils.TWO_PI);
+
+    /** exact Stirling expansion error for certain values. */
+    private static final double[] EXACT_STIRLING_ERRORS = {
+        0.0, /* 0.0 */
+        0.1534264097200273452913848, /* 0.5 */
+        0.0810614667953272582196702, /* 1.0 */
+        0.0548141210519176538961390, /* 1.5 */
+        0.0413406959554092940938221, /* 2.0 */
+        0.03316287351993628748511048, /* 2.5 */
+        0.02767792568499833914878929, /* 3.0 */
+        0.02374616365629749597132920, /* 3.5 */
+        0.02079067210376509311152277, /* 4.0 */
+        0.01848845053267318523077934, /* 4.5 */
+        0.01664469118982119216319487, /* 5.0 */
+        0.01513497322191737887351255, /* 5.5 */
+        0.01387612882307074799874573, /* 6.0 */
+        0.01281046524292022692424986, /* 6.5 */
+        0.01189670994589177009505572, /* 7.0 */
+        0.01110455975820691732662991, /* 7.5 */
+        0.010411265261972096497478567, /* 8.0 */
+        0.009799416126158803298389475, /* 8.5 */
+        0.009255462182712732917728637, /* 9.0 */
+        0.008768700134139385462952823, /* 9.5 */
+        0.008330563433362871256469318, /* 10.0 */
+        0.007934114564314020547248100, /* 10.5 */
+        0.007573675487951840794972024, /* 11.0 */
+        0.007244554301320383179543912, /* 11.5 */
+        0.006942840107209529865664152, /* 12.0 */
+        0.006665247032707682442354394, /* 12.5 */
+        0.006408994188004207068439631, /* 13.0 */
+        0.006171712263039457647532867, /* 13.5 */
+        0.005951370112758847735624416, /* 14.0 */
+        0.005746216513010115682023589, /* 14.5 */
+        0.005554733551962801371038690 /* 15.0 */
+    };
+
+    /** Default constructor. */
+    private SaddlePointExpansion() {
+        super();
+    }
+
+    /**
+     * Compute the error of Stirling's series at the given value.
+     *
+     * <p>References:
+     *
+     * <ol>
+     *   <li>Eric W. Weisstein. "Stirling's Series." From MathWorld--A Wolfram Web Resource. <a
+     *       target="_blank" href="http://mathworld.wolfram.com/StirlingsSeries.html">
+     *       http://mathworld.wolfram.com/StirlingsSeries.html</a>
+     * </ol>
+     *
+     * @param z the value.
+     * @return the Striling's series error.
+     */
+    static double getStirlingError(double z) {
+        double ret;
+        if (z < 15.0) {
+            double z2 = 2.0 * z;
+            if (FastMath.floor(z2) == z2) {
+                ret = EXACT_STIRLING_ERRORS[(int) z2];
+            } else {
+                ret = Gamma.logGamma(z + 1.0) - (z + 0.5) * FastMath.log(z) + z - HALF_LOG_2_PI;
+            }
+        } else {
+            double z2 = z * z;
+            ret =
+                    (0.083333333333333333333
+                                    - (0.00277777777777777777778
+                                                    - (0.00079365079365079365079365
+                                                                    - (0.000595238095238095238095238
+                                                                                    - 0.0008417508417508417508417508
+                                                                                            / z2)
+                                                                            / z2)
+                                                            / z2)
+                                            / z2)
+                            / z;
+        }
+        return ret;
+    }
+
+    /**
+     * A part of the deviance portion of the saddle point approximation.
+     *
+     * <p>References:
+     *
+     * <ol>
+     *   <li>Catherine Loader (2000). "Fast and Accurate Computation of Binomial Probabilities.". <a
+     *       target="_blank" href="http://www.herine.net/stat/papers/dbinom.pdf">
+     *       http://www.herine.net/stat/papers/dbinom.pdf</a>
+     * </ol>
+     *
+     * @param x the x value.
+     * @param mu the average.
+     * @return a part of the deviance.
+     */
+    static double getDeviancePart(double x, double mu) {
+        double ret;
+        if (FastMath.abs(x - mu) < 0.1 * (x + mu)) {
+            double d = x - mu;
+            double v = d / (x + mu);
+            double s1 = v * d;
+            double s = Double.NaN;
+            double ej = 2.0 * x * v;
+            v *= v;
+            int j = 1;
+            while (s1 != s) {
+                s = s1;
+                ej *= v;
+                s1 = s + ej / ((j * 2) + 1);
+                ++j;
+            }
+            ret = s1;
+        } else {
+            ret = x * FastMath.log(x / mu) + mu - x;
+        }
+        return ret;
+    }
+
+    /**
+     * Compute the logarithm of the PMF for a binomial distribution using the saddle point
+     * expansion.
+     *
+     * @param x the value at which the probability is evaluated.
+     * @param n the number of trials.
+     * @param p the probability of success.
+     * @param q the probability of failure (1 - p).
+     * @return log(p(x)).
+     */
+    static double logBinomialProbability(int x, int n, double p, double q) {
+        double ret;
+        if (x == 0) {
+            if (p < 0.1) {
+                ret = -getDeviancePart(n, n * q) - n * p;
+            } else {
+                ret = n * FastMath.log(q);
+            }
+        } else if (x == n) {
+            if (q < 0.1) {
+                ret = -getDeviancePart(n, n * p) - n * q;
+            } else {
+                ret = n * FastMath.log(p);
+            }
+        } else {
+            ret =
+                    getStirlingError(n)
+                            - getStirlingError(x)
+                            - getStirlingError(n - x)
+                            - getDeviancePart(x, n * p)
+                            - getDeviancePart(n - x, n * q);
+            double f = (MathUtils.TWO_PI * x * (n - x)) / n;
+            ret = -0.5 * FastMath.log(f) + ret;
+        }
+        return ret;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/TDistribution.java b/src/main/java/org/apache/commons/math3/distribution/TDistribution.java
new file mode 100644
index 0000000..8e6053a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/TDistribution.java
@@ -0,0 +1,270 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well19937c;
+import org.apache.commons.math3.special.Beta;
+import org.apache.commons.math3.special.Gamma;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Implementation of Student's t-distribution.
+ *
+ * @see "<a href='http://en.wikipedia.org/wiki/Student&apos;s_t-distribution'>Student's
+ *     t-distribution (Wikipedia)</a>"
+ * @see "<a href='http://mathworld.wolfram.com/Studentst-Distribution.html'>Student's t-distribution
+ *     (MathWorld)</a>"
+ */
+public class TDistribution extends AbstractRealDistribution {
+    /**
+     * Default inverse cumulative probability accuracy.
+     *
+     * @since 2.1
+     */
+    public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9;
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -5852615386664158222L;
+
+    /** The degrees of freedom. */
+    private final double degreesOfFreedom;
+
+    /** Inverse cumulative probability accuracy. */
+    private final double solverAbsoluteAccuracy;
+
+    /** Static computation factor based on degreesOfFreedom. */
+    private final double factor;
+
+    /**
+     * Create a t distribution using the given degrees of freedom.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param degreesOfFreedom Degrees of freedom.
+     * @throws NotStrictlyPositiveException if {@code degreesOfFreedom <= 0}
+     */
+    public TDistribution(double degreesOfFreedom) throws NotStrictlyPositiveException {
+        this(degreesOfFreedom, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Create a t distribution using the given degrees of freedom and the specified inverse
+     * cumulative probability absolute accuracy.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param degreesOfFreedom Degrees of freedom.
+     * @param inverseCumAccuracy the maximum absolute error in inverse cumulative probability
+     *     estimates (defaults to {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}).
+     * @throws NotStrictlyPositiveException if {@code degreesOfFreedom <= 0}
+     * @since 2.1
+     */
+    public TDistribution(double degreesOfFreedom, double inverseCumAccuracy)
+            throws NotStrictlyPositiveException {
+        this(new Well19937c(), degreesOfFreedom, inverseCumAccuracy);
+    }
+
+    /**
+     * Creates a t distribution.
+     *
+     * @param rng Random number generator.
+     * @param degreesOfFreedom Degrees of freedom.
+     * @throws NotStrictlyPositiveException if {@code degreesOfFreedom <= 0}
+     * @since 3.3
+     */
+    public TDistribution(RandomGenerator rng, double degreesOfFreedom)
+            throws NotStrictlyPositiveException {
+        this(rng, degreesOfFreedom, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Creates a t distribution.
+     *
+     * @param rng Random number generator.
+     * @param degreesOfFreedom Degrees of freedom.
+     * @param inverseCumAccuracy the maximum absolute error in inverse cumulative probability
+     *     estimates (defaults to {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}).
+     * @throws NotStrictlyPositiveException if {@code degreesOfFreedom <= 0}
+     * @since 3.1
+     */
+    public TDistribution(RandomGenerator rng, double degreesOfFreedom, double inverseCumAccuracy)
+            throws NotStrictlyPositiveException {
+        super(rng);
+
+        if (degreesOfFreedom <= 0) {
+            throw new NotStrictlyPositiveException(
+                    LocalizedFormats.DEGREES_OF_FREEDOM, degreesOfFreedom);
+        }
+        this.degreesOfFreedom = degreesOfFreedom;
+        solverAbsoluteAccuracy = inverseCumAccuracy;
+
+        final double n = degreesOfFreedom;
+        final double nPlus1Over2 = (n + 1) / 2;
+        factor =
+                Gamma.logGamma(nPlus1Over2)
+                        - 0.5 * (FastMath.log(FastMath.PI) + FastMath.log(n))
+                        - Gamma.logGamma(n / 2);
+    }
+
+    /**
+     * Access the degrees of freedom.
+     *
+     * @return the degrees of freedom.
+     */
+    public double getDegreesOfFreedom() {
+        return degreesOfFreedom;
+    }
+
+    /** {@inheritDoc} */
+    public double density(double x) {
+        return FastMath.exp(logDensity(x));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double logDensity(double x) {
+        final double n = degreesOfFreedom;
+        final double nPlus1Over2 = (n + 1) / 2;
+        return factor - nPlus1Over2 * FastMath.log(1 + x * x / n);
+    }
+
+    /** {@inheritDoc} */
+    public double cumulativeProbability(double x) {
+        double ret;
+        if (x == 0) {
+            ret = 0.5;
+        } else {
+            double t =
+                    Beta.regularizedBeta(
+                            degreesOfFreedom / (degreesOfFreedom + (x * x)),
+                            0.5 * degreesOfFreedom,
+                            0.5);
+            if (x < 0.0) {
+                ret = 0.5 * t;
+            } else {
+                ret = 1.0 - 0.5 * t;
+            }
+        }
+
+        return ret;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected double getSolverAbsoluteAccuracy() {
+        return solverAbsoluteAccuracy;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For degrees of freedom parameter {@code df}, the mean is
+     *
+     * <ul>
+     *   <li>if {@code df > 1} then {@code 0},
+     *   <li>else undefined ({@code Double.NaN}).
+     * </ul>
+     */
+    public double getNumericalMean() {
+        final double df = getDegreesOfFreedom();
+
+        if (df > 1) {
+            return 0;
+        }
+
+        return Double.NaN;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For degrees of freedom parameter {@code df}, the variance is
+     *
+     * <ul>
+     *   <li>if {@code df > 2} then {@code df / (df - 2)},
+     *   <li>if {@code 1 < df <= 2} then positive infinity ({@code Double.POSITIVE_INFINITY}),
+     *   <li>else undefined ({@code Double.NaN}).
+     * </ul>
+     */
+    public double getNumericalVariance() {
+        final double df = getDegreesOfFreedom();
+
+        if (df > 2) {
+            return df / (df - 2);
+        }
+
+        if (df > 1 && df <= 2) {
+            return Double.POSITIVE_INFINITY;
+        }
+
+        return Double.NaN;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The lower bound of the support is always negative infinity no matter the parameters.
+     *
+     * @return lower bound of the support (always {@code Double.NEGATIVE_INFINITY})
+     */
+    public double getSupportLowerBound() {
+        return Double.NEGATIVE_INFINITY;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The upper bound of the support is always positive infinity no matter the parameters.
+     *
+     * @return upper bound of the support (always {@code Double.POSITIVE_INFINITY})
+     */
+    public double getSupportUpperBound() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportLowerBoundInclusive() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportUpperBoundInclusive() {
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The support of this distribution is connected.
+     *
+     * @return {@code true}
+     */
+    public boolean isSupportConnected() {
+        return true;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/TriangularDistribution.java b/src/main/java/org/apache/commons/math3/distribution/TriangularDistribution.java
new file mode 100644
index 0000000..a7feadc
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/TriangularDistribution.java
@@ -0,0 +1,274 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well19937c;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Implementation of the triangular real distribution.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Triangular_distribution">Triangular distribution
+ *     (Wikipedia)</a>
+ * @since 3.0
+ */
+public class TriangularDistribution extends AbstractRealDistribution {
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 20120112L;
+
+    /** Lower limit of this distribution (inclusive). */
+    private final double a;
+
+    /** Upper limit of this distribution (inclusive). */
+    private final double b;
+
+    /** Mode of this distribution. */
+    private final double c;
+
+    /** Inverse cumulative probability accuracy. */
+    private final double solverAbsoluteAccuracy;
+
+    /**
+     * Creates a triangular real distribution using the given lower limit, upper limit, and mode.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param a Lower limit of this distribution (inclusive).
+     * @param b Upper limit of this distribution (inclusive).
+     * @param c Mode of this distribution.
+     * @throws NumberIsTooLargeException if {@code a >= b} or if {@code c > b}.
+     * @throws NumberIsTooSmallException if {@code c < a}.
+     */
+    public TriangularDistribution(double a, double c, double b)
+            throws NumberIsTooLargeException, NumberIsTooSmallException {
+        this(new Well19937c(), a, c, b);
+    }
+
+    /**
+     * Creates a triangular distribution.
+     *
+     * @param rng Random number generator.
+     * @param a Lower limit of this distribution (inclusive).
+     * @param b Upper limit of this distribution (inclusive).
+     * @param c Mode of this distribution.
+     * @throws NumberIsTooLargeException if {@code a >= b} or if {@code c > b}.
+     * @throws NumberIsTooSmallException if {@code c < a}.
+     * @since 3.1
+     */
+    public TriangularDistribution(RandomGenerator rng, double a, double c, double b)
+            throws NumberIsTooLargeException, NumberIsTooSmallException {
+        super(rng);
+
+        if (a >= b) {
+            throw new NumberIsTooLargeException(
+                    LocalizedFormats.LOWER_BOUND_NOT_BELOW_UPPER_BOUND, a, b, false);
+        }
+        if (c < a) {
+            throw new NumberIsTooSmallException(LocalizedFormats.NUMBER_TOO_SMALL, c, a, true);
+        }
+        if (c > b) {
+            throw new NumberIsTooLargeException(LocalizedFormats.NUMBER_TOO_LARGE, c, b, true);
+        }
+
+        this.a = a;
+        this.c = c;
+        this.b = b;
+        solverAbsoluteAccuracy = FastMath.max(FastMath.ulp(a), FastMath.ulp(b));
+    }
+
+    /**
+     * Returns the mode {@code c} of this distribution.
+     *
+     * @return the mode {@code c} of this distribution
+     */
+    public double getMode() {
+        return c;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For this distribution, the returned value is not really meaningful, since exact formulas
+     * are implemented for the computation of the {@link #inverseCumulativeProbability(double)} (no
+     * solver is invoked).
+     *
+     * <p>For lower limit {@code a} and upper limit {@code b}, the current implementation returns
+     * {@code max(ulp(a), ulp(b)}.
+     */
+    @Override
+    protected double getSolverAbsoluteAccuracy() {
+        return solverAbsoluteAccuracy;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For lower limit {@code a}, upper limit {@code b} and mode {@code c}, the PDF is given by
+     *
+     * <ul>
+     *   <li>{@code 2 * (x - a) / [(b - a) * (c - a)]} if {@code a <= x < c},
+     *   <li>{@code 2 / (b - a)} if {@code x = c},
+     *   <li>{@code 2 * (b - x) / [(b - a) * (b - c)]} if {@code c < x <= b},
+     *   <li>{@code 0} otherwise.
+     * </ul>
+     */
+    public double density(double x) {
+        if (x < a) {
+            return 0;
+        }
+        if (a <= x && x < c) {
+            double divident = 2 * (x - a);
+            double divisor = (b - a) * (c - a);
+            return divident / divisor;
+        }
+        if (x == c) {
+            return 2 / (b - a);
+        }
+        if (c < x && x <= b) {
+            double divident = 2 * (b - x);
+            double divisor = (b - a) * (b - c);
+            return divident / divisor;
+        }
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For lower limit {@code a}, upper limit {@code b} and mode {@code c}, the CDF is given by
+     *
+     * <ul>
+     *   <li>{@code 0} if {@code x < a},
+     *   <li>{@code (x - a)^2 / [(b - a) * (c - a)]} if {@code a <= x < c},
+     *   <li>{@code (c - a) / (b - a)} if {@code x = c},
+     *   <li>{@code 1 - (b - x)^2 / [(b - a) * (b - c)]} if {@code c < x <= b},
+     *   <li>{@code 1} if {@code x > b}.
+     * </ul>
+     */
+    public double cumulativeProbability(double x) {
+        if (x < a) {
+            return 0;
+        }
+        if (a <= x && x < c) {
+            double divident = (x - a) * (x - a);
+            double divisor = (b - a) * (c - a);
+            return divident / divisor;
+        }
+        if (x == c) {
+            return (c - a) / (b - a);
+        }
+        if (c < x && x <= b) {
+            double divident = (b - x) * (b - x);
+            double divisor = (b - a) * (b - c);
+            return 1 - (divident / divisor);
+        }
+        return 1;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For lower limit {@code a}, upper limit {@code b}, and mode {@code c}, the mean is {@code
+     * (a + b + c) / 3}.
+     */
+    public double getNumericalMean() {
+        return (a + b + c) / 3;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For lower limit {@code a}, upper limit {@code b}, and mode {@code c}, the variance is
+     * {@code (a^2 + b^2 + c^2 - a * b - a * c - b * c) / 18}.
+     */
+    public double getNumericalVariance() {
+        return (a * a + b * b + c * c - a * b - a * c - b * c) / 18;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The lower bound of the support is equal to the lower limit parameter {@code a} of the
+     * distribution.
+     *
+     * @return lower bound of the support
+     */
+    public double getSupportLowerBound() {
+        return a;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The upper bound of the support is equal to the upper limit parameter {@code b} of the
+     * distribution.
+     *
+     * @return upper bound of the support
+     */
+    public double getSupportUpperBound() {
+        return b;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportLowerBoundInclusive() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportUpperBoundInclusive() {
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The support of this distribution is connected.
+     *
+     * @return {@code true}
+     */
+    public boolean isSupportConnected() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double inverseCumulativeProbability(double p) throws OutOfRangeException {
+        if (p < 0 || p > 1) {
+            throw new OutOfRangeException(p, 0, 1);
+        }
+        if (p == 0) {
+            return a;
+        }
+        if (p == 1) {
+            return b;
+        }
+        if (p < (c - a) / (b - a)) {
+            return a + FastMath.sqrt(p * (b - a) * (c - a));
+        }
+        return b - FastMath.sqrt((1 - p) * (b - a) * (b - c));
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/UniformIntegerDistribution.java b/src/main/java/org/apache/commons/math3/distribution/UniformIntegerDistribution.java
new file mode 100644
index 0000000..8a3a98b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/UniformIntegerDistribution.java
@@ -0,0 +1,174 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well19937c;
+
+/**
+ * Implementation of the uniform integer distribution.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Uniform_distribution_(discrete)" >Uniform distribution
+ *     (discrete), at Wikipedia</a>
+ * @since 3.0
+ */
+public class UniformIntegerDistribution extends AbstractIntegerDistribution {
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 20120109L;
+
+    /** Lower bound (inclusive) of this distribution. */
+    private final int lower;
+
+    /** Upper bound (inclusive) of this distribution. */
+    private final int upper;
+
+    /**
+     * Creates a new uniform integer distribution using the given lower and upper bounds (both
+     * inclusive).
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param lower Lower bound (inclusive) of this distribution.
+     * @param upper Upper bound (inclusive) of this distribution.
+     * @throws NumberIsTooLargeException if {@code lower >= upper}.
+     */
+    public UniformIntegerDistribution(int lower, int upper) throws NumberIsTooLargeException {
+        this(new Well19937c(), lower, upper);
+    }
+
+    /**
+     * Creates a new uniform integer distribution using the given lower and upper bounds (both
+     * inclusive).
+     *
+     * @param rng Random number generator.
+     * @param lower Lower bound (inclusive) of this distribution.
+     * @param upper Upper bound (inclusive) of this distribution.
+     * @throws NumberIsTooLargeException if {@code lower > upper}.
+     * @since 3.1
+     */
+    public UniformIntegerDistribution(RandomGenerator rng, int lower, int upper)
+            throws NumberIsTooLargeException {
+        super(rng);
+
+        if (lower > upper) {
+            throw new NumberIsTooLargeException(
+                    LocalizedFormats.LOWER_BOUND_NOT_BELOW_UPPER_BOUND, lower, upper, true);
+        }
+        this.lower = lower;
+        this.upper = upper;
+    }
+
+    /** {@inheritDoc} */
+    public double probability(int x) {
+        if (x < lower || x > upper) {
+            return 0;
+        }
+        return 1.0 / (upper - lower + 1);
+    }
+
+    /** {@inheritDoc} */
+    public double cumulativeProbability(int x) {
+        if (x < lower) {
+            return 0;
+        }
+        if (x > upper) {
+            return 1;
+        }
+        return (x - lower + 1.0) / (upper - lower + 1.0);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For lower bound {@code lower} and upper bound {@code upper}, the mean is {@code 0.5 *
+     * (lower + upper)}.
+     */
+    public double getNumericalMean() {
+        return 0.5 * (lower + upper);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For lower bound {@code lower} and upper bound {@code upper}, and {@code n = upper - lower
+     * + 1}, the variance is {@code (n^2 - 1) / 12}.
+     */
+    public double getNumericalVariance() {
+        double n = upper - lower + 1;
+        return (n * n - 1) / 12.0;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The lower bound of the support is equal to the lower bound parameter of the distribution.
+     *
+     * @return lower bound of the support
+     */
+    public int getSupportLowerBound() {
+        return lower;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The upper bound of the support is equal to the upper bound parameter of the distribution.
+     *
+     * @return upper bound of the support
+     */
+    public int getSupportUpperBound() {
+        return upper;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The support of this distribution is connected.
+     *
+     * @return {@code true}
+     */
+    public boolean isSupportConnected() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int sample() {
+        final int max = (upper - lower) + 1;
+        if (max <= 0) {
+            // The range is too wide to fit in a positive int (larger
+            // than 2^31); as it covers more than half the integer range,
+            // we use a simple rejection method.
+            while (true) {
+                final int r = random.nextInt();
+                if (r >= lower && r <= upper) {
+                    return r;
+                }
+            }
+        } else {
+            // We can shift the range and directly generate a positive int.
+            return lower + random.nextInt(max);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/UniformRealDistribution.java b/src/main/java/org/apache/commons/math3/distribution/UniformRealDistribution.java
new file mode 100644
index 0000000..a3ccd97
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/UniformRealDistribution.java
@@ -0,0 +1,234 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well19937c;
+
+/**
+ * Implementation of the uniform real distribution.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Uniform_distribution_(continuous)" >Uniform
+ *     distribution (continuous), at Wikipedia</a>
+ * @since 3.0
+ */
+public class UniformRealDistribution extends AbstractRealDistribution {
+    /**
+     * Default inverse cumulative probability accuracy.
+     *
+     * @deprecated as of 3.2 not used anymore, will be removed in 4.0
+     */
+    @Deprecated public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9;
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 20120109L;
+
+    /** Lower bound of this distribution (inclusive). */
+    private final double lower;
+
+    /** Upper bound of this distribution (exclusive). */
+    private final double upper;
+
+    /**
+     * Create a standard uniform real distribution with lower bound (inclusive) equal to zero and
+     * upper bound (exclusive) equal to one.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     */
+    public UniformRealDistribution() {
+        this(0, 1);
+    }
+
+    /**
+     * Create a uniform real distribution using the given lower and upper bounds.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param lower Lower bound of this distribution (inclusive).
+     * @param upper Upper bound of this distribution (exclusive).
+     * @throws NumberIsTooLargeException if {@code lower >= upper}.
+     */
+    public UniformRealDistribution(double lower, double upper) throws NumberIsTooLargeException {
+        this(new Well19937c(), lower, upper);
+    }
+
+    /**
+     * Create a uniform distribution.
+     *
+     * @param lower Lower bound of this distribution (inclusive).
+     * @param upper Upper bound of this distribution (exclusive).
+     * @param inverseCumAccuracy Inverse cumulative probability accuracy.
+     * @throws NumberIsTooLargeException if {@code lower >= upper}.
+     * @deprecated as of 3.2, inverse CDF is now calculated analytically, use {@link
+     *     #UniformRealDistribution(double, double)} instead.
+     */
+    @Deprecated
+    public UniformRealDistribution(double lower, double upper, double inverseCumAccuracy)
+            throws NumberIsTooLargeException {
+        this(new Well19937c(), lower, upper);
+    }
+
+    /**
+     * Creates a uniform distribution.
+     *
+     * @param rng Random number generator.
+     * @param lower Lower bound of this distribution (inclusive).
+     * @param upper Upper bound of this distribution (exclusive).
+     * @param inverseCumAccuracy Inverse cumulative probability accuracy.
+     * @throws NumberIsTooLargeException if {@code lower >= upper}.
+     * @since 3.1
+     * @deprecated as of 3.2, inverse CDF is now calculated analytically, use {@link
+     *     #UniformRealDistribution(RandomGenerator, double, double)} instead.
+     */
+    @Deprecated
+    public UniformRealDistribution(
+            RandomGenerator rng, double lower, double upper, double inverseCumAccuracy) {
+        this(rng, lower, upper);
+    }
+
+    /**
+     * Creates a uniform distribution.
+     *
+     * @param rng Random number generator.
+     * @param lower Lower bound of this distribution (inclusive).
+     * @param upper Upper bound of this distribution (exclusive).
+     * @throws NumberIsTooLargeException if {@code lower >= upper}.
+     * @since 3.1
+     */
+    public UniformRealDistribution(RandomGenerator rng, double lower, double upper)
+            throws NumberIsTooLargeException {
+        super(rng);
+        if (lower >= upper) {
+            throw new NumberIsTooLargeException(
+                    LocalizedFormats.LOWER_BOUND_NOT_BELOW_UPPER_BOUND, lower, upper, false);
+        }
+
+        this.lower = lower;
+        this.upper = upper;
+    }
+
+    /** {@inheritDoc} */
+    public double density(double x) {
+        if (x < lower || x > upper) {
+            return 0.0;
+        }
+        return 1 / (upper - lower);
+    }
+
+    /** {@inheritDoc} */
+    public double cumulativeProbability(double x) {
+        if (x <= lower) {
+            return 0;
+        }
+        if (x >= upper) {
+            return 1;
+        }
+        return (x - lower) / (upper - lower);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double inverseCumulativeProbability(final double p) throws OutOfRangeException {
+        if (p < 0.0 || p > 1.0) {
+            throw new OutOfRangeException(p, 0, 1);
+        }
+        return p * (upper - lower) + lower;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For lower bound {@code lower} and upper bound {@code upper}, the mean is {@code 0.5 *
+     * (lower + upper)}.
+     */
+    public double getNumericalMean() {
+        return 0.5 * (lower + upper);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For lower bound {@code lower} and upper bound {@code upper}, the variance is {@code (upper
+     * - lower)^2 / 12}.
+     */
+    public double getNumericalVariance() {
+        double ul = upper - lower;
+        return ul * ul / 12;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The lower bound of the support is equal to the lower bound parameter of the distribution.
+     *
+     * @return lower bound of the support
+     */
+    public double getSupportLowerBound() {
+        return lower;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The upper bound of the support is equal to the upper bound parameter of the distribution.
+     *
+     * @return upper bound of the support
+     */
+    public double getSupportUpperBound() {
+        return upper;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportLowerBoundInclusive() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportUpperBoundInclusive() {
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The support of this distribution is connected.
+     *
+     * @return {@code true}
+     */
+    public boolean isSupportConnected() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double sample() {
+        final double u = random.nextDouble();
+        return u * upper + (1 - u) * lower;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/WeibullDistribution.java b/src/main/java/org/apache/commons/math3/distribution/WeibullDistribution.java
new file mode 100644
index 0000000..b7d2953
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/WeibullDistribution.java
@@ -0,0 +1,346 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well19937c;
+import org.apache.commons.math3.special.Gamma;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Implementation of the Weibull distribution. This implementation uses the two parameter form of
+ * the distribution defined by <a href="http://mathworld.wolfram.com/WeibullDistribution.html">
+ * Weibull Distribution</a>, equations (1) and (2).
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Weibull_distribution">Weibull distribution
+ *     (Wikipedia)</a>
+ * @see <a href="http://mathworld.wolfram.com/WeibullDistribution.html">Weibull distribution
+ *     (MathWorld)</a>
+ * @since 1.1 (changed to concrete class in 3.0)
+ */
+public class WeibullDistribution extends AbstractRealDistribution {
+    /**
+     * Default inverse cumulative probability accuracy.
+     *
+     * @since 2.1
+     */
+    public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9;
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 8589540077390120676L;
+
+    /** The shape parameter. */
+    private final double shape;
+
+    /** The scale parameter. */
+    private final double scale;
+
+    /** Inverse cumulative probability accuracy. */
+    private final double solverAbsoluteAccuracy;
+
+    /** Cached numerical mean */
+    private double numericalMean = Double.NaN;
+
+    /** Whether or not the numerical mean has been calculated */
+    private boolean numericalMeanIsCalculated = false;
+
+    /** Cached numerical variance */
+    private double numericalVariance = Double.NaN;
+
+    /** Whether or not the numerical variance has been calculated */
+    private boolean numericalVarianceIsCalculated = false;
+
+    /**
+     * Create a Weibull distribution with the given shape and scale and a location equal to zero.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param alpha Shape parameter.
+     * @param beta Scale parameter.
+     * @throws NotStrictlyPositiveException if {@code alpha <= 0} or {@code beta <= 0}.
+     */
+    public WeibullDistribution(double alpha, double beta) throws NotStrictlyPositiveException {
+        this(alpha, beta, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Create a Weibull distribution with the given shape, scale and inverse cumulative probability
+     * accuracy and a location equal to zero.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param alpha Shape parameter.
+     * @param beta Scale parameter.
+     * @param inverseCumAccuracy Maximum absolute error in inverse cumulative probability estimates
+     *     (defaults to {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}).
+     * @throws NotStrictlyPositiveException if {@code alpha <= 0} or {@code beta <= 0}.
+     * @since 2.1
+     */
+    public WeibullDistribution(double alpha, double beta, double inverseCumAccuracy) {
+        this(new Well19937c(), alpha, beta, inverseCumAccuracy);
+    }
+
+    /**
+     * Creates a Weibull distribution.
+     *
+     * @param rng Random number generator.
+     * @param alpha Shape parameter.
+     * @param beta Scale parameter.
+     * @throws NotStrictlyPositiveException if {@code alpha <= 0} or {@code beta <= 0}.
+     * @since 3.3
+     */
+    public WeibullDistribution(RandomGenerator rng, double alpha, double beta)
+            throws NotStrictlyPositiveException {
+        this(rng, alpha, beta, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Creates a Weibull distribution.
+     *
+     * @param rng Random number generator.
+     * @param alpha Shape parameter.
+     * @param beta Scale parameter.
+     * @param inverseCumAccuracy Maximum absolute error in inverse cumulative probability estimates
+     *     (defaults to {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}).
+     * @throws NotStrictlyPositiveException if {@code alpha <= 0} or {@code beta <= 0}.
+     * @since 3.1
+     */
+    public WeibullDistribution(
+            RandomGenerator rng, double alpha, double beta, double inverseCumAccuracy)
+            throws NotStrictlyPositiveException {
+        super(rng);
+
+        if (alpha <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.SHAPE, alpha);
+        }
+        if (beta <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.SCALE, beta);
+        }
+        scale = beta;
+        shape = alpha;
+        solverAbsoluteAccuracy = inverseCumAccuracy;
+    }
+
+    /**
+     * Access the shape parameter, {@code alpha}.
+     *
+     * @return the shape parameter, {@code alpha}.
+     */
+    public double getShape() {
+        return shape;
+    }
+
+    /**
+     * Access the scale parameter, {@code beta}.
+     *
+     * @return the scale parameter, {@code beta}.
+     */
+    public double getScale() {
+        return scale;
+    }
+
+    /** {@inheritDoc} */
+    public double density(double x) {
+        if (x < 0) {
+            return 0;
+        }
+
+        final double xscale = x / scale;
+        final double xscalepow = FastMath.pow(xscale, shape - 1);
+
+        /*
+         * FastMath.pow(x / scale, shape) =
+         * FastMath.pow(xscale, shape) =
+         * FastMath.pow(xscale, shape - 1) * xscale
+         */
+        final double xscalepowshape = xscalepow * xscale;
+
+        return (shape / scale) * xscalepow * FastMath.exp(-xscalepowshape);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double logDensity(double x) {
+        if (x < 0) {
+            return Double.NEGATIVE_INFINITY;
+        }
+
+        final double xscale = x / scale;
+        final double logxscalepow = FastMath.log(xscale) * (shape - 1);
+
+        /*
+         * FastMath.pow(x / scale, shape) =
+         * FastMath.pow(xscale, shape) =
+         * FastMath.pow(xscale, shape - 1) * xscale
+         */
+        final double xscalepowshape = FastMath.exp(logxscalepow) * xscale;
+
+        return FastMath.log(shape / scale) + logxscalepow - xscalepowshape;
+    }
+
+    /** {@inheritDoc} */
+    public double cumulativeProbability(double x) {
+        double ret;
+        if (x <= 0.0) {
+            ret = 0.0;
+        } else {
+            ret = 1.0 - FastMath.exp(-FastMath.pow(x / scale, shape));
+        }
+        return ret;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>Returns {@code 0} when {@code p == 0} and {@code Double.POSITIVE_INFINITY} when {@code p
+     * == 1}.
+     */
+    @Override
+    public double inverseCumulativeProbability(double p) {
+        double ret;
+        if (p < 0.0 || p > 1.0) {
+            throw new OutOfRangeException(p, 0.0, 1.0);
+        } else if (p == 0) {
+            ret = 0.0;
+        } else if (p == 1) {
+            ret = Double.POSITIVE_INFINITY;
+        } else {
+            ret = scale * FastMath.pow(-FastMath.log1p(-p), 1.0 / shape);
+        }
+        return ret;
+    }
+
+    /**
+     * Return the absolute accuracy setting of the solver used to estimate inverse cumulative
+     * probabilities.
+     *
+     * @return the solver absolute accuracy.
+     * @since 2.1
+     */
+    @Override
+    protected double getSolverAbsoluteAccuracy() {
+        return solverAbsoluteAccuracy;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The mean is {@code scale * Gamma(1 + (1 / shape))}, where {@code Gamma()} is the
+     * Gamma-function.
+     */
+    public double getNumericalMean() {
+        if (!numericalMeanIsCalculated) {
+            numericalMean = calculateNumericalMean();
+            numericalMeanIsCalculated = true;
+        }
+        return numericalMean;
+    }
+
+    /**
+     * used by {@link #getNumericalMean()}
+     *
+     * @return the mean of this distribution
+     */
+    protected double calculateNumericalMean() {
+        final double sh = getShape();
+        final double sc = getScale();
+
+        return sc * FastMath.exp(Gamma.logGamma(1 + (1 / sh)));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The variance is {@code scale^2 * Gamma(1 + (2 / shape)) - mean^2} where {@code Gamma()} is
+     * the Gamma-function.
+     */
+    public double getNumericalVariance() {
+        if (!numericalVarianceIsCalculated) {
+            numericalVariance = calculateNumericalVariance();
+            numericalVarianceIsCalculated = true;
+        }
+        return numericalVariance;
+    }
+
+    /**
+     * used by {@link #getNumericalVariance()}
+     *
+     * @return the variance of this distribution
+     */
+    protected double calculateNumericalVariance() {
+        final double sh = getShape();
+        final double sc = getScale();
+        final double mn = getNumericalMean();
+
+        return (sc * sc) * FastMath.exp(Gamma.logGamma(1 + (2 / sh))) - (mn * mn);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The lower bound of the support is always 0 no matter the parameters.
+     *
+     * @return lower bound of the support (always 0)
+     */
+    public double getSupportLowerBound() {
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The upper bound of the support is always positive infinity no matter the parameters.
+     *
+     * @return upper bound of the support (always {@code Double.POSITIVE_INFINITY})
+     */
+    public double getSupportUpperBound() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportLowerBoundInclusive() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupportUpperBoundInclusive() {
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The support of this distribution is connected.
+     *
+     * @return {@code true}
+     */
+    public boolean isSupportConnected() {
+        return true;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/ZipfDistribution.java b/src/main/java/org/apache/commons/math3/distribution/ZipfDistribution.java
new file mode 100644
index 0000000..d452122
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/ZipfDistribution.java
@@ -0,0 +1,502 @@
+/*
+ * 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.math3.distribution;
+
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well19937c;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Implementation of the Zipf distribution.
+ *
+ * <p><strong>Parameters:</strong> For a random variable {@code X} whose values are distributed
+ * according to this distribution, the probability mass function is given by
+ *
+ * <pre>
+ *   P(X = k) = H(N,s) * 1 / k^s    for {@code k = 1,2,...,N}.
+ * </pre>
+ *
+ * {@code H(N,s)} is the normalizing constant which corresponds to the generalized harmonic number
+ * of order N of s.
+ *
+ * <p>
+ *
+ * <ul>
+ *   <li>{@code N} is the number of elements
+ *   <li>{@code s} is the exponent
+ * </ul>
+ *
+ * @see <a href="https://en.wikipedia.org/wiki/Zipf's_law">Zipf's law (Wikipedia)</a>
+ * @see <a
+ *     href="https://en.wikipedia.org/wiki/Harmonic_number#Generalized_harmonic_numbers">Generalized
+ *     harmonic numbers</a>
+ */
+public class ZipfDistribution extends AbstractIntegerDistribution {
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -140627372283420404L;
+
+    /** Number of elements. */
+    private final int numberOfElements;
+
+    /** Exponent parameter of the distribution. */
+    private final double exponent;
+
+    /** Cached numerical mean */
+    private double numericalMean = Double.NaN;
+
+    /** Whether or not the numerical mean has been calculated */
+    private boolean numericalMeanIsCalculated = false;
+
+    /** Cached numerical variance */
+    private double numericalVariance = Double.NaN;
+
+    /** Whether or not the numerical variance has been calculated */
+    private boolean numericalVarianceIsCalculated = false;
+
+    /** The sampler to be used for the sample() method */
+    private transient ZipfRejectionInversionSampler sampler;
+
+    /**
+     * Create a new Zipf distribution with the given number of elements and exponent.
+     *
+     * <p><b>Note:</b> this constructor will implicitly create an instance of {@link Well19937c} as
+     * random generator to be used for sampling only (see {@link #sample()} and {@link
+     * #sample(int)}). In case no sampling is needed for the created distribution, it is advised to
+     * pass {@code null} as random generator via the appropriate constructors to avoid the
+     * additional initialisation overhead.
+     *
+     * @param numberOfElements Number of elements.
+     * @param exponent Exponent.
+     * @exception NotStrictlyPositiveException if {@code numberOfElements <= 0} or {@code exponent
+     *     <= 0}.
+     */
+    public ZipfDistribution(final int numberOfElements, final double exponent) {
+        this(new Well19937c(), numberOfElements, exponent);
+    }
+
+    /**
+     * Creates a Zipf distribution.
+     *
+     * @param rng Random number generator.
+     * @param numberOfElements Number of elements.
+     * @param exponent Exponent.
+     * @exception NotStrictlyPositiveException if {@code numberOfElements <= 0} or {@code exponent
+     *     <= 0}.
+     * @since 3.1
+     */
+    public ZipfDistribution(RandomGenerator rng, int numberOfElements, double exponent)
+            throws NotStrictlyPositiveException {
+        super(rng);
+
+        if (numberOfElements <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.DIMENSION, numberOfElements);
+        }
+        if (exponent <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.EXPONENT, exponent);
+        }
+
+        this.numberOfElements = numberOfElements;
+        this.exponent = exponent;
+    }
+
+    /**
+     * Get the number of elements (e.g. corpus size) for the distribution.
+     *
+     * @return the number of elements
+     */
+    public int getNumberOfElements() {
+        return numberOfElements;
+    }
+
+    /**
+     * Get the exponent characterizing the distribution.
+     *
+     * @return the exponent
+     */
+    public double getExponent() {
+        return exponent;
+    }
+
+    /** {@inheritDoc} */
+    public double probability(final int x) {
+        if (x <= 0 || x > numberOfElements) {
+            return 0.0;
+        }
+
+        return (1.0 / FastMath.pow(x, exponent)) / generalizedHarmonic(numberOfElements, exponent);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double logProbability(int x) {
+        if (x <= 0 || x > numberOfElements) {
+            return Double.NEGATIVE_INFINITY;
+        }
+
+        return -FastMath.log(x) * exponent
+                - FastMath.log(generalizedHarmonic(numberOfElements, exponent));
+    }
+
+    /** {@inheritDoc} */
+    public double cumulativeProbability(final int x) {
+        if (x <= 0) {
+            return 0.0;
+        } else if (x >= numberOfElements) {
+            return 1.0;
+        }
+
+        return generalizedHarmonic(x, exponent) / generalizedHarmonic(numberOfElements, exponent);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For number of elements {@code N} and exponent {@code s}, the mean is {@code Hs1 / Hs},
+     * where
+     *
+     * <ul>
+     *   <li>{@code Hs1 = generalizedHarmonic(N, s - 1)},
+     *   <li>{@code Hs = generalizedHarmonic(N, s)}.
+     * </ul>
+     */
+    public double getNumericalMean() {
+        if (!numericalMeanIsCalculated) {
+            numericalMean = calculateNumericalMean();
+            numericalMeanIsCalculated = true;
+        }
+        return numericalMean;
+    }
+
+    /**
+     * Used by {@link #getNumericalMean()}.
+     *
+     * @return the mean of this distribution
+     */
+    protected double calculateNumericalMean() {
+        final int N = getNumberOfElements();
+        final double s = getExponent();
+
+        final double Hs1 = generalizedHarmonic(N, s - 1);
+        final double Hs = generalizedHarmonic(N, s);
+
+        return Hs1 / Hs;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>For number of elements {@code N} and exponent {@code s}, the mean is {@code (Hs2 / Hs) -
+     * (Hs1^2 / Hs^2)}, where
+     *
+     * <ul>
+     *   <li>{@code Hs2 = generalizedHarmonic(N, s - 2)},
+     *   <li>{@code Hs1 = generalizedHarmonic(N, s - 1)},
+     *   <li>{@code Hs = generalizedHarmonic(N, s)}.
+     * </ul>
+     */
+    public double getNumericalVariance() {
+        if (!numericalVarianceIsCalculated) {
+            numericalVariance = calculateNumericalVariance();
+            numericalVarianceIsCalculated = true;
+        }
+        return numericalVariance;
+    }
+
+    /**
+     * Used by {@link #getNumericalVariance()}.
+     *
+     * @return the variance of this distribution
+     */
+    protected double calculateNumericalVariance() {
+        final int N = getNumberOfElements();
+        final double s = getExponent();
+
+        final double Hs2 = generalizedHarmonic(N, s - 2);
+        final double Hs1 = generalizedHarmonic(N, s - 1);
+        final double Hs = generalizedHarmonic(N, s);
+
+        return (Hs2 / Hs) - ((Hs1 * Hs1) / (Hs * Hs));
+    }
+
+    /**
+     * Calculates the Nth generalized harmonic number. See <a
+     * href="http://mathworld.wolfram.com/HarmonicSeries.html">Harmonic Series</a>.
+     *
+     * @param n Term in the series to calculate (must be larger than 1)
+     * @param m Exponent (special case {@code m = 1} is the harmonic series).
+     * @return the n<sup>th</sup> generalized harmonic number.
+     */
+    private double generalizedHarmonic(final int n, final double m) {
+        double value = 0;
+        for (int k = n; k > 0; --k) {
+            value += 1.0 / FastMath.pow(k, m);
+        }
+        return value;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The lower bound of the support is always 1 no matter the parameters.
+     *
+     * @return lower bound of the support (always 1)
+     */
+    public int getSupportLowerBound() {
+        return 1;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The upper bound of the support is the number of elements.
+     *
+     * @return upper bound of the support
+     */
+    public int getSupportUpperBound() {
+        return getNumberOfElements();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The support of this distribution is connected.
+     *
+     * @return {@code true}
+     */
+    public boolean isSupportConnected() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int sample() {
+        if (sampler == null) {
+            sampler = new ZipfRejectionInversionSampler(numberOfElements, exponent);
+        }
+        return sampler.sample(random);
+    }
+
+    /**
+     * Utility class implementing a rejection inversion sampling method for a discrete, bounded Zipf
+     * distribution that is based on the method described in
+     *
+     * <p>Wolfgang Hörmann and Gerhard Derflinger "Rejection-inversion to generate variates from
+     * monotone discrete distributions." ACM Transactions on Modeling and Computer Simulation
+     * (TOMACS) 6.3 (1996): 169-184.
+     *
+     * <p>The paper describes an algorithm for exponents larger than 1 (Algorithm ZRI). The original
+     * method uses {@code H(x) := (v + x)^(1 - q) / (1 - q)} as the integral of the hat function.
+     * This function is undefined for q = 1, which is the reason for the limitation of the exponent.
+     * If instead the integral function {@code H(x) := ((v + x)^(1 - q) - 1) / (1 - q)} is used, for
+     * which a meaningful limit exists for q = 1, the method works for all positive exponents.
+     *
+     * <p>The following implementation uses v := 0 and generates integral numbers in the range [1,
+     * numberOfElements]. This is different to the original method where v is defined to be positive
+     * and numbers are taken from [0, i_max]. This explains why the implementation looks slightly
+     * different.
+     *
+     * @since 3.6
+     */
+    static final class ZipfRejectionInversionSampler {
+
+        /** Exponent parameter of the distribution. */
+        private final double exponent;
+
+        /** Number of elements. */
+        private final int numberOfElements;
+
+        /** Constant equal to {@code hIntegral(1.5) - 1}. */
+        private final double hIntegralX1;
+
+        /** Constant equal to {@code hIntegral(numberOfElements + 0.5)}. */
+        private final double hIntegralNumberOfElements;
+
+        /** Constant equal to {@code 2 - hIntegralInverse(hIntegral(2.5) - h(2)}. */
+        private final double s;
+
+        /**
+         * Simple constructor.
+         *
+         * @param numberOfElements number of elements
+         * @param exponent exponent parameter of the distribution
+         */
+        ZipfRejectionInversionSampler(final int numberOfElements, final double exponent) {
+            this.exponent = exponent;
+            this.numberOfElements = numberOfElements;
+            this.hIntegralX1 = hIntegral(1.5) - 1d;
+            this.hIntegralNumberOfElements = hIntegral(numberOfElements + 0.5);
+            this.s = 2d - hIntegralInverse(hIntegral(2.5) - h(2));
+        }
+
+        /**
+         * Generate one integral number in the range [1, numberOfElements].
+         *
+         * @param random random generator to use
+         * @return generated integral number in the range [1, numberOfElements]
+         */
+        int sample(final RandomGenerator random) {
+            while (true) {
+
+                final double u =
+                        hIntegralNumberOfElements
+                                + random.nextDouble() * (hIntegralX1 - hIntegralNumberOfElements);
+                // u is uniformly distributed in (hIntegralX1, hIntegralNumberOfElements]
+
+                double x = hIntegralInverse(u);
+
+                int k = (int) (x + 0.5);
+
+                // Limit k to the range [1, numberOfElements]
+                // (k could be outside due to numerical inaccuracies)
+                if (k < 1) {
+                    k = 1;
+                } else if (k > numberOfElements) {
+                    k = numberOfElements;
+                }
+
+                // Here, the distribution of k is given by:
+                //
+                //   P(k = 1) = C * (hIntegral(1.5) - hIntegralX1) = C
+                //   P(k = m) = C * (hIntegral(m + 1/2) - hIntegral(m - 1/2)) for m >= 2
+                //
+                //   where C := 1 / (hIntegralNumberOfElements - hIntegralX1)
+
+                if (k - x <= s || u >= hIntegral(k + 0.5) - h(k)) {
+
+                    // Case k = 1:
+                    //
+                    //   The right inequality is always true, because replacing k by 1 gives
+                    //   u >= hIntegral(1.5) - h(1) = hIntegralX1 and u is taken from
+                    //   (hIntegralX1, hIntegralNumberOfElements].
+                    //
+                    //   Therefore, the acceptance rate for k = 1 is P(accepted | k = 1) = 1
+                    //   and the probability that 1 is returned as random value is
+                    //   P(k = 1 and accepted) = P(accepted | k = 1) * P(k = 1) = C = C / 1^exponent
+                    //
+                    // Case k >= 2:
+                    //
+                    //   The left inequality (k - x <= s) is just a short cut
+                    //   to avoid the more expensive evaluation of the right inequality
+                    //   (u >= hIntegral(k + 0.5) - h(k)) in many cases.
+                    //
+                    //   If the left inequality is true, the right inequality is also true:
+                    //     Theorem 2 in the paper is valid for all positive exponents, because
+                    //     the requirements h'(x) = -exponent/x^(exponent + 1) < 0 and
+                    //     (-1/hInverse'(x))'' = (1+1/exponent) * x^(1/exponent-1) >= 0
+                    //     are both fulfilled.
+                    //     Therefore, f(x) := x - hIntegralInverse(hIntegral(x + 0.5) - h(x))
+                    //     is a non-decreasing function. If k - x <= s holds,
+                    //     k - x <= s + f(k) - f(2) is obviously also true which is equivalent to
+                    //     -x <= -hIntegralInverse(hIntegral(k + 0.5) - h(k)),
+                    //     -hIntegralInverse(u) <= -hIntegralInverse(hIntegral(k + 0.5) - h(k)),
+                    //     and finally u >= hIntegral(k + 0.5) - h(k).
+                    //
+                    //   Hence, the right inequality determines the acceptance rate:
+                    //   P(accepted | k = m) = h(m) / (hIntegrated(m+1/2) - hIntegrated(m-1/2))
+                    //   The probability that m is returned is given by
+                    //   P(k = m and accepted) = P(accepted | k = m) * P(k = m) = C * h(m) = C /
+                    // m^exponent.
+                    //
+                    // In both cases the probabilities are proportional to the probability mass
+                    // function
+                    // of the Zipf distribution.
+
+                    return k;
+                }
+            }
+        }
+
+        /**
+         * {@code H(x) :=}
+         *
+         * <ul>
+         *   <li>{@code (x^(1-exponent) - 1)/(1 - exponent)}, if {@code exponent != 1}
+         *   <li>{@code log(x)}, if {@code exponent == 1}
+         * </ul>
+         *
+         * H(x) is an integral function of h(x), the derivative of H(x) is h(x).
+         *
+         * @param x free parameter
+         * @return {@code H(x)}
+         */
+        private double hIntegral(final double x) {
+            final double logX = FastMath.log(x);
+            return helper2((1d - exponent) * logX) * logX;
+        }
+
+        /**
+         * {@code h(x) := 1/x^exponent}
+         *
+         * @param x free parameter
+         * @return h(x)
+         */
+        private double h(final double x) {
+            return FastMath.exp(-exponent * FastMath.log(x));
+        }
+
+        /**
+         * The inverse function of H(x).
+         *
+         * @param x free parameter
+         * @return y for which {@code H(y) = x}
+         */
+        private double hIntegralInverse(final double x) {
+            double t = x * (1d - exponent);
+            if (t < -1d) {
+                // Limit value to the range [-1, +inf).
+                // t could be smaller than -1 in some rare cases due to numerical errors.
+                t = -1;
+            }
+            return FastMath.exp(helper1(t) * x);
+        }
+
+        /**
+         * Helper function that calculates {@code log(1+x)/x}.
+         *
+         * <p>A Taylor series expansion is used, if x is close to 0.
+         *
+         * @param x a value larger than or equal to -1
+         * @return {@code log(1+x)/x}
+         */
+        static double helper1(final double x) {
+            if (FastMath.abs(x) > 1e-8) {
+                return FastMath.log1p(x) / x;
+            } else {
+                return 1. - x * ((1. / 2.) - x * ((1. / 3.) - x * (1. / 4.)));
+            }
+        }
+
+        /**
+         * Helper function to calculate {@code (exp(x)-1)/x}.
+         *
+         * <p>A Taylor series expansion is used, if x is close to 0.
+         *
+         * @param x free parameter
+         * @return {@code (exp(x)-1)/x} if x is non-zero, or 1 if x=0
+         */
+        static double helper2(final double x) {
+            if (FastMath.abs(x) > 1e-8) {
+                return FastMath.expm1(x) / x;
+            } else {
+                return 1. + x * (1. / 2.) * (1. + x * (1. / 3.) * (1. + x * (1. / 4.)));
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/distribution/fitting/MultivariateNormalMixtureExpectationMaximization.java b/src/main/java/org/apache/commons/math3/distribution/fitting/MultivariateNormalMixtureExpectationMaximization.java
new file mode 100644
index 0000000..0b4ac0d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/fitting/MultivariateNormalMixtureExpectationMaximization.java
@@ -0,0 +1,454 @@
+/*
+ * 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.math3.distribution.fitting;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.math3.distribution.MultivariateNormalDistribution;
+import org.apache.commons.math3.distribution.MixtureMultivariateNormalDistribution;
+import org.apache.commons.math3.exception.ConvergenceException;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.linear.Array2DRowRealMatrix;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.linear.SingularMatrixException;
+import org.apache.commons.math3.stat.correlation.Covariance;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.util.Pair;
+
+/**
+ * Expectation-Maximization</a> algorithm for fitting the parameters of
+ * multivariate normal mixture model distributions.
+ *
+ * This implementation is pure original code based on <a
+ * href="https://www.ee.washington.edu/techsite/papers/documents/UWEETR-2010-0002.pdf">
+ * EM Demystified: An Expectation-Maximization Tutorial</a> by Yihua Chen and Maya R. Gupta,
+ * Department of Electrical Engineering, University of Washington, Seattle, WA 98195.
+ * It was verified using external tools like <a
+ * href="http://cran.r-project.org/web/packages/mixtools/index.html">CRAN Mixtools</a>
+ * (see the JUnit test cases) but it is <strong>not</strong> based on Mixtools code at all.
+ * The discussion of the origin of this class can be seen in the comments of the <a
+ * href="https://issues.apache.org/jira/browse/MATH-817">MATH-817</a> JIRA issue.
+ * @since 3.2
+ */
+public class MultivariateNormalMixtureExpectationMaximization {
+    /**
+     * Default maximum number of iterations allowed per fitting process.
+     */
+    private static final int DEFAULT_MAX_ITERATIONS = 1000;
+    /**
+     * Default convergence threshold for fitting.
+     */
+    private static final double DEFAULT_THRESHOLD = 1E-5;
+    /**
+     * The data to fit.
+     */
+    private final double[][] data;
+    /**
+     * The model fit against the data.
+     */
+    private MixtureMultivariateNormalDistribution fittedModel;
+    /**
+     * The log likelihood of the data given the fitted model.
+     */
+    private double logLikelihood = 0d;
+
+    /**
+     * Creates an object to fit a multivariate normal mixture model to data.
+     *
+     * @param data Data to use in fitting procedure
+     * @throws NotStrictlyPositiveException if data has no rows
+     * @throws DimensionMismatchException if rows of data have different numbers
+     *             of columns
+     * @throws NumberIsTooSmallException if the number of columns in the data is
+     *             less than 2
+     */
+    public MultivariateNormalMixtureExpectationMaximization(double[][] data)
+        throws NotStrictlyPositiveException,
+               DimensionMismatchException,
+               NumberIsTooSmallException {
+        if (data.length < 1) {
+            throw new NotStrictlyPositiveException(data.length);
+        }
+
+        this.data = new double[data.length][data[0].length];
+
+        for (int i = 0; i < data.length; i++) {
+            if (data[i].length != data[0].length) {
+                // Jagged arrays not allowed
+                throw new DimensionMismatchException(data[i].length,
+                                                     data[0].length);
+            }
+            if (data[i].length < 2) {
+                throw new NumberIsTooSmallException(LocalizedFormats.NUMBER_TOO_SMALL,
+                                                    data[i].length, 2, true);
+            }
+            this.data[i] = MathArrays.copyOf(data[i], data[i].length);
+        }
+    }
+
+    /**
+     * Fit a mixture model to the data supplied to the constructor.
+     *
+     * The quality of the fit depends on the concavity of the data provided to
+     * the constructor and the initial mixture provided to this function. If the
+     * data has many local optima, multiple runs of the fitting function with
+     * different initial mixtures may be required to find the optimal solution.
+     * If a SingularMatrixException is encountered, it is possible that another
+     * initialization would work.
+     *
+     * @param initialMixture Model containing initial values of weights and
+     *            multivariate normals
+     * @param maxIterations Maximum iterations allowed for fit
+     * @param threshold Convergence threshold computed as difference in
+     *             logLikelihoods between successive iterations
+     * @throws SingularMatrixException if any component's covariance matrix is
+     *             singular during fitting
+     * @throws NotStrictlyPositiveException if numComponents is less than one
+     *             or threshold is less than Double.MIN_VALUE
+     * @throws DimensionMismatchException if initialMixture mean vector and data
+     *             number of columns are not equal
+     */
+    public void fit(final MixtureMultivariateNormalDistribution initialMixture,
+                    final int maxIterations,
+                    final double threshold)
+            throws SingularMatrixException,
+                   NotStrictlyPositiveException,
+                   DimensionMismatchException {
+        if (maxIterations < 1) {
+            throw new NotStrictlyPositiveException(maxIterations);
+        }
+
+        if (threshold < Double.MIN_VALUE) {
+            throw new NotStrictlyPositiveException(threshold);
+        }
+
+        final int n = data.length;
+
+        // Number of data columns. Jagged data already rejected in constructor,
+        // so we can assume the lengths of each row are equal.
+        final int numCols = data[0].length;
+        final int k = initialMixture.getComponents().size();
+
+        final int numMeanColumns
+            = initialMixture.getComponents().get(0).getSecond().getMeans().length;
+
+        if (numMeanColumns != numCols) {
+            throw new DimensionMismatchException(numMeanColumns, numCols);
+        }
+
+        int numIterations = 0;
+        double previousLogLikelihood = 0d;
+
+        logLikelihood = Double.NEGATIVE_INFINITY;
+
+        // Initialize model to fit to initial mixture.
+        fittedModel = new MixtureMultivariateNormalDistribution(initialMixture.getComponents());
+
+        while (numIterations++ <= maxIterations &&
+               FastMath.abs(previousLogLikelihood - logLikelihood) > threshold) {
+            previousLogLikelihood = logLikelihood;
+            double sumLogLikelihood = 0d;
+
+            // Mixture components
+            final List<Pair<Double, MultivariateNormalDistribution>> components
+                = fittedModel.getComponents();
+
+            // Weight and distribution of each component
+            final double[] weights = new double[k];
+
+            final MultivariateNormalDistribution[] mvns = new MultivariateNormalDistribution[k];
+
+            for (int j = 0; j < k; j++) {
+                weights[j] = components.get(j).getFirst();
+                mvns[j] = components.get(j).getSecond();
+            }
+
+            // E-step: compute the data dependent parameters of the expectation
+            // function.
+            // The percentage of row's total density between a row and a
+            // component
+            final double[][] gamma = new double[n][k];
+
+            // Sum of gamma for each component
+            final double[] gammaSums = new double[k];
+
+            // Sum of gamma times its row for each each component
+            final double[][] gammaDataProdSums = new double[k][numCols];
+
+            for (int i = 0; i < n; i++) {
+                final double rowDensity = fittedModel.density(data[i]);
+                sumLogLikelihood += FastMath.log(rowDensity);
+
+                for (int j = 0; j < k; j++) {
+                    gamma[i][j] = weights[j] * mvns[j].density(data[i]) / rowDensity;
+                    gammaSums[j] += gamma[i][j];
+
+                    for (int col = 0; col < numCols; col++) {
+                        gammaDataProdSums[j][col] += gamma[i][j] * data[i][col];
+                    }
+                }
+            }
+
+            logLikelihood = sumLogLikelihood / n;
+
+            // M-step: compute the new parameters based on the expectation
+            // function.
+            final double[] newWeights = new double[k];
+            final double[][] newMeans = new double[k][numCols];
+
+            for (int j = 0; j < k; j++) {
+                newWeights[j] = gammaSums[j] / n;
+                for (int col = 0; col < numCols; col++) {
+                    newMeans[j][col] = gammaDataProdSums[j][col] / gammaSums[j];
+                }
+            }
+
+            // Compute new covariance matrices
+            final RealMatrix[] newCovMats = new RealMatrix[k];
+            for (int j = 0; j < k; j++) {
+                newCovMats[j] = new Array2DRowRealMatrix(numCols, numCols);
+            }
+            for (int i = 0; i < n; i++) {
+                for (int j = 0; j < k; j++) {
+                    final RealMatrix vec
+                        = new Array2DRowRealMatrix(MathArrays.ebeSubtract(data[i], newMeans[j]));
+                    final RealMatrix dataCov
+                        = vec.multiply(vec.transpose()).scalarMultiply(gamma[i][j]);
+                    newCovMats[j] = newCovMats[j].add(dataCov);
+                }
+            }
+
+            // Converting to arrays for use by fitted model
+            final double[][][] newCovMatArrays = new double[k][numCols][numCols];
+            for (int j = 0; j < k; j++) {
+                newCovMats[j] = newCovMats[j].scalarMultiply(1d / gammaSums[j]);
+                newCovMatArrays[j] = newCovMats[j].getData();
+            }
+
+            // Update current model
+            fittedModel = new MixtureMultivariateNormalDistribution(newWeights,
+                                                                    newMeans,
+                                                                    newCovMatArrays);
+        }
+
+        if (FastMath.abs(previousLogLikelihood - logLikelihood) > threshold) {
+            // Did not converge before the maximum number of iterations
+            throw new ConvergenceException();
+        }
+    }
+
+    /**
+     * Fit a mixture model to the data supplied to the constructor.
+     *
+     * The quality of the fit depends on the concavity of the data provided to
+     * the constructor and the initial mixture provided to this function. If the
+     * data has many local optima, multiple runs of the fitting function with
+     * different initial mixtures may be required to find the optimal solution.
+     * If a SingularMatrixException is encountered, it is possible that another
+     * initialization would work.
+     *
+     * @param initialMixture Model containing initial values of weights and
+     *            multivariate normals
+     * @throws SingularMatrixException if any component's covariance matrix is
+     *             singular during fitting
+     * @throws NotStrictlyPositiveException if numComponents is less than one or
+     *             threshold is less than Double.MIN_VALUE
+     */
+    public void fit(MixtureMultivariateNormalDistribution initialMixture)
+        throws SingularMatrixException,
+               NotStrictlyPositiveException {
+        fit(initialMixture, DEFAULT_MAX_ITERATIONS, DEFAULT_THRESHOLD);
+    }
+
+    /**
+     * Helper method to create a multivariate normal mixture model which can be
+     * used to initialize {@link #fit(MixtureMultivariateNormalDistribution)}.
+     *
+     * This method uses the data supplied to the constructor to try to determine
+     * a good mixture model at which to start the fit, but it is not guaranteed
+     * to supply a model which will find the optimal solution or even converge.
+     *
+     * @param data Data to estimate distribution
+     * @param numComponents Number of components for estimated mixture
+     * @return Multivariate normal mixture model estimated from the data
+     * @throws NumberIsTooLargeException if {@code numComponents} is greater
+     * than the number of data rows.
+     * @throws NumberIsTooSmallException if {@code numComponents < 2}.
+     * @throws NotStrictlyPositiveException if data has less than 2 rows
+     * @throws DimensionMismatchException if rows of data have different numbers
+     *             of columns
+     */
+    public static MixtureMultivariateNormalDistribution estimate(final double[][] data,
+                                                                 final int numComponents)
+        throws NotStrictlyPositiveException,
+               DimensionMismatchException {
+        if (data.length < 2) {
+            throw new NotStrictlyPositiveException(data.length);
+        }
+        if (numComponents < 2) {
+            throw new NumberIsTooSmallException(numComponents, 2, true);
+        }
+        if (numComponents > data.length) {
+            throw new NumberIsTooLargeException(numComponents, data.length, true);
+        }
+
+        final int numRows = data.length;
+        final int numCols = data[0].length;
+
+        // sort the data
+        final DataRow[] sortedData = new DataRow[numRows];
+        for (int i = 0; i < numRows; i++) {
+            sortedData[i] = new DataRow(data[i]);
+        }
+        Arrays.sort(sortedData);
+
+        // uniform weight for each bin
+        final double weight = 1d / numComponents;
+
+        // components of mixture model to be created
+        final List<Pair<Double, MultivariateNormalDistribution>> components =
+                new ArrayList<Pair<Double, MultivariateNormalDistribution>>(numComponents);
+
+        // create a component based on data in each bin
+        for (int binIndex = 0; binIndex < numComponents; binIndex++) {
+            // minimum index (inclusive) from sorted data for this bin
+            final int minIndex = (binIndex * numRows) / numComponents;
+
+            // maximum index (exclusive) from sorted data for this bin
+            final int maxIndex = ((binIndex + 1) * numRows) / numComponents;
+
+            // number of data records that will be in this bin
+            final int numBinRows = maxIndex - minIndex;
+
+            // data for this bin
+            final double[][] binData = new double[numBinRows][numCols];
+
+            // mean of each column for the data in the this bin
+            final double[] columnMeans = new double[numCols];
+
+            // populate bin and create component
+            for (int i = minIndex, iBin = 0; i < maxIndex; i++, iBin++) {
+                for (int j = 0; j < numCols; j++) {
+                    final double val = sortedData[i].getRow()[j];
+                    columnMeans[j] += val;
+                    binData[iBin][j] = val;
+                }
+            }
+
+            MathArrays.scaleInPlace(1d / numBinRows, columnMeans);
+
+            // covariance matrix for this bin
+            final double[][] covMat
+                = new Covariance(binData).getCovarianceMatrix().getData();
+            final MultivariateNormalDistribution mvn
+                = new MultivariateNormalDistribution(columnMeans, covMat);
+
+            components.add(new Pair<Double, MultivariateNormalDistribution>(weight, mvn));
+        }
+
+        return new MixtureMultivariateNormalDistribution(components);
+    }
+
+    /**
+     * Gets the log likelihood of the data under the fitted model.
+     *
+     * @return Log likelihood of data or zero of no data has been fit
+     */
+    public double getLogLikelihood() {
+        return logLikelihood;
+    }
+
+    /**
+     * Gets the fitted model.
+     *
+     * @return fitted model or {@code null} if no fit has been performed yet.
+     */
+    public MixtureMultivariateNormalDistribution getFittedModel() {
+        return new MixtureMultivariateNormalDistribution(fittedModel.getComponents());
+    }
+
+    /**
+     * Class used for sorting user-supplied data.
+     */
+    private static class DataRow implements Comparable<DataRow> {
+        /** One data row. */
+        private final double[] row;
+        /** Mean of the data row. */
+        private Double mean;
+
+        /**
+         * Create a data row.
+         * @param data Data to use for the row
+         */
+        DataRow(final double[] data) {
+            // Store reference.
+            row = data;
+            // Compute mean.
+            mean = 0d;
+            for (int i = 0; i < data.length; i++) {
+                mean += data[i];
+            }
+            mean /= data.length;
+        }
+
+        /**
+         * Compare two data rows.
+         * @param other The other row
+         * @return int for sorting
+         */
+        public int compareTo(final DataRow other) {
+            return mean.compareTo(other.mean);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public boolean equals(Object other) {
+
+            if (this == other) {
+                return true;
+            }
+
+            if (other instanceof DataRow) {
+                return MathArrays.equals(row, ((DataRow) other).row);
+            }
+
+            return false;
+
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public int hashCode() {
+            return Arrays.hashCode(row);
+        }
+        /**
+         * Get a data row.
+         * @return data row array
+         */
+        public double[] getRow() {
+            return row;
+        }
+    }
+}
+
diff --git a/src/main/java/org/apache/commons/math3/distribution/fitting/package-info.java b/src/main/java/org/apache/commons/math3/distribution/fitting/package-info.java
new file mode 100644
index 0000000..aa95c6d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/fitting/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+/**
+ * Fitting of parameters against distributions.
+ */
+package org.apache.commons.math3.distribution.fitting;
diff --git a/src/main/java/org/apache/commons/math3/distribution/package-info.java b/src/main/java/org/apache/commons/math3/distribution/package-info.java
new file mode 100644
index 0000000..3a9fbc9
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/distribution/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+/** Implementations of common discrete and continuous distributions. */
+package org.apache.commons.math3.distribution;
diff --git a/src/main/java/org/apache/commons/math3/exception/ConvergenceException.java b/src/main/java/org/apache/commons/math3/exception/ConvergenceException.java
new file mode 100644
index 0000000..034a83a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/ConvergenceException.java
@@ -0,0 +1,46 @@
+/*
+ * 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.math3.exception;
+
+import org.apache.commons.math3.exception.util.Localizable;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Error thrown when a numerical computation can not be performed because the numerical result
+ * failed to converge to a finite value.
+ *
+ * @since 2.2
+ */
+public class ConvergenceException extends MathIllegalStateException {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = 4330003017885151975L;
+
+    /** Construct the exception. */
+    public ConvergenceException() {
+        this(LocalizedFormats.CONVERGENCE_FAILED);
+    }
+
+    /**
+     * Construct the exception with a specific context and arguments.
+     *
+     * @param pattern Message pattern providing the specific context of the error.
+     * @param args Arguments.
+     */
+    public ConvergenceException(Localizable pattern, Object... args) {
+        getContext().addMessage(pattern, args);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/exception/DimensionMismatchException.java b/src/main/java/org/apache/commons/math3/exception/DimensionMismatchException.java
new file mode 100644
index 0000000..a0f8dae
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/DimensionMismatchException.java
@@ -0,0 +1,62 @@
+/*
+ * 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.math3.exception;
+
+import org.apache.commons.math3.exception.util.Localizable;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Exception to be thrown when two dimensions differ.
+ *
+ * @since 2.2
+ */
+public class DimensionMismatchException extends MathIllegalNumberException {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = -8415396756375798143L;
+
+    /** Correct dimension. */
+    private final int dimension;
+
+    /**
+     * Construct an exception from the mismatched dimensions.
+     *
+     * @param specific Specific context information pattern.
+     * @param wrong Wrong dimension.
+     * @param expected Expected dimension.
+     */
+    public DimensionMismatchException(Localizable specific, int wrong, int expected) {
+        super(specific, Integer.valueOf(wrong), Integer.valueOf(expected));
+        dimension = expected;
+    }
+
+    /**
+     * Construct an exception from the mismatched dimensions.
+     *
+     * @param wrong Wrong dimension.
+     * @param expected Expected dimension.
+     */
+    public DimensionMismatchException(int wrong, int expected) {
+        this(LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, wrong, expected);
+    }
+
+    /**
+     * @return the expected dimension.
+     */
+    public int getDimension() {
+        return dimension;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/exception/InsufficientDataException.java b/src/main/java/org/apache/commons/math3/exception/InsufficientDataException.java
new file mode 100644
index 0000000..59fc6da
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/InsufficientDataException.java
@@ -0,0 +1,46 @@
+/*
+ * 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.math3.exception;
+
+import org.apache.commons.math3.exception.util.Localizable;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Exception to be thrown when there is insufficient data to perform a computation.
+ *
+ * @since 3.3
+ */
+public class InsufficientDataException extends MathIllegalArgumentException {
+
+    /** Serializable version Id. */
+    private static final long serialVersionUID = -2629324471511903359L;
+
+    /** Construct the exception. */
+    public InsufficientDataException() {
+        this(LocalizedFormats.INSUFFICIENT_DATA);
+    }
+
+    /**
+     * Construct the exception with a specific context.
+     *
+     * @param pattern Message pattern providing the specific context of the error.
+     * @param arguments Values for replacing the placeholders in {@code pattern}.
+     */
+    public InsufficientDataException(Localizable pattern, Object... arguments) {
+        super(pattern, arguments);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/exception/MathArithmeticException.java b/src/main/java/org/apache/commons/math3/exception/MathArithmeticException.java
new file mode 100644
index 0000000..d8bd723
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/MathArithmeticException.java
@@ -0,0 +1,71 @@
+/*
+ * 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.math3.exception;
+
+import org.apache.commons.math3.exception.util.ExceptionContext;
+import org.apache.commons.math3.exception.util.ExceptionContextProvider;
+import org.apache.commons.math3.exception.util.Localizable;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Base class for arithmetic exceptions. It is used for all the exceptions that have the semantics
+ * of the standard {@link ArithmeticException}, but must also provide a localized message.
+ *
+ * @since 3.0
+ */
+public class MathArithmeticException extends ArithmeticException
+        implements ExceptionContextProvider {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = -6024911025449780478L;
+
+    /** Context. */
+    private final ExceptionContext context;
+
+    /** Default constructor. */
+    public MathArithmeticException() {
+        context = new ExceptionContext(this);
+        context.addMessage(LocalizedFormats.ARITHMETIC_EXCEPTION);
+    }
+
+    /**
+     * Constructor with a specific message.
+     *
+     * @param pattern Message pattern providing the specific context of the error.
+     * @param args Arguments.
+     */
+    public MathArithmeticException(Localizable pattern, Object... args) {
+        context = new ExceptionContext(this);
+        context.addMessage(pattern, args);
+    }
+
+    /** {@inheritDoc} */
+    public ExceptionContext getContext() {
+        return context;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getMessage() {
+        return context.getMessage();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getLocalizedMessage() {
+        return context.getLocalizedMessage();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/exception/MathIllegalArgumentException.java b/src/main/java/org/apache/commons/math3/exception/MathIllegalArgumentException.java
new file mode 100644
index 0000000..73baf13
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/MathIllegalArgumentException.java
@@ -0,0 +1,63 @@
+/*
+ * 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.math3.exception;
+
+import org.apache.commons.math3.exception.util.ExceptionContext;
+import org.apache.commons.math3.exception.util.ExceptionContextProvider;
+import org.apache.commons.math3.exception.util.Localizable;
+
+/**
+ * Base class for all preconditions violation exceptions. In most cases, this class should not be
+ * instantiated directly: it should serve as a base class to create all the exceptions that have the
+ * semantics of the standard {@link IllegalArgumentException}.
+ *
+ * @since 2.2
+ */
+public class MathIllegalArgumentException extends IllegalArgumentException
+        implements ExceptionContextProvider {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = -6024911025449780478L;
+
+    /** Context. */
+    private final ExceptionContext context;
+
+    /**
+     * @param pattern Message pattern explaining the cause of the error.
+     * @param args Arguments.
+     */
+    public MathIllegalArgumentException(Localizable pattern, Object... args) {
+        context = new ExceptionContext(this);
+        context.addMessage(pattern, args);
+    }
+
+    /** {@inheritDoc} */
+    public ExceptionContext getContext() {
+        return context;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getMessage() {
+        return context.getMessage();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getLocalizedMessage() {
+        return context.getLocalizedMessage();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/exception/MathIllegalNumberException.java b/src/main/java/org/apache/commons/math3/exception/MathIllegalNumberException.java
new file mode 100644
index 0000000..a0d632b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/MathIllegalNumberException.java
@@ -0,0 +1,57 @@
+/*
+ * 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.math3.exception;
+
+import org.apache.commons.math3.exception.util.Localizable;
+
+/**
+ * Base class for exceptions raised by a wrong number. This class is not intended to be instantiated
+ * directly: it should serve as a base class to create all the exceptions that are raised because
+ * some precondition is violated by a number argument.
+ *
+ * @since 2.2
+ */
+public class MathIllegalNumberException extends MathIllegalArgumentException {
+
+    /** Helper to avoid boxing warnings. @since 3.3 */
+    protected static final Integer INTEGER_ZERO = Integer.valueOf(0);
+
+    /** Serializable version Id. */
+    private static final long serialVersionUID = -7447085893598031110L;
+
+    /** Requested. */
+    private final Number argument;
+
+    /**
+     * Construct an exception.
+     *
+     * @param pattern Localizable pattern.
+     * @param wrong Wrong number.
+     * @param arguments Arguments.
+     */
+    protected MathIllegalNumberException(Localizable pattern, Number wrong, Object... arguments) {
+        super(pattern, wrong, arguments);
+        argument = wrong;
+    }
+
+    /**
+     * @return the requested value.
+     */
+    public Number getArgument() {
+        return argument;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/exception/MathIllegalStateException.java b/src/main/java/org/apache/commons/math3/exception/MathIllegalStateException.java
new file mode 100644
index 0000000..fa7bf60
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/MathIllegalStateException.java
@@ -0,0 +1,83 @@
+/*
+ * 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.math3.exception;
+
+import org.apache.commons.math3.exception.util.ExceptionContext;
+import org.apache.commons.math3.exception.util.ExceptionContextProvider;
+import org.apache.commons.math3.exception.util.Localizable;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Base class for all exceptions that signal that the process throwing the exception is in a state
+ * that does not comply with the set of states that it is designed to be in.
+ *
+ * @since 2.2
+ */
+public class MathIllegalStateException extends IllegalStateException
+        implements ExceptionContextProvider {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = -6024911025449780478L;
+
+    /** Context. */
+    private final ExceptionContext context;
+
+    /**
+     * Simple constructor.
+     *
+     * @param pattern Message pattern explaining the cause of the error.
+     * @param args Arguments.
+     */
+    public MathIllegalStateException(Localizable pattern, Object... args) {
+        context = new ExceptionContext(this);
+        context.addMessage(pattern, args);
+    }
+
+    /**
+     * Simple constructor.
+     *
+     * @param cause Root cause.
+     * @param pattern Message pattern explaining the cause of the error.
+     * @param args Arguments.
+     */
+    public MathIllegalStateException(Throwable cause, Localizable pattern, Object... args) {
+        super(cause);
+        context = new ExceptionContext(this);
+        context.addMessage(pattern, args);
+    }
+
+    /** Default constructor. */
+    public MathIllegalStateException() {
+        this(LocalizedFormats.ILLEGAL_STATE);
+    }
+
+    /** {@inheritDoc} */
+    public ExceptionContext getContext() {
+        return context;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getMessage() {
+        return context.getMessage();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getLocalizedMessage() {
+        return context.getLocalizedMessage();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/exception/MathInternalError.java b/src/main/java/org/apache/commons/math3/exception/MathInternalError.java
new file mode 100644
index 0000000..bf1b06c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/MathInternalError.java
@@ -0,0 +1,57 @@
+/*
+ * 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.math3.exception;
+
+import org.apache.commons.math3.exception.util.Localizable;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Exception triggered when something that shouldn't happen does happen.
+ *
+ * @since 2.2
+ */
+public class MathInternalError extends MathIllegalStateException {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = -6276776513966934846L;
+
+    /** URL for reporting problems. */
+    private static final String REPORT_URL = "https://issues.apache.org/jira/browse/MATH";
+
+    /** Simple constructor. */
+    public MathInternalError() {
+        getContext().addMessage(LocalizedFormats.INTERNAL_ERROR, REPORT_URL);
+    }
+
+    /**
+     * Simple constructor.
+     *
+     * @param cause root cause
+     */
+    public MathInternalError(final Throwable cause) {
+        super(cause, LocalizedFormats.INTERNAL_ERROR, REPORT_URL);
+    }
+
+    /**
+     * Constructor accepting a localized message.
+     *
+     * @param pattern Message pattern explaining the cause of the error.
+     * @param args Arguments.
+     */
+    public MathInternalError(Localizable pattern, Object... args) {
+        super(pattern, args);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/exception/MathParseException.java b/src/main/java/org/apache/commons/math3/exception/MathParseException.java
new file mode 100644
index 0000000..0bf7948
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/MathParseException.java
@@ -0,0 +1,53 @@
+/*
+ * 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.math3.exception;
+
+import org.apache.commons.math3.exception.util.ExceptionContextProvider;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Class to signal parse failures.
+ *
+ * @since 2.2
+ */
+public class MathParseException extends MathIllegalStateException
+        implements ExceptionContextProvider {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = -6024911025449780478L;
+
+    /**
+     * @param wrong Bad string representation of the object.
+     * @param position Index, in the {@code wrong} string, that caused the parsing to fail.
+     * @param type Class of the object supposedly represented by the {@code wrong} string.
+     */
+    public MathParseException(String wrong, int position, Class<?> type) {
+        getContext()
+                .addMessage(
+                        LocalizedFormats.CANNOT_PARSE_AS_TYPE,
+                        wrong,
+                        Integer.valueOf(position),
+                        type.getName());
+    }
+
+    /**
+     * @param wrong Bad string representation of the object.
+     * @param position Index, in the {@code wrong} string, that caused the parsing to fail.
+     */
+    public MathParseException(String wrong, int position) {
+        getContext().addMessage(LocalizedFormats.CANNOT_PARSE, wrong, Integer.valueOf(position));
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/exception/MathRuntimeException.java b/src/main/java/org/apache/commons/math3/exception/MathRuntimeException.java
new file mode 100644
index 0000000..9bd2bd0
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/MathRuntimeException.java
@@ -0,0 +1,63 @@
+/*
+ * 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.math3.exception;
+
+import org.apache.commons.math3.exception.util.ExceptionContext;
+import org.apache.commons.math3.exception.util.ExceptionContextProvider;
+import org.apache.commons.math3.exception.util.Localizable;
+
+/**
+ * As of release 4.0, all exceptions thrown by the Commons Math code (except {@link
+ * NullArgumentException}) inherit from this class. In most cases, this class should not be
+ * instantiated directly: it should serve as a base class for implementing exception classes that
+ * describe a specific "problem".
+ *
+ * @since 3.1
+ */
+public class MathRuntimeException extends RuntimeException implements ExceptionContextProvider {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = 20120926L;
+
+    /** Context. */
+    private final ExceptionContext context;
+
+    /**
+     * @param pattern Message pattern explaining the cause of the error.
+     * @param args Arguments.
+     */
+    public MathRuntimeException(Localizable pattern, Object... args) {
+        context = new ExceptionContext(this);
+        context.addMessage(pattern, args);
+    }
+
+    /** {@inheritDoc} */
+    public ExceptionContext getContext() {
+        return context;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getMessage() {
+        return context.getMessage();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getLocalizedMessage() {
+        return context.getLocalizedMessage();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/exception/MathUnsupportedOperationException.java b/src/main/java/org/apache/commons/math3/exception/MathUnsupportedOperationException.java
new file mode 100644
index 0000000..992527f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/MathUnsupportedOperationException.java
@@ -0,0 +1,69 @@
+/*
+ * 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.math3.exception;
+
+import org.apache.commons.math3.exception.util.ExceptionContext;
+import org.apache.commons.math3.exception.util.ExceptionContextProvider;
+import org.apache.commons.math3.exception.util.Localizable;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Base class for all unsupported features. It is used for all the exceptions that have the
+ * semantics of the standard {@link UnsupportedOperationException}, but must also provide a
+ * localized message.
+ *
+ * @since 2.2
+ */
+public class MathUnsupportedOperationException extends UnsupportedOperationException
+        implements ExceptionContextProvider {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = -6024911025449780478L;
+
+    /** Context. */
+    private final ExceptionContext context;
+
+    /** Default constructor. */
+    public MathUnsupportedOperationException() {
+        this(LocalizedFormats.UNSUPPORTED_OPERATION);
+    }
+
+    /**
+     * @param pattern Message pattern providing the specific context of the error.
+     * @param args Arguments.
+     */
+    public MathUnsupportedOperationException(Localizable pattern, Object... args) {
+        context = new ExceptionContext(this);
+        context.addMessage(pattern, args);
+    }
+
+    /** {@inheritDoc} */
+    public ExceptionContext getContext() {
+        return context;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getMessage() {
+        return context.getMessage();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getLocalizedMessage() {
+        return context.getLocalizedMessage();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/exception/MaxCountExceededException.java b/src/main/java/org/apache/commons/math3/exception/MaxCountExceededException.java
new file mode 100644
index 0000000..bc97c84
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/MaxCountExceededException.java
@@ -0,0 +1,61 @@
+/*
+ * 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.math3.exception;
+
+import org.apache.commons.math3.exception.util.Localizable;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Exception to be thrown when some counter maximum value is exceeded.
+ *
+ * @since 3.0
+ */
+public class MaxCountExceededException extends MathIllegalStateException {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = 4330003017885151975L;
+
+    /** Maximum number of evaluations. */
+    private final Number max;
+
+    /**
+     * Construct the exception.
+     *
+     * @param max Maximum.
+     */
+    public MaxCountExceededException(Number max) {
+        this(LocalizedFormats.MAX_COUNT_EXCEEDED, max);
+    }
+
+    /**
+     * Construct the exception with a specific context.
+     *
+     * @param specific Specific context pattern.
+     * @param max Maximum.
+     * @param args Additional arguments.
+     */
+    public MaxCountExceededException(Localizable specific, Number max, Object... args) {
+        getContext().addMessage(specific, max, args);
+        this.max = max;
+    }
+
+    /**
+     * @return the maximum number of evaluations.
+     */
+    public Number getMax() {
+        return max;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/exception/MultiDimensionMismatchException.java b/src/main/java/org/apache/commons/math3/exception/MultiDimensionMismatchException.java
new file mode 100644
index 0000000..1db0971
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/MultiDimensionMismatchException.java
@@ -0,0 +1,90 @@
+/*
+ * 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.math3.exception;
+
+import org.apache.commons.math3.exception.util.Localizable;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Exception to be thrown when two sets of dimensions differ.
+ *
+ * @since 3.0
+ */
+public class MultiDimensionMismatchException extends MathIllegalArgumentException {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = -8415396756375798143L;
+
+    /** Wrong dimensions. */
+    private final Integer[] wrong;
+
+    /** Correct dimensions. */
+    private final Integer[] expected;
+
+    /**
+     * Construct an exception from the mismatched dimensions.
+     *
+     * @param wrong Wrong dimensions.
+     * @param expected Expected dimensions.
+     */
+    public MultiDimensionMismatchException(Integer[] wrong, Integer[] expected) {
+        this(LocalizedFormats.DIMENSIONS_MISMATCH, wrong, expected);
+    }
+
+    /**
+     * Construct an exception from the mismatched dimensions.
+     *
+     * @param specific Message pattern providing the specific context of the error.
+     * @param wrong Wrong dimensions.
+     * @param expected Expected dimensions.
+     */
+    public MultiDimensionMismatchException(
+            Localizable specific, Integer[] wrong, Integer[] expected) {
+        super(specific, wrong, expected);
+        this.wrong = wrong.clone();
+        this.expected = expected.clone();
+    }
+
+    /**
+     * @return an array containing the wrong dimensions.
+     */
+    public Integer[] getWrongDimensions() {
+        return wrong.clone();
+    }
+
+    /**
+     * @return an array containing the expected dimensions.
+     */
+    public Integer[] getExpectedDimensions() {
+        return expected.clone();
+    }
+
+    /**
+     * @param index Dimension index.
+     * @return the wrong dimension stored at {@code index}.
+     */
+    public int getWrongDimension(int index) {
+        return wrong[index].intValue();
+    }
+
+    /**
+     * @param index Dimension index.
+     * @return the expected dimension stored at {@code index}.
+     */
+    public int getExpectedDimension(int index) {
+        return expected[index].intValue();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/exception/NoBracketingException.java b/src/main/java/org/apache/commons/math3/exception/NoBracketingException.java
new file mode 100644
index 0000000..f0ff514
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/NoBracketingException.java
@@ -0,0 +1,115 @@
+/*
+ * 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.math3.exception;
+
+import org.apache.commons.math3.exception.util.Localizable;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Exception to be thrown when function values have the same sign at both ends of an interval.
+ *
+ * @since 3.0
+ */
+public class NoBracketingException extends MathIllegalArgumentException {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = -3629324471511904459L;
+
+    /** Lower end of the interval. */
+    private final double lo;
+
+    /** Higher end of the interval. */
+    private final double hi;
+
+    /** Value at lower end of the interval. */
+    private final double fLo;
+
+    /** Value at higher end of the interval. */
+    private final double fHi;
+
+    /**
+     * Construct the exception.
+     *
+     * @param lo Lower end of the interval.
+     * @param hi Higher end of the interval.
+     * @param fLo Value at lower end of the interval.
+     * @param fHi Value at higher end of the interval.
+     */
+    public NoBracketingException(double lo, double hi, double fLo, double fHi) {
+        this(LocalizedFormats.SAME_SIGN_AT_ENDPOINTS, lo, hi, fLo, fHi);
+    }
+
+    /**
+     * Construct the exception with a specific context.
+     *
+     * @param specific Contextual information on what caused the exception.
+     * @param lo Lower end of the interval.
+     * @param hi Higher end of the interval.
+     * @param fLo Value at lower end of the interval.
+     * @param fHi Value at higher end of the interval.
+     * @param args Additional arguments.
+     */
+    public NoBracketingException(
+            Localizable specific, double lo, double hi, double fLo, double fHi, Object... args) {
+        super(
+                specific,
+                Double.valueOf(lo),
+                Double.valueOf(hi),
+                Double.valueOf(fLo),
+                Double.valueOf(fHi),
+                args);
+        this.lo = lo;
+        this.hi = hi;
+        this.fLo = fLo;
+        this.fHi = fHi;
+    }
+
+    /**
+     * Get the lower end of the interval.
+     *
+     * @return the lower end.
+     */
+    public double getLo() {
+        return lo;
+    }
+
+    /**
+     * Get the higher end of the interval.
+     *
+     * @return the higher end.
+     */
+    public double getHi() {
+        return hi;
+    }
+
+    /**
+     * Get the value at the lower end of the interval.
+     *
+     * @return the value at the lower end.
+     */
+    public double getFLo() {
+        return fLo;
+    }
+
+    /**
+     * Get the value at the higher end of the interval.
+     *
+     * @return the value at the higher end.
+     */
+    public double getFHi() {
+        return fHi;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/exception/NoDataException.java b/src/main/java/org/apache/commons/math3/exception/NoDataException.java
new file mode 100644
index 0000000..e475877
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/NoDataException.java
@@ -0,0 +1,45 @@
+/*
+ * 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.math3.exception;
+
+import org.apache.commons.math3.exception.util.Localizable;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Exception to be thrown when the required data is missing.
+ *
+ * @since 2.2
+ */
+public class NoDataException extends MathIllegalArgumentException {
+
+    /** Serializable version Id. */
+    private static final long serialVersionUID = -3629324471511904459L;
+
+    /** Construct the exception. */
+    public NoDataException() {
+        this(LocalizedFormats.NO_DATA);
+    }
+
+    /**
+     * Construct the exception with a specific context.
+     *
+     * @param specific Contextual information on what caused the exception.
+     */
+    public NoDataException(Localizable specific) {
+        super(specific);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/exception/NonMonotonicSequenceException.java b/src/main/java/org/apache/commons/math3/exception/NonMonotonicSequenceException.java
new file mode 100644
index 0000000..a2e37f4
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/NonMonotonicSequenceException.java
@@ -0,0 +1,120 @@
+/*
+ * 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.math3.exception;
+
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * Exception to be thrown when the a sequence of values is not monotonically increasing or
+ * decreasing.
+ *
+ * @since 2.2 (name changed to "NonMonotonicSequenceException" in 3.0)
+ */
+public class NonMonotonicSequenceException extends MathIllegalNumberException {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = 3596849179428944575L;
+
+    /** Direction (positive for increasing, negative for decreasing). */
+    private final MathArrays.OrderDirection direction;
+
+    /** Whether the sequence must be strictly increasing or decreasing. */
+    private final boolean strict;
+
+    /** Index of the wrong value. */
+    private final int index;
+
+    /** Previous value. */
+    private final Number previous;
+
+    /**
+     * Construct the exception. This constructor uses default values assuming that the sequence
+     * should have been strictly increasing.
+     *
+     * @param wrong Value that did not match the requirements.
+     * @param previous Previous value in the sequence.
+     * @param index Index of the value that did not match the requirements.
+     */
+    public NonMonotonicSequenceException(Number wrong, Number previous, int index) {
+        this(wrong, previous, index, MathArrays.OrderDirection.INCREASING, true);
+    }
+
+    /**
+     * Construct the exception.
+     *
+     * @param wrong Value that did not match the requirements.
+     * @param previous Previous value in the sequence.
+     * @param index Index of the value that did not match the requirements.
+     * @param direction Strictly positive for a sequence required to be increasing, negative (or
+     *     zero) for a decreasing sequence.
+     * @param strict Whether the sequence must be strictly increasing or decreasing.
+     */
+    public NonMonotonicSequenceException(
+            Number wrong,
+            Number previous,
+            int index,
+            MathArrays.OrderDirection direction,
+            boolean strict) {
+        super(
+                direction == MathArrays.OrderDirection.INCREASING
+                        ? (strict
+                                ? LocalizedFormats.NOT_STRICTLY_INCREASING_SEQUENCE
+                                : LocalizedFormats.NOT_INCREASING_SEQUENCE)
+                        : (strict
+                                ? LocalizedFormats.NOT_STRICTLY_DECREASING_SEQUENCE
+                                : LocalizedFormats.NOT_DECREASING_SEQUENCE),
+                wrong,
+                previous,
+                Integer.valueOf(index),
+                Integer.valueOf(index - 1));
+
+        this.direction = direction;
+        this.strict = strict;
+        this.index = index;
+        this.previous = previous;
+    }
+
+    /**
+     * @return the order direction.
+     */
+    public MathArrays.OrderDirection getDirection() {
+        return direction;
+    }
+
+    /**
+     * @return {@code true} is the sequence should be strictly monotonic.
+     */
+    public boolean getStrict() {
+        return strict;
+    }
+
+    /**
+     * Get the index of the wrong value.
+     *
+     * @return the current index.
+     */
+    public int getIndex() {
+        return index;
+    }
+
+    /**
+     * @return the previous value.
+     */
+    public Number getPrevious() {
+        return previous;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/exception/NotANumberException.java b/src/main/java/org/apache/commons/math3/exception/NotANumberException.java
new file mode 100644
index 0000000..60d3cff
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/NotANumberException.java
@@ -0,0 +1,34 @@
+/*
+ * 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.math3.exception;
+
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Exception to be thrown when a number is not a number.
+ *
+ * @since 3.1
+ */
+public class NotANumberException extends MathIllegalNumberException {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = 20120906L;
+
+    /** Construct the exception. */
+    public NotANumberException() {
+        super(LocalizedFormats.NAN_NOT_ALLOWED, Double.valueOf(Double.NaN));
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/exception/NotFiniteNumberException.java b/src/main/java/org/apache/commons/math3/exception/NotFiniteNumberException.java
new file mode 100644
index 0000000..5774256
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/NotFiniteNumberException.java
@@ -0,0 +1,51 @@
+/*
+ * 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.math3.exception;
+
+import org.apache.commons.math3.exception.util.Localizable;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Exception to be thrown when a number is not finite.
+ *
+ * @since 3.0
+ */
+public class NotFiniteNumberException extends MathIllegalNumberException {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = -6100997100383932834L;
+
+    /**
+     * Construct the exception.
+     *
+     * @param wrong Value that is infinite or NaN.
+     * @param args Optional arguments.
+     */
+    public NotFiniteNumberException(Number wrong, Object... args) {
+        this(LocalizedFormats.NOT_FINITE_NUMBER, wrong, args);
+    }
+
+    /**
+     * Construct the exception with a specific context.
+     *
+     * @param specific Specific context pattern.
+     * @param wrong Value that is infinite or NaN.
+     * @param args Optional arguments.
+     */
+    public NotFiniteNumberException(Localizable specific, Number wrong, Object... args) {
+        super(specific, wrong, args);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/exception/NotPositiveException.java b/src/main/java/org/apache/commons/math3/exception/NotPositiveException.java
new file mode 100644
index 0000000..32c601d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/NotPositiveException.java
@@ -0,0 +1,48 @@
+/*
+ * 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.math3.exception;
+
+import org.apache.commons.math3.exception.util.Localizable;
+
+/**
+ * Exception to be thrown when the argument is negative.
+ *
+ * @since 2.2
+ */
+public class NotPositiveException extends NumberIsTooSmallException {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = -2250556892093726375L;
+
+    /**
+     * Construct the exception.
+     *
+     * @param value Argument.
+     */
+    public NotPositiveException(Number value) {
+        super(value, INTEGER_ZERO, true);
+    }
+
+    /**
+     * Construct the exception with a specific context.
+     *
+     * @param specific Specific context where the error occurred.
+     * @param value Argument.
+     */
+    public NotPositiveException(Localizable specific, Number value) {
+        super(specific, value, INTEGER_ZERO, true);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/exception/NotStrictlyPositiveException.java b/src/main/java/org/apache/commons/math3/exception/NotStrictlyPositiveException.java
new file mode 100644
index 0000000..28283ec
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/NotStrictlyPositiveException.java
@@ -0,0 +1,49 @@
+/*
+ * 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.math3.exception;
+
+import org.apache.commons.math3.exception.util.Localizable;
+
+/**
+ * Exception to be thrown when the argument is not greater than 0.
+ *
+ * @since 2.2
+ */
+public class NotStrictlyPositiveException extends NumberIsTooSmallException {
+
+    /** Serializable version Id. */
+    private static final long serialVersionUID = -7824848630829852237L;
+
+    /**
+     * Construct the exception.
+     *
+     * @param value Argument.
+     */
+    public NotStrictlyPositiveException(Number value) {
+        super(value, INTEGER_ZERO, false);
+    }
+
+    /**
+     * Construct the exception with a specific context.
+     *
+     * @param specific Specific context where the error occurred.
+     * @param value Argument.
+     */
+    public NotStrictlyPositiveException(Localizable specific, Number value) {
+        super(specific, value, INTEGER_ZERO, false);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/exception/NullArgumentException.java b/src/main/java/org/apache/commons/math3/exception/NullArgumentException.java
new file mode 100644
index 0000000..301b7df
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/NullArgumentException.java
@@ -0,0 +1,46 @@
+/*
+ * 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.math3.exception;
+
+import org.apache.commons.math3.exception.util.Localizable;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * All conditions checks that fail due to a {@code null} argument must throw this exception. This
+ * class is meant to signal a precondition violation ("null is an illegal argument") and so does not
+ * extend the standard {@code NullPointerException}. Propagation of {@code NullPointerException}
+ * from within Commons-Math is construed to be a bug.
+ *
+ * @since 2.2
+ */
+public class NullArgumentException extends MathIllegalArgumentException {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = -6024911025449780478L;
+
+    /** Default constructor. */
+    public NullArgumentException() {
+        this(LocalizedFormats.NULL_NOT_ALLOWED);
+    }
+
+    /**
+     * @param pattern Message pattern providing the specific context of the error.
+     * @param arguments Values for replacing the placeholders in {@code pattern}.
+     */
+    public NullArgumentException(Localizable pattern, Object... arguments) {
+        super(pattern, arguments);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/exception/NumberIsTooLargeException.java b/src/main/java/org/apache/commons/math3/exception/NumberIsTooLargeException.java
new file mode 100644
index 0000000..49d1a53
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/NumberIsTooLargeException.java
@@ -0,0 +1,83 @@
+/*
+ * 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.math3.exception;
+
+import org.apache.commons.math3.exception.util.Localizable;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Exception to be thrown when a number is too large.
+ *
+ * @since 2.2
+ */
+public class NumberIsTooLargeException extends MathIllegalNumberException {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = 4330003017885151975L;
+
+    /** Higher bound. */
+    private final Number max;
+
+    /** Whether the maximum is included in the allowed range. */
+    private final boolean boundIsAllowed;
+
+    /**
+     * Construct the exception.
+     *
+     * @param wrong Value that is larger than the maximum.
+     * @param max Maximum.
+     * @param boundIsAllowed if true the maximum is included in the allowed range.
+     */
+    public NumberIsTooLargeException(Number wrong, Number max, boolean boundIsAllowed) {
+        this(
+                boundIsAllowed
+                        ? LocalizedFormats.NUMBER_TOO_LARGE
+                        : LocalizedFormats.NUMBER_TOO_LARGE_BOUND_EXCLUDED,
+                wrong,
+                max,
+                boundIsAllowed);
+    }
+
+    /**
+     * Construct the exception with a specific context.
+     *
+     * @param specific Specific context pattern.
+     * @param wrong Value that is larger than the maximum.
+     * @param max Maximum.
+     * @param boundIsAllowed if true the maximum is included in the allowed range.
+     */
+    public NumberIsTooLargeException(
+            Localizable specific, Number wrong, Number max, boolean boundIsAllowed) {
+        super(specific, wrong, max);
+
+        this.max = max;
+        this.boundIsAllowed = boundIsAllowed;
+    }
+
+    /**
+     * @return {@code true} if the maximum is included in the allowed range.
+     */
+    public boolean getBoundIsAllowed() {
+        return boundIsAllowed;
+    }
+
+    /**
+     * @return the maximum.
+     */
+    public Number getMax() {
+        return max;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/exception/NumberIsTooSmallException.java b/src/main/java/org/apache/commons/math3/exception/NumberIsTooSmallException.java
new file mode 100644
index 0000000..ec5b986
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/NumberIsTooSmallException.java
@@ -0,0 +1,83 @@
+/*
+ * 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.math3.exception;
+
+import org.apache.commons.math3.exception.util.Localizable;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Exception to be thrown when a number is too small.
+ *
+ * @since 2.2
+ */
+public class NumberIsTooSmallException extends MathIllegalNumberException {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = -6100997100383932834L;
+
+    /** Higher bound. */
+    private final Number min;
+
+    /** Whether the maximum is included in the allowed range. */
+    private final boolean boundIsAllowed;
+
+    /**
+     * Construct the exception.
+     *
+     * @param wrong Value that is smaller than the minimum.
+     * @param min Minimum.
+     * @param boundIsAllowed Whether {@code min} is included in the allowed range.
+     */
+    public NumberIsTooSmallException(Number wrong, Number min, boolean boundIsAllowed) {
+        this(
+                boundIsAllowed
+                        ? LocalizedFormats.NUMBER_TOO_SMALL
+                        : LocalizedFormats.NUMBER_TOO_SMALL_BOUND_EXCLUDED,
+                wrong,
+                min,
+                boundIsAllowed);
+    }
+
+    /**
+     * Construct the exception with a specific context.
+     *
+     * @param specific Specific context pattern.
+     * @param wrong Value that is smaller than the minimum.
+     * @param min Minimum.
+     * @param boundIsAllowed Whether {@code min} is included in the allowed range.
+     */
+    public NumberIsTooSmallException(
+            Localizable specific, Number wrong, Number min, boolean boundIsAllowed) {
+        super(specific, wrong, min);
+
+        this.min = min;
+        this.boundIsAllowed = boundIsAllowed;
+    }
+
+    /**
+     * @return {@code true} if the minimum is included in the allowed range.
+     */
+    public boolean getBoundIsAllowed() {
+        return boundIsAllowed;
+    }
+
+    /**
+     * @return the minimum.
+     */
+    public Number getMin() {
+        return min;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/exception/OutOfRangeException.java b/src/main/java/org/apache/commons/math3/exception/OutOfRangeException.java
new file mode 100644
index 0000000..b064346
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/OutOfRangeException.java
@@ -0,0 +1,75 @@
+/*
+ * 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.math3.exception;
+
+import org.apache.commons.math3.exception.util.Localizable;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Exception to be thrown when some argument is out of range.
+ *
+ * @since 2.2
+ */
+public class OutOfRangeException extends MathIllegalNumberException {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = 111601815794403609L;
+
+    /** Lower bound. */
+    private final Number lo;
+
+    /** Higher bound. */
+    private final Number hi;
+
+    /**
+     * Construct an exception from the mismatched dimensions.
+     *
+     * @param wrong Requested value.
+     * @param lo Lower bound.
+     * @param hi Higher bound.
+     */
+    public OutOfRangeException(Number wrong, Number lo, Number hi) {
+        this(LocalizedFormats.OUT_OF_RANGE_SIMPLE, wrong, lo, hi);
+    }
+
+    /**
+     * Construct an exception from the mismatched dimensions with a specific context information.
+     *
+     * @param specific Context information.
+     * @param wrong Requested value.
+     * @param lo Lower bound.
+     * @param hi Higher bound.
+     */
+    public OutOfRangeException(Localizable specific, Number wrong, Number lo, Number hi) {
+        super(specific, wrong, lo, hi);
+        this.lo = lo;
+        this.hi = hi;
+    }
+
+    /**
+     * @return the lower bound.
+     */
+    public Number getLo() {
+        return lo;
+    }
+
+    /**
+     * @return the higher bound.
+     */
+    public Number getHi() {
+        return hi;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/exception/TooManyEvaluationsException.java b/src/main/java/org/apache/commons/math3/exception/TooManyEvaluationsException.java
new file mode 100644
index 0000000..09da0a0
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/TooManyEvaluationsException.java
@@ -0,0 +1,39 @@
+/*
+ * 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.math3.exception;
+
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Exception to be thrown when the maximal number of evaluations is exceeded.
+ *
+ * @since 3.0
+ */
+public class TooManyEvaluationsException extends MaxCountExceededException {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = 4330003017885151975L;
+
+    /**
+     * Construct the exception.
+     *
+     * @param max Maximum number of evaluations.
+     */
+    public TooManyEvaluationsException(Number max) {
+        super(max);
+        getContext().addMessage(LocalizedFormats.EVALUATIONS);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/exception/TooManyIterationsException.java b/src/main/java/org/apache/commons/math3/exception/TooManyIterationsException.java
new file mode 100644
index 0000000..c49f405
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/TooManyIterationsException.java
@@ -0,0 +1,39 @@
+/*
+ * 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.math3.exception;
+
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Exception to be thrown when the maximal number of iterations is exceeded.
+ *
+ * @since 3.1
+ */
+public class TooManyIterationsException extends MaxCountExceededException {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = 20121211L;
+
+    /**
+     * Construct the exception.
+     *
+     * @param max Maximum number of evaluations.
+     */
+    public TooManyIterationsException(Number max) {
+        super(max);
+        getContext().addMessage(LocalizedFormats.ITERATIONS);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/exception/ZeroException.java b/src/main/java/org/apache/commons/math3/exception/ZeroException.java
new file mode 100644
index 0000000..90933ae
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/ZeroException.java
@@ -0,0 +1,46 @@
+/*
+ * 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.math3.exception;
+
+import org.apache.commons.math3.exception.util.Localizable;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Exception to be thrown when zero is provided where it is not allowed.
+ *
+ * @since 2.2
+ */
+public class ZeroException extends MathIllegalNumberException {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -1960874856936000015L;
+
+    /** Construct the exception. */
+    public ZeroException() {
+        this(LocalizedFormats.ZERO_NOT_ALLOWED);
+    }
+
+    /**
+     * Construct the exception with a specific context.
+     *
+     * @param specific Specific context pattern.
+     * @param arguments Arguments.
+     */
+    public ZeroException(Localizable specific, Object... arguments) {
+        super(specific, INTEGER_ZERO, arguments);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/exception/package-info.java b/src/main/java/org/apache/commons/math3/exception/package-info.java
new file mode 100644
index 0000000..65cc05b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+/**
+ * Specialized exceptions for algorithms errors. The exceptions can be localized using simple java
+ * properties.
+ */
+package org.apache.commons.math3.exception;
diff --git a/src/main/java/org/apache/commons/math3/exception/util/ArgUtils.java b/src/main/java/org/apache/commons/math3/exception/util/ArgUtils.java
new file mode 100644
index 0000000..74214cc
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/util/ArgUtils.java
@@ -0,0 +1,55 @@
+/*
+ * 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.math3.exception.util;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Utility class for transforming the list of arguments passed to
+ * constructors of exceptions.
+ *
+ */
+public class ArgUtils {
+    /**
+     * Class contains only static methods.
+     */
+    private ArgUtils() {}
+
+    /**
+     * Transform a multidimensional array into a one-dimensional list.
+     *
+     * @param array Array (possibly multidimensional).
+     * @return a list of all the {@code Object} instances contained in
+     * {@code array}.
+     */
+    public static Object[] flatten(Object[] array) {
+        final List<Object> list = new ArrayList<Object>();
+        if (array != null) {
+            for (Object o : array) {
+                if (o instanceof Object[]) {
+                    for (Object oR : flatten((Object[]) o)) {
+                        list.add(oR);
+                    }
+                } else {
+                    list.add(o);
+                }
+            }
+        }
+        return list.toArray();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/exception/util/DummyLocalizable.java b/src/main/java/org/apache/commons/math3/exception/util/DummyLocalizable.java
new file mode 100644
index 0000000..cd56708
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/util/DummyLocalizable.java
@@ -0,0 +1,57 @@
+/*
+ * 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.math3.exception.util;
+
+import java.util.Locale;
+
+/**
+ * Dummy implementation of the {@link Localizable} interface, without localization.
+ *
+ * @since 2.2
+ */
+public class DummyLocalizable implements Localizable {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 8843275624471387299L;
+
+    /** Source string. */
+    private final String source;
+
+    /** Simple constructor.
+     * @param source source text
+     */
+    public DummyLocalizable(final String source) {
+        this.source = source;
+    }
+
+    /** {@inheritDoc} */
+    public String getSourceString() {
+        return source;
+    }
+
+    /** {@inheritDoc} */
+    public String getLocalizedString(Locale locale) {
+        return source;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return source;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/exception/util/ExceptionContext.java b/src/main/java/org/apache/commons/math3/exception/util/ExceptionContext.java
new file mode 100644
index 0000000..1a78bd3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/util/ExceptionContext.java
@@ -0,0 +1,334 @@
+/*
+ * 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.math3.exception.util;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Set;
+import java.util.Map;
+import java.io.IOException;
+import java.io.Serializable;
+import java.io.ObjectOutputStream;
+import java.io.ObjectInputStream;
+import java.util.HashMap;
+import java.text.MessageFormat;
+import java.util.Locale;
+
+/**
+ * Class that contains the actual implementation of the functionality mandated
+ * by the {@link ExceptionContext} interface.
+ * All Commons Math exceptions delegate the interface's methods to this class.
+ *
+ * @since 3.0
+ */
+public class ExceptionContext implements Serializable {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = -6024911025449780478L;
+    /**
+     * The throwable to which this context refers to.
+     */
+    private Throwable throwable;
+    /**
+     * Various informations that enrich the informative message.
+     */
+    private List<Localizable> msgPatterns;
+    /**
+     * Various informations that enrich the informative message.
+     * The arguments will replace the corresponding place-holders in
+     * {@link #msgPatterns}.
+     */
+    private List<Object[]> msgArguments;
+    /**
+     * Arbitrary context information.
+     */
+    private Map<String, Object> context;
+
+    /** Simple constructor.
+     * @param throwable the exception this context refers too
+     */
+    public ExceptionContext(final Throwable throwable) {
+        this.throwable = throwable;
+        msgPatterns    = new ArrayList<Localizable>();
+        msgArguments   = new ArrayList<Object[]>();
+        context        = new HashMap<String, Object>();
+    }
+
+    /** Get a reference to the exception to which the context relates.
+     * @return a reference to the exception to which the context relates
+     */
+    public Throwable getThrowable() {
+        return throwable;
+    }
+
+    /**
+     * Adds a message.
+     *
+     * @param pattern Message pattern.
+     * @param arguments Values for replacing the placeholders in the message
+     * pattern.
+     */
+    public void addMessage(Localizable pattern,
+                           Object ... arguments) {
+        msgPatterns.add(pattern);
+        msgArguments.add(ArgUtils.flatten(arguments));
+    }
+
+    /**
+     * Sets the context (key, value) pair.
+     * Keys are assumed to be unique within an instance. If the same key is
+     * assigned a new value, the previous one will be lost.
+     *
+     * @param key Context key (not null).
+     * @param value Context value.
+     */
+    public void setValue(String key, Object value) {
+        context.put(key, value);
+    }
+
+    /**
+     * Gets the value associated to the given context key.
+     *
+     * @param key Context key.
+     * @return the context value or {@code null} if the key does not exist.
+     */
+    public Object getValue(String key) {
+        return context.get(key);
+    }
+
+    /**
+     * Gets all the keys stored in the exception
+     *
+     * @return the set of keys.
+     */
+    public Set<String> getKeys() {
+        return context.keySet();
+    }
+
+    /**
+     * Gets the default message.
+     *
+     * @return the message.
+     */
+    public String getMessage() {
+        return getMessage(Locale.US);
+    }
+
+    /**
+     * Gets the message in the default locale.
+     *
+     * @return the localized message.
+     */
+    public String getLocalizedMessage() {
+        return getMessage(Locale.getDefault());
+    }
+
+    /**
+     * Gets the message in a specified locale.
+     *
+     * @param locale Locale in which the message should be translated.
+     * @return the localized message.
+     */
+    public String getMessage(final Locale locale) {
+        return buildMessage(locale, ": ");
+    }
+
+    /**
+     * Gets the message in a specified locale.
+     *
+     * @param locale Locale in which the message should be translated.
+     * @param separator Separator inserted between the message parts.
+     * @return the localized message.
+     */
+    public String getMessage(final Locale locale,
+                             final String separator) {
+        return buildMessage(locale, separator);
+    }
+
+    /**
+     * Builds a message string.
+     *
+     * @param locale Locale in which the message should be translated.
+     * @param separator Message separator.
+     * @return a localized message string.
+     */
+    private String buildMessage(Locale locale,
+                                String separator) {
+        final StringBuilder sb = new StringBuilder();
+        int count = 0;
+        final int len = msgPatterns.size();
+        for (int i = 0; i < len; i++) {
+            final Localizable pat = msgPatterns.get(i);
+            final Object[] args = msgArguments.get(i);
+            final MessageFormat fmt = new MessageFormat(pat.getLocalizedString(locale),
+                                                        locale);
+            sb.append(fmt.format(args));
+            if (++count < len) {
+                // Add a separator if there are other messages.
+                sb.append(separator);
+            }
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Serialize this object to the given stream.
+     *
+     * @param out Stream.
+     * @throws IOException This should never happen.
+     */
+    private void writeObject(ObjectOutputStream out)
+        throws IOException {
+        out.writeObject(throwable);
+        serializeMessages(out);
+        serializeContext(out);
+    }
+    /**
+     * Deserialize this object from the given stream.
+     *
+     * @param in Stream.
+     * @throws IOException This should never happen.
+     * @throws ClassNotFoundException This should never happen.
+     */
+    private void readObject(ObjectInputStream in)
+        throws IOException,
+               ClassNotFoundException {
+        throwable = (Throwable) in.readObject();
+        deSerializeMessages(in);
+        deSerializeContext(in);
+    }
+
+    /**
+     * Serialize  {@link #msgPatterns} and {@link #msgArguments}.
+     *
+     * @param out Stream.
+     * @throws IOException This should never happen.
+     */
+    private void serializeMessages(ObjectOutputStream out)
+        throws IOException {
+        // Step 1.
+        final int len = msgPatterns.size();
+        out.writeInt(len);
+        // Step 2.
+        for (int i = 0; i < len; i++) {
+            final Localizable pat = msgPatterns.get(i);
+            // Step 3.
+            out.writeObject(pat);
+            final Object[] args = msgArguments.get(i);
+            final int aLen = args.length;
+            // Step 4.
+            out.writeInt(aLen);
+            for (int j = 0; j < aLen; j++) {
+                if (args[j] instanceof Serializable) {
+                    // Step 5a.
+                    out.writeObject(args[j]);
+                } else {
+                    // Step 5b.
+                    out.writeObject(nonSerializableReplacement(args[j]));
+                }
+            }
+        }
+    }
+
+    /**
+     * Deserialize {@link #msgPatterns} and {@link #msgArguments}.
+     *
+     * @param in Stream.
+     * @throws IOException This should never happen.
+     * @throws ClassNotFoundException This should never happen.
+     */
+    private void deSerializeMessages(ObjectInputStream in)
+        throws IOException,
+               ClassNotFoundException {
+        // Step 1.
+        final int len = in.readInt();
+        msgPatterns = new ArrayList<Localizable>(len);
+        msgArguments = new ArrayList<Object[]>(len);
+        // Step 2.
+        for (int i = 0; i < len; i++) {
+            // Step 3.
+            final Localizable pat = (Localizable) in.readObject();
+            msgPatterns.add(pat);
+            // Step 4.
+            final int aLen = in.readInt();
+            final Object[] args = new Object[aLen];
+            for (int j = 0; j < aLen; j++) {
+                // Step 5.
+                args[j] = in.readObject();
+            }
+            msgArguments.add(args);
+        }
+    }
+
+    /**
+     * Serialize {@link #context}.
+     *
+     * @param out Stream.
+     * @throws IOException This should never happen.
+     */
+    private void serializeContext(ObjectOutputStream out)
+        throws IOException {
+        // Step 1.
+        final int len = context.size();
+        out.writeInt(len);
+        for (Map.Entry<String, Object> entry : context.entrySet()) {
+            // Step 2.
+            out.writeObject(entry.getKey());
+            final Object value = entry.getValue();
+            if (value instanceof Serializable) {
+                // Step 3a.
+                out.writeObject(value);
+            } else {
+                // Step 3b.
+                out.writeObject(nonSerializableReplacement(value));
+            }
+        }
+    }
+
+    /**
+     * Deserialize {@link #context}.
+     *
+     * @param in Stream.
+     * @throws IOException This should never happen.
+     * @throws ClassNotFoundException This should never happen.
+     */
+    private void deSerializeContext(ObjectInputStream in)
+        throws IOException,
+               ClassNotFoundException {
+        // Step 1.
+        final int len = in.readInt();
+        context = new HashMap<String, Object>();
+        for (int i = 0; i < len; i++) {
+            // Step 2.
+            final String key = (String) in.readObject();
+            // Step 3.
+            final Object value = in.readObject();
+            context.put(key, value);
+        }
+    }
+
+    /**
+     * Replaces a non-serializable object with an error message string.
+     *
+     * @param obj Object that does not implement the {@code Serializable}
+     * interface.
+     * @return a string that mentions which class could not be serialized.
+     */
+    private String nonSerializableReplacement(Object obj) {
+        return "[Object could not be serialized: " + obj.getClass().getName() + "]";
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/exception/util/ExceptionContextProvider.java b/src/main/java/org/apache/commons/math3/exception/util/ExceptionContextProvider.java
new file mode 100644
index 0000000..913f66a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/util/ExceptionContextProvider.java
@@ -0,0 +1,33 @@
+/*
+ * 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.math3.exception.util;
+
+/**
+ * Interface for accessing the context data structure stored in Commons Math
+ * exceptions.
+ *
+ */
+public interface ExceptionContextProvider {
+    /**
+     * Gets a reference to the "rich context" data structure that allows to
+     * customize error messages and store key, value pairs in exceptions.
+     *
+     * @return a reference to the exception context.
+     */
+    ExceptionContext getContext();
+
+}
diff --git a/src/main/java/org/apache/commons/math3/exception/util/Localizable.java b/src/main/java/org/apache/commons/math3/exception/util/Localizable.java
new file mode 100644
index 0000000..9758bc2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/util/Localizable.java
@@ -0,0 +1,43 @@
+/*
+ * 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.math3.exception.util;
+
+import java.io.Serializable;
+import java.util.Locale;
+
+/**
+ * Interface for localizable strings.
+ *
+ * @since 2.2
+ */
+public interface Localizable extends Serializable {
+    /**
+     * Gets the source (non-localized) string.
+     *
+     * @return the source string.
+     */
+    String getSourceString();
+
+    /**
+     * Gets the localized string.
+     *
+     * @param locale locale into which to get the string.
+     * @return the localized string or the source string if no
+     * localized version is available.
+     */
+    String getLocalizedString(Locale locale);
+}
diff --git a/src/main/java/org/apache/commons/math3/exception/util/LocalizedFormats.java b/src/main/java/org/apache/commons/math3/exception/util/LocalizedFormats.java
new file mode 100644
index 0000000..8be9639
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/util/LocalizedFormats.java
@@ -0,0 +1,414 @@
+/*
+ * 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.math3.exception.util;
+
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+/**
+ * Enumeration for localized messages formats used in exceptions messages.
+ * <p>
+ * The constants in this enumeration represent the available
+ * formats as localized strings. These formats are intended to be
+ * localized using simple properties files, using the constant
+ * name as the key and the property value as the message format.
+ * The source English format is provided in the constants themselves
+ * to serve both as a reminder for developers to understand the parameters
+ * needed by each format, as a basis for translators to create
+ * localized properties files, and as a default format if some
+ * translation is missing.
+ * </p>
+ * @since 2.2
+ */
+public enum LocalizedFormats implements Localizable {
+
+    // CHECKSTYLE: stop MultipleVariableDeclarations
+    // CHECKSTYLE: stop JavadocVariable
+
+    ARGUMENT_OUTSIDE_DOMAIN("Argument {0} outside domain [{1} ; {2}]"),
+    ARRAY_SIZE_EXCEEDS_MAX_VARIABLES("array size cannot be greater than {0}"),
+    ARRAY_SIZES_SHOULD_HAVE_DIFFERENCE_1("array sizes should have difference 1 ({0} != {1} + 1)"),
+    ARRAY_SUMS_TO_ZERO("array sums to zero"),
+    ASSYMETRIC_EIGEN_NOT_SUPPORTED("eigen decomposition of assymetric matrices not supported yet"),
+    AT_LEAST_ONE_COLUMN("matrix must have at least one column"),
+    AT_LEAST_ONE_ROW("matrix must have at least one row"),
+    BANDWIDTH("bandwidth ({0})"),
+    BESSEL_FUNCTION_BAD_ARGUMENT("Bessel function of order {0} cannot be computed for x = {1}"),
+    BESSEL_FUNCTION_FAILED_CONVERGENCE("Bessel function of order {0} failed to converge for x = {1}"),
+    BINOMIAL_INVALID_PARAMETERS_ORDER("must have n >= k for binomial coefficient (n, k), got k = {0}, n = {1}"),
+    BINOMIAL_NEGATIVE_PARAMETER("must have n >= 0 for binomial coefficient (n, k), got n = {0}"),
+    CANNOT_CLEAR_STATISTIC_CONSTRUCTED_FROM_EXTERNAL_MOMENTS("statistics constructed from external moments cannot be cleared"),
+    CANNOT_COMPUTE_0TH_ROOT_OF_UNITY("cannot compute 0-th root of unity, indefinite result"),
+    CANNOT_COMPUTE_BETA_DENSITY_AT_0_FOR_SOME_ALPHA("cannot compute beta density at 0 when alpha = {0,number}"),
+    CANNOT_COMPUTE_BETA_DENSITY_AT_1_FOR_SOME_BETA("cannot compute beta density at 1 when beta = %.3g"),
+    CANNOT_COMPUTE_NTH_ROOT_FOR_NEGATIVE_N("cannot compute nth root for null or negative n: {0}"),
+    CANNOT_DISCARD_NEGATIVE_NUMBER_OF_ELEMENTS("cannot discard a negative number of elements ({0})"),
+    CANNOT_FORMAT_INSTANCE_AS_3D_VECTOR("cannot format a {0} instance as a 3D vector"),
+    CANNOT_FORMAT_INSTANCE_AS_COMPLEX("cannot format a {0} instance as a complex number"),
+    CANNOT_FORMAT_INSTANCE_AS_REAL_VECTOR("cannot format a {0} instance as a real vector"),
+    CANNOT_FORMAT_OBJECT_TO_FRACTION("cannot format given object as a fraction number"),
+    CANNOT_INCREMENT_STATISTIC_CONSTRUCTED_FROM_EXTERNAL_MOMENTS("statistics constructed from external moments cannot be incremented"),
+    CANNOT_NORMALIZE_A_ZERO_NORM_VECTOR("cannot normalize a zero norm vector"),
+    CANNOT_RETRIEVE_AT_NEGATIVE_INDEX("elements cannot be retrieved from a negative array index {0}"),
+    CANNOT_SET_AT_NEGATIVE_INDEX("cannot set an element at a negative index {0}"),
+    CANNOT_SUBSTITUTE_ELEMENT_FROM_EMPTY_ARRAY("cannot substitute an element from an empty array"),
+    CANNOT_TRANSFORM_TO_DOUBLE("Conversion Exception in Transformation: {0}"),
+    CARDAN_ANGLES_SINGULARITY("Cardan angles singularity"),
+    CLASS_DOESNT_IMPLEMENT_COMPARABLE("class ({0}) does not implement Comparable"),
+    CLOSE_VERTICES("too close vertices near point ({0}, {1}, {2})"),
+    CLOSEST_ORTHOGONAL_MATRIX_HAS_NEGATIVE_DETERMINANT("the closest orthogonal matrix has a negative determinant {0}"),
+    COLUMN_INDEX_OUT_OF_RANGE("column index {0} out of allowed range [{1}, {2}]"),
+    COLUMN_INDEX("column index ({0})"), /* keep */
+    CONSTRAINT("constraint"), /* keep */
+    CONTINUED_FRACTION_INFINITY_DIVERGENCE("Continued fraction convergents diverged to +/- infinity for value {0}"),
+    CONTINUED_FRACTION_NAN_DIVERGENCE("Continued fraction diverged to NaN for value {0}"),
+    CONTRACTION_CRITERIA_SMALLER_THAN_EXPANSION_FACTOR("contraction criteria ({0}) smaller than the expansion factor ({1}).  This would lead to a never ending loop of expansion and contraction as a newly expanded internal storage array would immediately satisfy the criteria for contraction."),
+    CONTRACTION_CRITERIA_SMALLER_THAN_ONE("contraction criteria smaller than one ({0}).  This would lead to a never ending loop of expansion and contraction as an internal storage array length equal to the number of elements would satisfy the contraction criteria."),
+    CONVERGENCE_FAILED("convergence failed"), /* keep */
+    CROSSING_BOUNDARY_LOOPS("some outline boundary loops cross each other"),
+    CROSSOVER_RATE("crossover rate ({0})"),
+    CUMULATIVE_PROBABILITY_RETURNED_NAN("Cumulative probability function returned NaN for argument {0} p = {1}"),
+    DIFFERENT_ROWS_LENGTHS("some rows have length {0} while others have length {1}"),
+    DIFFERENT_ORIG_AND_PERMUTED_DATA("original and permuted data must contain the same elements"),
+    DIGEST_NOT_INITIALIZED("digest not initialized"),
+    DIMENSIONS_MISMATCH_2x2("got {0}x{1} but expected {2}x{3}"), /* keep */
+    DIMENSIONS_MISMATCH_SIMPLE("{0} != {1}"), /* keep */
+    DIMENSIONS_MISMATCH("dimensions mismatch"), /* keep */
+    DISCRETE_CUMULATIVE_PROBABILITY_RETURNED_NAN("Discrete cumulative probability function returned NaN for argument {0}"),
+    DISTRIBUTION_NOT_LOADED("distribution not loaded"),
+    DUPLICATED_ABSCISSA_DIVISION_BY_ZERO("duplicated abscissa {0} causes division by zero"),
+    EDGE_CONNECTED_TO_ONE_FACET("edge joining points ({0}, {1}, {2}) and ({3}, {4}, {5}) is connected to one facet only"),
+    ELITISM_RATE("elitism rate ({0})"),
+    EMPTY_CLUSTER_IN_K_MEANS("empty cluster in k-means"),
+    EMPTY_INTERPOLATION_SAMPLE("sample for interpolation is empty"),
+    EMPTY_POLYNOMIALS_COEFFICIENTS_ARRAY("empty polynomials coefficients array"), /* keep */
+    EMPTY_SELECTED_COLUMN_INDEX_ARRAY("empty selected column index array"),
+    EMPTY_SELECTED_ROW_INDEX_ARRAY("empty selected row index array"),
+    EMPTY_STRING_FOR_IMAGINARY_CHARACTER("empty string for imaginary character"),
+    ENDPOINTS_NOT_AN_INTERVAL("endpoints do not specify an interval: [{0}, {1}]"),
+    EQUAL_VERTICES_IN_SIMPLEX("equal vertices {0} and {1} in simplex configuration"),
+    EULER_ANGLES_SINGULARITY("Euler angles singularity"),
+    EVALUATION("evaluation"), /* keep */
+    EXPANSION_FACTOR_SMALLER_THAN_ONE("expansion factor smaller than one ({0})"),
+    FACET_ORIENTATION_MISMATCH("facets orientation mismatch around edge joining points ({0}, {1}, {2}) and ({3}, {4}, {5})"),
+    FACTORIAL_NEGATIVE_PARAMETER("must have n >= 0 for n!, got n = {0}"),
+    FAILED_BRACKETING("number of iterations={4}, maximum iterations={5}, initial={6}, lower bound={7}, upper bound={8}, final a value={0}, final b value={1}, f(a)={2}, f(b)={3}"),
+    FAILED_FRACTION_CONVERSION("Unable to convert {0} to fraction after {1} iterations"),
+    FIRST_COLUMNS_NOT_INITIALIZED_YET("first {0} columns are not initialized yet"),
+    FIRST_ELEMENT_NOT_ZERO("first element is not 0: {0}"),
+    FIRST_ROWS_NOT_INITIALIZED_YET("first {0} rows are not initialized yet"),
+    FRACTION_CONVERSION_OVERFLOW("Overflow trying to convert {0} to fraction ({1}/{2})"),
+    FUNCTION_NOT_DIFFERENTIABLE("function is not differentiable"),
+    FUNCTION_NOT_POLYNOMIAL("function is not polynomial"),
+    GCD_OVERFLOW_32_BITS("overflow: gcd({0}, {1}) is 2^31"),
+    GCD_OVERFLOW_64_BITS("overflow: gcd({0}, {1}) is 2^63"),
+    HOLE_BETWEEN_MODELS_TIME_RANGES("{0} wide hole between models time ranges"),
+    ILL_CONDITIONED_OPERATOR("condition number {1} is too high "),
+    INCONSISTENT_STATE_AT_2_PI_WRAPPING("inconsistent state at 2\u03c0 wrapping"),
+    INDEX_LARGER_THAN_MAX("the index specified: {0} is larger than the current maximal index {1}"),
+    INDEX_NOT_POSITIVE("index ({0}) is not positive"),
+    INDEX_OUT_OF_RANGE("index {0} out of allowed range [{1}, {2}]"),
+    INDEX("index ({0})"), /* keep */
+    NOT_FINITE_NUMBER("{0} is not a finite number"), /* keep */
+    INFINITE_BOUND("interval bounds must be finite"),
+    ARRAY_ELEMENT("value {0} at index {1}"), /* keep */
+    INFINITE_ARRAY_ELEMENT("Array contains an infinite element, {0} at index {1}"),
+    INFINITE_VALUE_CONVERSION("cannot convert infinite value"),
+    INITIAL_CAPACITY_NOT_POSITIVE("initial capacity ({0}) is not positive"),
+    INITIAL_COLUMN_AFTER_FINAL_COLUMN("initial column {1} after final column {0}"),
+    INITIAL_ROW_AFTER_FINAL_ROW("initial row {1} after final row {0}"),
+    @Deprecated
+    INPUT_DATA_FROM_UNSUPPORTED_DATASOURCE("input data comes from unsupported datasource: {0}, supported sources: {1}, {2}"),
+    INSTANCES_NOT_COMPARABLE_TO_EXISTING_VALUES("instance of class {0} not comparable to existing values"),
+    INSUFFICIENT_DATA("insufficient data"),
+    INSUFFICIENT_DATA_FOR_T_STATISTIC("insufficient data for t statistic, needs at least 2, got {0}"),
+    INSUFFICIENT_DIMENSION("insufficient dimension {0}, must be at least {1}"),
+    DIMENSION("dimension ({0})"), /* keep */
+    INSUFFICIENT_OBSERVED_POINTS_IN_SAMPLE("sample contains {0} observed points, at least {1} are required"),
+    INSUFFICIENT_ROWS_AND_COLUMNS("insufficient data: only {0} rows and {1} columns."),
+    INTEGRATION_METHOD_NEEDS_AT_LEAST_TWO_PREVIOUS_POINTS("multistep method needs at least {0} previous steps, got {1}"),
+    INTERNAL_ERROR("internal error, please fill a bug report at {0}"),
+    INVALID_BINARY_DIGIT("invalid binary digit: {0}"),
+    INVALID_BINARY_CHROMOSOME("binary mutation works on BinaryChromosome only"),
+    INVALID_BRACKETING_PARAMETERS("invalid bracketing parameters:  lower bound={0},  initial={1}, upper bound={2}"),
+    INVALID_FIXED_LENGTH_CHROMOSOME("one-point crossover only works with fixed-length chromosomes"),
+    INVALID_IMPLEMENTATION("required functionality is missing in {0}"),
+    INVALID_INTERVAL_INITIAL_VALUE_PARAMETERS("invalid interval, initial value parameters:  lower={0}, initial={1}, upper={2}"),
+    INVALID_ITERATIONS_LIMITS("invalid iteration limits: min={0}, max={1}"),
+    INVALID_MAX_ITERATIONS("bad value for maximum iterations number: {0}"),
+    NOT_ENOUGH_DATA_REGRESSION("the number of observations is not sufficient to conduct regression"),
+    INVALID_REGRESSION_ARRAY("input data array length = {0} does not match the number of observations = {1} and the number of regressors = {2}"),
+    INVALID_REGRESSION_OBSERVATION("length of regressor array = {0} does not match the number of variables = {1} in the model"),
+    INVALID_ROUNDING_METHOD("invalid rounding method {0}, valid methods: {1} ({2}), {3} ({4}), {5} ({6}), {7} ({8}), {9} ({10}), {11} ({12}), {13} ({14}), {15} ({16})"),
+    ITERATOR_EXHAUSTED("iterator exhausted"),
+    ITERATIONS("iterations"), /* keep */
+    LCM_OVERFLOW_32_BITS("overflow: lcm({0}, {1}) is 2^31"),
+    LCM_OVERFLOW_64_BITS("overflow: lcm({0}, {1}) is 2^63"),
+    LIST_OF_CHROMOSOMES_BIGGER_THAN_POPULATION_SIZE("list of chromosomes bigger than maxPopulationSize"),
+    LOESS_EXPECTS_AT_LEAST_ONE_POINT("Loess expects at least 1 point"),
+    LOWER_BOUND_NOT_BELOW_UPPER_BOUND("lower bound ({0}) must be strictly less than upper bound ({1})"), /* keep */
+    LOWER_ENDPOINT_ABOVE_UPPER_ENDPOINT("lower endpoint ({0}) must be less than or equal to upper endpoint ({1})"),
+    MAP_MODIFIED_WHILE_ITERATING("map has been modified while iterating"),
+    MULTISTEP_STARTER_STOPPED_EARLY("multistep integrator starter stopped early, maybe too large step size"),
+    EVALUATIONS("evaluations"), /* keep */
+    MAX_COUNT_EXCEEDED("maximal count ({0}) exceeded"), /* keep */
+    MAX_ITERATIONS_EXCEEDED("maximal number of iterations ({0}) exceeded"),
+    MINIMAL_STEPSIZE_REACHED_DURING_INTEGRATION("minimal step size ({1,number,0.00E00}) reached, integration needs {0,number,0.00E00}"),
+    MISMATCHED_LOESS_ABSCISSA_ORDINATE_ARRAYS("Loess expects the abscissa and ordinate arrays to be of the same size, but got {0} abscissae and {1} ordinatae"),
+    MUTATION_RATE("mutation rate ({0})"),
+    NAN_ELEMENT_AT_INDEX("element {0} is NaN"),
+    NAN_VALUE_CONVERSION("cannot convert NaN value"),
+    NEGATIVE_BRIGHTNESS_EXPONENT("brightness exponent should be positive or null, but got {0}"),
+    NEGATIVE_COMPLEX_MODULE("negative complex module {0}"),
+    NEGATIVE_ELEMENT_AT_2D_INDEX("element ({0}, {1}) is negative: {2}"),
+    NEGATIVE_ELEMENT_AT_INDEX("element {0} is negative: {1}"),
+    NEGATIVE_NUMBER_OF_SUCCESSES("number of successes must be non-negative ({0})"),
+    NUMBER_OF_SUCCESSES("number of successes ({0})"), /* keep */
+    NEGATIVE_NUMBER_OF_TRIALS("number of trials must be non-negative ({0})"),
+    NUMBER_OF_INTERPOLATION_POINTS("number of interpolation points ({0})"), /* keep */
+    NUMBER_OF_TRIALS("number of trials ({0})"),
+    NOT_CONVEX("vertices do not form a convex hull in CCW winding"),
+    NOT_CONVEX_HYPERPLANES("hyperplanes do not define a convex region"),
+    ROBUSTNESS_ITERATIONS("number of robustness iterations ({0})"),
+    START_POSITION("start position ({0})"), /* keep */
+    NON_CONVERGENT_CONTINUED_FRACTION("Continued fraction convergents failed to converge (in less than {0} iterations) for value {1}"),
+    NON_INVERTIBLE_TRANSFORM("non-invertible affine transform collapses some lines into single points"),
+    NON_POSITIVE_MICROSPHERE_ELEMENTS("number of microsphere elements must be positive, but got {0}"),
+    NON_POSITIVE_POLYNOMIAL_DEGREE("polynomial degree must be positive: degree={0}"),
+    NON_REAL_FINITE_ABSCISSA("all abscissae must be finite real numbers, but {0}-th is {1}"),
+    NON_REAL_FINITE_ORDINATE("all ordinatae must be finite real numbers, but {0}-th is {1}"),
+    NON_REAL_FINITE_WEIGHT("all weights must be finite real numbers, but {0}-th is {1}"),
+    NON_SQUARE_MATRIX("non square ({0}x{1}) matrix"),
+    NORM("Norm ({0})"), /* keep */
+    NORMALIZE_INFINITE("Cannot normalize to an infinite value"),
+    NORMALIZE_NAN("Cannot normalize to NaN"),
+    NOT_ADDITION_COMPATIBLE_MATRICES("{0}x{1} and {2}x{3} matrices are not addition compatible"),
+    NOT_DECREASING_NUMBER_OF_POINTS("points {0} and {1} are not decreasing ({2} < {3})"),
+    NOT_DECREASING_SEQUENCE("points {3} and {2} are not decreasing ({1} < {0})"), /* keep */
+    NOT_ENOUGH_DATA_FOR_NUMBER_OF_PREDICTORS("not enough data ({0} rows) for this many predictors ({1} predictors)"),
+    NOT_ENOUGH_POINTS_IN_SPLINE_PARTITION("spline partition must have at least {0} points, got {1}"),
+    NOT_INCREASING_NUMBER_OF_POINTS("points {0} and {1} are not increasing ({2} > {3})"),
+    NOT_INCREASING_SEQUENCE("points {3} and {2} are not increasing ({1} > {0})"), /* keep */
+    NOT_MULTIPLICATION_COMPATIBLE_MATRICES("{0}x{1} and {2}x{3} matrices are not multiplication compatible"),
+    NOT_POSITIVE_DEFINITE_MATRIX("not positive definite matrix"), /* keep */
+    NON_POSITIVE_DEFINITE_MATRIX("not positive definite matrix: diagonal element at ({1},{1}) is smaller than {2} ({0})"),
+    NON_POSITIVE_DEFINITE_OPERATOR("non positive definite linear operator"), /* keep */
+    NON_SELF_ADJOINT_OPERATOR("non self-adjoint linear operator"), /* keep */
+    NON_SQUARE_OPERATOR("non square ({0}x{1}) linear operator"), /* keep */
+    DEGREES_OF_FREEDOM("degrees of freedom ({0})"), /* keep */
+    NOT_POSITIVE_DEGREES_OF_FREEDOM("degrees of freedom must be positive ({0})"),
+    NOT_POSITIVE_ELEMENT_AT_INDEX("element {0} is not positive: {1}"),
+    NOT_POSITIVE_EXPONENT("invalid exponent {0} (must be positive)"),
+    NUMBER_OF_ELEMENTS_SHOULD_BE_POSITIVE("number of elements should be positive ({0})"),
+    BASE("base ({0})"), /* keep */
+    EXPONENT("exponent ({0})"), /* keep */
+    NOT_POSITIVE_LENGTH("length must be positive ({0})"),
+    LENGTH("length ({0})"), /* keep */
+    NOT_POSITIVE_MEAN("mean must be positive ({0})"),
+    MEAN("mean ({0})"), /* keep */
+    NOT_POSITIVE_NUMBER_OF_SAMPLES("number of sample is not positive: {0}"),
+    NUMBER_OF_SAMPLES("number of samples ({0})"), /* keep */
+    NOT_POSITIVE_PERMUTATION("permutation k ({0}) must be positive"),
+    PERMUTATION_SIZE("permutation size ({0}"), /* keep */
+    NOT_POSITIVE_POISSON_MEAN("the Poisson mean must be positive ({0})"),
+    NOT_POSITIVE_POPULATION_SIZE("population size must be positive ({0})"),
+    POPULATION_SIZE("population size ({0})"), /* keep */
+    NOT_POSITIVE_ROW_DIMENSION("invalid row dimension: {0} (must be positive)"),
+    NOT_POSITIVE_SAMPLE_SIZE("sample size must be positive ({0})"),
+    NOT_POSITIVE_SCALE("scale must be positive ({0})"),
+    SCALE("scale ({0})"), /* keep */
+    NOT_POSITIVE_SHAPE("shape must be positive ({0})"),
+    SHAPE("shape ({0})"), /* keep */
+    NOT_POSITIVE_STANDARD_DEVIATION("standard deviation must be positive ({0})"),
+    STANDARD_DEVIATION("standard deviation ({0})"), /* keep */
+    NOT_POSITIVE_UPPER_BOUND("upper bound must be positive ({0})"),
+    NOT_POSITIVE_WINDOW_SIZE("window size must be positive ({0})"),
+    NOT_POWER_OF_TWO("{0} is not a power of 2"),
+    NOT_POWER_OF_TWO_CONSIDER_PADDING("{0} is not a power of 2, consider padding for fix"),
+    NOT_POWER_OF_TWO_PLUS_ONE("{0} is not a power of 2 plus one"),
+    NOT_STRICTLY_DECREASING_NUMBER_OF_POINTS("points {0} and {1} are not strictly decreasing ({2} <= {3})"),
+    NOT_STRICTLY_DECREASING_SEQUENCE("points {3} and {2} are not strictly decreasing ({1} <= {0})"), /* keep */
+    NOT_STRICTLY_INCREASING_KNOT_VALUES("knot values must be strictly increasing"),
+    NOT_STRICTLY_INCREASING_NUMBER_OF_POINTS("points {0} and {1} are not strictly increasing ({2} >= {3})"),
+    NOT_STRICTLY_INCREASING_SEQUENCE("points {3} and {2} are not strictly increasing ({1} >= {0})"), /* keep */
+    NOT_SUBTRACTION_COMPATIBLE_MATRICES("{0}x{1} and {2}x{3} matrices are not subtraction compatible"),
+    NOT_SUPPORTED_IN_DIMENSION_N("method not supported in dimension {0}"),
+    NOT_SYMMETRIC_MATRIX("not symmetric matrix"),
+    NON_SYMMETRIC_MATRIX("non symmetric matrix: the difference between entries at ({0},{1}) and ({1},{0}) is larger than {2}"), /* keep */
+    NO_BIN_SELECTED("no bin selected"),
+    NO_CONVERGENCE_WITH_ANY_START_POINT("none of the {0} start points lead to convergence"), /* keep */
+    NO_DATA("no data"), /* keep */
+    NO_DEGREES_OF_FREEDOM("no degrees of freedom ({0} measurements, {1} parameters)"),
+    NO_DENSITY_FOR_THIS_DISTRIBUTION("This distribution does not have a density function implemented"),
+    NO_FEASIBLE_SOLUTION("no feasible solution"),
+    NO_OPTIMUM_COMPUTED_YET("no optimum computed yet"), /* keep */
+    NO_REGRESSORS("Regression model must include at least one regressor"),
+    NO_RESULT_AVAILABLE("no result available"),
+    NO_SUCH_MATRIX_ENTRY("no entry at indices ({0}, {1}) in a {2}x{3} matrix"),
+    NAN_NOT_ALLOWED("NaN is not allowed"),
+    NULL_NOT_ALLOWED("null is not allowed"), /* keep */
+    ARRAY_ZERO_LENGTH_OR_NULL_NOT_ALLOWED("a null or zero length array not allowed"),
+    COVARIANCE_MATRIX("covariance matrix"), /* keep */
+    DENOMINATOR("denominator"), /* keep */
+    DENOMINATOR_FORMAT("denominator format"), /* keep */
+    FRACTION("fraction"), /* keep */
+    FUNCTION("function"), /* keep */
+    IMAGINARY_FORMAT("imaginary format"), /* keep */
+    INPUT_ARRAY("input array"), /* keep */
+    NUMERATOR("numerator"), /* keep */
+    NUMERATOR_FORMAT("numerator format"), /* keep */
+    OBJECT_TRANSFORMATION("conversion exception in transformation"), /* keep */
+    REAL_FORMAT("real format"), /* keep */
+    WHOLE_FORMAT("whole format"), /* keep */
+    NUMBER_TOO_LARGE("{0} is larger than the maximum ({1})"), /* keep */
+    NUMBER_TOO_SMALL("{0} is smaller than the minimum ({1})"), /* keep */
+    NUMBER_TOO_LARGE_BOUND_EXCLUDED("{0} is larger than, or equal to, the maximum ({1})"), /* keep */
+    NUMBER_TOO_SMALL_BOUND_EXCLUDED("{0} is smaller than, or equal to, the minimum ({1})"), /* keep */
+    NUMBER_OF_SUCCESS_LARGER_THAN_POPULATION_SIZE("number of successes ({0}) must be less than or equal to population size ({1})"),
+    NUMERATOR_OVERFLOW_AFTER_MULTIPLY("overflow, numerator too large after multiply: {0}"),
+    N_POINTS_GAUSS_LEGENDRE_INTEGRATOR_NOT_SUPPORTED("{0} points Legendre-Gauss integrator not supported, number of points must be in the {1}-{2} range"),
+    OBSERVED_COUNTS_ALL_ZERO("observed counts are all 0 in observed array {0}"),
+    OBSERVED_COUNTS_BOTTH_ZERO_FOR_ENTRY("observed counts are both zero for entry {0}"),
+    BOBYQA_BOUND_DIFFERENCE_CONDITION("the difference between the upper and lower bound must be larger than twice the initial trust region radius ({0})"),
+    OUT_OF_BOUNDS_QUANTILE_VALUE("out of bounds quantile value: {0}, must be in (0, 100]"),
+    OUT_OF_BOUNDS_CONFIDENCE_LEVEL("out of bounds confidence level {0}, must be between {1} and {2}"),
+    OUT_OF_BOUND_SIGNIFICANCE_LEVEL("out of bounds significance level {0}, must be between {1} and {2}"),
+    SIGNIFICANCE_LEVEL("significance level ({0})"), /* keep */
+    OUT_OF_ORDER_ABSCISSA_ARRAY("the abscissae array must be sorted in a strictly increasing order, but the {0}-th element is {1} whereas {2}-th is {3}"),
+    OUT_OF_PLANE("point ({0}, {1}, {2}) is out of plane"),
+    OUT_OF_RANGE_ROOT_OF_UNITY_INDEX("out of range root of unity index {0} (must be in [{1};{2}])"),
+    OUT_OF_RANGE("out of range"), /* keep */
+    OUT_OF_RANGE_SIMPLE("{0} out of [{1}, {2}] range"), /* keep */
+    OUT_OF_RANGE_LEFT("{0} out of ({1}, {2}] range"),
+    OUT_OF_RANGE_RIGHT("{0} out of [{1}, {2}) range"),
+    OUTLINE_BOUNDARY_LOOP_OPEN("an outline boundary loop is open"),
+    OVERFLOW("overflow"), /* keep */
+    OVERFLOW_IN_FRACTION("overflow in fraction {0}/{1}, cannot negate"),
+    OVERFLOW_IN_ADDITION("overflow in addition: {0} + {1}"),
+    OVERFLOW_IN_SUBTRACTION("overflow in subtraction: {0} - {1}"),
+    OVERFLOW_IN_MULTIPLICATION("overflow in multiplication: {0} * {1}"),
+    PERCENTILE_IMPLEMENTATION_CANNOT_ACCESS_METHOD("cannot access {0} method in percentile implementation {1}"),
+    PERCENTILE_IMPLEMENTATION_UNSUPPORTED_METHOD("percentile implementation {0} does not support {1}"),
+    PERMUTATION_EXCEEDS_N("permutation size ({0}) exceeds permuation domain ({1})"), /* keep */
+    POLYNOMIAL("polynomial"), /* keep */
+    POLYNOMIAL_INTERPOLANTS_MISMATCH_SEGMENTS("number of polynomial interpolants must match the number of segments ({0} != {1} - 1)"),
+    POPULATION_LIMIT_NOT_POSITIVE("population limit has to be positive"),
+    POWER_NEGATIVE_PARAMETERS("cannot raise an integral value to a negative power ({0}^{1})"),
+    PROPAGATION_DIRECTION_MISMATCH("propagation direction mismatch"),
+    RANDOMKEY_MUTATION_WRONG_CLASS("RandomKeyMutation works only with RandomKeys, not {0}"),
+    ROOTS_OF_UNITY_NOT_COMPUTED_YET("roots of unity have not been computed yet"),
+    ROTATION_MATRIX_DIMENSIONS("a {0}x{1} matrix cannot be a rotation matrix"),
+    ROW_INDEX_OUT_OF_RANGE("row index {0} out of allowed range [{1}, {2}]"),
+    ROW_INDEX("row index ({0})"), /* keep */
+    SAME_SIGN_AT_ENDPOINTS("function values at endpoints do not have different signs, endpoints: [{0}, {1}], values: [{2}, {3}]"),
+    SAMPLE_SIZE_EXCEEDS_COLLECTION_SIZE("sample size ({0}) exceeds collection size ({1})"), /* keep */
+    SAMPLE_SIZE_LARGER_THAN_POPULATION_SIZE("sample size ({0}) must be less than or equal to population size ({1})"),
+    SIMPLEX_NEED_ONE_POINT("simplex must contain at least one point"),
+    SIMPLE_MESSAGE("{0}"),
+    SINGULAR_MATRIX("matrix is singular"), /* keep */
+    SINGULAR_OPERATOR("operator is singular"),
+    SUBARRAY_ENDS_AFTER_ARRAY_END("subarray ends after array end"),
+    TOO_LARGE_CUTOFF_SINGULAR_VALUE("cutoff singular value is {0}, should be at most {1}"),
+    TOO_LARGE_TOURNAMENT_ARITY("tournament arity ({0}) cannot be bigger than population size ({1})"),
+    TOO_MANY_ELEMENTS_TO_DISCARD_FROM_ARRAY("cannot discard {0} elements from a {1} elements array"),
+    TOO_MANY_REGRESSORS("too many regressors ({0}) specified, only {1} in the model"),
+    TOO_SMALL_COST_RELATIVE_TOLERANCE("cost relative tolerance is too small ({0}), no further reduction in the sum of squares is possible"),
+    TOO_SMALL_INTEGRATION_INTERVAL("too small integration interval: length = {0}"),
+    TOO_SMALL_ORTHOGONALITY_TOLERANCE("orthogonality tolerance is too small ({0}), solution is orthogonal to the jacobian"),
+    TOO_SMALL_PARAMETERS_RELATIVE_TOLERANCE("parameters relative tolerance is too small ({0}), no further improvement in the approximate solution is possible"),
+    TRUST_REGION_STEP_FAILED("trust region step has failed to reduce Q"),
+    TWO_OR_MORE_CATEGORIES_REQUIRED("two or more categories required, got {0}"),
+    TWO_OR_MORE_VALUES_IN_CATEGORY_REQUIRED("two or more values required in each category, one has {0}"),
+    UNABLE_TO_BRACKET_OPTIMUM_IN_LINE_SEARCH("unable to bracket optimum in line search"),
+    UNABLE_TO_COMPUTE_COVARIANCE_SINGULAR_PROBLEM("unable to compute covariances: singular problem"),
+    UNABLE_TO_FIRST_GUESS_HARMONIC_COEFFICIENTS("unable to first guess the harmonic coefficients"),
+    UNABLE_TO_ORTHOGONOLIZE_MATRIX("unable to orthogonalize matrix in {0} iterations"),
+    UNABLE_TO_PERFORM_QR_DECOMPOSITION_ON_JACOBIAN("unable to perform Q.R decomposition on the {0}x{1} jacobian matrix"),
+    UNABLE_TO_SOLVE_SINGULAR_PROBLEM("unable to solve: singular problem"),
+    UNBOUNDED_SOLUTION("unbounded solution"),
+    UNKNOWN_MODE("unknown mode {0}, known modes: {1} ({2}), {3} ({4}), {5} ({6}), {7} ({8}), {9} ({10}) and {11} ({12})"),
+    UNKNOWN_PARAMETER("unknown parameter {0}"),
+    UNMATCHED_ODE_IN_EXPANDED_SET("ode does not match the main ode set in the extended set"),
+    CANNOT_PARSE_AS_TYPE("string \"{0}\" unparseable (from position {1}) as an object of type {2}"), /* keep */
+    CANNOT_PARSE("string \"{0}\" unparseable (from position {1})"), /* keep */
+    UNPARSEABLE_3D_VECTOR("unparseable 3D vector: \"{0}\""),
+    UNPARSEABLE_COMPLEX_NUMBER("unparseable complex number: \"{0}\""),
+    UNPARSEABLE_REAL_VECTOR("unparseable real vector: \"{0}\""),
+    UNSUPPORTED_EXPANSION_MODE("unsupported expansion mode {0}, supported modes are {1} ({2}) and {3} ({4})"),
+    UNSUPPORTED_OPERATION("unsupported operation"), /* keep */
+    ARITHMETIC_EXCEPTION("arithmetic exception"), /* keep */
+    ILLEGAL_STATE("illegal state"), /* keep */
+    USER_EXCEPTION("exception generated in user code"), /* keep */
+    URL_CONTAINS_NO_DATA("URL {0} contains no data"),
+    VALUES_ADDED_BEFORE_CONFIGURING_STATISTIC("{0} values have been added before statistic is configured"),
+    VECTOR_LENGTH_MISMATCH("vector length mismatch: got {0} but expected {1}"),
+    VECTOR_MUST_HAVE_AT_LEAST_ONE_ELEMENT("vector must have at least one element"),
+    WEIGHT_AT_LEAST_ONE_NON_ZERO("weigth array must contain at least one non-zero value"),
+    WRONG_BLOCK_LENGTH("wrong array shape (block length = {0}, expected {1})"),
+    WRONG_NUMBER_OF_POINTS("{0} points are required, got only {1}"),
+    NUMBER_OF_POINTS("number of points ({0})"), /* keep */
+    ZERO_DENOMINATOR("denominator must be different from 0"), /* keep */
+    ZERO_DENOMINATOR_IN_FRACTION("zero denominator in fraction {0}/{1}"),
+    ZERO_FRACTION_TO_DIVIDE_BY("the fraction to divide by must not be zero: {0}/{1}"),
+    ZERO_NORM("zero norm"),
+    ZERO_NORM_FOR_ROTATION_AXIS("zero norm for rotation axis"),
+    ZERO_NORM_FOR_ROTATION_DEFINING_VECTOR("zero norm for rotation defining vector"),
+    ZERO_NOT_ALLOWED("zero not allowed here");
+
+    // CHECKSTYLE: resume JavadocVariable
+    // CHECKSTYLE: resume MultipleVariableDeclarations
+
+
+    /** Source English format. */
+    private final String sourceFormat;
+
+    /** Simple constructor.
+     * @param sourceFormat source English format to use when no
+     * localized version is available
+     */
+    LocalizedFormats(final String sourceFormat) {
+        this.sourceFormat = sourceFormat;
+    }
+
+    /** {@inheritDoc} */
+    public String getSourceString() {
+        return sourceFormat;
+    }
+
+    /** {@inheritDoc} */
+    public String getLocalizedString(final Locale locale) {
+        try {
+            final String path = LocalizedFormats.class.getName().replaceAll("\\.", "/");
+            ResourceBundle bundle =
+                    ResourceBundle.getBundle("assets/" + path, locale);
+            if (bundle.getLocale().getLanguage().equals(locale.getLanguage())) {
+                // the value of the resource is the translated format
+                return bundle.getString(toString());
+            }
+
+        } catch (MissingResourceException mre) { // NOPMD
+            // do nothing here
+        }
+
+        // either the locale is not supported or the resource is unknown
+        // don't translate and fall back to using the source format
+        return sourceFormat;
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/exception/util/package-info.java b/src/main/java/org/apache/commons/math3/exception/util/package-info.java
new file mode 100644
index 0000000..6439a8d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/exception/util/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+/**
+ *
+ *    Classes supporting exception localization.
+ *
+ */
+package org.apache.commons.math3.exception.util;
diff --git a/src/main/java/org/apache/commons/math3/filter/DefaultMeasurementModel.java b/src/main/java/org/apache/commons/math3/filter/DefaultMeasurementModel.java
new file mode 100644
index 0000000..d0b4440
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/filter/DefaultMeasurementModel.java
@@ -0,0 +1,77 @@
+/*
+ * 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.math3.filter;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.linear.Array2DRowRealMatrix;
+import org.apache.commons.math3.linear.RealMatrix;
+
+/**
+ * Default implementation of a {@link MeasurementModel} for the use with a {@link KalmanFilter}.
+ *
+ * @since 3.0
+ */
+public class DefaultMeasurementModel implements MeasurementModel {
+
+    /**
+     * The measurement matrix, used to associate the measurement vector to the internal state
+     * estimation vector.
+     */
+    private RealMatrix measurementMatrix;
+
+    /** The measurement noise covariance matrix. */
+    private RealMatrix measurementNoise;
+
+    /**
+     * Create a new {@link MeasurementModel}, taking double arrays as input parameters for the
+     * respective measurement matrix and noise.
+     *
+     * @param measMatrix the measurement matrix
+     * @param measNoise the measurement noise matrix
+     * @throws NullArgumentException if any of the input matrices is {@code null}
+     * @throws NoDataException if any row / column dimension of the input matrices is zero
+     * @throws DimensionMismatchException if any of the input matrices is non-rectangular
+     */
+    public DefaultMeasurementModel(final double[][] measMatrix, final double[][] measNoise)
+            throws NullArgumentException, NoDataException, DimensionMismatchException {
+        this(new Array2DRowRealMatrix(measMatrix), new Array2DRowRealMatrix(measNoise));
+    }
+
+    /**
+     * Create a new {@link MeasurementModel}, taking {@link RealMatrix} objects as input parameters
+     * for the respective measurement matrix and noise.
+     *
+     * @param measMatrix the measurement matrix
+     * @param measNoise the measurement noise matrix
+     */
+    public DefaultMeasurementModel(final RealMatrix measMatrix, final RealMatrix measNoise) {
+        this.measurementMatrix = measMatrix;
+        this.measurementNoise = measNoise;
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix getMeasurementMatrix() {
+        return measurementMatrix;
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix getMeasurementNoise() {
+        return measurementNoise;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/filter/DefaultProcessModel.java b/src/main/java/org/apache/commons/math3/filter/DefaultProcessModel.java
new file mode 100644
index 0000000..24ae97a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/filter/DefaultProcessModel.java
@@ -0,0 +1,151 @@
+/*
+ * 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.math3.filter;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.linear.Array2DRowRealMatrix;
+import org.apache.commons.math3.linear.ArrayRealVector;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.linear.RealVector;
+
+/**
+ * Default implementation of a {@link ProcessModel} for the use with a {@link KalmanFilter}.
+ *
+ * @since 3.0
+ */
+public class DefaultProcessModel implements ProcessModel {
+    /**
+     * The state transition matrix, used to advance the internal state estimation each time-step.
+     */
+    private RealMatrix stateTransitionMatrix;
+
+    /** The control matrix, used to integrate a control input into the state estimation. */
+    private RealMatrix controlMatrix;
+
+    /** The process noise covariance matrix. */
+    private RealMatrix processNoiseCovMatrix;
+
+    /** The initial state estimation of the observed process. */
+    private RealVector initialStateEstimateVector;
+
+    /** The initial error covariance matrix of the observed process. */
+    private RealMatrix initialErrorCovMatrix;
+
+    /**
+     * Create a new {@link ProcessModel}, taking double arrays as input parameters.
+     *
+     * @param stateTransition the state transition matrix
+     * @param control the control matrix
+     * @param processNoise the process noise matrix
+     * @param initialStateEstimate the initial state estimate vector
+     * @param initialErrorCovariance the initial error covariance matrix
+     * @throws NullArgumentException if any of the input arrays is {@code null}
+     * @throws NoDataException if any row / column dimension of the input matrices is zero
+     * @throws DimensionMismatchException if any of the input matrices is non-rectangular
+     */
+    public DefaultProcessModel(
+            final double[][] stateTransition,
+            final double[][] control,
+            final double[][] processNoise,
+            final double[] initialStateEstimate,
+            final double[][] initialErrorCovariance)
+            throws NullArgumentException, NoDataException, DimensionMismatchException {
+
+        this(
+                new Array2DRowRealMatrix(stateTransition),
+                new Array2DRowRealMatrix(control),
+                new Array2DRowRealMatrix(processNoise),
+                new ArrayRealVector(initialStateEstimate),
+                new Array2DRowRealMatrix(initialErrorCovariance));
+    }
+
+    /**
+     * Create a new {@link ProcessModel}, taking double arrays as input parameters.
+     *
+     * <p>The initial state estimate and error covariance are omitted and will be initialized by the
+     * {@link KalmanFilter} to default values.
+     *
+     * @param stateTransition the state transition matrix
+     * @param control the control matrix
+     * @param processNoise the process noise matrix
+     * @throws NullArgumentException if any of the input arrays is {@code null}
+     * @throws NoDataException if any row / column dimension of the input matrices is zero
+     * @throws DimensionMismatchException if any of the input matrices is non-rectangular
+     */
+    public DefaultProcessModel(
+            final double[][] stateTransition,
+            final double[][] control,
+            final double[][] processNoise)
+            throws NullArgumentException, NoDataException, DimensionMismatchException {
+
+        this(
+                new Array2DRowRealMatrix(stateTransition),
+                new Array2DRowRealMatrix(control),
+                new Array2DRowRealMatrix(processNoise),
+                null,
+                null);
+    }
+
+    /**
+     * Create a new {@link ProcessModel}, taking double arrays as input parameters.
+     *
+     * @param stateTransition the state transition matrix
+     * @param control the control matrix
+     * @param processNoise the process noise matrix
+     * @param initialStateEstimate the initial state estimate vector
+     * @param initialErrorCovariance the initial error covariance matrix
+     */
+    public DefaultProcessModel(
+            final RealMatrix stateTransition,
+            final RealMatrix control,
+            final RealMatrix processNoise,
+            final RealVector initialStateEstimate,
+            final RealMatrix initialErrorCovariance) {
+        this.stateTransitionMatrix = stateTransition;
+        this.controlMatrix = control;
+        this.processNoiseCovMatrix = processNoise;
+        this.initialStateEstimateVector = initialStateEstimate;
+        this.initialErrorCovMatrix = initialErrorCovariance;
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix getStateTransitionMatrix() {
+        return stateTransitionMatrix;
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix getControlMatrix() {
+        return controlMatrix;
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix getProcessNoise() {
+        return processNoiseCovMatrix;
+    }
+
+    /** {@inheritDoc} */
+    public RealVector getInitialStateEstimate() {
+        return initialStateEstimateVector;
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix getInitialErrorCovariance() {
+        return initialErrorCovMatrix;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/filter/KalmanFilter.java b/src/main/java/org/apache/commons/math3/filter/KalmanFilter.java
new file mode 100644
index 0000000..7b2e63d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/filter/KalmanFilter.java
@@ -0,0 +1,385 @@
+/*
+ * 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.math3.filter;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.linear.Array2DRowRealMatrix;
+import org.apache.commons.math3.linear.ArrayRealVector;
+import org.apache.commons.math3.linear.CholeskyDecomposition;
+import org.apache.commons.math3.linear.MatrixDimensionMismatchException;
+import org.apache.commons.math3.linear.MatrixUtils;
+import org.apache.commons.math3.linear.NonSquareMatrixException;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.linear.RealVector;
+import org.apache.commons.math3.linear.SingularMatrixException;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * Implementation of a Kalman filter to estimate the state <i>x<sub>k</sub></i> of a discrete-time
+ * controlled process that is governed by the linear stochastic difference equation:
+ *
+ * <pre>
+ * <i>x<sub>k</sub></i> = <b>A</b><i>x<sub>k-1</sub></i> + <b>B</b><i>u<sub>k-1</sub></i> + <i>w<sub>k-1</sub></i>
+ * </pre>
+ *
+ * with a measurement <i>x<sub>k</sub></i> that is
+ *
+ * <pre>
+ * <i>z<sub>k</sub></i> = <b>H</b><i>x<sub>k</sub></i> + <i>v<sub>k</sub></i>.
+ * </pre>
+ *
+ * <p>The random variables <i>w<sub>k</sub></i> and <i>v<sub>k</sub></i> represent the process and
+ * measurement noise and are assumed to be independent of each other and distributed with normal
+ * probability (white noise).
+ *
+ * <p>The Kalman filter cycle involves the following steps:
+ *
+ * <ol>
+ *   <li>predict: project the current state estimate ahead in time
+ *   <li>correct: adjust the projected estimate by an actual measurement
+ * </ol>
+ *
+ * <p>The Kalman filter is initialized with a {@link ProcessModel} and a {@link MeasurementModel},
+ * which contain the corresponding transformation and noise covariance matrices. The parameter names
+ * used in the respective models correspond to the following names commonly used in the mathematical
+ * literature:
+ *
+ * <ul>
+ *   <li>A - state transition matrix
+ *   <li>B - control input matrix
+ *   <li>H - measurement matrix
+ *   <li>Q - process noise covariance matrix
+ *   <li>R - measurement noise covariance matrix
+ *   <li>P - error covariance matrix
+ * </ul>
+ *
+ * @see <a href="http://www.cs.unc.edu/~welch/kalman/">Kalman filter resources</a>
+ * @see <a href="http://www.cs.unc.edu/~welch/media/pdf/kalman_intro.pdf">An introduction to the
+ *     Kalman filter by Greg Welch and Gary Bishop</a>
+ * @see <a href="http://academic.csuohio.edu/simond/courses/eec644/kalman.pdf">Kalman filter example
+ *     by Dan Simon</a>
+ * @see ProcessModel
+ * @see MeasurementModel
+ * @since 3.0
+ */
+public class KalmanFilter {
+    /** The process model used by this filter instance. */
+    private final ProcessModel processModel;
+
+    /** The measurement model used by this filter instance. */
+    private final MeasurementModel measurementModel;
+
+    /** The transition matrix, equivalent to A. */
+    private RealMatrix transitionMatrix;
+
+    /** The transposed transition matrix. */
+    private RealMatrix transitionMatrixT;
+
+    /** The control matrix, equivalent to B. */
+    private RealMatrix controlMatrix;
+
+    /** The measurement matrix, equivalent to H. */
+    private RealMatrix measurementMatrix;
+
+    /** The transposed measurement matrix. */
+    private RealMatrix measurementMatrixT;
+
+    /** The internal state estimation vector, equivalent to x hat. */
+    private RealVector stateEstimation;
+
+    /** The error covariance matrix, equivalent to P. */
+    private RealMatrix errorCovariance;
+
+    /**
+     * Creates a new Kalman filter with the given process and measurement models.
+     *
+     * @param process the model defining the underlying process dynamics
+     * @param measurement the model defining the given measurement characteristics
+     * @throws NullArgumentException if any of the given inputs is null (except for the control
+     *     matrix)
+     * @throws NonSquareMatrixException if the transition matrix is non square
+     * @throws DimensionMismatchException if the column dimension of the transition matrix does not
+     *     match the dimension of the initial state estimation vector
+     * @throws MatrixDimensionMismatchException if the matrix dimensions do not fit together
+     */
+    public KalmanFilter(final ProcessModel process, final MeasurementModel measurement)
+            throws NullArgumentException,
+                    NonSquareMatrixException,
+                    DimensionMismatchException,
+                    MatrixDimensionMismatchException {
+
+        MathUtils.checkNotNull(process);
+        MathUtils.checkNotNull(measurement);
+
+        this.processModel = process;
+        this.measurementModel = measurement;
+
+        transitionMatrix = processModel.getStateTransitionMatrix();
+        MathUtils.checkNotNull(transitionMatrix);
+        transitionMatrixT = transitionMatrix.transpose();
+
+        // create an empty matrix if no control matrix was given
+        if (processModel.getControlMatrix() == null) {
+            controlMatrix = new Array2DRowRealMatrix();
+        } else {
+            controlMatrix = processModel.getControlMatrix();
+        }
+
+        measurementMatrix = measurementModel.getMeasurementMatrix();
+        MathUtils.checkNotNull(measurementMatrix);
+        measurementMatrixT = measurementMatrix.transpose();
+
+        // check that the process and measurement noise matrices are not null
+        // they will be directly accessed from the model as they may change
+        // over time
+        RealMatrix processNoise = processModel.getProcessNoise();
+        MathUtils.checkNotNull(processNoise);
+        RealMatrix measNoise = measurementModel.getMeasurementNoise();
+        MathUtils.checkNotNull(measNoise);
+
+        // set the initial state estimate to a zero vector if it is not
+        // available from the process model
+        if (processModel.getInitialStateEstimate() == null) {
+            stateEstimation = new ArrayRealVector(transitionMatrix.getColumnDimension());
+        } else {
+            stateEstimation = processModel.getInitialStateEstimate();
+        }
+
+        if (transitionMatrix.getColumnDimension() != stateEstimation.getDimension()) {
+            throw new DimensionMismatchException(
+                    transitionMatrix.getColumnDimension(), stateEstimation.getDimension());
+        }
+
+        // initialize the error covariance to the process noise if it is not
+        // available from the process model
+        if (processModel.getInitialErrorCovariance() == null) {
+            errorCovariance = processNoise.copy();
+        } else {
+            errorCovariance = processModel.getInitialErrorCovariance();
+        }
+
+        // sanity checks, the control matrix B may be null
+
+        // A must be a square matrix
+        if (!transitionMatrix.isSquare()) {
+            throw new NonSquareMatrixException(
+                    transitionMatrix.getRowDimension(), transitionMatrix.getColumnDimension());
+        }
+
+        // row dimension of B must be equal to A
+        // if no control matrix is available, the row and column dimension will be 0
+        if (controlMatrix != null
+                && controlMatrix.getRowDimension() > 0
+                && controlMatrix.getColumnDimension() > 0
+                && controlMatrix.getRowDimension() != transitionMatrix.getRowDimension()) {
+            throw new MatrixDimensionMismatchException(
+                    controlMatrix.getRowDimension(),
+                    controlMatrix.getColumnDimension(),
+                    transitionMatrix.getRowDimension(),
+                    controlMatrix.getColumnDimension());
+        }
+
+        // Q must be equal to A
+        MatrixUtils.checkAdditionCompatible(transitionMatrix, processNoise);
+
+        // column dimension of H must be equal to row dimension of A
+        if (measurementMatrix.getColumnDimension() != transitionMatrix.getRowDimension()) {
+            throw new MatrixDimensionMismatchException(
+                    measurementMatrix.getRowDimension(),
+                    measurementMatrix.getColumnDimension(),
+                    measurementMatrix.getRowDimension(),
+                    transitionMatrix.getRowDimension());
+        }
+
+        // row dimension of R must be equal to row dimension of H
+        if (measNoise.getRowDimension() != measurementMatrix.getRowDimension()) {
+            throw new MatrixDimensionMismatchException(
+                    measNoise.getRowDimension(),
+                    measNoise.getColumnDimension(),
+                    measurementMatrix.getRowDimension(),
+                    measNoise.getColumnDimension());
+        }
+    }
+
+    /**
+     * Returns the dimension of the state estimation vector.
+     *
+     * @return the state dimension
+     */
+    public int getStateDimension() {
+        return stateEstimation.getDimension();
+    }
+
+    /**
+     * Returns the dimension of the measurement vector.
+     *
+     * @return the measurement vector dimension
+     */
+    public int getMeasurementDimension() {
+        return measurementMatrix.getRowDimension();
+    }
+
+    /**
+     * Returns the current state estimation vector.
+     *
+     * @return the state estimation vector
+     */
+    public double[] getStateEstimation() {
+        return stateEstimation.toArray();
+    }
+
+    /**
+     * Returns a copy of the current state estimation vector.
+     *
+     * @return the state estimation vector
+     */
+    public RealVector getStateEstimationVector() {
+        return stateEstimation.copy();
+    }
+
+    /**
+     * Returns the current error covariance matrix.
+     *
+     * @return the error covariance matrix
+     */
+    public double[][] getErrorCovariance() {
+        return errorCovariance.getData();
+    }
+
+    /**
+     * Returns a copy of the current error covariance matrix.
+     *
+     * @return the error covariance matrix
+     */
+    public RealMatrix getErrorCovarianceMatrix() {
+        return errorCovariance.copy();
+    }
+
+    /** Predict the internal state estimation one time step ahead. */
+    public void predict() {
+        predict((RealVector) null);
+    }
+
+    /**
+     * Predict the internal state estimation one time step ahead.
+     *
+     * @param u the control vector
+     * @throws DimensionMismatchException if the dimension of the control vector does not fit
+     */
+    public void predict(final double[] u) throws DimensionMismatchException {
+        predict(new ArrayRealVector(u, false));
+    }
+
+    /**
+     * Predict the internal state estimation one time step ahead.
+     *
+     * @param u the control vector
+     * @throws DimensionMismatchException if the dimension of the control vector does not match
+     */
+    public void predict(final RealVector u) throws DimensionMismatchException {
+        // sanity checks
+        if (u != null && u.getDimension() != controlMatrix.getColumnDimension()) {
+            throw new DimensionMismatchException(
+                    u.getDimension(), controlMatrix.getColumnDimension());
+        }
+
+        // project the state estimation ahead (a priori state)
+        // xHat(k)- = A * xHat(k-1) + B * u(k-1)
+        stateEstimation = transitionMatrix.operate(stateEstimation);
+
+        // add control input if it is available
+        if (u != null) {
+            stateEstimation = stateEstimation.add(controlMatrix.operate(u));
+        }
+
+        // project the error covariance ahead
+        // P(k)- = A * P(k-1) * A' + Q
+        errorCovariance =
+                transitionMatrix
+                        .multiply(errorCovariance)
+                        .multiply(transitionMatrixT)
+                        .add(processModel.getProcessNoise());
+    }
+
+    /**
+     * Correct the current state estimate with an actual measurement.
+     *
+     * @param z the measurement vector
+     * @throws NullArgumentException if the measurement vector is {@code null}
+     * @throws DimensionMismatchException if the dimension of the measurement vector does not fit
+     * @throws SingularMatrixException if the covariance matrix could not be inverted
+     */
+    public void correct(final double[] z)
+            throws NullArgumentException, DimensionMismatchException, SingularMatrixException {
+        correct(new ArrayRealVector(z, false));
+    }
+
+    /**
+     * Correct the current state estimate with an actual measurement.
+     *
+     * @param z the measurement vector
+     * @throws NullArgumentException if the measurement vector is {@code null}
+     * @throws DimensionMismatchException if the dimension of the measurement vector does not fit
+     * @throws SingularMatrixException if the covariance matrix could not be inverted
+     */
+    public void correct(final RealVector z)
+            throws NullArgumentException, DimensionMismatchException, SingularMatrixException {
+
+        // sanity checks
+        MathUtils.checkNotNull(z);
+        if (z.getDimension() != measurementMatrix.getRowDimension()) {
+            throw new DimensionMismatchException(
+                    z.getDimension(), measurementMatrix.getRowDimension());
+        }
+
+        // S = H * P(k) * H' + R
+        RealMatrix s =
+                measurementMatrix
+                        .multiply(errorCovariance)
+                        .multiply(measurementMatrixT)
+                        .add(measurementModel.getMeasurementNoise());
+
+        // Inn = z(k) - H * xHat(k)-
+        RealVector innovation = z.subtract(measurementMatrix.operate(stateEstimation));
+
+        // calculate gain matrix
+        // K(k) = P(k)- * H' * (H * P(k)- * H' + R)^-1
+        // K(k) = P(k)- * H' * S^-1
+
+        // instead of calculating the inverse of S we can rearrange the formula,
+        // and then solve the linear equation A x X = B with A = S', X = K' and B = (H * P)'
+
+        // K(k) * S = P(k)- * H'
+        // S' * K(k)' = H * P(k)-'
+        RealMatrix kalmanGain =
+                new CholeskyDecomposition(s)
+                        .getSolver()
+                        .solve(measurementMatrix.multiply(errorCovariance.transpose()))
+                        .transpose();
+
+        // update estimate with measurement z(k)
+        // xHat(k) = xHat(k)- + K * Inn
+        stateEstimation = stateEstimation.add(kalmanGain.operate(innovation));
+
+        // update covariance of prediction error
+        // P(k) = (I - K * H) * P(k)-
+        RealMatrix identity = MatrixUtils.createRealIdentityMatrix(kalmanGain.getRowDimension());
+        errorCovariance =
+                identity.subtract(kalmanGain.multiply(measurementMatrix)).multiply(errorCovariance);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/filter/MeasurementModel.java b/src/main/java/org/apache/commons/math3/filter/MeasurementModel.java
new file mode 100644
index 0000000..2e0a379
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/filter/MeasurementModel.java
@@ -0,0 +1,44 @@
+/*
+ * 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.math3.filter;
+
+import org.apache.commons.math3.linear.RealMatrix;
+
+/**
+ * Defines the measurement model for the use with a {@link KalmanFilter}.
+ *
+ * @since 3.0
+ */
+public interface MeasurementModel {
+    /**
+     * Returns the measurement matrix.
+     *
+     * @return the measurement matrix
+     */
+    RealMatrix getMeasurementMatrix();
+
+    /**
+     * Returns the measurement noise matrix. This method is called by the {@link KalmanFilter} every
+     * correction step, so implementations of this interface may return a modified measurement noise
+     * depending on the current iteration step.
+     *
+     * @return the measurement noise matrix
+     * @see KalmanFilter#correct(double[])
+     * @see KalmanFilter#correct(org.apache.commons.math3.linear.RealVector)
+     */
+    RealMatrix getMeasurementNoise();
+}
diff --git a/src/main/java/org/apache/commons/math3/filter/ProcessModel.java b/src/main/java/org/apache/commons/math3/filter/ProcessModel.java
new file mode 100644
index 0000000..5e0b427
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/filter/ProcessModel.java
@@ -0,0 +1,73 @@
+/*
+ * 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.math3.filter;
+
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.linear.RealVector;
+
+/**
+ * Defines the process dynamics model for the use with a {@link KalmanFilter}.
+ *
+ * @since 3.0
+ */
+public interface ProcessModel {
+    /**
+     * Returns the state transition matrix.
+     *
+     * @return the state transition matrix
+     */
+    RealMatrix getStateTransitionMatrix();
+
+    /**
+     * Returns the control matrix.
+     *
+     * @return the control matrix
+     */
+    RealMatrix getControlMatrix();
+
+    /**
+     * Returns the process noise matrix. This method is called by the {@link KalmanFilter} every
+     * prediction step, so implementations of this interface may return a modified process noise
+     * depending on the current iteration step.
+     *
+     * @return the process noise matrix
+     * @see KalmanFilter#predict()
+     * @see KalmanFilter#predict(double[])
+     * @see KalmanFilter#predict(RealVector)
+     */
+    RealMatrix getProcessNoise();
+
+    /**
+     * Returns the initial state estimation vector.
+     *
+     * <p><b>Note:</b> if the return value is zero, the Kalman filter will initialize the state
+     * estimation with a zero vector.
+     *
+     * @return the initial state estimation vector
+     */
+    RealVector getInitialStateEstimate();
+
+    /**
+     * Returns the initial error covariance matrix.
+     *
+     * <p><b>Note:</b> if the return value is zero, the Kalman filter will initialize the error
+     * covariance with the process noise matrix.
+     *
+     * @return the initial error covariance matrix
+     */
+    RealMatrix getInitialErrorCovariance();
+}
diff --git a/src/main/java/org/apache/commons/math3/filter/package-info.java b/src/main/java/org/apache/commons/math3/filter/package-info.java
new file mode 100644
index 0000000..159b133
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/filter/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+/** Implementations of common discrete-time linear filters. */
+package org.apache.commons.math3.filter;
diff --git a/src/main/java/org/apache/commons/math3/fitting/AbstractCurveFitter.java b/src/main/java/org/apache/commons/math3/fitting/AbstractCurveFitter.java
new file mode 100644
index 0000000..c3f7239
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fitting/AbstractCurveFitter.java
@@ -0,0 +1,141 @@
+/*
+ * 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.math3.fitting;
+
+import org.apache.commons.math3.analysis.MultivariateMatrixFunction;
+import org.apache.commons.math3.analysis.MultivariateVectorFunction;
+import org.apache.commons.math3.analysis.ParametricUnivariateFunction;
+import org.apache.commons.math3.fitting.leastsquares.LeastSquaresOptimizer;
+import org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem;
+import org.apache.commons.math3.fitting.leastsquares.LevenbergMarquardtOptimizer;
+
+import java.util.Collection;
+
+/**
+ * Base class that contains common code for fitting parametric univariate real functions <code>
+ * y = f(p<sub>i</sub>;x)</code>, where {@code x} is the independent variable and the <code>
+ * p<sub>i</sub></code> are the <em>parameters</em>. <br>
+ * A fitter will find the optimal values of the parameters by <em>fitting</em> the curve so it
+ * remains very close to a set of {@code N} observed points <code>(x<sub>k</sub>, y<sub>k</sub>)
+ * </code>, {@code 0 <= k < N}. <br>
+ * An algorithm usually performs the fit by finding the parameter values that minimizes the
+ * objective function
+ *
+ * <pre><code>
+ *  &sum;y<sub>k</sub> - f(x<sub>k</sub>)<sup>2</sup>,
+ * </code></pre>
+ *
+ * which is actually a least-squares problem. This class contains boilerplate code for calling the
+ * {@link #fit(Collection)} method for obtaining the parameters. The problem setup, such as the
+ * choice of optimization algorithm for fitting a specific function is delegated to subclasses.
+ *
+ * @since 3.3
+ */
+public abstract class AbstractCurveFitter {
+    /**
+     * Fits a curve. This method computes the coefficients of the curve that best fit the sample of
+     * observed points.
+     *
+     * @param points Observations.
+     * @return the fitted parameters.
+     */
+    public double[] fit(Collection<WeightedObservedPoint> points) {
+        // Perform the fit.
+        return getOptimizer().optimize(getProblem(points)).getPoint().toArray();
+    }
+
+    /**
+     * Creates an optimizer set up to fit the appropriate curve.
+     *
+     * <p>The default implementation uses a {@link LevenbergMarquardtOptimizer Levenberg-Marquardt}
+     * optimizer.
+     *
+     * @return the optimizer to use for fitting the curve to the given {@code points}.
+     */
+    protected LeastSquaresOptimizer getOptimizer() {
+        return new LevenbergMarquardtOptimizer();
+    }
+
+    /**
+     * Creates a least squares problem corresponding to the appropriate curve.
+     *
+     * @param points Sample points.
+     * @return the least squares problem to use for fitting the curve to the given {@code points}.
+     */
+    protected abstract LeastSquaresProblem getProblem(Collection<WeightedObservedPoint> points);
+
+    /** Vector function for computing function theoretical values. */
+    protected static class TheoreticalValuesFunction {
+        /** Function to fit. */
+        private final ParametricUnivariateFunction f;
+
+        /** Observations. */
+        private final double[] points;
+
+        /**
+         * @param f function to fit.
+         * @param observations Observations.
+         */
+        public TheoreticalValuesFunction(
+                final ParametricUnivariateFunction f,
+                final Collection<WeightedObservedPoint> observations) {
+            this.f = f;
+
+            final int len = observations.size();
+            this.points = new double[len];
+            int i = 0;
+            for (WeightedObservedPoint obs : observations) {
+                this.points[i++] = obs.getX();
+            }
+        }
+
+        /**
+         * @return the model function values.
+         */
+        public MultivariateVectorFunction getModelFunction() {
+            return new MultivariateVectorFunction() {
+                /** {@inheritDoc} */
+                public double[] value(double[] p) {
+                    final int len = points.length;
+                    final double[] values = new double[len];
+                    for (int i = 0; i < len; i++) {
+                        values[i] = f.value(points[i], p);
+                    }
+
+                    return values;
+                }
+            };
+        }
+
+        /**
+         * @return the model function Jacobian.
+         */
+        public MultivariateMatrixFunction getModelFunctionJacobian() {
+            return new MultivariateMatrixFunction() {
+                /** {@inheritDoc} */
+                public double[][] value(double[] p) {
+                    final int len = points.length;
+                    final double[][] jacobian = new double[len][];
+                    for (int i = 0; i < len; i++) {
+                        jacobian[i] = f.gradient(points[i], p);
+                    }
+                    return jacobian;
+                }
+            };
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/fitting/CurveFitter.java b/src/main/java/org/apache/commons/math3/fitting/CurveFitter.java
new file mode 100644
index 0000000..09dd7f2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fitting/CurveFitter.java
@@ -0,0 +1,235 @@
+/*
+ * 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.math3.fitting;
+
+import org.apache.commons.math3.analysis.MultivariateMatrixFunction;
+import org.apache.commons.math3.analysis.MultivariateVectorFunction;
+import org.apache.commons.math3.analysis.ParametricUnivariateFunction;
+import org.apache.commons.math3.optim.InitialGuess;
+import org.apache.commons.math3.optim.MaxEval;
+import org.apache.commons.math3.optim.PointVectorValuePair;
+import org.apache.commons.math3.optim.nonlinear.vector.ModelFunction;
+import org.apache.commons.math3.optim.nonlinear.vector.ModelFunctionJacobian;
+import org.apache.commons.math3.optim.nonlinear.vector.MultivariateVectorOptimizer;
+import org.apache.commons.math3.optim.nonlinear.vector.Target;
+import org.apache.commons.math3.optim.nonlinear.vector.Weight;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Fitter for parametric univariate real functions y = f(x). <br>
+ * When a univariate real function y = f(x) does depend on some unknown parameters p<sub>0</sub>,
+ * p<sub>1</sub> ... p<sub>n-1</sub>, this class can be used to find these parameters. It does this
+ * by <em>fitting</em> the curve so it remains very close to a set of observed points
+ * (x<sub>0</sub>, y<sub>0</sub>), (x<sub>1</sub>, y<sub>1</sub>) ... (x<sub>k-1</sub>,
+ * y<sub>k-1</sub>). This fitting is done by finding the parameters values that minimizes the
+ * objective function &sum;(y<sub>i</sub>-f(x<sub>i</sub>))<sup>2</sup>. This is really a least
+ * squares problem.
+ *
+ * @param <T> Function to use for the fit.
+ * @since 2.0
+ * @deprecated As of 3.3. Please use {@link AbstractCurveFitter} and {@link WeightedObservedPoints}
+ *     instead.
+ */
+@Deprecated
+public class CurveFitter<T extends ParametricUnivariateFunction> {
+    /** Optimizer to use for the fitting. */
+    private final MultivariateVectorOptimizer optimizer;
+
+    /** Observed points. */
+    private final List<WeightedObservedPoint> observations;
+
+    /**
+     * Simple constructor.
+     *
+     * @param optimizer Optimizer to use for the fitting.
+     * @since 3.1
+     */
+    public CurveFitter(final MultivariateVectorOptimizer optimizer) {
+        this.optimizer = optimizer;
+        observations = new ArrayList<WeightedObservedPoint>();
+    }
+
+    /**
+     * Add an observed (x,y) point to the sample with unit weight.
+     *
+     * <p>Calling this method is equivalent to call {@code addObservedPoint(1.0, x, y)}.
+     *
+     * @param x abscissa of the point
+     * @param y observed value of the point at x, after fitting we should have f(x) as close as
+     *     possible to this value
+     * @see #addObservedPoint(double, double, double)
+     * @see #addObservedPoint(WeightedObservedPoint)
+     * @see #getObservations()
+     */
+    public void addObservedPoint(double x, double y) {
+        addObservedPoint(1.0, x, y);
+    }
+
+    /**
+     * Add an observed weighted (x,y) point to the sample.
+     *
+     * @param weight weight of the observed point in the fit
+     * @param x abscissa of the point
+     * @param y observed value of the point at x, after fitting we should have f(x) as close as
+     *     possible to this value
+     * @see #addObservedPoint(double, double)
+     * @see #addObservedPoint(WeightedObservedPoint)
+     * @see #getObservations()
+     */
+    public void addObservedPoint(double weight, double x, double y) {
+        observations.add(new WeightedObservedPoint(weight, x, y));
+    }
+
+    /**
+     * Add an observed weighted (x,y) point to the sample.
+     *
+     * @param observed observed point to add
+     * @see #addObservedPoint(double, double)
+     * @see #addObservedPoint(double, double, double)
+     * @see #getObservations()
+     */
+    public void addObservedPoint(WeightedObservedPoint observed) {
+        observations.add(observed);
+    }
+
+    /**
+     * Get the observed points.
+     *
+     * @return observed points
+     * @see #addObservedPoint(double, double)
+     * @see #addObservedPoint(double, double, double)
+     * @see #addObservedPoint(WeightedObservedPoint)
+     */
+    public WeightedObservedPoint[] getObservations() {
+        return observations.toArray(new WeightedObservedPoint[observations.size()]);
+    }
+
+    /** Remove all observations. */
+    public void clearObservations() {
+        observations.clear();
+    }
+
+    /**
+     * Fit a curve. This method compute the coefficients of the curve that best fit the sample of
+     * observed points previously given through calls to the {@link
+     * #addObservedPoint(WeightedObservedPoint) addObservedPoint} method.
+     *
+     * @param f parametric function to fit.
+     * @param initialGuess first guess of the function parameters.
+     * @return the fitted parameters.
+     * @throws org.apache.commons.math3.exception.DimensionMismatchException if the start point
+     *     dimension is wrong.
+     */
+    public double[] fit(T f, final double[] initialGuess) {
+        return fit(Integer.MAX_VALUE, f, initialGuess);
+    }
+
+    /**
+     * Fit a curve. This method compute the coefficients of the curve that best fit the sample of
+     * observed points previously given through calls to the {@link
+     * #addObservedPoint(WeightedObservedPoint) addObservedPoint} method.
+     *
+     * @param f parametric function to fit.
+     * @param initialGuess first guess of the function parameters.
+     * @param maxEval Maximum number of function evaluations.
+     * @return the fitted parameters.
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException if the number of
+     *     allowed evaluations is exceeded.
+     * @throws org.apache.commons.math3.exception.DimensionMismatchException if the start point
+     *     dimension is wrong.
+     * @since 3.0
+     */
+    public double[] fit(int maxEval, T f, final double[] initialGuess) {
+        // Prepare least squares problem.
+        double[] target = new double[observations.size()];
+        double[] weights = new double[observations.size()];
+        int i = 0;
+        for (WeightedObservedPoint point : observations) {
+            target[i] = point.getY();
+            weights[i] = point.getWeight();
+            ++i;
+        }
+
+        // Input to the optimizer: the model and its Jacobian.
+        final TheoreticalValuesFunction model = new TheoreticalValuesFunction(f);
+
+        // Perform the fit.
+        final PointVectorValuePair optimum =
+                optimizer.optimize(
+                        new MaxEval(maxEval),
+                        model.getModelFunction(),
+                        model.getModelFunctionJacobian(),
+                        new Target(target),
+                        new Weight(weights),
+                        new InitialGuess(initialGuess));
+        // Extract the coefficients.
+        return optimum.getPointRef();
+    }
+
+    /** Vectorial function computing function theoretical values. */
+    private class TheoreticalValuesFunction {
+        /** Function to fit. */
+        private final ParametricUnivariateFunction f;
+
+        /**
+         * @param f function to fit.
+         */
+        TheoreticalValuesFunction(final ParametricUnivariateFunction f) {
+            this.f = f;
+        }
+
+        /**
+         * @return the model function values.
+         */
+        public ModelFunction getModelFunction() {
+            return new ModelFunction(
+                    new MultivariateVectorFunction() {
+                        /** {@inheritDoc} */
+                        public double[] value(double[] point) {
+                            // compute the residuals
+                            final double[] values = new double[observations.size()];
+                            int i = 0;
+                            for (WeightedObservedPoint observed : observations) {
+                                values[i++] = f.value(observed.getX(), point);
+                            }
+
+                            return values;
+                        }
+                    });
+        }
+
+        /**
+         * @return the model function Jacobian.
+         */
+        public ModelFunctionJacobian getModelFunctionJacobian() {
+            return new ModelFunctionJacobian(
+                    new MultivariateMatrixFunction() {
+                        /** {@inheritDoc} */
+                        public double[][] value(double[] point) {
+                            final double[][] jacobian = new double[observations.size()][];
+                            int i = 0;
+                            for (WeightedObservedPoint observed : observations) {
+                                jacobian[i++] = f.gradient(observed.getX(), point);
+                            }
+                            return jacobian;
+                        }
+                    });
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/fitting/GaussianCurveFitter.java b/src/main/java/org/apache/commons/math3/fitting/GaussianCurveFitter.java
new file mode 100644
index 0000000..685df28
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fitting/GaussianCurveFitter.java
@@ -0,0 +1,425 @@
+/*
+ * 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.math3.fitting;
+
+import org.apache.commons.math3.analysis.function.Gaussian;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.ZeroException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.fitting.leastsquares.LeastSquaresBuilder;
+import org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem;
+import org.apache.commons.math3.linear.DiagonalMatrix;
+import org.apache.commons.math3.util.FastMath;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Fits points to a {@link org.apache.commons.math3.analysis.function.Gaussian.Parametric Gaussian}
+ * function. <br>
+ * The {@link #withStartPoint(double[]) initial guess values} must be passed in the following order:
+ *
+ * <ul>
+ *   <li>Normalization
+ *   <li>Mean
+ *   <li>Sigma
+ * </ul>
+ *
+ * The optimal values will be returned in the same order.
+ *
+ * <p>Usage example:
+ *
+ * <pre>
+ *   WeightedObservedPoints obs = new WeightedObservedPoints();
+ *   obs.add(4.0254623,  531026.0);
+ *   obs.add(4.03128248, 984167.0);
+ *   obs.add(4.03839603, 1887233.0);
+ *   obs.add(4.04421621, 2687152.0);
+ *   obs.add(4.05132976, 3461228.0);
+ *   obs.add(4.05326982, 3580526.0);
+ *   obs.add(4.05779662, 3439750.0);
+ *   obs.add(4.0636168,  2877648.0);
+ *   obs.add(4.06943698, 2175960.0);
+ *   obs.add(4.07525716, 1447024.0);
+ *   obs.add(4.08237071, 717104.0);
+ *   obs.add(4.08366408, 620014.0);
+ *   double[] parameters = GaussianCurveFitter.create().fit(obs.toList());
+ * </pre>
+ *
+ * @since 3.3
+ */
+public class GaussianCurveFitter extends AbstractCurveFitter {
+    /** Parametric function to be fitted. */
+    private static final Gaussian.Parametric FUNCTION =
+            new Gaussian.Parametric() {
+                /** {@inheritDoc} */
+                @Override
+                public double value(double x, double... p) {
+                    double v = Double.POSITIVE_INFINITY;
+                    try {
+                        v = super.value(x, p);
+                    } catch (NotStrictlyPositiveException e) { // NOPMD
+                        // Do nothing.
+                    }
+                    return v;
+                }
+
+                /** {@inheritDoc} */
+                @Override
+                public double[] gradient(double x, double... p) {
+                    double[] v = {
+                        Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY
+                    };
+                    try {
+                        v = super.gradient(x, p);
+                    } catch (NotStrictlyPositiveException e) { // NOPMD
+                        // Do nothing.
+                    }
+                    return v;
+                }
+            };
+
+    /** Initial guess. */
+    private final double[] initialGuess;
+
+    /** Maximum number of iterations of the optimization algorithm. */
+    private final int maxIter;
+
+    /**
+     * Contructor used by the factory methods.
+     *
+     * @param initialGuess Initial guess. If set to {@code null}, the initial guess will be
+     *     estimated using the {@link ParameterGuesser}.
+     * @param maxIter Maximum number of iterations of the optimization algorithm.
+     */
+    private GaussianCurveFitter(double[] initialGuess, int maxIter) {
+        this.initialGuess = initialGuess;
+        this.maxIter = maxIter;
+    }
+
+    /**
+     * Creates a default curve fitter. The initial guess for the parameters will be {@link
+     * ParameterGuesser} computed automatically, and the maximum number of iterations of the
+     * optimization algorithm is set to {@link Integer#MAX_VALUE}.
+     *
+     * @return a curve fitter.
+     * @see #withStartPoint(double[])
+     * @see #withMaxIterations(int)
+     */
+    public static GaussianCurveFitter create() {
+        return new GaussianCurveFitter(null, Integer.MAX_VALUE);
+    }
+
+    /**
+     * Configure the start point (initial guess).
+     *
+     * @param newStart new start point (initial guess)
+     * @return a new instance.
+     */
+    public GaussianCurveFitter withStartPoint(double[] newStart) {
+        return new GaussianCurveFitter(newStart.clone(), maxIter);
+    }
+
+    /**
+     * Configure the maximum number of iterations.
+     *
+     * @param newMaxIter maximum number of iterations
+     * @return a new instance.
+     */
+    public GaussianCurveFitter withMaxIterations(int newMaxIter) {
+        return new GaussianCurveFitter(initialGuess, newMaxIter);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected LeastSquaresProblem getProblem(Collection<WeightedObservedPoint> observations) {
+
+        // Prepare least-squares problem.
+        final int len = observations.size();
+        final double[] target = new double[len];
+        final double[] weights = new double[len];
+
+        int i = 0;
+        for (WeightedObservedPoint obs : observations) {
+            target[i] = obs.getY();
+            weights[i] = obs.getWeight();
+            ++i;
+        }
+
+        final AbstractCurveFitter.TheoreticalValuesFunction model =
+                new AbstractCurveFitter.TheoreticalValuesFunction(FUNCTION, observations);
+
+        final double[] startPoint =
+                initialGuess != null
+                        ? initialGuess
+                        :
+                        // Compute estimation.
+                        new ParameterGuesser(observations).guess();
+
+        // Return a new least squares problem set up to fit a Gaussian curve to the
+        // observed points.
+        return new LeastSquaresBuilder()
+                .maxEvaluations(Integer.MAX_VALUE)
+                .maxIterations(maxIter)
+                .start(startPoint)
+                .target(target)
+                .weight(new DiagonalMatrix(weights))
+                .model(model.getModelFunction(), model.getModelFunctionJacobian())
+                .build();
+    }
+
+    /**
+     * Guesses the parameters {@code norm}, {@code mean}, and {@code sigma} of a {@link
+     * org.apache.commons.math3.analysis.function.Gaussian.Parametric} based on the specified
+     * observed points.
+     */
+    public static class ParameterGuesser {
+        /** Normalization factor. */
+        private final double norm;
+
+        /** Mean. */
+        private final double mean;
+
+        /** Standard deviation. */
+        private final double sigma;
+
+        /**
+         * Constructs instance with the specified observed points.
+         *
+         * @param observations Observed points from which to guess the parameters of the Gaussian.
+         * @throws NullArgumentException if {@code observations} is {@code null}.
+         * @throws NumberIsTooSmallException if there are less than 3 observations.
+         */
+        public ParameterGuesser(Collection<WeightedObservedPoint> observations) {
+            if (observations == null) {
+                throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY);
+            }
+            if (observations.size() < 3) {
+                throw new NumberIsTooSmallException(observations.size(), 3, true);
+            }
+
+            final List<WeightedObservedPoint> sorted = sortObservations(observations);
+            final double[] params = basicGuess(sorted.toArray(new WeightedObservedPoint[0]));
+
+            norm = params[0];
+            mean = params[1];
+            sigma = params[2];
+        }
+
+        /**
+         * Gets an estimation of the parameters.
+         *
+         * @return the guessed parameters, in the following order:
+         *     <ul>
+         *       <li>Normalization factor
+         *       <li>Mean
+         *       <li>Standard deviation
+         *     </ul>
+         */
+        public double[] guess() {
+            return new double[] {norm, mean, sigma};
+        }
+
+        /**
+         * Sort the observations.
+         *
+         * @param unsorted Input observations.
+         * @return the input observations, sorted.
+         */
+        private List<WeightedObservedPoint> sortObservations(
+                Collection<WeightedObservedPoint> unsorted) {
+            final List<WeightedObservedPoint> observations =
+                    new ArrayList<WeightedObservedPoint>(unsorted);
+
+            final Comparator<WeightedObservedPoint> cmp =
+                    new Comparator<WeightedObservedPoint>() {
+                        /** {@inheritDoc} */
+                        public int compare(WeightedObservedPoint p1, WeightedObservedPoint p2) {
+                            if (p1 == null && p2 == null) {
+                                return 0;
+                            }
+                            if (p1 == null) {
+                                return -1;
+                            }
+                            if (p2 == null) {
+                                return 1;
+                            }
+                            final int cmpX = Double.compare(p1.getX(), p2.getX());
+                            if (cmpX < 0) {
+                                return -1;
+                            }
+                            if (cmpX > 0) {
+                                return 1;
+                            }
+                            final int cmpY = Double.compare(p1.getY(), p2.getY());
+                            if (cmpY < 0) {
+                                return -1;
+                            }
+                            if (cmpY > 0) {
+                                return 1;
+                            }
+                            final int cmpW = Double.compare(p1.getWeight(), p2.getWeight());
+                            if (cmpW < 0) {
+                                return -1;
+                            }
+                            if (cmpW > 0) {
+                                return 1;
+                            }
+                            return 0;
+                        }
+                    };
+
+            Collections.sort(observations, cmp);
+            return observations;
+        }
+
+        /**
+         * Guesses the parameters based on the specified observed points.
+         *
+         * @param points Observed points, sorted.
+         * @return the guessed parameters (normalization factor, mean and sigma).
+         */
+        private double[] basicGuess(WeightedObservedPoint[] points) {
+            final int maxYIdx = findMaxY(points);
+            final double n = points[maxYIdx].getY();
+            final double m = points[maxYIdx].getX();
+
+            double fwhmApprox;
+            try {
+                final double halfY = n + ((m - n) / 2);
+                final double fwhmX1 = interpolateXAtY(points, maxYIdx, -1, halfY);
+                final double fwhmX2 = interpolateXAtY(points, maxYIdx, 1, halfY);
+                fwhmApprox = fwhmX2 - fwhmX1;
+            } catch (OutOfRangeException e) {
+                // TODO: Exceptions should not be used for flow control.
+                fwhmApprox = points[points.length - 1].getX() - points[0].getX();
+            }
+            final double s = fwhmApprox / (2 * FastMath.sqrt(2 * FastMath.log(2)));
+
+            return new double[] {n, m, s};
+        }
+
+        /**
+         * Finds index of point in specified points with the largest Y.
+         *
+         * @param points Points to search.
+         * @return the index in specified points array.
+         */
+        private int findMaxY(WeightedObservedPoint[] points) {
+            int maxYIdx = 0;
+            for (int i = 1; i < points.length; i++) {
+                if (points[i].getY() > points[maxYIdx].getY()) {
+                    maxYIdx = i;
+                }
+            }
+            return maxYIdx;
+        }
+
+        /**
+         * Interpolates using the specified points to determine X at the specified Y.
+         *
+         * @param points Points to use for interpolation.
+         * @param startIdx Index within points from which to start the search for interpolation
+         *     bounds points.
+         * @param idxStep Index step for searching interpolation bounds points.
+         * @param y Y value for which X should be determined.
+         * @return the value of X for the specified Y.
+         * @throws ZeroException if {@code idxStep} is 0.
+         * @throws OutOfRangeException if specified {@code y} is not within the range of the
+         *     specified {@code points}.
+         */
+        private double interpolateXAtY(
+                WeightedObservedPoint[] points, int startIdx, int idxStep, double y)
+                throws OutOfRangeException {
+            if (idxStep == 0) {
+                throw new ZeroException();
+            }
+            final WeightedObservedPoint[] twoPoints =
+                    getInterpolationPointsForY(points, startIdx, idxStep, y);
+            final WeightedObservedPoint p1 = twoPoints[0];
+            final WeightedObservedPoint p2 = twoPoints[1];
+            if (p1.getY() == y) {
+                return p1.getX();
+            }
+            if (p2.getY() == y) {
+                return p2.getX();
+            }
+            return p1.getX()
+                    + (((y - p1.getY()) * (p2.getX() - p1.getX())) / (p2.getY() - p1.getY()));
+        }
+
+        /**
+         * Gets the two bounding interpolation points from the specified points suitable for
+         * determining X at the specified Y.
+         *
+         * @param points Points to use for interpolation.
+         * @param startIdx Index within points from which to start search for interpolation bounds
+         *     points.
+         * @param idxStep Index step for search for interpolation bounds points.
+         * @param y Y value for which X should be determined.
+         * @return the array containing two points suitable for determining X at the specified Y.
+         * @throws ZeroException if {@code idxStep} is 0.
+         * @throws OutOfRangeException if specified {@code y} is not within the range of the
+         *     specified {@code points}.
+         */
+        private WeightedObservedPoint[] getInterpolationPointsForY(
+                WeightedObservedPoint[] points, int startIdx, int idxStep, double y)
+                throws OutOfRangeException {
+            if (idxStep == 0) {
+                throw new ZeroException();
+            }
+            for (int i = startIdx;
+                    idxStep < 0 ? i + idxStep >= 0 : i + idxStep < points.length;
+                    i += idxStep) {
+                final WeightedObservedPoint p1 = points[i];
+                final WeightedObservedPoint p2 = points[i + idxStep];
+                if (isBetween(y, p1.getY(), p2.getY())) {
+                    if (idxStep < 0) {
+                        return new WeightedObservedPoint[] {p2, p1};
+                    } else {
+                        return new WeightedObservedPoint[] {p1, p2};
+                    }
+                }
+            }
+
+            // Boundaries are replaced by dummy values because the raised
+            // exception is caught and the message never displayed.
+            // TODO: Exceptions should not be used for flow control.
+            throw new OutOfRangeException(y, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
+        }
+
+        /**
+         * Determines whether a value is between two other values.
+         *
+         * @param value Value to test whether it is between {@code boundary1} and {@code boundary2}.
+         * @param boundary1 One end of the range.
+         * @param boundary2 Other end of the range.
+         * @return {@code true} if {@code value} is between {@code boundary1} and {@code boundary2}
+         *     (inclusive), {@code false} otherwise.
+         */
+        private boolean isBetween(double value, double boundary1, double boundary2) {
+            return (value >= boundary1 && value <= boundary2)
+                    || (value >= boundary2 && value <= boundary1);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/fitting/GaussianFitter.java b/src/main/java/org/apache/commons/math3/fitting/GaussianFitter.java
new file mode 100644
index 0000000..fe25c05
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fitting/GaussianFitter.java
@@ -0,0 +1,362 @@
+/*
+ * 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.math3.fitting;
+
+import org.apache.commons.math3.analysis.function.Gaussian;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.ZeroException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.optim.nonlinear.vector.MultivariateVectorOptimizer;
+import org.apache.commons.math3.util.FastMath;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * Fits points to a {@link org.apache.commons.math3.analysis.function.Gaussian.Parametric Gaussian}
+ * function.
+ *
+ * <p>Usage example:
+ *
+ * <pre>
+ *   GaussianFitter fitter = new GaussianFitter(
+ *     new LevenbergMarquardtOptimizer());
+ *   fitter.addObservedPoint(4.0254623,  531026.0);
+ *   fitter.addObservedPoint(4.03128248, 984167.0);
+ *   fitter.addObservedPoint(4.03839603, 1887233.0);
+ *   fitter.addObservedPoint(4.04421621, 2687152.0);
+ *   fitter.addObservedPoint(4.05132976, 3461228.0);
+ *   fitter.addObservedPoint(4.05326982, 3580526.0);
+ *   fitter.addObservedPoint(4.05779662, 3439750.0);
+ *   fitter.addObservedPoint(4.0636168,  2877648.0);
+ *   fitter.addObservedPoint(4.06943698, 2175960.0);
+ *   fitter.addObservedPoint(4.07525716, 1447024.0);
+ *   fitter.addObservedPoint(4.08237071, 717104.0);
+ *   fitter.addObservedPoint(4.08366408, 620014.0);
+ *   double[] parameters = fitter.fit();
+ * </pre>
+ *
+ * @since 2.2
+ * @deprecated As of 3.3. Please use {@link GaussianCurveFitter} and {@link WeightedObservedPoints}
+ *     instead.
+ */
+@Deprecated
+public class GaussianFitter extends CurveFitter<Gaussian.Parametric> {
+    /**
+     * Constructs an instance using the specified optimizer.
+     *
+     * @param optimizer Optimizer to use for the fitting.
+     */
+    public GaussianFitter(MultivariateVectorOptimizer optimizer) {
+        super(optimizer);
+    }
+
+    /**
+     * Fits a Gaussian function to the observed points.
+     *
+     * @param initialGuess First guess values in the following order:
+     *     <ul>
+     *       <li>Norm
+     *       <li>Mean
+     *       <li>Sigma
+     *     </ul>
+     *
+     * @return the parameters of the Gaussian function that best fits the observed points (in the
+     *     same order as above).
+     * @since 3.0
+     */
+    public double[] fit(double[] initialGuess) {
+        final Gaussian.Parametric f =
+                new Gaussian.Parametric() {
+                    /** {@inheritDoc} */
+                    @Override
+                    public double value(double x, double... p) {
+                        double v = Double.POSITIVE_INFINITY;
+                        try {
+                            v = super.value(x, p);
+                        } catch (NotStrictlyPositiveException e) { // NOPMD
+                            // Do nothing.
+                        }
+                        return v;
+                    }
+
+                    /** {@inheritDoc} */
+                    @Override
+                    public double[] gradient(double x, double... p) {
+                        double[] v = {
+                            Double.POSITIVE_INFINITY,
+                            Double.POSITIVE_INFINITY,
+                            Double.POSITIVE_INFINITY
+                        };
+                        try {
+                            v = super.gradient(x, p);
+                        } catch (NotStrictlyPositiveException e) { // NOPMD
+                            // Do nothing.
+                        }
+                        return v;
+                    }
+                };
+
+        return fit(f, initialGuess);
+    }
+
+    /**
+     * Fits a Gaussian function to the observed points.
+     *
+     * @return the parameters of the Gaussian function that best fits the observed points (in the
+     *     same order as above).
+     */
+    public double[] fit() {
+        final double[] guess = (new ParameterGuesser(getObservations())).guess();
+        return fit(guess);
+    }
+
+    /**
+     * Guesses the parameters {@code norm}, {@code mean}, and {@code sigma} of a {@link
+     * org.apache.commons.math3.analysis.function.Gaussian.Parametric} based on the specified
+     * observed points.
+     */
+    public static class ParameterGuesser {
+        /** Normalization factor. */
+        private final double norm;
+
+        /** Mean. */
+        private final double mean;
+
+        /** Standard deviation. */
+        private final double sigma;
+
+        /**
+         * Constructs instance with the specified observed points.
+         *
+         * @param observations Observed points from which to guess the parameters of the Gaussian.
+         * @throws NullArgumentException if {@code observations} is {@code null}.
+         * @throws NumberIsTooSmallException if there are less than 3 observations.
+         */
+        public ParameterGuesser(WeightedObservedPoint[] observations) {
+            if (observations == null) {
+                throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY);
+            }
+            if (observations.length < 3) {
+                throw new NumberIsTooSmallException(observations.length, 3, true);
+            }
+
+            final WeightedObservedPoint[] sorted = sortObservations(observations);
+            final double[] params = basicGuess(sorted);
+
+            norm = params[0];
+            mean = params[1];
+            sigma = params[2];
+        }
+
+        /**
+         * Gets an estimation of the parameters.
+         *
+         * @return the guessed parameters, in the following order:
+         *     <ul>
+         *       <li>Normalization factor
+         *       <li>Mean
+         *       <li>Standard deviation
+         *     </ul>
+         */
+        public double[] guess() {
+            return new double[] {norm, mean, sigma};
+        }
+
+        /**
+         * Sort the observations.
+         *
+         * @param unsorted Input observations.
+         * @return the input observations, sorted.
+         */
+        private WeightedObservedPoint[] sortObservations(WeightedObservedPoint[] unsorted) {
+            final WeightedObservedPoint[] observations = unsorted.clone();
+            final Comparator<WeightedObservedPoint> cmp =
+                    new Comparator<WeightedObservedPoint>() {
+                        /** {@inheritDoc} */
+                        public int compare(WeightedObservedPoint p1, WeightedObservedPoint p2) {
+                            if (p1 == null && p2 == null) {
+                                return 0;
+                            }
+                            if (p1 == null) {
+                                return -1;
+                            }
+                            if (p2 == null) {
+                                return 1;
+                            }
+                            final int cmpX = Double.compare(p1.getX(), p2.getX());
+                            if (cmpX < 0) {
+                                return -1;
+                            }
+                            if (cmpX > 0) {
+                                return 1;
+                            }
+                            final int cmpY = Double.compare(p1.getY(), p2.getY());
+                            if (cmpY < 0) {
+                                return -1;
+                            }
+                            if (cmpY > 0) {
+                                return 1;
+                            }
+                            final int cmpW = Double.compare(p1.getWeight(), p2.getWeight());
+                            if (cmpW < 0) {
+                                return -1;
+                            }
+                            if (cmpW > 0) {
+                                return 1;
+                            }
+                            return 0;
+                        }
+                    };
+
+            Arrays.sort(observations, cmp);
+            return observations;
+        }
+
+        /**
+         * Guesses the parameters based on the specified observed points.
+         *
+         * @param points Observed points, sorted.
+         * @return the guessed parameters (normalization factor, mean and sigma).
+         */
+        private double[] basicGuess(WeightedObservedPoint[] points) {
+            final int maxYIdx = findMaxY(points);
+            final double n = points[maxYIdx].getY();
+            final double m = points[maxYIdx].getX();
+
+            double fwhmApprox;
+            try {
+                final double halfY = n + ((m - n) / 2);
+                final double fwhmX1 = interpolateXAtY(points, maxYIdx, -1, halfY);
+                final double fwhmX2 = interpolateXAtY(points, maxYIdx, 1, halfY);
+                fwhmApprox = fwhmX2 - fwhmX1;
+            } catch (OutOfRangeException e) {
+                // TODO: Exceptions should not be used for flow control.
+                fwhmApprox = points[points.length - 1].getX() - points[0].getX();
+            }
+            final double s = fwhmApprox / (2 * FastMath.sqrt(2 * FastMath.log(2)));
+
+            return new double[] {n, m, s};
+        }
+
+        /**
+         * Finds index of point in specified points with the largest Y.
+         *
+         * @param points Points to search.
+         * @return the index in specified points array.
+         */
+        private int findMaxY(WeightedObservedPoint[] points) {
+            int maxYIdx = 0;
+            for (int i = 1; i < points.length; i++) {
+                if (points[i].getY() > points[maxYIdx].getY()) {
+                    maxYIdx = i;
+                }
+            }
+            return maxYIdx;
+        }
+
+        /**
+         * Interpolates using the specified points to determine X at the specified Y.
+         *
+         * @param points Points to use for interpolation.
+         * @param startIdx Index within points from which to start the search for interpolation
+         *     bounds points.
+         * @param idxStep Index step for searching interpolation bounds points.
+         * @param y Y value for which X should be determined.
+         * @return the value of X for the specified Y.
+         * @throws ZeroException if {@code idxStep} is 0.
+         * @throws OutOfRangeException if specified {@code y} is not within the range of the
+         *     specified {@code points}.
+         */
+        private double interpolateXAtY(
+                WeightedObservedPoint[] points, int startIdx, int idxStep, double y)
+                throws OutOfRangeException {
+            if (idxStep == 0) {
+                throw new ZeroException();
+            }
+            final WeightedObservedPoint[] twoPoints =
+                    getInterpolationPointsForY(points, startIdx, idxStep, y);
+            final WeightedObservedPoint p1 = twoPoints[0];
+            final WeightedObservedPoint p2 = twoPoints[1];
+            if (p1.getY() == y) {
+                return p1.getX();
+            }
+            if (p2.getY() == y) {
+                return p2.getX();
+            }
+            return p1.getX()
+                    + (((y - p1.getY()) * (p2.getX() - p1.getX())) / (p2.getY() - p1.getY()));
+        }
+
+        /**
+         * Gets the two bounding interpolation points from the specified points suitable for
+         * determining X at the specified Y.
+         *
+         * @param points Points to use for interpolation.
+         * @param startIdx Index within points from which to start search for interpolation bounds
+         *     points.
+         * @param idxStep Index step for search for interpolation bounds points.
+         * @param y Y value for which X should be determined.
+         * @return the array containing two points suitable for determining X at the specified Y.
+         * @throws ZeroException if {@code idxStep} is 0.
+         * @throws OutOfRangeException if specified {@code y} is not within the range of the
+         *     specified {@code points}.
+         */
+        private WeightedObservedPoint[] getInterpolationPointsForY(
+                WeightedObservedPoint[] points, int startIdx, int idxStep, double y)
+                throws OutOfRangeException {
+            if (idxStep == 0) {
+                throw new ZeroException();
+            }
+            for (int i = startIdx;
+                    idxStep < 0 ? i + idxStep >= 0 : i + idxStep < points.length;
+                    i += idxStep) {
+                final WeightedObservedPoint p1 = points[i];
+                final WeightedObservedPoint p2 = points[i + idxStep];
+                if (isBetween(y, p1.getY(), p2.getY())) {
+                    if (idxStep < 0) {
+                        return new WeightedObservedPoint[] {p2, p1};
+                    } else {
+                        return new WeightedObservedPoint[] {p1, p2};
+                    }
+                }
+            }
+
+            // Boundaries are replaced by dummy values because the raised
+            // exception is caught and the message never displayed.
+            // TODO: Exceptions should not be used for flow control.
+            throw new OutOfRangeException(y, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
+        }
+
+        /**
+         * Determines whether a value is between two other values.
+         *
+         * @param value Value to test whether it is between {@code boundary1} and {@code boundary2}.
+         * @param boundary1 One end of the range.
+         * @param boundary2 Other end of the range.
+         * @return {@code true} if {@code value} is between {@code boundary1} and {@code boundary2}
+         *     (inclusive), {@code false} otherwise.
+         */
+        private boolean isBetween(double value, double boundary1, double boundary2) {
+            return (value >= boundary1 && value <= boundary2)
+                    || (value >= boundary2 && value <= boundary1);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/fitting/HarmonicCurveFitter.java b/src/main/java/org/apache/commons/math3/fitting/HarmonicCurveFitter.java
new file mode 100644
index 0000000..29a49c7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fitting/HarmonicCurveFitter.java
@@ -0,0 +1,410 @@
+/*
+ * 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.math3.fitting;
+
+import org.apache.commons.math3.analysis.function.HarmonicOscillator;
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.ZeroException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.fitting.leastsquares.LeastSquaresBuilder;
+import org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem;
+import org.apache.commons.math3.linear.DiagonalMatrix;
+import org.apache.commons.math3.util.FastMath;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Fits points to a {@link org.apache.commons.math3.analysis.function.HarmonicOscillator.Parametric
+ * harmonic oscillator} function. <br>
+ * The {@link #withStartPoint(double[]) initial guess values} must be passed in the following order:
+ *
+ * <ul>
+ *   <li>Amplitude
+ *   <li>Angular frequency
+ *   <li>phase
+ * </ul>
+ *
+ * The optimal values will be returned in the same order.
+ *
+ * @since 3.3
+ */
+public class HarmonicCurveFitter extends AbstractCurveFitter {
+    /** Parametric function to be fitted. */
+    private static final HarmonicOscillator.Parametric FUNCTION =
+            new HarmonicOscillator.Parametric();
+
+    /** Initial guess. */
+    private final double[] initialGuess;
+
+    /** Maximum number of iterations of the optimization algorithm. */
+    private final int maxIter;
+
+    /**
+     * Contructor used by the factory methods.
+     *
+     * @param initialGuess Initial guess. If set to {@code null}, the initial guess will be
+     *     estimated using the {@link ParameterGuesser}.
+     * @param maxIter Maximum number of iterations of the optimization algorithm.
+     */
+    private HarmonicCurveFitter(double[] initialGuess, int maxIter) {
+        this.initialGuess = initialGuess;
+        this.maxIter = maxIter;
+    }
+
+    /**
+     * Creates a default curve fitter. The initial guess for the parameters will be {@link
+     * ParameterGuesser} computed automatically, and the maximum number of iterations of the
+     * optimization algorithm is set to {@link Integer#MAX_VALUE}.
+     *
+     * @return a curve fitter.
+     * @see #withStartPoint(double[])
+     * @see #withMaxIterations(int)
+     */
+    public static HarmonicCurveFitter create() {
+        return new HarmonicCurveFitter(null, Integer.MAX_VALUE);
+    }
+
+    /**
+     * Configure the start point (initial guess).
+     *
+     * @param newStart new start point (initial guess)
+     * @return a new instance.
+     */
+    public HarmonicCurveFitter withStartPoint(double[] newStart) {
+        return new HarmonicCurveFitter(newStart.clone(), maxIter);
+    }
+
+    /**
+     * Configure the maximum number of iterations.
+     *
+     * @param newMaxIter maximum number of iterations
+     * @return a new instance.
+     */
+    public HarmonicCurveFitter withMaxIterations(int newMaxIter) {
+        return new HarmonicCurveFitter(initialGuess, newMaxIter);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected LeastSquaresProblem getProblem(Collection<WeightedObservedPoint> observations) {
+        // Prepare least-squares problem.
+        final int len = observations.size();
+        final double[] target = new double[len];
+        final double[] weights = new double[len];
+
+        int i = 0;
+        for (WeightedObservedPoint obs : observations) {
+            target[i] = obs.getY();
+            weights[i] = obs.getWeight();
+            ++i;
+        }
+
+        final AbstractCurveFitter.TheoreticalValuesFunction model =
+                new AbstractCurveFitter.TheoreticalValuesFunction(FUNCTION, observations);
+
+        final double[] startPoint =
+                initialGuess != null
+                        ? initialGuess
+                        :
+                        // Compute estimation.
+                        new ParameterGuesser(observations).guess();
+
+        // Return a new optimizer set up to fit a Gaussian curve to the
+        // observed points.
+        return new LeastSquaresBuilder()
+                .maxEvaluations(Integer.MAX_VALUE)
+                .maxIterations(maxIter)
+                .start(startPoint)
+                .target(target)
+                .weight(new DiagonalMatrix(weights))
+                .model(model.getModelFunction(), model.getModelFunctionJacobian())
+                .build();
+    }
+
+    /**
+     * This class guesses harmonic coefficients from a sample.
+     *
+     * <p>The algorithm used to guess the coefficients is as follows:
+     *
+     * <p>We know \( f(t) \) at some sampling points \( t_i \) and want to find \( a \), \( \omega
+     * \) and \( \phi \) such that \( f(t) = a \cos (\omega t + \phi) \).
+     *
+     * <p>From the analytical expression, we can compute two primitives : \[ If2(t) = \int f^2 dt =
+     * a^2 (t + S(t)) / 2 \] \[ If'2(t) = \int f'^2 dt = a^2 \omega^2 (t - S(t)) / 2 \] where \(S(t)
+     * = \frac{\sin(2 (\omega t + \phi))}{2\omega}\)
+     *
+     * <p>We can remove \(S\) between these expressions : \[ If'2(t) = a^2 \omega^2 t - \omega^2
+     * If2(t) \]
+     *
+     * <p>The preceding expression shows that \(If'2 (t)\) is a linear combination of both \(t\) and
+     * \(If2(t)\): \[ If'2(t) = A t + B If2(t) \]
+     *
+     * <p>From the primitive, we can deduce the same form for definite integrals between \(t_1\) and
+     * \(t_i\) for each \(t_i\) : \[ If2(t_i) - If2(t_1) = A (t_i - t_1) + B (If2 (t_i) - If2(t_1))
+     * \]
+     *
+     * <p>We can find the coefficients \(A\) and \(B\) that best fit the sample to this linear
+     * expression by computing the definite integrals for each sample points.
+     *
+     * <p>For a bilinear expression \(z(x_i, y_i) = A x_i + B y_i\), the coefficients \(A\) and
+     * \(B\) that minimize a least-squares criterion \(\sum (z_i - z(x_i, y_i))^2\) are given by
+     * these expressions: \[ A = \frac{\sum y_i y_i \sum x_i z_i - \sum x_i y_i \sum y_i z_i} {\sum
+     * x_i x_i \sum y_i y_i - \sum x_i y_i \sum x_i y_i} \] \[ B = \frac{\sum x_i x_i \sum y_i z_i -
+     * \sum x_i y_i \sum x_i z_i} {\sum x_i x_i \sum y_i y_i - \sum x_i y_i \sum x_i y_i}
+     *
+     * <p>\]
+     *
+     * <p>In fact, we can assume that both \(a\) and \(\omega\) are positive and compute them
+     * directly, knowing that \(A = a^2 \omega^2\) and that \(B = -\omega^2\). The complete
+     * algorithm is therefore: For each \(t_i\) from \(t_1\) to \(t_{n-1}\), compute: \[ f(t_i) \]
+     * \[ f'(t_i) = \frac{f (t_{i+1}) - f(t_{i-1})}{t_{i+1} - t_{i-1}} \] \[ x_i = t_i - t_1 \] \[
+     * y_i = \int_{t_1}^{t_i} f^2(t) dt \] \[ z_i = \int_{t_1}^{t_i} f'^2(t) dt \] and update the
+     * sums: \[ \sum x_i x_i, \sum y_i y_i, \sum x_i y_i, \sum x_i z_i, \sum y_i z_i \]
+     *
+     * <p>Then: \[ a = \sqrt{\frac{\sum y_i y_i \sum x_i z_i - \sum x_i y_i \sum y_i z_i } {\sum x_i
+     * y_i \sum x_i z_i - \sum x_i x_i \sum y_i z_i }} \] \[ \omega = \sqrt{\frac{\sum x_i y_i \sum
+     * x_i z_i - \sum x_i x_i \sum y_i z_i} {\sum x_i x_i \sum y_i y_i - \sum x_i y_i \sum x_i y_i}}
+     * \]
+     *
+     * <p>Once we know \(\omega\) we can compute: \[ fc = \omega f(t) \cos(\omega t) - f'(t)
+     * \sin(\omega t) \] \[ fs = \omega f(t) \sin(\omega t) + f'(t) \cos(\omega t) \]
+     *
+     * <p>It appears that \(fc = a \omega \cos(\phi)\) and \(fs = -a \omega \sin(\phi)\), so we can
+     * use these expressions to compute \(\phi\). The best estimate over the sample is given by
+     * averaging these expressions.
+     *
+     * <p>Since integrals and means are involved in the preceding estimations, these operations run
+     * in \(O(n)\) time, where \(n\) is the number of measurements.
+     */
+    public static class ParameterGuesser {
+        /** Amplitude. */
+        private final double a;
+
+        /** Angular frequency. */
+        private final double omega;
+
+        /** Phase. */
+        private final double phi;
+
+        /**
+         * Simple constructor.
+         *
+         * @param observations Sampled observations.
+         * @throws NumberIsTooSmallException if the sample is too short.
+         * @throws ZeroException if the abscissa range is zero.
+         * @throws MathIllegalStateException when the guessing procedure cannot produce sensible
+         *     results.
+         */
+        public ParameterGuesser(Collection<WeightedObservedPoint> observations) {
+            if (observations.size() < 4) {
+                throw new NumberIsTooSmallException(
+                        LocalizedFormats.INSUFFICIENT_OBSERVED_POINTS_IN_SAMPLE,
+                        observations.size(),
+                        4,
+                        true);
+            }
+
+            final WeightedObservedPoint[] sorted =
+                    sortObservations(observations).toArray(new WeightedObservedPoint[0]);
+
+            final double aOmega[] = guessAOmega(sorted);
+            a = aOmega[0];
+            omega = aOmega[1];
+
+            phi = guessPhi(sorted);
+        }
+
+        /**
+         * Gets an estimation of the parameters.
+         *
+         * @return the guessed parameters, in the following order:
+         *     <ul>
+         *       <li>Amplitude
+         *       <li>Angular frequency
+         *       <li>Phase
+         *     </ul>
+         */
+        public double[] guess() {
+            return new double[] {a, omega, phi};
+        }
+
+        /**
+         * Sort the observations with respect to the abscissa.
+         *
+         * @param unsorted Input observations.
+         * @return the input observations, sorted.
+         */
+        private List<WeightedObservedPoint> sortObservations(
+                Collection<WeightedObservedPoint> unsorted) {
+            final List<WeightedObservedPoint> observations =
+                    new ArrayList<WeightedObservedPoint>(unsorted);
+
+            // Since the samples are almost always already sorted, this
+            // method is implemented as an insertion sort that reorders the
+            // elements in place. Insertion sort is very efficient in this case.
+            WeightedObservedPoint curr = observations.get(0);
+            final int len = observations.size();
+            for (int j = 1; j < len; j++) {
+                WeightedObservedPoint prec = curr;
+                curr = observations.get(j);
+                if (curr.getX() < prec.getX()) {
+                    // the current element should be inserted closer to the beginning
+                    int i = j - 1;
+                    WeightedObservedPoint mI = observations.get(i);
+                    while ((i >= 0) && (curr.getX() < mI.getX())) {
+                        observations.set(i + 1, mI);
+                        if (i-- != 0) {
+                            mI = observations.get(i);
+                        }
+                    }
+                    observations.set(i + 1, curr);
+                    curr = observations.get(j);
+                }
+            }
+
+            return observations;
+        }
+
+        /**
+         * Estimate a first guess of the amplitude and angular frequency.
+         *
+         * @param observations Observations, sorted w.r.t. abscissa.
+         * @throws ZeroException if the abscissa range is zero.
+         * @throws MathIllegalStateException when the guessing procedure cannot produce sensible
+         *     results.
+         * @return the guessed amplitude (at index 0) and circular frequency (at index 1).
+         */
+        private double[] guessAOmega(WeightedObservedPoint[] observations) {
+            final double[] aOmega = new double[2];
+
+            // initialize the sums for the linear model between the two integrals
+            double sx2 = 0;
+            double sy2 = 0;
+            double sxy = 0;
+            double sxz = 0;
+            double syz = 0;
+
+            double currentX = observations[0].getX();
+            double currentY = observations[0].getY();
+            double f2Integral = 0;
+            double fPrime2Integral = 0;
+            final double startX = currentX;
+            for (int i = 1; i < observations.length; ++i) {
+                // one step forward
+                final double previousX = currentX;
+                final double previousY = currentY;
+                currentX = observations[i].getX();
+                currentY = observations[i].getY();
+
+                // update the integrals of f<sup>2</sup> and f'<sup>2</sup>
+                // considering a linear model for f (and therefore constant f')
+                final double dx = currentX - previousX;
+                final double dy = currentY - previousY;
+                final double f2StepIntegral =
+                        dx
+                                * (previousY * previousY
+                                        + previousY * currentY
+                                        + currentY * currentY)
+                                / 3;
+                final double fPrime2StepIntegral = dy * dy / dx;
+
+                final double x = currentX - startX;
+                f2Integral += f2StepIntegral;
+                fPrime2Integral += fPrime2StepIntegral;
+
+                sx2 += x * x;
+                sy2 += f2Integral * f2Integral;
+                sxy += x * f2Integral;
+                sxz += x * fPrime2Integral;
+                syz += f2Integral * fPrime2Integral;
+            }
+
+            // compute the amplitude and pulsation coefficients
+            double c1 = sy2 * sxz - sxy * syz;
+            double c2 = sxy * sxz - sx2 * syz;
+            double c3 = sx2 * sy2 - sxy * sxy;
+            if ((c1 / c2 < 0) || (c2 / c3 < 0)) {
+                final int last = observations.length - 1;
+                // Range of the observations, assuming that the
+                // observations are sorted.
+                final double xRange = observations[last].getX() - observations[0].getX();
+                if (xRange == 0) {
+                    throw new ZeroException();
+                }
+                aOmega[1] = 2 * Math.PI / xRange;
+
+                double yMin = Double.POSITIVE_INFINITY;
+                double yMax = Double.NEGATIVE_INFINITY;
+                for (int i = 1; i < observations.length; ++i) {
+                    final double y = observations[i].getY();
+                    if (y < yMin) {
+                        yMin = y;
+                    }
+                    if (y > yMax) {
+                        yMax = y;
+                    }
+                }
+                aOmega[0] = 0.5 * (yMax - yMin);
+            } else {
+                if (c2 == 0) {
+                    // In some ill-conditioned cases (cf. MATH-844), the guesser
+                    // procedure cannot produce sensible results.
+                    throw new MathIllegalStateException(LocalizedFormats.ZERO_DENOMINATOR);
+                }
+
+                aOmega[0] = FastMath.sqrt(c1 / c2);
+                aOmega[1] = FastMath.sqrt(c2 / c3);
+            }
+
+            return aOmega;
+        }
+
+        /**
+         * Estimate a first guess of the phase.
+         *
+         * @param observations Observations, sorted w.r.t. abscissa.
+         * @return the guessed phase.
+         */
+        private double guessPhi(WeightedObservedPoint[] observations) {
+            // initialize the means
+            double fcMean = 0;
+            double fsMean = 0;
+
+            double currentX = observations[0].getX();
+            double currentY = observations[0].getY();
+            for (int i = 1; i < observations.length; ++i) {
+                // one step forward
+                final double previousX = currentX;
+                final double previousY = currentY;
+                currentX = observations[i].getX();
+                currentY = observations[i].getY();
+                final double currentYPrime = (currentY - previousY) / (currentX - previousX);
+
+                double omegaX = omega * currentX;
+                double cosine = FastMath.cos(omegaX);
+                double sine = FastMath.sin(omegaX);
+                fcMean += omega * currentY * cosine - currentYPrime * sine;
+                fsMean += omega * currentY * sine + currentYPrime * cosine;
+            }
+
+            return FastMath.atan2(-fsMean, fcMean);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/fitting/HarmonicFitter.java b/src/main/java/org/apache/commons/math3/fitting/HarmonicFitter.java
new file mode 100644
index 0000000..1a41398
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fitting/HarmonicFitter.java
@@ -0,0 +1,386 @@
+/*
+ * 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.math3.fitting;
+
+import org.apache.commons.math3.analysis.function.HarmonicOscillator;
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.ZeroException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.optim.nonlinear.vector.MultivariateVectorOptimizer;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Class that implements a curve fitting specialized for sinusoids.
+ *
+ * <p>Harmonic fitting is a very simple case of curve fitting. The estimated coefficients are the
+ * amplitude a, the pulsation &omega; and the phase &phi;: <code>f (t) = a cos (&omega; t + &phi;)
+ * </code>. They are searched by a least square estimator initialized with a rough guess based on
+ * integrals.
+ *
+ * @since 2.0
+ * @deprecated As of 3.3. Please use {@link HarmonicCurveFitter} and {@link WeightedObservedPoints}
+ *     instead.
+ */
+@Deprecated
+public class HarmonicFitter extends CurveFitter<HarmonicOscillator.Parametric> {
+    /**
+     * Simple constructor.
+     *
+     * @param optimizer Optimizer to use for the fitting.
+     */
+    public HarmonicFitter(final MultivariateVectorOptimizer optimizer) {
+        super(optimizer);
+    }
+
+    /**
+     * Fit an harmonic function to the observed points.
+     *
+     * @param initialGuess First guess values in the following order:
+     *     <ul>
+     *       <li>Amplitude
+     *       <li>Angular frequency
+     *       <li>Phase
+     *     </ul>
+     *
+     * @return the parameters of the harmonic function that best fits the observed points (in the
+     *     same order as above).
+     */
+    public double[] fit(double[] initialGuess) {
+        return fit(new HarmonicOscillator.Parametric(), initialGuess);
+    }
+
+    /**
+     * Fit an harmonic function to the observed points. An initial guess will be automatically
+     * computed.
+     *
+     * @return the parameters of the harmonic function that best fits the observed points (see the
+     *     other {@link #fit(double[]) fit} method.
+     * @throws NumberIsTooSmallException if the sample is too short for the the first guess to be
+     *     computed.
+     * @throws ZeroException if the first guess cannot be computed because the abscissa range is
+     *     zero.
+     */
+    public double[] fit() {
+        return fit((new ParameterGuesser(getObservations())).guess());
+    }
+
+    /**
+     * This class guesses harmonic coefficients from a sample.
+     *
+     * <p>The algorithm used to guess the coefficients is as follows:
+     *
+     * <p>We know f (t) at some sampling points t<sub>i</sub> and want to find a, &omega; and &phi;
+     * such that f (t) = a cos (&omega; t + &phi;).
+     *
+     * <p>From the analytical expression, we can compute two primitives :
+     *
+     * <pre>
+     *     If2  (t) = &int; f<sup>2</sup>  = a<sup>2</sup> &times; [t + S (t)] / 2
+     *     If'2 (t) = &int; f'<sup>2</sup> = a<sup>2</sup> &omega;<sup>2</sup> &times; [t - S (t)] / 2
+     *     where S (t) = sin (2 (&omega; t + &phi;)) / (2 &omega;)
+     * </pre>
+     *
+     * <p>We can remove S between these expressions :
+     *
+     * <pre>
+     *     If'2 (t) = a<sup>2</sup> &omega;<sup>2</sup> t - &omega;<sup>2</sup> If2 (t)
+     * </pre>
+     *
+     * <p>The preceding expression shows that If'2 (t) is a linear combination of both t and If2
+     * (t): If'2 (t) = A &times; t + B &times; If2 (t)
+     *
+     * <p>From the primitive, we can deduce the same form for definite integrals between
+     * t<sub>1</sub> and t<sub>i</sub> for each t<sub>i</sub> :
+     *
+     * <pre>
+     *   If2 (t<sub>i</sub>) - If2 (t<sub>1</sub>) = A &times; (t<sub>i</sub> - t<sub>1</sub>) + B &times; (If2 (t<sub>i</sub>) - If2 (t<sub>1</sub>))
+     * </pre>
+     *
+     * <p>We can find the coefficients A and B that best fit the sample to this linear expression by
+     * computing the definite integrals for each sample points.
+     *
+     * <p>For a bilinear expression z (x<sub>i</sub>, y<sub>i</sub>) = A &times; x<sub>i</sub> + B
+     * &times; y<sub>i</sub>, the coefficients A and B that minimize a least square criterion &sum;
+     * (z<sub>i</sub> - z (x<sub>i</sub>, y<sub>i</sub>))<sup>2</sup> are given by these
+     * expressions:
+     *
+     * <pre>
+     *
+     *         &sum;y<sub>i</sub>y<sub>i</sub> &sum;x<sub>i</sub>z<sub>i</sub> - &sum;x<sub>i</sub>y<sub>i</sub> &sum;y<sub>i</sub>z<sub>i</sub>
+     *     A = ------------------------
+     *         &sum;x<sub>i</sub>x<sub>i</sub> &sum;y<sub>i</sub>y<sub>i</sub> - &sum;x<sub>i</sub>y<sub>i</sub> &sum;x<sub>i</sub>y<sub>i</sub>
+     *
+     *         &sum;x<sub>i</sub>x<sub>i</sub> &sum;y<sub>i</sub>z<sub>i</sub> - &sum;x<sub>i</sub>y<sub>i</sub> &sum;x<sub>i</sub>z<sub>i</sub>
+     *     B = ------------------------
+     *         &sum;x<sub>i</sub>x<sub>i</sub> &sum;y<sub>i</sub>y<sub>i</sub> - &sum;x<sub>i</sub>y<sub>i</sub> &sum;x<sub>i</sub>y<sub>i</sub>
+     * </pre>
+     *
+     * <p>In fact, we can assume both a and &omega; are positive and compute them directly, knowing
+     * that A = a<sup>2</sup> &omega;<sup>2</sup> and that B = - &omega;<sup>2</sup>. The complete
+     * algorithm is therefore:
+     *
+     * <pre>
+     *
+     * for each t<sub>i</sub> from t<sub>1</sub> to t<sub>n-1</sub>, compute:
+     *   f  (t<sub>i</sub>)
+     *   f' (t<sub>i</sub>) = (f (t<sub>i+1</sub>) - f(t<sub>i-1</sub>)) / (t<sub>i+1</sub> - t<sub>i-1</sub>)
+     *   x<sub>i</sub> = t<sub>i</sub> - t<sub>1</sub>
+     *   y<sub>i</sub> = &int; f<sup>2</sup> from t<sub>1</sub> to t<sub>i</sub>
+     *   z<sub>i</sub> = &int; f'<sup>2</sup> from t<sub>1</sub> to t<sub>i</sub>
+     *   update the sums &sum;x<sub>i</sub>x<sub>i</sub>, &sum;y<sub>i</sub>y<sub>i</sub>, &sum;x<sub>i</sub>y<sub>i</sub>, &sum;x<sub>i</sub>z<sub>i</sub> and &sum;y<sub>i</sub>z<sub>i</sub>
+     * end for
+     *
+     *            |--------------------------
+     *         \  | &sum;y<sub>i</sub>y<sub>i</sub> &sum;x<sub>i</sub>z<sub>i</sub> - &sum;x<sub>i</sub>y<sub>i</sub> &sum;y<sub>i</sub>z<sub>i</sub>
+     * a     =  \ | ------------------------
+     *           \| &sum;x<sub>i</sub>y<sub>i</sub> &sum;x<sub>i</sub>z<sub>i</sub> - &sum;x<sub>i</sub>x<sub>i</sub> &sum;y<sub>i</sub>z<sub>i</sub>
+     *
+     *
+     *            |--------------------------
+     *         \  | &sum;x<sub>i</sub>y<sub>i</sub> &sum;x<sub>i</sub>z<sub>i</sub> - &sum;x<sub>i</sub>x<sub>i</sub> &sum;y<sub>i</sub>z<sub>i</sub>
+     * &omega;     =  \ | ------------------------
+     *           \| &sum;x<sub>i</sub>x<sub>i</sub> &sum;y<sub>i</sub>y<sub>i</sub> - &sum;x<sub>i</sub>y<sub>i</sub> &sum;x<sub>i</sub>y<sub>i</sub>
+     *
+     * </pre>
+     *
+     * <p>Once we know &omega;, we can compute:
+     *
+     * <pre>
+     *    fc = &omega; f (t) cos (&omega; t) - f' (t) sin (&omega; t)
+     *    fs = &omega; f (t) sin (&omega; t) + f' (t) cos (&omega; t)
+     * </pre>
+     *
+     * <p>It appears that <code>fc = a &omega; cos (&phi;)</code> and <code>
+     * fs = -a &omega; sin (&phi;)</code>, so we can use these expressions to compute &phi;. The
+     * best estimate over the sample is given by averaging these expressions.
+     *
+     * <p>Since integrals and means are involved in the preceding estimations, these operations run
+     * in O(n) time, where n is the number of measurements.
+     */
+    public static class ParameterGuesser {
+        /** Amplitude. */
+        private final double a;
+
+        /** Angular frequency. */
+        private final double omega;
+
+        /** Phase. */
+        private final double phi;
+
+        /**
+         * Simple constructor.
+         *
+         * @param observations Sampled observations.
+         * @throws NumberIsTooSmallException if the sample is too short.
+         * @throws ZeroException if the abscissa range is zero.
+         * @throws MathIllegalStateException when the guessing procedure cannot produce sensible
+         *     results.
+         */
+        public ParameterGuesser(WeightedObservedPoint[] observations) {
+            if (observations.length < 4) {
+                throw new NumberIsTooSmallException(
+                        LocalizedFormats.INSUFFICIENT_OBSERVED_POINTS_IN_SAMPLE,
+                        observations.length,
+                        4,
+                        true);
+            }
+
+            final WeightedObservedPoint[] sorted = sortObservations(observations);
+
+            final double aOmega[] = guessAOmega(sorted);
+            a = aOmega[0];
+            omega = aOmega[1];
+
+            phi = guessPhi(sorted);
+        }
+
+        /**
+         * Gets an estimation of the parameters.
+         *
+         * @return the guessed parameters, in the following order:
+         *     <ul>
+         *       <li>Amplitude
+         *       <li>Angular frequency
+         *       <li>Phase
+         *     </ul>
+         */
+        public double[] guess() {
+            return new double[] {a, omega, phi};
+        }
+
+        /**
+         * Sort the observations with respect to the abscissa.
+         *
+         * @param unsorted Input observations.
+         * @return the input observations, sorted.
+         */
+        private WeightedObservedPoint[] sortObservations(WeightedObservedPoint[] unsorted) {
+            final WeightedObservedPoint[] observations = unsorted.clone();
+
+            // Since the samples are almost always already sorted, this
+            // method is implemented as an insertion sort that reorders the
+            // elements in place. Insertion sort is very efficient in this case.
+            WeightedObservedPoint curr = observations[0];
+            for (int j = 1; j < observations.length; ++j) {
+                WeightedObservedPoint prec = curr;
+                curr = observations[j];
+                if (curr.getX() < prec.getX()) {
+                    // the current element should be inserted closer to the beginning
+                    int i = j - 1;
+                    WeightedObservedPoint mI = observations[i];
+                    while ((i >= 0) && (curr.getX() < mI.getX())) {
+                        observations[i + 1] = mI;
+                        if (i-- != 0) {
+                            mI = observations[i];
+                        }
+                    }
+                    observations[i + 1] = curr;
+                    curr = observations[j];
+                }
+            }
+
+            return observations;
+        }
+
+        /**
+         * Estimate a first guess of the amplitude and angular frequency. This method assumes that
+         * the {@link #sortObservations(WeightedObservedPoint[])} method has been called previously.
+         *
+         * @param observations Observations, sorted w.r.t. abscissa.
+         * @throws ZeroException if the abscissa range is zero.
+         * @throws MathIllegalStateException when the guessing procedure cannot produce sensible
+         *     results.
+         * @return the guessed amplitude (at index 0) and circular frequency (at index 1).
+         */
+        private double[] guessAOmega(WeightedObservedPoint[] observations) {
+            final double[] aOmega = new double[2];
+
+            // initialize the sums for the linear model between the two integrals
+            double sx2 = 0;
+            double sy2 = 0;
+            double sxy = 0;
+            double sxz = 0;
+            double syz = 0;
+
+            double currentX = observations[0].getX();
+            double currentY = observations[0].getY();
+            double f2Integral = 0;
+            double fPrime2Integral = 0;
+            final double startX = currentX;
+            for (int i = 1; i < observations.length; ++i) {
+                // one step forward
+                final double previousX = currentX;
+                final double previousY = currentY;
+                currentX = observations[i].getX();
+                currentY = observations[i].getY();
+
+                // update the integrals of f<sup>2</sup> and f'<sup>2</sup>
+                // considering a linear model for f (and therefore constant f')
+                final double dx = currentX - previousX;
+                final double dy = currentY - previousY;
+                final double f2StepIntegral =
+                        dx
+                                * (previousY * previousY
+                                        + previousY * currentY
+                                        + currentY * currentY)
+                                / 3;
+                final double fPrime2StepIntegral = dy * dy / dx;
+
+                final double x = currentX - startX;
+                f2Integral += f2StepIntegral;
+                fPrime2Integral += fPrime2StepIntegral;
+
+                sx2 += x * x;
+                sy2 += f2Integral * f2Integral;
+                sxy += x * f2Integral;
+                sxz += x * fPrime2Integral;
+                syz += f2Integral * fPrime2Integral;
+            }
+
+            // compute the amplitude and pulsation coefficients
+            double c1 = sy2 * sxz - sxy * syz;
+            double c2 = sxy * sxz - sx2 * syz;
+            double c3 = sx2 * sy2 - sxy * sxy;
+            if ((c1 / c2 < 0) || (c2 / c3 < 0)) {
+                final int last = observations.length - 1;
+                // Range of the observations, assuming that the
+                // observations are sorted.
+                final double xRange = observations[last].getX() - observations[0].getX();
+                if (xRange == 0) {
+                    throw new ZeroException();
+                }
+                aOmega[1] = 2 * Math.PI / xRange;
+
+                double yMin = Double.POSITIVE_INFINITY;
+                double yMax = Double.NEGATIVE_INFINITY;
+                for (int i = 1; i < observations.length; ++i) {
+                    final double y = observations[i].getY();
+                    if (y < yMin) {
+                        yMin = y;
+                    }
+                    if (y > yMax) {
+                        yMax = y;
+                    }
+                }
+                aOmega[0] = 0.5 * (yMax - yMin);
+            } else {
+                if (c2 == 0) {
+                    // In some ill-conditioned cases (cf. MATH-844), the guesser
+                    // procedure cannot produce sensible results.
+                    throw new MathIllegalStateException(LocalizedFormats.ZERO_DENOMINATOR);
+                }
+
+                aOmega[0] = FastMath.sqrt(c1 / c2);
+                aOmega[1] = FastMath.sqrt(c2 / c3);
+            }
+
+            return aOmega;
+        }
+
+        /**
+         * Estimate a first guess of the phase.
+         *
+         * @param observations Observations, sorted w.r.t. abscissa.
+         * @return the guessed phase.
+         */
+        private double guessPhi(WeightedObservedPoint[] observations) {
+            // initialize the means
+            double fcMean = 0;
+            double fsMean = 0;
+
+            double currentX = observations[0].getX();
+            double currentY = observations[0].getY();
+            for (int i = 1; i < observations.length; ++i) {
+                // one step forward
+                final double previousX = currentX;
+                final double previousY = currentY;
+                currentX = observations[i].getX();
+                currentY = observations[i].getY();
+                final double currentYPrime = (currentY - previousY) / (currentX - previousX);
+
+                double omegaX = omega * currentX;
+                double cosine = FastMath.cos(omegaX);
+                double sine = FastMath.sin(omegaX);
+                fcMean += omega * currentY * cosine - currentYPrime * sine;
+                fsMean += omega * currentY * sine + currentYPrime * cosine;
+            }
+
+            return FastMath.atan2(-fsMean, fcMean);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/fitting/PolynomialCurveFitter.java b/src/main/java/org/apache/commons/math3/fitting/PolynomialCurveFitter.java
new file mode 100644
index 0000000..ab2b5ca
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fitting/PolynomialCurveFitter.java
@@ -0,0 +1,127 @@
+/*
+ * 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.math3.fitting;
+
+import org.apache.commons.math3.analysis.polynomials.PolynomialFunction;
+import org.apache.commons.math3.exception.MathInternalError;
+import org.apache.commons.math3.fitting.leastsquares.LeastSquaresBuilder;
+import org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem;
+import org.apache.commons.math3.linear.DiagonalMatrix;
+
+import java.util.Collection;
+
+/**
+ * Fits points to a {@link
+ * org.apache.commons.math3.analysis.polynomials.PolynomialFunction.Parametric polynomial} function.
+ * <br>
+ * The size of the {@link #withStartPoint(double[]) initial guess} array defines the degree of the
+ * polynomial to be fitted. They must be sorted in increasing order of the polynomial's degree. The
+ * optimal values of the coefficients will be returned in the same order.
+ *
+ * @since 3.3
+ */
+public class PolynomialCurveFitter extends AbstractCurveFitter {
+    /** Parametric function to be fitted. */
+    private static final PolynomialFunction.Parametric FUNCTION =
+            new PolynomialFunction.Parametric();
+
+    /** Initial guess. */
+    private final double[] initialGuess;
+
+    /** Maximum number of iterations of the optimization algorithm. */
+    private final int maxIter;
+
+    /**
+     * Contructor used by the factory methods.
+     *
+     * @param initialGuess Initial guess.
+     * @param maxIter Maximum number of iterations of the optimization algorithm.
+     * @throws MathInternalError if {@code initialGuess} is {@code null}.
+     */
+    private PolynomialCurveFitter(double[] initialGuess, int maxIter) {
+        this.initialGuess = initialGuess;
+        this.maxIter = maxIter;
+    }
+
+    /**
+     * Creates a default curve fitter. Zero will be used as initial guess for the coefficients, and
+     * the maximum number of iterations of the optimization algorithm is set to {@link
+     * Integer#MAX_VALUE}.
+     *
+     * @param degree Degree of the polynomial to be fitted.
+     * @return a curve fitter.
+     * @see #withStartPoint(double[])
+     * @see #withMaxIterations(int)
+     */
+    public static PolynomialCurveFitter create(int degree) {
+        return new PolynomialCurveFitter(new double[degree + 1], Integer.MAX_VALUE);
+    }
+
+    /**
+     * Configure the start point (initial guess).
+     *
+     * @param newStart new start point (initial guess)
+     * @return a new instance.
+     */
+    public PolynomialCurveFitter withStartPoint(double[] newStart) {
+        return new PolynomialCurveFitter(newStart.clone(), maxIter);
+    }
+
+    /**
+     * Configure the maximum number of iterations.
+     *
+     * @param newMaxIter maximum number of iterations
+     * @return a new instance.
+     */
+    public PolynomialCurveFitter withMaxIterations(int newMaxIter) {
+        return new PolynomialCurveFitter(initialGuess, newMaxIter);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected LeastSquaresProblem getProblem(Collection<WeightedObservedPoint> observations) {
+        // Prepare least-squares problem.
+        final int len = observations.size();
+        final double[] target = new double[len];
+        final double[] weights = new double[len];
+
+        int i = 0;
+        for (WeightedObservedPoint obs : observations) {
+            target[i] = obs.getY();
+            weights[i] = obs.getWeight();
+            ++i;
+        }
+
+        final AbstractCurveFitter.TheoreticalValuesFunction model =
+                new AbstractCurveFitter.TheoreticalValuesFunction(FUNCTION, observations);
+
+        if (initialGuess == null) {
+            throw new MathInternalError();
+        }
+
+        // Return a new least squares problem set up to fit a polynomial curve to the
+        // observed points.
+        return new LeastSquaresBuilder()
+                .maxEvaluations(Integer.MAX_VALUE)
+                .maxIterations(maxIter)
+                .start(initialGuess)
+                .target(target)
+                .weight(new DiagonalMatrix(weights))
+                .model(model.getModelFunction(), model.getModelFunctionJacobian())
+                .build();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/fitting/PolynomialFitter.java b/src/main/java/org/apache/commons/math3/fitting/PolynomialFitter.java
new file mode 100644
index 0000000..0dd17a4
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fitting/PolynomialFitter.java
@@ -0,0 +1,71 @@
+/*
+ * 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.math3.fitting;
+
+import org.apache.commons.math3.analysis.polynomials.PolynomialFunction;
+import org.apache.commons.math3.optim.nonlinear.vector.MultivariateVectorOptimizer;
+
+/**
+ * Polynomial fitting is a very simple case of {@link CurveFitter curve fitting}. The estimated
+ * coefficients are the polynomial coefficients (see the {@link #fit(double[]) fit} method).
+ *
+ * @since 2.0
+ * @deprecated As of 3.3. Please use {@link PolynomialCurveFitter} and {@link
+ *     WeightedObservedPoints} instead.
+ */
+@Deprecated
+public class PolynomialFitter extends CurveFitter<PolynomialFunction.Parametric> {
+    /**
+     * Simple constructor.
+     *
+     * @param optimizer Optimizer to use for the fitting.
+     */
+    public PolynomialFitter(MultivariateVectorOptimizer optimizer) {
+        super(optimizer);
+    }
+
+    /**
+     * Get the coefficients of the polynomial fitting the weighted data points. The degree of the
+     * fitting polynomial is {@code guess.length - 1}.
+     *
+     * @param guess First guess for the coefficients. They must be sorted in increasing order of the
+     *     polynomial's degree.
+     * @param maxEval Maximum number of evaluations of the polynomial.
+     * @return the coefficients of the polynomial that best fits the observed points.
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException if the number of
+     *     evaluations exceeds {@code maxEval}.
+     * @throws org.apache.commons.math3.exception.ConvergenceException if the algorithm failed to
+     *     converge.
+     */
+    public double[] fit(int maxEval, double[] guess) {
+        return fit(maxEval, new PolynomialFunction.Parametric(), guess);
+    }
+
+    /**
+     * Get the coefficients of the polynomial fitting the weighted data points. The degree of the
+     * fitting polynomial is {@code guess.length - 1}.
+     *
+     * @param guess First guess for the coefficients. They must be sorted in increasing order of the
+     *     polynomial's degree.
+     * @return the coefficients of the polynomial that best fits the observed points.
+     * @throws org.apache.commons.math3.exception.ConvergenceException if the algorithm failed to
+     *     converge.
+     */
+    public double[] fit(double[] guess) {
+        return fit(new PolynomialFunction.Parametric(), guess);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/fitting/SimpleCurveFitter.java b/src/main/java/org/apache/commons/math3/fitting/SimpleCurveFitter.java
new file mode 100644
index 0000000..304f661
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fitting/SimpleCurveFitter.java
@@ -0,0 +1,119 @@
+/*
+ * 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.math3.fitting;
+
+import org.apache.commons.math3.analysis.ParametricUnivariateFunction;
+import org.apache.commons.math3.fitting.leastsquares.LeastSquaresBuilder;
+import org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem;
+import org.apache.commons.math3.linear.DiagonalMatrix;
+
+import java.util.Collection;
+
+/**
+ * Fits points to a user-defined {@link ParametricUnivariateFunction function}.
+ *
+ * @since 3.4
+ */
+public class SimpleCurveFitter extends AbstractCurveFitter {
+    /** Function to fit. */
+    private final ParametricUnivariateFunction function;
+
+    /** Initial guess for the parameters. */
+    private final double[] initialGuess;
+
+    /** Maximum number of iterations of the optimization algorithm. */
+    private final int maxIter;
+
+    /**
+     * Contructor used by the factory methods.
+     *
+     * @param function Function to fit.
+     * @param initialGuess Initial guess. Cannot be {@code null}. Its length must be consistent with
+     *     the number of parameters of the {@code function} to fit.
+     * @param maxIter Maximum number of iterations of the optimization algorithm.
+     */
+    private SimpleCurveFitter(
+            ParametricUnivariateFunction function, double[] initialGuess, int maxIter) {
+        this.function = function;
+        this.initialGuess = initialGuess;
+        this.maxIter = maxIter;
+    }
+
+    /**
+     * Creates a curve fitter. The maximum number of iterations of the optimization algorithm is set
+     * to {@link Integer#MAX_VALUE}.
+     *
+     * @param f Function to fit.
+     * @param start Initial guess for the parameters. Cannot be {@code null}. Its length must be
+     *     consistent with the number of parameters of the function to fit.
+     * @return a curve fitter.
+     * @see #withStartPoint(double[])
+     * @see #withMaxIterations(int)
+     */
+    public static SimpleCurveFitter create(ParametricUnivariateFunction f, double[] start) {
+        return new SimpleCurveFitter(f, start, Integer.MAX_VALUE);
+    }
+
+    /**
+     * Configure the start point (initial guess).
+     *
+     * @param newStart new start point (initial guess)
+     * @return a new instance.
+     */
+    public SimpleCurveFitter withStartPoint(double[] newStart) {
+        return new SimpleCurveFitter(function, newStart.clone(), maxIter);
+    }
+
+    /**
+     * Configure the maximum number of iterations.
+     *
+     * @param newMaxIter maximum number of iterations
+     * @return a new instance.
+     */
+    public SimpleCurveFitter withMaxIterations(int newMaxIter) {
+        return new SimpleCurveFitter(function, initialGuess, newMaxIter);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected LeastSquaresProblem getProblem(Collection<WeightedObservedPoint> observations) {
+        // Prepare least-squares problem.
+        final int len = observations.size();
+        final double[] target = new double[len];
+        final double[] weights = new double[len];
+
+        int count = 0;
+        for (WeightedObservedPoint obs : observations) {
+            target[count] = obs.getY();
+            weights[count] = obs.getWeight();
+            ++count;
+        }
+
+        final AbstractCurveFitter.TheoreticalValuesFunction model =
+                new AbstractCurveFitter.TheoreticalValuesFunction(function, observations);
+
+        // Create an optimizer for fitting the curve to the observed points.
+        return new LeastSquaresBuilder()
+                .maxEvaluations(Integer.MAX_VALUE)
+                .maxIterations(maxIter)
+                .start(initialGuess)
+                .target(target)
+                .weight(new DiagonalMatrix(weights))
+                .model(model.getModelFunction(), model.getModelFunctionJacobian())
+                .build();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/fitting/WeightedObservedPoint.java b/src/main/java/org/apache/commons/math3/fitting/WeightedObservedPoint.java
new file mode 100644
index 0000000..ec88747
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fitting/WeightedObservedPoint.java
@@ -0,0 +1,81 @@
+/*
+ * 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.math3.fitting;
+
+import java.io.Serializable;
+
+/**
+ * This class is a simple container for weighted observed point in {@link CurveFitter curve
+ * fitting}.
+ *
+ * <p>Instances of this class are guaranteed to be immutable.
+ *
+ * @since 2.0
+ */
+public class WeightedObservedPoint implements Serializable {
+    /** Serializable version id. */
+    private static final long serialVersionUID = 5306874947404636157L;
+
+    /** Weight of the measurement in the fitting process. */
+    private final double weight;
+
+    /** Abscissa of the point. */
+    private final double x;
+
+    /** Observed value of the function at x. */
+    private final double y;
+
+    /**
+     * Simple constructor.
+     *
+     * @param weight Weight of the measurement in the fitting process.
+     * @param x Abscissa of the measurement.
+     * @param y Ordinate of the measurement.
+     */
+    public WeightedObservedPoint(final double weight, final double x, final double y) {
+        this.weight = weight;
+        this.x = x;
+        this.y = y;
+    }
+
+    /**
+     * Gets the weight of the measurement in the fitting process.
+     *
+     * @return the weight of the measurement in the fitting process.
+     */
+    public double getWeight() {
+        return weight;
+    }
+
+    /**
+     * Gets the abscissa of the point.
+     *
+     * @return the abscissa of the point.
+     */
+    public double getX() {
+        return x;
+    }
+
+    /**
+     * Gets the observed value of the function at x.
+     *
+     * @return the observed value of the function at x.
+     */
+    public double getY() {
+        return y;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/fitting/WeightedObservedPoints.java b/src/main/java/org/apache/commons/math3/fitting/WeightedObservedPoints.java
new file mode 100644
index 0000000..69deaae
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fitting/WeightedObservedPoints.java
@@ -0,0 +1,100 @@
+/*
+ * 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.math3.fitting;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Simple container for weighted observed points used in {@link AbstractCurveFitter curve fitting}
+ * algorithms.
+ *
+ * @since 3.3
+ */
+public class WeightedObservedPoints implements Serializable {
+    /** Serializable version id. */
+    private static final long serialVersionUID = 20130813L;
+
+    /** Observed points. */
+    private final List<WeightedObservedPoint> observations = new ArrayList<WeightedObservedPoint>();
+
+    /**
+     * Adds a point to the sample. Calling this method is equivalent to calling {@code add(1.0, x,
+     * y)}.
+     *
+     * @param x Abscissa of the point.
+     * @param y Observed value at {@code x}. After fitting we should have {@code f(x)} as close as
+     *     possible to this value.
+     * @see #add(double, double, double)
+     * @see #add(WeightedObservedPoint)
+     * @see #toList()
+     */
+    public void add(double x, double y) {
+        add(1d, x, y);
+    }
+
+    /**
+     * Adds a point to the sample.
+     *
+     * @param weight Weight of the observed point.
+     * @param x Abscissa of the point.
+     * @param y Observed value at {@code x}. After fitting we should have {@code f(x)} as close as
+     *     possible to this value.
+     * @see #add(double, double)
+     * @see #add(WeightedObservedPoint)
+     * @see #toList()
+     */
+    public void add(double weight, double x, double y) {
+        observations.add(new WeightedObservedPoint(weight, x, y));
+    }
+
+    /**
+     * Adds a point to the sample.
+     *
+     * @param observed Observed point to add.
+     * @see #add(double, double)
+     * @see #add(double, double, double)
+     * @see #toList()
+     */
+    public void add(WeightedObservedPoint observed) {
+        observations.add(observed);
+    }
+
+    /**
+     * Gets a <em>snapshot</em> of the observed points. The list of stored points is copied in order
+     * to ensure that modification of the returned instance does not affect this container.
+     * Conversely, further modification of this container (through the {@code add} or {@code clear}
+     * methods) will not affect the returned list.
+     *
+     * @return the observed points, in the order they were added to this container.
+     * @see #add(double, double)
+     * @see #add(double, double, double)
+     * @see #add(WeightedObservedPoint)
+     */
+    public List<WeightedObservedPoint> toList() {
+        // The copy is necessary to ensure thread-safety because of the
+        // "clear" method (which otherwise would be able to empty the
+        // list of points while it is being used by another thread).
+        return new ArrayList<WeightedObservedPoint>(observations);
+    }
+
+    /** Removes all observations from this container. */
+    public void clear() {
+        observations.clear();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/fitting/leastsquares/AbstractEvaluation.java b/src/main/java/org/apache/commons/math3/fitting/leastsquares/AbstractEvaluation.java
new file mode 100644
index 0000000..b164380
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fitting/leastsquares/AbstractEvaluation.java
@@ -0,0 +1,87 @@
+/*
+ * 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.math3.fitting.leastsquares;
+
+import org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem.Evaluation;
+import org.apache.commons.math3.linear.ArrayRealVector;
+import org.apache.commons.math3.linear.DecompositionSolver;
+import org.apache.commons.math3.linear.QRDecomposition;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.linear.RealVector;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * An implementation of {@link Evaluation} that is designed for extension. All of the
+ * methods implemented here use the methods that are left unimplemented.
+ * <p/>
+ * TODO cache results?
+ *
+ * @since 3.3
+ */
+public abstract class AbstractEvaluation implements Evaluation {
+
+    /** number of observations */
+    private final int observationSize;
+
+    /**
+     * Constructor.
+     *
+     * @param observationSize the number of observation. Needed for {@link
+     *                        #getRMS()}.
+     */
+    AbstractEvaluation(final int observationSize) {
+        this.observationSize = observationSize;
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix getCovariances(double threshold) {
+        // Set up the Jacobian.
+        final RealMatrix j = this.getJacobian();
+
+        // Compute transpose(J)J.
+        final RealMatrix jTj = j.transpose().multiply(j);
+
+        // Compute the covariances matrix.
+        final DecompositionSolver solver
+                = new QRDecomposition(jTj, threshold).getSolver();
+        return solver.getInverse();
+    }
+
+    /** {@inheritDoc} */
+    public RealVector getSigma(double covarianceSingularityThreshold) {
+        final RealMatrix cov = this.getCovariances(covarianceSingularityThreshold);
+        final int nC = cov.getColumnDimension();
+        final RealVector sig = new ArrayRealVector(nC);
+        for (int i = 0; i < nC; ++i) {
+            sig.setEntry(i, FastMath.sqrt(cov.getEntry(i,i)));
+        }
+        return sig;
+    }
+
+    /** {@inheritDoc} */
+    public double getRMS() {
+        final double cost = this.getCost();
+        return FastMath.sqrt(cost * cost / this.observationSize);
+    }
+
+    /** {@inheritDoc} */
+    public double getCost() {
+        final ArrayRealVector r = new ArrayRealVector(this.getResiduals());
+        return FastMath.sqrt(r.dotProduct(r));
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/fitting/leastsquares/DenseWeightedEvaluation.java b/src/main/java/org/apache/commons/math3/fitting/leastsquares/DenseWeightedEvaluation.java
new file mode 100644
index 0000000..89f5f1f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fitting/leastsquares/DenseWeightedEvaluation.java
@@ -0,0 +1,68 @@
+/*
+ * 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.math3.fitting.leastsquares;
+
+import org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem.Evaluation;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.linear.RealVector;
+
+/**
+ * Applies a dense weight matrix to an evaluation.
+ *
+ * @since 3.3
+ */
+class DenseWeightedEvaluation extends AbstractEvaluation {
+
+    /** the unweighted evaluation */
+    private final Evaluation unweighted;
+    /** reference to the weight square root matrix */
+    private final RealMatrix weightSqrt;
+
+    /**
+     * Create a weighted evaluation from an unweighted one.
+     *
+     * @param unweighted the evalutation before weights are applied
+     * @param weightSqrt the matrix square root of the weight matrix
+     */
+    DenseWeightedEvaluation(final Evaluation unweighted,
+                            final RealMatrix weightSqrt) {
+        // weight square root is square, nR=nC=number of observations
+        super(weightSqrt.getColumnDimension());
+        this.unweighted = unweighted;
+        this.weightSqrt = weightSqrt;
+    }
+
+    /* apply weights */
+
+    /** {@inheritDoc} */
+    public RealMatrix getJacobian() {
+        return weightSqrt.multiply(this.unweighted.getJacobian());
+    }
+
+    /** {@inheritDoc} */
+    public RealVector getResiduals() {
+        return this.weightSqrt.operate(this.unweighted.getResiduals());
+    }
+
+    /* delegate */
+
+    /** {@inheritDoc} */
+    public RealVector getPoint() {
+        return unweighted.getPoint();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/fitting/leastsquares/EvaluationRmsChecker.java b/src/main/java/org/apache/commons/math3/fitting/leastsquares/EvaluationRmsChecker.java
new file mode 100644
index 0000000..ceb5988
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fitting/leastsquares/EvaluationRmsChecker.java
@@ -0,0 +1,75 @@
+/*
+ * 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.math3.fitting.leastsquares;
+
+import org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem.Evaluation;
+import org.apache.commons.math3.optim.ConvergenceChecker;
+import org.apache.commons.math3.util.Precision;
+
+/**
+ * Check if an optimization has converged based on the change in computed RMS.
+ *
+ * @since 3.4
+ */
+public class EvaluationRmsChecker implements ConvergenceChecker<Evaluation> {
+
+    /** relative tolerance for comparisons. */
+    private final double relTol;
+    /** absolute tolerance for comparisons. */
+    private final double absTol;
+
+    /**
+     * Create a convergence checker for the RMS with the same relative and absolute
+     * tolerance.
+     *
+     * <p>Convenience constructor for when the relative and absolute tolerances are the
+     * same. Same as {@code new EvaluationRmsChecker(tol, tol)}.
+     *
+     * @param tol the relative and absolute tolerance.
+     * @see #EvaluationRmsChecker(double, double)
+     */
+    public EvaluationRmsChecker(final double tol) {
+        this(tol, tol);
+    }
+
+    /**
+     * Create a convergence checker for the RMS with a relative and absolute tolerance.
+     *
+     * <p>The optimization has converged when the RMS of consecutive evaluations are equal
+     * to within the given relative tolerance or absolute tolerance.
+     *
+     * @param relTol the relative tolerance.
+     * @param absTol the absolute tolerance.
+     * @see Precision#equals(double, double, double)
+     * @see Precision#equalsWithRelativeTolerance(double, double, double)
+     */
+    public EvaluationRmsChecker(final double relTol, final double absTol) {
+        this.relTol = relTol;
+        this.absTol = absTol;
+    }
+
+    /** {@inheritDoc} */
+    public boolean converged(final int iteration,
+                             final Evaluation previous,
+                             final Evaluation current) {
+        final double prevRms = previous.getRMS();
+        final double currRms = current.getRMS();
+        return Precision.equals(prevRms, currRms, this.absTol) ||
+                Precision.equalsWithRelativeTolerance(prevRms, currRms, this.relTol);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/fitting/leastsquares/GaussNewtonOptimizer.java b/src/main/java/org/apache/commons/math3/fitting/leastsquares/GaussNewtonOptimizer.java
new file mode 100644
index 0000000..8157706
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fitting/leastsquares/GaussNewtonOptimizer.java
@@ -0,0 +1,299 @@
+/*
+ * 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.math3.fitting.leastsquares;
+
+import org.apache.commons.math3.exception.ConvergenceException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem.Evaluation;
+import org.apache.commons.math3.linear.ArrayRealVector;
+import org.apache.commons.math3.linear.CholeskyDecomposition;
+import org.apache.commons.math3.linear.LUDecomposition;
+import org.apache.commons.math3.linear.MatrixUtils;
+import org.apache.commons.math3.linear.NonPositiveDefiniteMatrixException;
+import org.apache.commons.math3.linear.QRDecomposition;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.linear.RealVector;
+import org.apache.commons.math3.linear.SingularMatrixException;
+import org.apache.commons.math3.linear.SingularValueDecomposition;
+import org.apache.commons.math3.optim.ConvergenceChecker;
+import org.apache.commons.math3.util.Incrementor;
+import org.apache.commons.math3.util.Pair;
+
+/**
+ * Gauss-Newton least-squares solver.
+ * <p> This class solve a least-square problem by
+ * solving the normal equations of the linearized problem at each iteration. Either LU
+ * decomposition or Cholesky decomposition can be used to solve the normal equations,
+ * or QR decomposition or SVD decomposition can be used to solve the linear system. LU
+ * decomposition is faster but QR decomposition is more robust for difficult problems,
+ * and SVD can compute a solution for rank-deficient problems.
+ * </p>
+ *
+ * @since 3.3
+ */
+public class GaussNewtonOptimizer implements LeastSquaresOptimizer {
+
+    /** The decomposition algorithm to use to solve the normal equations. */
+    //TODO move to linear package and expand options?
+    public enum Decomposition {
+        /**
+         * Solve by forming the normal equations (J<sup>T</sup>Jx=J<sup>T</sup>r) and
+         * using the {@link LUDecomposition}.
+         *
+         * <p> Theoretically this method takes mn<sup>2</sup>/2 operations to compute the
+         * normal matrix and n<sup>3</sup>/3 operations (m > n) to solve the system using
+         * the LU decomposition. </p>
+         */
+        LU {
+            @Override
+            protected RealVector solve(final RealMatrix jacobian,
+                                       final RealVector residuals) {
+                try {
+                    final Pair<RealMatrix, RealVector> normalEquation =
+                            computeNormalMatrix(jacobian, residuals);
+                    final RealMatrix normal = normalEquation.getFirst();
+                    final RealVector jTr = normalEquation.getSecond();
+                    return new LUDecomposition(normal, SINGULARITY_THRESHOLD)
+                            .getSolver()
+                            .solve(jTr);
+                } catch (SingularMatrixException e) {
+                    throw new ConvergenceException(LocalizedFormats.UNABLE_TO_SOLVE_SINGULAR_PROBLEM, e);
+                }
+            }
+        },
+        /**
+         * Solve the linear least squares problem (Jx=r) using the {@link
+         * QRDecomposition}.
+         *
+         * <p> Theoretically this method takes mn<sup>2</sup> - n<sup>3</sup>/3 operations
+         * (m > n) and has better numerical accuracy than any method that forms the normal
+         * equations. </p>
+         */
+        QR {
+            @Override
+            protected RealVector solve(final RealMatrix jacobian,
+                                       final RealVector residuals) {
+                try {
+                    return new QRDecomposition(jacobian, SINGULARITY_THRESHOLD)
+                            .getSolver()
+                            .solve(residuals);
+                } catch (SingularMatrixException e) {
+                    throw new ConvergenceException(LocalizedFormats.UNABLE_TO_SOLVE_SINGULAR_PROBLEM, e);
+                }
+            }
+        },
+        /**
+         * Solve by forming the normal equations (J<sup>T</sup>Jx=J<sup>T</sup>r) and
+         * using the {@link CholeskyDecomposition}.
+         *
+         * <p> Theoretically this method takes mn<sup>2</sup>/2 operations to compute the
+         * normal matrix and n<sup>3</sup>/6 operations (m > n) to solve the system using
+         * the Cholesky decomposition. </p>
+         */
+        CHOLESKY {
+            @Override
+            protected RealVector solve(final RealMatrix jacobian,
+                                       final RealVector residuals) {
+                try {
+                    final Pair<RealMatrix, RealVector> normalEquation =
+                            computeNormalMatrix(jacobian, residuals);
+                    final RealMatrix normal = normalEquation.getFirst();
+                    final RealVector jTr = normalEquation.getSecond();
+                    return new CholeskyDecomposition(
+                            normal, SINGULARITY_THRESHOLD, SINGULARITY_THRESHOLD)
+                            .getSolver()
+                            .solve(jTr);
+                } catch (NonPositiveDefiniteMatrixException e) {
+                    throw new ConvergenceException(LocalizedFormats.UNABLE_TO_SOLVE_SINGULAR_PROBLEM, e);
+                }
+            }
+        },
+        /**
+         * Solve the linear least squares problem using the {@link
+         * SingularValueDecomposition}.
+         *
+         * <p> This method is slower, but can provide a solution for rank deficient and
+         * nearly singular systems.
+         */
+        SVD {
+            @Override
+            protected RealVector solve(final RealMatrix jacobian,
+                                       final RealVector residuals) {
+                return new SingularValueDecomposition(jacobian)
+                        .getSolver()
+                        .solve(residuals);
+            }
+        };
+
+        /**
+         * Solve the linear least squares problem Jx=r.
+         *
+         * @param jacobian  the Jacobian matrix, J. the number of rows >= the number or
+         *                  columns.
+         * @param residuals the computed residuals, r.
+         * @return the solution x, to the linear least squares problem Jx=r.
+         * @throws ConvergenceException if the matrix properties (e.g. singular) do not
+         *                              permit a solution.
+         */
+        protected abstract RealVector solve(RealMatrix jacobian,
+                                            RealVector residuals);
+    }
+
+    /**
+     * The singularity threshold for matrix decompositions. Determines when a {@link
+     * ConvergenceException} is thrown. The current value was the default value for {@link
+     * LUDecomposition}.
+     */
+    private static final double SINGULARITY_THRESHOLD = 1e-11;
+
+    /** Indicator for using LU decomposition. */
+    private final Decomposition decomposition;
+
+    /**
+     * Creates a Gauss Newton optimizer.
+     * <p/>
+     * The default for the algorithm is to solve the normal equations using QR
+     * decomposition.
+     */
+    public GaussNewtonOptimizer() {
+        this(Decomposition.QR);
+    }
+
+    /**
+     * Create a Gauss Newton optimizer that uses the given decomposition algorithm to
+     * solve the normal equations.
+     *
+     * @param decomposition the {@link Decomposition} algorithm.
+     */
+    public GaussNewtonOptimizer(final Decomposition decomposition) {
+        this.decomposition = decomposition;
+    }
+
+    /**
+     * Get the matrix decomposition algorithm used to solve the normal equations.
+     *
+     * @return the matrix {@link Decomposition} algoritm.
+     */
+    public Decomposition getDecomposition() {
+        return this.decomposition;
+    }
+
+    /**
+     * Configure the decomposition algorithm.
+     *
+     * @param newDecomposition the {@link Decomposition} algorithm to use.
+     * @return a new instance.
+     */
+    public GaussNewtonOptimizer withDecomposition(final Decomposition newDecomposition) {
+        return new GaussNewtonOptimizer(newDecomposition);
+    }
+
+    /** {@inheritDoc} */
+    public Optimum optimize(final LeastSquaresProblem lsp) {
+        //create local evaluation and iteration counts
+        final Incrementor evaluationCounter = lsp.getEvaluationCounter();
+        final Incrementor iterationCounter = lsp.getIterationCounter();
+        final ConvergenceChecker<Evaluation> checker
+                = lsp.getConvergenceChecker();
+
+        // Computation will be useless without a checker (see "for-loop").
+        if (checker == null) {
+            throw new NullArgumentException();
+        }
+
+        RealVector currentPoint = lsp.getStart();
+
+        // iterate until convergence is reached
+        Evaluation current = null;
+        while (true) {
+            iterationCounter.incrementCount();
+
+            // evaluate the objective function and its jacobian
+            Evaluation previous = current;
+            // Value of the objective function at "currentPoint".
+            evaluationCounter.incrementCount();
+            current = lsp.evaluate(currentPoint);
+            final RealVector currentResiduals = current.getResiduals();
+            final RealMatrix weightedJacobian = current.getJacobian();
+            currentPoint = current.getPoint();
+
+            // Check convergence.
+            if (previous != null &&
+                checker.converged(iterationCounter.getCount(), previous, current)) {
+                return new OptimumImpl(current,
+                                       evaluationCounter.getCount(),
+                                       iterationCounter.getCount());
+            }
+
+            // solve the linearized least squares problem
+            final RealVector dX = this.decomposition.solve(weightedJacobian, currentResiduals);
+            // update the estimated parameters
+            currentPoint = currentPoint.add(dX);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return "GaussNewtonOptimizer{" +
+                "decomposition=" + decomposition +
+                '}';
+    }
+
+    /**
+     * Compute the normal matrix, J<sup>T</sup>J.
+     *
+     * @param jacobian  the m by n jacobian matrix, J. Input.
+     * @param residuals the m by 1 residual vector, r. Input.
+     * @return  the n by n normal matrix and  the n by 1 J<sup>Tr vector.
+     */
+    private static Pair<RealMatrix, RealVector> computeNormalMatrix(final RealMatrix jacobian,
+                                                                    final RealVector residuals) {
+        //since the normal matrix is symmetric, we only need to compute half of it.
+        final int nR = jacobian.getRowDimension();
+        final int nC = jacobian.getColumnDimension();
+        //allocate space for return values
+        final RealMatrix normal = MatrixUtils.createRealMatrix(nC, nC);
+        final RealVector jTr = new ArrayRealVector(nC);
+        //for each measurement
+        for (int i = 0; i < nR; ++i) {
+            //compute JTr for measurement i
+            for (int j = 0; j < nC; j++) {
+                jTr.setEntry(j, jTr.getEntry(j) +
+                        residuals.getEntry(i) * jacobian.getEntry(i, j));
+            }
+
+            // add the the contribution to the normal matrix for measurement i
+            for (int k = 0; k < nC; ++k) {
+                //only compute the upper triangular part
+                for (int l = k; l < nC; ++l) {
+                    normal.setEntry(k, l, normal.getEntry(k, l) +
+                            jacobian.getEntry(i, k) * jacobian.getEntry(i, l));
+                }
+            }
+        }
+        //copy the upper triangular part to the lower triangular part.
+        for (int i = 0; i < nC; i++) {
+            for (int j = 0; j < i; j++) {
+                normal.setEntry(i, j, normal.getEntry(j, i));
+            }
+        }
+        return new Pair<RealMatrix, RealVector>(normal, jTr);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/fitting/leastsquares/LeastSquaresAdapter.java b/src/main/java/org/apache/commons/math3/fitting/leastsquares/LeastSquaresAdapter.java
new file mode 100644
index 0000000..1c09874
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fitting/leastsquares/LeastSquaresAdapter.java
@@ -0,0 +1,77 @@
+/*
+ * 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.math3.fitting.leastsquares;
+
+import org.apache.commons.math3.linear.RealVector;
+import org.apache.commons.math3.optim.ConvergenceChecker;
+import org.apache.commons.math3.util.Incrementor;
+
+/**
+ * An adapter that delegates to another implementation of {@link LeastSquaresProblem}.
+ *
+ * @since 3.3
+ */
+public class LeastSquaresAdapter implements LeastSquaresProblem {
+
+    /** the delegate problem */
+    private final LeastSquaresProblem problem;
+
+    /**
+     * Delegate the {@link LeastSquaresProblem} interface to the given implementation.
+     *
+     * @param problem the delegate
+     */
+    public LeastSquaresAdapter(final LeastSquaresProblem problem) {
+        this.problem = problem;
+    }
+
+    /** {@inheritDoc} */
+    public RealVector getStart() {
+        return problem.getStart();
+    }
+
+    /** {@inheritDoc} */
+    public int getObservationSize() {
+        return problem.getObservationSize();
+    }
+
+    /** {@inheritDoc} */
+    public int getParameterSize() {
+        return problem.getParameterSize();
+    }
+
+    /** {@inheritDoc}
+     * @param point*/
+    public Evaluation evaluate(final RealVector point) {
+        return problem.evaluate(point);
+    }
+
+    /** {@inheritDoc} */
+    public Incrementor getEvaluationCounter() {
+        return problem.getEvaluationCounter();
+    }
+
+    /** {@inheritDoc} */
+    public Incrementor getIterationCounter() {
+        return problem.getIterationCounter();
+    }
+
+    /** {@inheritDoc} */
+    public ConvergenceChecker<Evaluation> getConvergenceChecker() {
+        return problem.getConvergenceChecker();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/fitting/leastsquares/LeastSquaresBuilder.java b/src/main/java/org/apache/commons/math3/fitting/leastsquares/LeastSquaresBuilder.java
new file mode 100644
index 0000000..7b14b37
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fitting/leastsquares/LeastSquaresBuilder.java
@@ -0,0 +1,226 @@
+/*
+ * 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.math3.fitting.leastsquares;
+
+import org.apache.commons.math3.analysis.MultivariateMatrixFunction;
+import org.apache.commons.math3.analysis.MultivariateVectorFunction;
+import org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem.Evaluation;
+import org.apache.commons.math3.linear.ArrayRealVector;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.linear.RealVector;
+import org.apache.commons.math3.optim.ConvergenceChecker;
+import org.apache.commons.math3.optim.PointVectorValuePair;
+
+/**
+ * A mutable builder for {@link LeastSquaresProblem}s.
+ *
+ * @see LeastSquaresFactory
+ * @since 3.3
+ */
+public class LeastSquaresBuilder {
+
+    /** max evaluations */
+    private int maxEvaluations;
+    /** max iterations */
+    private int maxIterations;
+    /** convergence checker */
+    private ConvergenceChecker<Evaluation> checker;
+    /** model function */
+    private MultivariateJacobianFunction model;
+    /** observed values */
+    private RealVector target;
+    /** initial guess */
+    private RealVector start;
+    /** weight matrix */
+    private RealMatrix weight;
+    /**
+     * Lazy evaluation.
+     *
+     * @since 3.4
+     */
+    private boolean lazyEvaluation;
+    /** Validator.
+     *
+     * @since 3.4
+     */
+    private ParameterValidator paramValidator;
+
+
+    /**
+     * Construct a {@link LeastSquaresProblem} from the data in this builder.
+     *
+     * @return a new {@link LeastSquaresProblem}.
+     */
+    public LeastSquaresProblem build() {
+        return LeastSquaresFactory.create(model,
+                                          target,
+                                          start,
+                                          weight,
+                                          checker,
+                                          maxEvaluations,
+                                          maxIterations,
+                                          lazyEvaluation,
+                                          paramValidator);
+    }
+
+    /**
+     * Configure the max evaluations.
+     *
+     * @param newMaxEvaluations the maximum number of evaluations permitted.
+     * @return this
+     */
+    public LeastSquaresBuilder maxEvaluations(final int newMaxEvaluations) {
+        this.maxEvaluations = newMaxEvaluations;
+        return this;
+    }
+
+    /**
+     * Configure the max iterations.
+     *
+     * @param newMaxIterations the maximum number of iterations permitted.
+     * @return this
+     */
+    public LeastSquaresBuilder maxIterations(final int newMaxIterations) {
+        this.maxIterations = newMaxIterations;
+        return this;
+    }
+
+    /**
+     * Configure the convergence checker.
+     *
+     * @param newChecker the convergence checker.
+     * @return this
+     */
+    public LeastSquaresBuilder checker(final ConvergenceChecker<Evaluation> newChecker) {
+        this.checker = newChecker;
+        return this;
+    }
+
+    /**
+     * Configure the convergence checker.
+     * <p/>
+     * This function is an overloaded version of {@link #checker(ConvergenceChecker)}.
+     *
+     * @param newChecker the convergence checker.
+     * @return this
+     */
+    public LeastSquaresBuilder checkerPair(final ConvergenceChecker<PointVectorValuePair> newChecker) {
+        return this.checker(LeastSquaresFactory.evaluationChecker(newChecker));
+    }
+
+    /**
+     * Configure the model function.
+     *
+     * @param value the model function value
+     * @param jacobian the Jacobian of {@code value}
+     * @return this
+     */
+    public LeastSquaresBuilder model(final MultivariateVectorFunction value,
+                                     final MultivariateMatrixFunction jacobian) {
+        return model(LeastSquaresFactory.model(value, jacobian));
+    }
+
+    /**
+     * Configure the model function.
+     *
+     * @param newModel the model function value and Jacobian
+     * @return this
+     */
+    public LeastSquaresBuilder model(final MultivariateJacobianFunction newModel) {
+        this.model = newModel;
+        return this;
+    }
+
+    /**
+     * Configure the observed data.
+     *
+     * @param newTarget the observed data.
+     * @return this
+     */
+    public LeastSquaresBuilder target(final RealVector newTarget) {
+        this.target = newTarget;
+        return this;
+    }
+
+    /**
+     * Configure the observed data.
+     *
+     * @param newTarget the observed data.
+     * @return this
+     */
+    public LeastSquaresBuilder target(final double[] newTarget) {
+        return target(new ArrayRealVector(newTarget, false));
+    }
+
+    /**
+     * Configure the initial guess.
+     *
+     * @param newStart the initial guess.
+     * @return this
+     */
+    public LeastSquaresBuilder start(final RealVector newStart) {
+        this.start = newStart;
+        return this;
+    }
+
+    /**
+     * Configure the initial guess.
+     *
+     * @param newStart the initial guess.
+     * @return this
+     */
+    public LeastSquaresBuilder start(final double[] newStart) {
+        return start(new ArrayRealVector(newStart, false));
+    }
+
+    /**
+     * Configure the weight matrix.
+     *
+     * @param newWeight the weight matrix
+     * @return this
+     */
+    public LeastSquaresBuilder weight(final RealMatrix newWeight) {
+        this.weight = newWeight;
+        return this;
+    }
+
+    /**
+     * Configure whether evaluation will be lazy or not.
+     *
+     * @param newValue Whether to perform lazy evaluation.
+     * @return this object.
+     *
+     * @since 3.4
+     */
+    public LeastSquaresBuilder lazyEvaluation(final boolean newValue) {
+        lazyEvaluation = newValue;
+        return this;
+    }
+
+    /**
+     * Configure the validator of the model parameters.
+     *
+     * @param newValidator Parameter validator.
+     * @return this object.
+     *
+     * @since 3.4
+     */
+    public LeastSquaresBuilder parameterValidator(final ParameterValidator newValidator) {
+        paramValidator = newValidator;
+        return this;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/fitting/leastsquares/LeastSquaresFactory.java b/src/main/java/org/apache/commons/math3/fitting/leastsquares/LeastSquaresFactory.java
new file mode 100644
index 0000000..42cdf89
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fitting/leastsquares/LeastSquaresFactory.java
@@ -0,0 +1,532 @@
+/*
+ * 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.math3.fitting.leastsquares;
+
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.analysis.MultivariateMatrixFunction;
+import org.apache.commons.math3.analysis.MultivariateVectorFunction;
+import org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem.Evaluation;
+import org.apache.commons.math3.linear.Array2DRowRealMatrix;
+import org.apache.commons.math3.linear.ArrayRealVector;
+import org.apache.commons.math3.linear.DiagonalMatrix;
+import org.apache.commons.math3.linear.EigenDecomposition;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.linear.RealVector;
+import org.apache.commons.math3.optim.AbstractOptimizationProblem;
+import org.apache.commons.math3.optim.ConvergenceChecker;
+import org.apache.commons.math3.optim.PointVectorValuePair;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.Incrementor;
+import org.apache.commons.math3.util.Pair;
+
+/**
+ * A Factory for creating {@link LeastSquaresProblem}s.
+ *
+ * @since 3.3
+ */
+public class LeastSquaresFactory {
+
+    /** Prevent instantiation. */
+    private LeastSquaresFactory() {}
+
+    /**
+     * Create a {@link org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem}
+     * from the given elements. There will be no weights applied (unit weights).
+     *
+     * @param model          the model function. Produces the computed values.
+     * @param observed       the observed (target) values
+     * @param start          the initial guess.
+     * @param weight         the weight matrix
+     * @param checker        convergence checker
+     * @param maxEvaluations the maximum number of times to evaluate the model
+     * @param maxIterations  the maximum number to times to iterate in the algorithm
+     * @param lazyEvaluation Whether the call to {@link Evaluation#evaluate(RealVector)}
+     * will defer the evaluation until access to the value is requested.
+     * @param paramValidator Model parameters validator.
+     * @return the specified General Least Squares problem.
+     *
+     * @since 3.4
+     */
+    public static LeastSquaresProblem create(final MultivariateJacobianFunction model,
+                                             final RealVector observed,
+                                             final RealVector start,
+                                             final RealMatrix weight,
+                                             final ConvergenceChecker<Evaluation> checker,
+                                             final int maxEvaluations,
+                                             final int maxIterations,
+                                             final boolean lazyEvaluation,
+                                             final ParameterValidator paramValidator) {
+        final LeastSquaresProblem p = new LocalLeastSquaresProblem(model,
+                                                                   observed,
+                                                                   start,
+                                                                   checker,
+                                                                   maxEvaluations,
+                                                                   maxIterations,
+                                                                   lazyEvaluation,
+                                                                   paramValidator);
+        if (weight != null) {
+            return weightMatrix(p, weight);
+        } else {
+            return p;
+        }
+    }
+
+    /**
+     * Create a {@link org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem}
+     * from the given elements. There will be no weights applied (unit weights).
+     *
+     * @param model          the model function. Produces the computed values.
+     * @param observed       the observed (target) values
+     * @param start          the initial guess.
+     * @param checker        convergence checker
+     * @param maxEvaluations the maximum number of times to evaluate the model
+     * @param maxIterations  the maximum number to times to iterate in the algorithm
+     * @return the specified General Least Squares problem.
+     */
+    public static LeastSquaresProblem create(final MultivariateJacobianFunction model,
+                                             final RealVector observed,
+                                             final RealVector start,
+                                             final ConvergenceChecker<Evaluation> checker,
+                                             final int maxEvaluations,
+                                             final int maxIterations) {
+        return create(model,
+                      observed,
+                      start,
+                      null,
+                      checker,
+                      maxEvaluations,
+                      maxIterations,
+                      false,
+                      null);
+    }
+
+    /**
+     * Create a {@link org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem}
+     * from the given elements.
+     *
+     * @param model          the model function. Produces the computed values.
+     * @param observed       the observed (target) values
+     * @param start          the initial guess.
+     * @param weight         the weight matrix
+     * @param checker        convergence checker
+     * @param maxEvaluations the maximum number of times to evaluate the model
+     * @param maxIterations  the maximum number to times to iterate in the algorithm
+     * @return the specified General Least Squares problem.
+     */
+    public static LeastSquaresProblem create(final MultivariateJacobianFunction model,
+                                             final RealVector observed,
+                                             final RealVector start,
+                                             final RealMatrix weight,
+                                             final ConvergenceChecker<Evaluation> checker,
+                                             final int maxEvaluations,
+                                             final int maxIterations) {
+        return weightMatrix(create(model,
+                                   observed,
+                                   start,
+                                   checker,
+                                   maxEvaluations,
+                                   maxIterations),
+                            weight);
+    }
+
+    /**
+     * Create a {@link org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem}
+     * from the given elements.
+     * <p>
+     * This factory method is provided for continuity with previous interfaces. Newer
+     * applications should use {@link #create(MultivariateJacobianFunction, RealVector,
+     * RealVector, ConvergenceChecker, int, int)}, or {@link #create(MultivariateJacobianFunction,
+     * RealVector, RealVector, RealMatrix, ConvergenceChecker, int, int)}.
+     *
+     * @param model          the model function. Produces the computed values.
+     * @param jacobian       the jacobian of the model with respect to the parameters
+     * @param observed       the observed (target) values
+     * @param start          the initial guess.
+     * @param weight         the weight matrix
+     * @param checker        convergence checker
+     * @param maxEvaluations the maximum number of times to evaluate the model
+     * @param maxIterations  the maximum number to times to iterate in the algorithm
+     * @return the specified General Least Squares problem.
+     */
+    public static LeastSquaresProblem create(final MultivariateVectorFunction model,
+                                             final MultivariateMatrixFunction jacobian,
+                                             final double[] observed,
+                                             final double[] start,
+                                             final RealMatrix weight,
+                                             final ConvergenceChecker<Evaluation> checker,
+                                             final int maxEvaluations,
+                                             final int maxIterations) {
+        return create(model(model, jacobian),
+                      new ArrayRealVector(observed, false),
+                      new ArrayRealVector(start, false),
+                      weight,
+                      checker,
+                      maxEvaluations,
+                      maxIterations);
+    }
+
+    /**
+     * Apply a dense weight matrix to the {@link LeastSquaresProblem}.
+     *
+     * @param problem the unweighted problem
+     * @param weights the matrix of weights
+     * @return a new {@link LeastSquaresProblem} with the weights applied. The original
+     *         {@code problem} is not modified.
+     */
+    public static LeastSquaresProblem weightMatrix(final LeastSquaresProblem problem,
+                                                   final RealMatrix weights) {
+        final RealMatrix weightSquareRoot = squareRoot(weights);
+        return new LeastSquaresAdapter(problem) {
+            /** {@inheritDoc} */
+            @Override
+            public Evaluation evaluate(final RealVector point) {
+                return new DenseWeightedEvaluation(super.evaluate(point), weightSquareRoot);
+            }
+        };
+    }
+
+    /**
+     * Apply a diagonal weight matrix to the {@link LeastSquaresProblem}.
+     *
+     * @param problem the unweighted problem
+     * @param weights the diagonal of the weight matrix
+     * @return a new {@link LeastSquaresProblem} with the weights applied. The original
+     *         {@code problem} is not modified.
+     */
+    public static LeastSquaresProblem weightDiagonal(final LeastSquaresProblem problem,
+                                                     final RealVector weights) {
+        // TODO more efficient implementation
+        return weightMatrix(problem, new DiagonalMatrix(weights.toArray()));
+    }
+
+    /**
+     * Count the evaluations of a particular problem. The {@code counter} will be
+     * incremented every time {@link LeastSquaresProblem#evaluate(RealVector)} is called on
+     * the <em>returned</em> problem.
+     *
+     * @param problem the problem to track.
+     * @param counter the counter to increment.
+     * @return a least squares problem that tracks evaluations
+     */
+    public static LeastSquaresProblem countEvaluations(final LeastSquaresProblem problem,
+                                                       final Incrementor counter) {
+        return new LeastSquaresAdapter(problem) {
+
+            /** {@inheritDoc} */
+            @Override
+            public Evaluation evaluate(final RealVector point) {
+                counter.incrementCount();
+                return super.evaluate(point);
+            }
+
+            // Delegate the rest.
+        };
+    }
+
+    /**
+     * View a convergence checker specified for a {@link PointVectorValuePair} as one
+     * specified for an {@link Evaluation}.
+     *
+     * @param checker the convergence checker to adapt.
+     * @return a convergence checker that delegates to {@code checker}.
+     */
+    public static ConvergenceChecker<Evaluation> evaluationChecker(final ConvergenceChecker<PointVectorValuePair> checker) {
+        return new ConvergenceChecker<Evaluation>() {
+            /** {@inheritDoc} */
+            public boolean converged(final int iteration,
+                                     final Evaluation previous,
+                                     final Evaluation current) {
+                return checker.converged(
+                        iteration,
+                        new PointVectorValuePair(
+                                previous.getPoint().toArray(),
+                                previous.getResiduals().toArray(),
+                                false),
+                        new PointVectorValuePair(
+                                current.getPoint().toArray(),
+                                current.getResiduals().toArray(),
+                                false)
+                );
+            }
+        };
+    }
+
+    /**
+     * Computes the square-root of the weight matrix.
+     *
+     * @param m Symmetric, positive-definite (weight) matrix.
+     * @return the square-root of the weight matrix.
+     */
+    private static RealMatrix squareRoot(final RealMatrix m) {
+        if (m instanceof DiagonalMatrix) {
+            final int dim = m.getRowDimension();
+            final RealMatrix sqrtM = new DiagonalMatrix(dim);
+            for (int i = 0; i < dim; i++) {
+                sqrtM.setEntry(i, i, FastMath.sqrt(m.getEntry(i, i)));
+            }
+            return sqrtM;
+        } else {
+            final EigenDecomposition dec = new EigenDecomposition(m);
+            return dec.getSquareRoot();
+        }
+    }
+
+    /**
+     * Combine a {@link MultivariateVectorFunction} with a {@link
+     * MultivariateMatrixFunction} to produce a {@link MultivariateJacobianFunction}.
+     *
+     * @param value    the vector value function
+     * @param jacobian the Jacobian function
+     * @return a function that computes both at the same time
+     */
+    public static MultivariateJacobianFunction model(final MultivariateVectorFunction value,
+                                                     final MultivariateMatrixFunction jacobian) {
+        return new LocalValueAndJacobianFunction(value, jacobian);
+    }
+
+    /**
+     * Combine a {@link MultivariateVectorFunction} with a {@link
+     * MultivariateMatrixFunction} to produce a {@link MultivariateJacobianFunction}.
+     *
+     * @param value    the vector value function
+     * @param jacobian the Jacobian function
+     * @return a function that computes both at the same time
+     */
+    private static class LocalValueAndJacobianFunction
+        implements ValueAndJacobianFunction {
+        /** Model. */
+        private final MultivariateVectorFunction value;
+        /** Model's Jacobian. */
+        private final MultivariateMatrixFunction jacobian;
+
+        /**
+         * @param value Model function.
+         * @param jacobian Model's Jacobian function.
+         */
+        LocalValueAndJacobianFunction(final MultivariateVectorFunction value,
+                                      final MultivariateMatrixFunction jacobian) {
+            this.value = value;
+            this.jacobian = jacobian;
+        }
+
+        /** {@inheritDoc} */
+        public Pair<RealVector, RealMatrix> value(final RealVector point) {
+            //TODO get array from RealVector without copying?
+            final double[] p = point.toArray();
+
+            // Evaluate.
+            return new Pair<RealVector, RealMatrix>(computeValue(p),
+                                                    computeJacobian(p));
+        }
+
+        /** {@inheritDoc} */
+        public RealVector computeValue(final double[] params) {
+            return new ArrayRealVector(value.value(params), false);
+        }
+
+        /** {@inheritDoc} */
+        public RealMatrix computeJacobian(final double[] params) {
+            return new Array2DRowRealMatrix(jacobian.value(params), false);
+        }
+    }
+
+
+    /**
+     * A private, "field" immutable (not "real" immutable) implementation of {@link
+     * LeastSquaresProblem}.
+     * @since 3.3
+     */
+    private static class LocalLeastSquaresProblem
+            extends AbstractOptimizationProblem<Evaluation>
+            implements LeastSquaresProblem {
+
+        /** Target values for the model function at optimum. */
+        private final RealVector target;
+        /** Model function. */
+        private final MultivariateJacobianFunction model;
+        /** Initial guess. */
+        private final RealVector start;
+        /** Whether to use lazy evaluation. */
+        private final boolean lazyEvaluation;
+        /** Model parameters validator. */
+        private final ParameterValidator paramValidator;
+
+        /**
+         * Create a {@link LeastSquaresProblem} from the given data.
+         *
+         * @param model          the model function
+         * @param target         the observed data
+         * @param start          the initial guess
+         * @param checker        the convergence checker
+         * @param maxEvaluations the allowed evaluations
+         * @param maxIterations  the allowed iterations
+         * @param lazyEvaluation Whether the call to {@link Evaluation#evaluate(RealVector)}
+         * will defer the evaluation until access to the value is requested.
+         * @param paramValidator Model parameters validator.
+         */
+        LocalLeastSquaresProblem(final MultivariateJacobianFunction model,
+                                 final RealVector target,
+                                 final RealVector start,
+                                 final ConvergenceChecker<Evaluation> checker,
+                                 final int maxEvaluations,
+                                 final int maxIterations,
+                                 final boolean lazyEvaluation,
+                                 final ParameterValidator paramValidator) {
+            super(maxEvaluations, maxIterations, checker);
+            this.target = target;
+            this.model = model;
+            this.start = start;
+            this.lazyEvaluation = lazyEvaluation;
+            this.paramValidator = paramValidator;
+
+            if (lazyEvaluation &&
+                !(model instanceof ValueAndJacobianFunction)) {
+                // Lazy evaluation requires that value and Jacobian
+                // can be computed separately.
+                throw new MathIllegalStateException(LocalizedFormats.INVALID_IMPLEMENTATION,
+                                                    model.getClass().getName());
+            }
+        }
+
+        /** {@inheritDoc} */
+        public int getObservationSize() {
+            return target.getDimension();
+        }
+
+        /** {@inheritDoc} */
+        public int getParameterSize() {
+            return start.getDimension();
+        }
+
+        /** {@inheritDoc} */
+        public RealVector getStart() {
+            return start == null ? null : start.copy();
+        }
+
+        /** {@inheritDoc} */
+        public Evaluation evaluate(final RealVector point) {
+            // Copy so optimizer can change point without changing our instance.
+            final RealVector p = paramValidator == null ?
+                point.copy() :
+                paramValidator.validate(point.copy());
+
+            if (lazyEvaluation) {
+                return new LazyUnweightedEvaluation((ValueAndJacobianFunction) model,
+                                                    target,
+                                                    p);
+            } else {
+                // Evaluate value and jacobian in one function call.
+                final Pair<RealVector, RealMatrix> value = model.value(p);
+                return new UnweightedEvaluation(value.getFirst(),
+                                                value.getSecond(),
+                                                target,
+                                                p);
+            }
+        }
+
+        /**
+         * Container with the model evaluation at a particular point.
+         */
+        private static class UnweightedEvaluation extends AbstractEvaluation {
+            /** Point of evaluation. */
+            private final RealVector point;
+            /** Derivative at point. */
+            private final RealMatrix jacobian;
+            /** Computed residuals. */
+            private final RealVector residuals;
+
+            /**
+             * Create an {@link Evaluation} with no weights.
+             *
+             * @param values   the computed function values
+             * @param jacobian the computed function Jacobian
+             * @param target   the observed values
+             * @param point    the abscissa
+             */
+            private UnweightedEvaluation(final RealVector values,
+                                         final RealMatrix jacobian,
+                                         final RealVector target,
+                                         final RealVector point) {
+                super(target.getDimension());
+                this.jacobian = jacobian;
+                this.point = point;
+                this.residuals = target.subtract(values);
+            }
+
+            /** {@inheritDoc} */
+            public RealMatrix getJacobian() {
+                return jacobian;
+            }
+
+            /** {@inheritDoc} */
+            public RealVector getPoint() {
+                return point;
+            }
+
+            /** {@inheritDoc} */
+            public RealVector getResiduals() {
+                return residuals;
+            }
+        }
+
+        /**
+         * Container with the model <em>lazy</em> evaluation at a particular point.
+         */
+        private static class LazyUnweightedEvaluation extends AbstractEvaluation {
+            /** Point of evaluation. */
+            private final RealVector point;
+            /** Model and Jacobian functions. */
+            private final ValueAndJacobianFunction model;
+            /** Target values for the model function at optimum. */
+            private final RealVector target;
+
+            /**
+             * Create an {@link Evaluation} with no weights.
+             *
+             * @param model  the model function
+             * @param target the observed values
+             * @param point  the abscissa
+             */
+            private LazyUnweightedEvaluation(final ValueAndJacobianFunction model,
+                                             final RealVector target,
+                                             final RealVector point) {
+                super(target.getDimension());
+                // Safe to cast as long as we control usage of this class.
+                this.model = model;
+                this.point = point;
+                this.target = target;
+            }
+
+            /** {@inheritDoc} */
+            public RealMatrix getJacobian() {
+                return model.computeJacobian(point.toArray());
+            }
+
+            /** {@inheritDoc} */
+            public RealVector getPoint() {
+                return point;
+            }
+
+            /** {@inheritDoc} */
+            public RealVector getResiduals() {
+                return target.subtract(model.computeValue(point.toArray()));
+            }
+        }
+    }
+}
+
diff --git a/src/main/java/org/apache/commons/math3/fitting/leastsquares/LeastSquaresOptimizer.java b/src/main/java/org/apache/commons/math3/fitting/leastsquares/LeastSquaresOptimizer.java
new file mode 100644
index 0000000..50d5b8a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fitting/leastsquares/LeastSquaresOptimizer.java
@@ -0,0 +1,62 @@
+/*
+ * 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.math3.fitting.leastsquares;
+
+/**
+ * An algorithm that can be applied to a non-linear least squares problem.
+ *
+ * @since 3.3
+ */
+public interface LeastSquaresOptimizer {
+
+    /**
+     * Solve the non-linear least squares problem.
+     *
+     *
+     * @param leastSquaresProblem the problem definition, including model function and
+     *                            convergence criteria.
+     * @return The optimum.
+     */
+    Optimum optimize(LeastSquaresProblem leastSquaresProblem);
+
+    /**
+     * The optimum found by the optimizer. This object contains the point, its value, and
+     * some metadata.
+     */
+    //TODO Solution?
+    interface Optimum extends LeastSquaresProblem.Evaluation {
+
+        /**
+         * Get the number of times the model was evaluated in order to produce this
+         * optimum.
+         *
+         * @return the number of model (objective) function evaluations
+         */
+        int getEvaluations();
+
+        /**
+         * Get the number of times the algorithm iterated in order to produce this
+         * optimum. In general least squares it is common to have one {@link
+         * #getEvaluations() evaluation} per iterations.
+         *
+         * @return the number of iterations
+         */
+        int getIterations();
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/fitting/leastsquares/LeastSquaresProblem.java b/src/main/java/org/apache/commons/math3/fitting/leastsquares/LeastSquaresProblem.java
new file mode 100644
index 0000000..097ff81
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fitting/leastsquares/LeastSquaresProblem.java
@@ -0,0 +1,156 @@
+/*
+ * 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.math3.fitting.leastsquares;
+
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.linear.RealVector;
+import org.apache.commons.math3.optim.OptimizationProblem;
+
+/**
+ * The data necessary to define a non-linear least squares problem.
+ * <p>
+ * Includes the observed values, computed model function, and
+ * convergence/divergence criteria. Weights are implicit in {@link
+ * Evaluation#getResiduals()} and {@link Evaluation#getJacobian()}.
+ * </p>
+ * <p>
+ * Instances are typically either created progressively using a {@link
+ * LeastSquaresBuilder builder} or created at once using a {@link LeastSquaresFactory
+ * factory}.
+ * </p>
+ * @see LeastSquaresBuilder
+ * @see LeastSquaresFactory
+ * @see LeastSquaresAdapter
+ *
+ * @since 3.3
+ */
+public interface LeastSquaresProblem extends OptimizationProblem<LeastSquaresProblem.Evaluation> {
+
+    /**
+     * Gets the initial guess.
+     *
+     * @return the initial guess values.
+     */
+    RealVector getStart();
+
+    /**
+     * Get the number of observations (rows in the Jacobian) in this problem.
+     *
+     * @return the number of scalar observations
+     */
+    int getObservationSize();
+
+    /**
+     * Get the number of parameters (columns in the Jacobian) in this problem.
+     *
+     * @return the number of scalar parameters
+     */
+    int getParameterSize();
+
+    /**
+     * Evaluate the model at the specified point.
+     *
+     *
+     * @param point the parameter values.
+     * @return the model's value and derivative at the given point.
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException
+     *          if the maximal number of evaluations (of the model vector function) is
+     *          exceeded.
+     */
+    Evaluation evaluate(RealVector point);
+
+    /**
+     * An evaluation of a {@link LeastSquaresProblem} at a particular point. This class
+     * also computes several quantities derived from the value and its Jacobian.
+     */
+    public interface Evaluation {
+
+        /**
+         * Get the covariance matrix of the optimized parameters. <br/> Note that this
+         * operation involves the inversion of the <code>J<sup>T</sup>J</code> matrix,
+         * where {@code J} is the Jacobian matrix. The {@code threshold} parameter is a
+         * way for the caller to specify that the result of this computation should be
+         * considered meaningless, and thus trigger an exception.
+         *
+         *
+         * @param threshold Singularity threshold.
+         * @return the covariance matrix.
+         * @throws org.apache.commons.math3.linear.SingularMatrixException
+         *          if the covariance matrix cannot be computed (singular problem).
+         */
+        RealMatrix getCovariances(double threshold);
+
+        /**
+         * Get an estimate of the standard deviation of the parameters. The returned
+         * values are the square root of the diagonal coefficients of the covariance
+         * matrix, {@code sd(a[i]) ~= sqrt(C[i][i])}, where {@code a[i]} is the optimized
+         * value of the {@code i}-th parameter, and {@code C} is the covariance matrix.
+         *
+         *
+         * @param covarianceSingularityThreshold Singularity threshold (see {@link
+         *                                       #getCovariances(double) computeCovariances}).
+         * @return an estimate of the standard deviation of the optimized parameters
+         * @throws org.apache.commons.math3.linear.SingularMatrixException
+         *          if the covariance matrix cannot be computed.
+         */
+        RealVector getSigma(double covarianceSingularityThreshold);
+
+        /**
+         * Get the normalized cost. It is the square-root of the sum of squared of
+         * the residuals, divided by the number of measurements.
+         *
+         * @return the cost.
+         */
+        double getRMS();
+
+        /**
+         * Get the weighted Jacobian matrix.
+         *
+         * @return the weighted Jacobian: W<sup>1/2</sup> J.
+         * @throws org.apache.commons.math3.exception.DimensionMismatchException
+         * if the Jacobian dimension does not match problem dimension.
+         */
+        RealMatrix getJacobian();
+
+        /**
+         * Get the cost.
+         *
+         * @return the cost.
+         * @see #getResiduals()
+         */
+        double getCost();
+
+        /**
+         * Get the weighted residuals. The residual is the difference between the
+         * observed (target) values and the model (objective function) value. There is one
+         * residual for each element of the vector-valued function. The raw residuals are
+         * then multiplied by the square root of the weight matrix.
+         *
+         * @return the weighted residuals: W<sup>1/2</sup> K.
+         * @throws org.apache.commons.math3.exception.DimensionMismatchException
+         * if the residuals have the wrong length.
+         */
+        RealVector getResiduals();
+
+        /**
+         * Get the abscissa (independent variables) of this evaluation.
+         *
+         * @return the point provided to {@link #evaluate(RealVector)}.
+         */
+        RealVector getPoint();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/fitting/leastsquares/LevenbergMarquardtOptimizer.java b/src/main/java/org/apache/commons/math3/fitting/leastsquares/LevenbergMarquardtOptimizer.java
new file mode 100644
index 0000000..358d240
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fitting/leastsquares/LevenbergMarquardtOptimizer.java
@@ -0,0 +1,1042 @@
+/*
+ * 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.math3.fitting.leastsquares;
+
+import java.util.Arrays;
+
+import org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem.Evaluation;
+import org.apache.commons.math3.linear.ArrayRealVector;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.exception.ConvergenceException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.optim.ConvergenceChecker;
+import org.apache.commons.math3.util.Incrementor;
+import org.apache.commons.math3.util.Precision;
+import org.apache.commons.math3.util.FastMath;
+
+
+/**
+ * This class solves a least-squares problem using the Levenberg-Marquardt
+ * algorithm.
+ *
+ * <p>This implementation <em>should</em> work even for over-determined systems
+ * (i.e. systems having more point than equations). Over-determined systems
+ * are solved by ignoring the point which have the smallest impact according
+ * to their jacobian column norm. Only the rank of the matrix and some loop bounds
+ * are changed to implement this.</p>
+ *
+ * <p>The resolution engine is a simple translation of the MINPACK <a
+ * href="http://www.netlib.org/minpack/lmder.f">lmder</a> routine with minor
+ * changes. The changes include the over-determined resolution, the use of
+ * inherited convergence checker and the Q.R. decomposition which has been
+ * rewritten following the algorithm described in the
+ * P. Lascaux and R. Theodor book <i>Analyse num&eacute;rique matricielle
+ * appliqu&eacute;e &agrave; l'art de l'ing&eacute;nieur</i>, Masson 1986.</p>
+ * <p>The authors of the original fortran version are:
+ * <ul>
+ * <li>Argonne National Laboratory. MINPACK project. March 1980</li>
+ * <li>Burton S. Garbow</li>
+ * <li>Kenneth E. Hillstrom</li>
+ * <li>Jorge J. More</li>
+ * </ul>
+ * The redistribution policy for MINPACK is available <a
+ * href="http://www.netlib.org/minpack/disclaimer">here</a>, for convenience, it
+ * is reproduced below.</p>
+ *
+ * <table border="0" width="80%" cellpadding="10" align="center" bgcolor="#E0E0E0">
+ * <tr><td>
+ *    Minpack Copyright Notice (1999) University of Chicago.
+ *    All rights reserved
+ * </td></tr>
+ * <tr><td>
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * <ol>
+ *  <li>Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.</li>
+ * <li>Redistributions in binary form must reproduce the above
+ *     copyright notice, this list of conditions and the following
+ *     disclaimer in the documentation and/or other materials provided
+ *     with the distribution.</li>
+ * <li>The end-user documentation included with the redistribution, if any,
+ *     must include the following acknowledgment:
+ *     <code>This product includes software developed by the University of
+ *           Chicago, as Operator of Argonne National Laboratory.</code>
+ *     Alternately, this acknowledgment may appear in the software itself,
+ *     if and wherever such third-party acknowledgments normally appear.</li>
+ * <li><strong>WARRANTY DISCLAIMER. THE SOFTWARE IS SUPPLIED "AS IS"
+ *     WITHOUT WARRANTY OF ANY KIND. THE COPYRIGHT HOLDER, THE
+ *     UNITED STATES, THE UNITED STATES DEPARTMENT OF ENERGY, AND
+ *     THEIR EMPLOYEES: (1) DISCLAIM ANY WARRANTIES, EXPRESS OR
+ *     IMPLIED, INCLUDING BUT NOT LIMITED TO ANY IMPLIED WARRANTIES
+ *     OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE
+ *     OR NON-INFRINGEMENT, (2) DO NOT ASSUME ANY LEGAL LIABILITY
+ *     OR RESPONSIBILITY FOR THE ACCURACY, COMPLETENESS, OR
+ *     USEFULNESS OF THE SOFTWARE, (3) DO NOT REPRESENT THAT USE OF
+ *     THE SOFTWARE WOULD NOT INFRINGE PRIVATELY OWNED RIGHTS, (4)
+ *     DO NOT WARRANT THAT THE SOFTWARE WILL FUNCTION
+ *     UNINTERRUPTED, THAT IT IS ERROR-FREE OR THAT ANY ERRORS WILL
+ *     BE CORRECTED.</strong></li>
+ * <li><strong>LIMITATION OF LIABILITY. IN NO EVENT WILL THE COPYRIGHT
+ *     HOLDER, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF
+ *     ENERGY, OR THEIR EMPLOYEES: BE LIABLE FOR ANY INDIRECT,
+ *     INCIDENTAL, CONSEQUENTIAL, SPECIAL OR PUNITIVE DAMAGES OF
+ *     ANY KIND OR NATURE, INCLUDING BUT NOT LIMITED TO LOSS OF
+ *     PROFITS OR LOSS OF DATA, FOR ANY REASON WHATSOEVER, WHETHER
+ *     SUCH LIABILITY IS ASSERTED ON THE BASIS OF CONTRACT, TORT
+ *     (INCLUDING NEGLIGENCE OR STRICT LIABILITY), OR OTHERWISE,
+ *     EVEN IF ANY OF SAID PARTIES HAS BEEN WARNED OF THE
+ *     POSSIBILITY OF SUCH LOSS OR DAMAGES.</strong></li>
+ * <ol></td></tr>
+ * </table>
+ *
+ * @since 3.3
+ */
+public class LevenbergMarquardtOptimizer implements LeastSquaresOptimizer {
+
+    /** Twice the "epsilon machine". */
+    private static final double TWO_EPS = 2 * Precision.EPSILON;
+
+    /* configuration parameters */
+    /** Positive input variable used in determining the initial step bound. */
+    private final double initialStepBoundFactor;
+    /** Desired relative error in the sum of squares. */
+    private final double costRelativeTolerance;
+    /**  Desired relative error in the approximate solution parameters. */
+    private final double parRelativeTolerance;
+    /** Desired max cosine on the orthogonality between the function vector
+     * and the columns of the jacobian. */
+    private final double orthoTolerance;
+    /** Threshold for QR ranking. */
+    private final double qrRankingThreshold;
+
+    /** Default constructor.
+     * <p>
+     * The default values for the algorithm settings are:
+     * <ul>
+     *  <li>Initial step bound factor: 100</li>
+     *  <li>Cost relative tolerance: 1e-10</li>
+     *  <li>Parameters relative tolerance: 1e-10</li>
+     *  <li>Orthogonality tolerance: 1e-10</li>
+     *  <li>QR ranking threshold: {@link Precision#SAFE_MIN}</li>
+     * </ul>
+     **/
+    public LevenbergMarquardtOptimizer() {
+        this(100, 1e-10, 1e-10, 1e-10, Precision.SAFE_MIN);
+    }
+
+    /**
+     * Construct an instance with all parameters specified.
+     *
+     * @param initialStepBoundFactor initial step bound factor
+     * @param costRelativeTolerance  cost relative tolerance
+     * @param parRelativeTolerance   parameters relative tolerance
+     * @param orthoTolerance         orthogonality tolerance
+     * @param qrRankingThreshold     threshold in the QR decomposition. Columns with a 2
+     *                               norm less than this threshold are considered to be
+     *                               all 0s.
+     */
+    public LevenbergMarquardtOptimizer(
+            final double initialStepBoundFactor,
+            final double costRelativeTolerance,
+            final double parRelativeTolerance,
+            final double orthoTolerance,
+            final double qrRankingThreshold) {
+        this.initialStepBoundFactor = initialStepBoundFactor;
+        this.costRelativeTolerance = costRelativeTolerance;
+        this.parRelativeTolerance = parRelativeTolerance;
+        this.orthoTolerance = orthoTolerance;
+        this.qrRankingThreshold = qrRankingThreshold;
+    }
+
+    /**
+     * @param newInitialStepBoundFactor Positive input variable used in
+     * determining the initial step bound. This bound is set to the
+     * product of initialStepBoundFactor and the euclidean norm of
+     * {@code diag * x} if non-zero, or else to {@code newInitialStepBoundFactor}
+     * itself. In most cases factor should lie in the interval
+     * {@code (0.1, 100.0)}. {@code 100} is a generally recommended value.
+     * of the matrix is reduced.
+     * @return a new instance.
+     */
+    public LevenbergMarquardtOptimizer withInitialStepBoundFactor(double newInitialStepBoundFactor) {
+        return new LevenbergMarquardtOptimizer(
+                newInitialStepBoundFactor,
+                costRelativeTolerance,
+                parRelativeTolerance,
+                orthoTolerance,
+                qrRankingThreshold);
+    }
+
+    /**
+     * @param newCostRelativeTolerance Desired relative error in the sum of squares.
+     * @return a new instance.
+     */
+    public LevenbergMarquardtOptimizer withCostRelativeTolerance(double newCostRelativeTolerance) {
+        return new LevenbergMarquardtOptimizer(
+                initialStepBoundFactor,
+                newCostRelativeTolerance,
+                parRelativeTolerance,
+                orthoTolerance,
+                qrRankingThreshold);
+    }
+
+    /**
+     * @param newParRelativeTolerance Desired relative error in the approximate solution
+     * parameters.
+     * @return a new instance.
+     */
+    public LevenbergMarquardtOptimizer withParameterRelativeTolerance(double newParRelativeTolerance) {
+        return new LevenbergMarquardtOptimizer(
+                initialStepBoundFactor,
+                costRelativeTolerance,
+                newParRelativeTolerance,
+                orthoTolerance,
+                qrRankingThreshold);
+    }
+
+    /**
+     * Modifies the given parameter.
+     *
+     * @param newOrthoTolerance Desired max cosine on the orthogonality between
+     * the function vector and the columns of the Jacobian.
+     * @return a new instance.
+     */
+    public LevenbergMarquardtOptimizer withOrthoTolerance(double newOrthoTolerance) {
+        return new LevenbergMarquardtOptimizer(
+                initialStepBoundFactor,
+                costRelativeTolerance,
+                parRelativeTolerance,
+                newOrthoTolerance,
+                qrRankingThreshold);
+    }
+
+    /**
+     * @param newQRRankingThreshold Desired threshold for QR ranking.
+     * If the squared norm of a column vector is smaller or equal to this
+     * threshold during QR decomposition, it is considered to be a zero vector
+     * and hence the rank of the matrix is reduced.
+     * @return a new instance.
+     */
+    public LevenbergMarquardtOptimizer withRankingThreshold(double newQRRankingThreshold) {
+        return new LevenbergMarquardtOptimizer(
+                initialStepBoundFactor,
+                costRelativeTolerance,
+                parRelativeTolerance,
+                orthoTolerance,
+                newQRRankingThreshold);
+    }
+
+    /**
+     * Gets the value of a tuning parameter.
+     * @see #withInitialStepBoundFactor(double)
+     *
+     * @return the parameter's value.
+     */
+    public double getInitialStepBoundFactor() {
+        return initialStepBoundFactor;
+    }
+
+    /**
+     * Gets the value of a tuning parameter.
+     * @see #withCostRelativeTolerance(double)
+     *
+     * @return the parameter's value.
+     */
+    public double getCostRelativeTolerance() {
+        return costRelativeTolerance;
+    }
+
+    /**
+     * Gets the value of a tuning parameter.
+     * @see #withParameterRelativeTolerance(double)
+     *
+     * @return the parameter's value.
+     */
+    public double getParameterRelativeTolerance() {
+        return parRelativeTolerance;
+    }
+
+    /**
+     * Gets the value of a tuning parameter.
+     * @see #withOrthoTolerance(double)
+     *
+     * @return the parameter's value.
+     */
+    public double getOrthoTolerance() {
+        return orthoTolerance;
+    }
+
+    /**
+     * Gets the value of a tuning parameter.
+     * @see #withRankingThreshold(double)
+     *
+     * @return the parameter's value.
+     */
+    public double getRankingThreshold() {
+        return qrRankingThreshold;
+    }
+
+    /** {@inheritDoc} */
+    public Optimum optimize(final LeastSquaresProblem problem) {
+        // Pull in relevant data from the problem as locals.
+        final int nR = problem.getObservationSize(); // Number of observed data.
+        final int nC = problem.getParameterSize(); // Number of parameters.
+        // Counters.
+        final Incrementor iterationCounter = problem.getIterationCounter();
+        final Incrementor evaluationCounter = problem.getEvaluationCounter();
+        // Convergence criterion.
+        final ConvergenceChecker<Evaluation> checker = problem.getConvergenceChecker();
+
+        // arrays shared with the other private methods
+        final int solvedCols  = FastMath.min(nR, nC);
+        /* Parameters evolution direction associated with lmPar. */
+        double[] lmDir = new double[nC];
+        /* Levenberg-Marquardt parameter. */
+        double lmPar = 0;
+
+        // local point
+        double   delta   = 0;
+        double   xNorm   = 0;
+        double[] diag    = new double[nC];
+        double[] oldX    = new double[nC];
+        double[] oldRes  = new double[nR];
+        double[] qtf     = new double[nR];
+        double[] work1   = new double[nC];
+        double[] work2   = new double[nC];
+        double[] work3   = new double[nC];
+
+
+        // Evaluate the function at the starting point and calculate its norm.
+        evaluationCounter.incrementCount();
+        //value will be reassigned in the loop
+        Evaluation current = problem.evaluate(problem.getStart());
+        double[] currentResiduals = current.getResiduals().toArray();
+        double currentCost = current.getCost();
+        double[] currentPoint = current.getPoint().toArray();
+
+        // Outer loop.
+        boolean firstIteration = true;
+        while (true) {
+            iterationCounter.incrementCount();
+
+            final Evaluation previous = current;
+
+            // QR decomposition of the jacobian matrix
+            final InternalData internalData
+                    = qrDecomposition(current.getJacobian(), solvedCols);
+            final double[][] weightedJacobian = internalData.weightedJacobian;
+            final int[] permutation = internalData.permutation;
+            final double[] diagR = internalData.diagR;
+            final double[] jacNorm = internalData.jacNorm;
+
+            //residuals already have weights applied
+            double[] weightedResidual = currentResiduals;
+            for (int i = 0; i < nR; i++) {
+                qtf[i] = weightedResidual[i];
+            }
+
+            // compute Qt.res
+            qTy(qtf, internalData);
+
+            // now we don't need Q anymore,
+            // so let jacobian contain the R matrix with its diagonal elements
+            for (int k = 0; k < solvedCols; ++k) {
+                int pk = permutation[k];
+                weightedJacobian[k][pk] = diagR[pk];
+            }
+
+            if (firstIteration) {
+                // scale the point according to the norms of the columns
+                // of the initial jacobian
+                xNorm = 0;
+                for (int k = 0; k < nC; ++k) {
+                    double dk = jacNorm[k];
+                    if (dk == 0) {
+                        dk = 1.0;
+                    }
+                    double xk = dk * currentPoint[k];
+                    xNorm  += xk * xk;
+                    diag[k] = dk;
+                }
+                xNorm = FastMath.sqrt(xNorm);
+
+                // initialize the step bound delta
+                delta = (xNorm == 0) ? initialStepBoundFactor : (initialStepBoundFactor * xNorm);
+            }
+
+            // check orthogonality between function vector and jacobian columns
+            double maxCosine = 0;
+            if (currentCost != 0) {
+                for (int j = 0; j < solvedCols; ++j) {
+                    int    pj = permutation[j];
+                    double s  = jacNorm[pj];
+                    if (s != 0) {
+                        double sum = 0;
+                        for (int i = 0; i <= j; ++i) {
+                            sum += weightedJacobian[i][pj] * qtf[i];
+                        }
+                        maxCosine = FastMath.max(maxCosine, FastMath.abs(sum) / (s * currentCost));
+                    }
+                }
+            }
+            if (maxCosine <= orthoTolerance) {
+                // Convergence has been reached.
+                return new OptimumImpl(
+                        current,
+                        evaluationCounter.getCount(),
+                        iterationCounter.getCount());
+            }
+
+            // rescale if necessary
+            for (int j = 0; j < nC; ++j) {
+                diag[j] = FastMath.max(diag[j], jacNorm[j]);
+            }
+
+            // Inner loop.
+            for (double ratio = 0; ratio < 1.0e-4;) {
+
+                // save the state
+                for (int j = 0; j < solvedCols; ++j) {
+                    int pj = permutation[j];
+                    oldX[pj] = currentPoint[pj];
+                }
+                final double previousCost = currentCost;
+                double[] tmpVec = weightedResidual;
+                weightedResidual = oldRes;
+                oldRes    = tmpVec;
+
+                // determine the Levenberg-Marquardt parameter
+                lmPar = determineLMParameter(qtf, delta, diag,
+                                     internalData, solvedCols,
+                                     work1, work2, work3, lmDir, lmPar);
+
+                // compute the new point and the norm of the evolution direction
+                double lmNorm = 0;
+                for (int j = 0; j < solvedCols; ++j) {
+                    int pj = permutation[j];
+                    lmDir[pj] = -lmDir[pj];
+                    currentPoint[pj] = oldX[pj] + lmDir[pj];
+                    double s = diag[pj] * lmDir[pj];
+                    lmNorm  += s * s;
+                }
+                lmNorm = FastMath.sqrt(lmNorm);
+                // on the first iteration, adjust the initial step bound.
+                if (firstIteration) {
+                    delta = FastMath.min(delta, lmNorm);
+                }
+
+                // Evaluate the function at x + p and calculate its norm.
+                evaluationCounter.incrementCount();
+                current = problem.evaluate(new ArrayRealVector(currentPoint));
+                currentResiduals = current.getResiduals().toArray();
+                currentCost = current.getCost();
+                currentPoint = current.getPoint().toArray();
+
+                // compute the scaled actual reduction
+                double actRed = -1.0;
+                if (0.1 * currentCost < previousCost) {
+                    double r = currentCost / previousCost;
+                    actRed = 1.0 - r * r;
+                }
+
+                // compute the scaled predicted reduction
+                // and the scaled directional derivative
+                for (int j = 0; j < solvedCols; ++j) {
+                    int pj = permutation[j];
+                    double dirJ = lmDir[pj];
+                    work1[j] = 0;
+                    for (int i = 0; i <= j; ++i) {
+                        work1[i] += weightedJacobian[i][pj] * dirJ;
+                    }
+                }
+                double coeff1 = 0;
+                for (int j = 0; j < solvedCols; ++j) {
+                    coeff1 += work1[j] * work1[j];
+                }
+                double pc2 = previousCost * previousCost;
+                coeff1 /= pc2;
+                double coeff2 = lmPar * lmNorm * lmNorm / pc2;
+                double preRed = coeff1 + 2 * coeff2;
+                double dirDer = -(coeff1 + coeff2);
+
+                // ratio of the actual to the predicted reduction
+                ratio = (preRed == 0) ? 0 : (actRed / preRed);
+
+                // update the step bound
+                if (ratio <= 0.25) {
+                    double tmp =
+                        (actRed < 0) ? (0.5 * dirDer / (dirDer + 0.5 * actRed)) : 0.5;
+                        if ((0.1 * currentCost >= previousCost) || (tmp < 0.1)) {
+                            tmp = 0.1;
+                        }
+                        delta = tmp * FastMath.min(delta, 10.0 * lmNorm);
+                        lmPar /= tmp;
+                } else if ((lmPar == 0) || (ratio >= 0.75)) {
+                    delta = 2 * lmNorm;
+                    lmPar *= 0.5;
+                }
+
+                // test for successful iteration.
+                if (ratio >= 1.0e-4) {
+                    // successful iteration, update the norm
+                    firstIteration = false;
+                    xNorm = 0;
+                    for (int k = 0; k < nC; ++k) {
+                        double xK = diag[k] * currentPoint[k];
+                        xNorm += xK * xK;
+                    }
+                    xNorm = FastMath.sqrt(xNorm);
+
+                    // tests for convergence.
+                    if (checker != null && checker.converged(iterationCounter.getCount(), previous, current)) {
+                        return new OptimumImpl(current, evaluationCounter.getCount(), iterationCounter.getCount());
+                    }
+                } else {
+                    // failed iteration, reset the previous values
+                    currentCost = previousCost;
+                    for (int j = 0; j < solvedCols; ++j) {
+                        int pj = permutation[j];
+                        currentPoint[pj] = oldX[pj];
+                    }
+                    tmpVec    = weightedResidual;
+                    weightedResidual = oldRes;
+                    oldRes    = tmpVec;
+                    // Reset "current" to previous values.
+                    current = previous;
+                }
+
+                // Default convergence criteria.
+                if ((FastMath.abs(actRed) <= costRelativeTolerance &&
+                     preRed <= costRelativeTolerance &&
+                     ratio <= 2.0) ||
+                    delta <= parRelativeTolerance * xNorm) {
+                    return new OptimumImpl(current, evaluationCounter.getCount(), iterationCounter.getCount());
+                }
+
+                // tests for termination and stringent tolerances
+                if (FastMath.abs(actRed) <= TWO_EPS &&
+                    preRed <= TWO_EPS &&
+                    ratio <= 2.0) {
+                    throw new ConvergenceException(LocalizedFormats.TOO_SMALL_COST_RELATIVE_TOLERANCE,
+                                                   costRelativeTolerance);
+                } else if (delta <= TWO_EPS * xNorm) {
+                    throw new ConvergenceException(LocalizedFormats.TOO_SMALL_PARAMETERS_RELATIVE_TOLERANCE,
+                                                   parRelativeTolerance);
+                } else if (maxCosine <= TWO_EPS) {
+                    throw new ConvergenceException(LocalizedFormats.TOO_SMALL_ORTHOGONALITY_TOLERANCE,
+                                                   orthoTolerance);
+                }
+            }
+        }
+    }
+
+    /**
+     * Holds internal data.
+     * This structure was created so that all optimizer fields can be "final".
+     * Code should be further refactored in order to not pass around arguments
+     * that will modified in-place (cf. "work" arrays).
+     */
+    private static class InternalData {
+        /** Weighted Jacobian. */
+        private final double[][] weightedJacobian;
+        /** Columns permutation array. */
+        private final int[] permutation;
+        /** Rank of the Jacobian matrix. */
+        private final int rank;
+        /** Diagonal elements of the R matrix in the QR decomposition. */
+        private final double[] diagR;
+        /** Norms of the columns of the jacobian matrix. */
+        private final double[] jacNorm;
+        /** Coefficients of the Householder transforms vectors. */
+        private final double[] beta;
+
+        /**
+         * @param weightedJacobian Weighted Jacobian.
+         * @param permutation Columns permutation array.
+         * @param rank Rank of the Jacobian matrix.
+         * @param diagR Diagonal elements of the R matrix in the QR decomposition.
+         * @param jacNorm Norms of the columns of the jacobian matrix.
+         * @param beta Coefficients of the Householder transforms vectors.
+         */
+        InternalData(double[][] weightedJacobian,
+                     int[] permutation,
+                     int rank,
+                     double[] diagR,
+                     double[] jacNorm,
+                     double[] beta) {
+            this.weightedJacobian = weightedJacobian;
+            this.permutation = permutation;
+            this.rank = rank;
+            this.diagR = diagR;
+            this.jacNorm = jacNorm;
+            this.beta = beta;
+        }
+    }
+
+    /**
+     * Determines the Levenberg-Marquardt parameter.
+     *
+     * <p>This implementation is a translation in Java of the MINPACK
+     * <a href="http://www.netlib.org/minpack/lmpar.f">lmpar</a>
+     * routine.</p>
+     * <p>This method sets the lmPar and lmDir attributes.</p>
+     * <p>The authors of the original fortran function are:</p>
+     * <ul>
+     *   <li>Argonne National Laboratory. MINPACK project. March 1980</li>
+     *   <li>Burton  S. Garbow</li>
+     *   <li>Kenneth E. Hillstrom</li>
+     *   <li>Jorge   J. More</li>
+     * </ul>
+     * <p>Luc Maisonobe did the Java translation.</p>
+     *
+     * @param qy Array containing qTy.
+     * @param delta Upper bound on the euclidean norm of diagR * lmDir.
+     * @param diag Diagonal matrix.
+     * @param internalData Data (modified in-place in this method).
+     * @param solvedCols Number of solved point.
+     * @param work1 work array
+     * @param work2 work array
+     * @param work3 work array
+     * @param lmDir the "returned" LM direction will be stored in this array.
+     * @param lmPar the value of the LM parameter from the previous iteration.
+     * @return the new LM parameter
+     */
+    private double determineLMParameter(double[] qy, double delta, double[] diag,
+                                      InternalData internalData, int solvedCols,
+                                      double[] work1, double[] work2, double[] work3,
+                                      double[] lmDir, double lmPar) {
+        final double[][] weightedJacobian = internalData.weightedJacobian;
+        final int[] permutation = internalData.permutation;
+        final int rank = internalData.rank;
+        final double[] diagR = internalData.diagR;
+
+        final int nC = weightedJacobian[0].length;
+
+        // compute and store in x the gauss-newton direction, if the
+        // jacobian is rank-deficient, obtain a least squares solution
+        for (int j = 0; j < rank; ++j) {
+            lmDir[permutation[j]] = qy[j];
+        }
+        for (int j = rank; j < nC; ++j) {
+            lmDir[permutation[j]] = 0;
+        }
+        for (int k = rank - 1; k >= 0; --k) {
+            int pk = permutation[k];
+            double ypk = lmDir[pk] / diagR[pk];
+            for (int i = 0; i < k; ++i) {
+                lmDir[permutation[i]] -= ypk * weightedJacobian[i][pk];
+            }
+            lmDir[pk] = ypk;
+        }
+
+        // evaluate the function at the origin, and test
+        // for acceptance of the Gauss-Newton direction
+        double dxNorm = 0;
+        for (int j = 0; j < solvedCols; ++j) {
+            int pj = permutation[j];
+            double s = diag[pj] * lmDir[pj];
+            work1[pj] = s;
+            dxNorm += s * s;
+        }
+        dxNorm = FastMath.sqrt(dxNorm);
+        double fp = dxNorm - delta;
+        if (fp <= 0.1 * delta) {
+            lmPar = 0;
+            return lmPar;
+        }
+
+        // if the jacobian is not rank deficient, the Newton step provides
+        // a lower bound, parl, for the zero of the function,
+        // otherwise set this bound to zero
+        double sum2;
+        double parl = 0;
+        if (rank == solvedCols) {
+            for (int j = 0; j < solvedCols; ++j) {
+                int pj = permutation[j];
+                work1[pj] *= diag[pj] / dxNorm;
+            }
+            sum2 = 0;
+            for (int j = 0; j < solvedCols; ++j) {
+                int pj = permutation[j];
+                double sum = 0;
+                for (int i = 0; i < j; ++i) {
+                    sum += weightedJacobian[i][pj] * work1[permutation[i]];
+                }
+                double s = (work1[pj] - sum) / diagR[pj];
+                work1[pj] = s;
+                sum2 += s * s;
+            }
+            parl = fp / (delta * sum2);
+        }
+
+        // calculate an upper bound, paru, for the zero of the function
+        sum2 = 0;
+        for (int j = 0; j < solvedCols; ++j) {
+            int pj = permutation[j];
+            double sum = 0;
+            for (int i = 0; i <= j; ++i) {
+                sum += weightedJacobian[i][pj] * qy[i];
+            }
+            sum /= diag[pj];
+            sum2 += sum * sum;
+        }
+        double gNorm = FastMath.sqrt(sum2);
+        double paru = gNorm / delta;
+        if (paru == 0) {
+            paru = Precision.SAFE_MIN / FastMath.min(delta, 0.1);
+        }
+
+        // if the input par lies outside of the interval (parl,paru),
+        // set par to the closer endpoint
+        lmPar = FastMath.min(paru, FastMath.max(lmPar, parl));
+        if (lmPar == 0) {
+            lmPar = gNorm / dxNorm;
+        }
+
+        for (int countdown = 10; countdown >= 0; --countdown) {
+
+            // evaluate the function at the current value of lmPar
+            if (lmPar == 0) {
+                lmPar = FastMath.max(Precision.SAFE_MIN, 0.001 * paru);
+            }
+            double sPar = FastMath.sqrt(lmPar);
+            for (int j = 0; j < solvedCols; ++j) {
+                int pj = permutation[j];
+                work1[pj] = sPar * diag[pj];
+            }
+            determineLMDirection(qy, work1, work2, internalData, solvedCols, work3, lmDir);
+
+            dxNorm = 0;
+            for (int j = 0; j < solvedCols; ++j) {
+                int pj = permutation[j];
+                double s = diag[pj] * lmDir[pj];
+                work3[pj] = s;
+                dxNorm += s * s;
+            }
+            dxNorm = FastMath.sqrt(dxNorm);
+            double previousFP = fp;
+            fp = dxNorm - delta;
+
+            // if the function is small enough, accept the current value
+            // of lmPar, also test for the exceptional cases where parl is zero
+            if (FastMath.abs(fp) <= 0.1 * delta ||
+                (parl == 0 &&
+                 fp <= previousFP &&
+                 previousFP < 0)) {
+                return lmPar;
+            }
+
+            // compute the Newton correction
+            for (int j = 0; j < solvedCols; ++j) {
+                int pj = permutation[j];
+                work1[pj] = work3[pj] * diag[pj] / dxNorm;
+            }
+            for (int j = 0; j < solvedCols; ++j) {
+                int pj = permutation[j];
+                work1[pj] /= work2[j];
+                double tmp = work1[pj];
+                for (int i = j + 1; i < solvedCols; ++i) {
+                    work1[permutation[i]] -= weightedJacobian[i][pj] * tmp;
+                }
+            }
+            sum2 = 0;
+            for (int j = 0; j < solvedCols; ++j) {
+                double s = work1[permutation[j]];
+                sum2 += s * s;
+            }
+            double correction = fp / (delta * sum2);
+
+            // depending on the sign of the function, update parl or paru.
+            if (fp > 0) {
+                parl = FastMath.max(parl, lmPar);
+            } else if (fp < 0) {
+                paru = FastMath.min(paru, lmPar);
+            }
+
+            // compute an improved estimate for lmPar
+            lmPar = FastMath.max(parl, lmPar + correction);
+        }
+
+        return lmPar;
+    }
+
+    /**
+     * Solve a*x = b and d*x = 0 in the least squares sense.
+     * <p>This implementation is a translation in Java of the MINPACK
+     * <a href="http://www.netlib.org/minpack/qrsolv.f">qrsolv</a>
+     * routine.</p>
+     * <p>This method sets the lmDir and lmDiag attributes.</p>
+     * <p>The authors of the original fortran function are:</p>
+     * <ul>
+     *   <li>Argonne National Laboratory. MINPACK project. March 1980</li>
+     *   <li>Burton  S. Garbow</li>
+     *   <li>Kenneth E. Hillstrom</li>
+     *   <li>Jorge   J. More</li>
+     * </ul>
+     * <p>Luc Maisonobe did the Java translation.</p>
+     *
+     * @param qy array containing qTy
+     * @param diag diagonal matrix
+     * @param lmDiag diagonal elements associated with lmDir
+     * @param internalData Data (modified in-place in this method).
+     * @param solvedCols Number of sloved point.
+     * @param work work array
+     * @param lmDir the "returned" LM direction is stored in this array
+     */
+    private void determineLMDirection(double[] qy, double[] diag,
+                                      double[] lmDiag,
+                                      InternalData internalData,
+                                      int solvedCols,
+                                      double[] work,
+                                      double[] lmDir) {
+        final int[] permutation = internalData.permutation;
+        final double[][] weightedJacobian = internalData.weightedJacobian;
+        final double[] diagR = internalData.diagR;
+
+        // copy R and Qty to preserve input and initialize s
+        //  in particular, save the diagonal elements of R in lmDir
+        for (int j = 0; j < solvedCols; ++j) {
+            int pj = permutation[j];
+            for (int i = j + 1; i < solvedCols; ++i) {
+                weightedJacobian[i][pj] = weightedJacobian[j][permutation[i]];
+            }
+            lmDir[j] = diagR[pj];
+            work[j]  = qy[j];
+        }
+
+        // eliminate the diagonal matrix d using a Givens rotation
+        for (int j = 0; j < solvedCols; ++j) {
+
+            // prepare the row of d to be eliminated, locating the
+            // diagonal element using p from the Q.R. factorization
+            int pj = permutation[j];
+            double dpj = diag[pj];
+            if (dpj != 0) {
+                Arrays.fill(lmDiag, j + 1, lmDiag.length, 0);
+            }
+            lmDiag[j] = dpj;
+
+            //  the transformations to eliminate the row of d
+            // modify only a single element of Qty
+            // beyond the first n, which is initially zero.
+            double qtbpj = 0;
+            for (int k = j; k < solvedCols; ++k) {
+                int pk = permutation[k];
+
+                // determine a Givens rotation which eliminates the
+                // appropriate element in the current row of d
+                if (lmDiag[k] != 0) {
+
+                    final double sin;
+                    final double cos;
+                    double rkk = weightedJacobian[k][pk];
+                    if (FastMath.abs(rkk) < FastMath.abs(lmDiag[k])) {
+                        final double cotan = rkk / lmDiag[k];
+                        sin   = 1.0 / FastMath.sqrt(1.0 + cotan * cotan);
+                        cos   = sin * cotan;
+                    } else {
+                        final double tan = lmDiag[k] / rkk;
+                        cos = 1.0 / FastMath.sqrt(1.0 + tan * tan);
+                        sin = cos * tan;
+                    }
+
+                    // compute the modified diagonal element of R and
+                    // the modified element of (Qty,0)
+                    weightedJacobian[k][pk] = cos * rkk + sin * lmDiag[k];
+                    final double temp = cos * work[k] + sin * qtbpj;
+                    qtbpj = -sin * work[k] + cos * qtbpj;
+                    work[k] = temp;
+
+                    // accumulate the tranformation in the row of s
+                    for (int i = k + 1; i < solvedCols; ++i) {
+                        double rik = weightedJacobian[i][pk];
+                        final double temp2 = cos * rik + sin * lmDiag[i];
+                        lmDiag[i] = -sin * rik + cos * lmDiag[i];
+                        weightedJacobian[i][pk] = temp2;
+                    }
+                }
+            }
+
+            // store the diagonal element of s and restore
+            // the corresponding diagonal element of R
+            lmDiag[j] = weightedJacobian[j][permutation[j]];
+            weightedJacobian[j][permutation[j]] = lmDir[j];
+        }
+
+        // solve the triangular system for z, if the system is
+        // singular, then obtain a least squares solution
+        int nSing = solvedCols;
+        for (int j = 0; j < solvedCols; ++j) {
+            if ((lmDiag[j] == 0) && (nSing == solvedCols)) {
+                nSing = j;
+            }
+            if (nSing < solvedCols) {
+                work[j] = 0;
+            }
+        }
+        if (nSing > 0) {
+            for (int j = nSing - 1; j >= 0; --j) {
+                int pj = permutation[j];
+                double sum = 0;
+                for (int i = j + 1; i < nSing; ++i) {
+                    sum += weightedJacobian[i][pj] * work[i];
+                }
+                work[j] = (work[j] - sum) / lmDiag[j];
+            }
+        }
+
+        // permute the components of z back to components of lmDir
+        for (int j = 0; j < lmDir.length; ++j) {
+            lmDir[permutation[j]] = work[j];
+        }
+    }
+
+    /**
+     * Decompose a matrix A as A.P = Q.R using Householder transforms.
+     * <p>As suggested in the P. Lascaux and R. Theodor book
+     * <i>Analyse num&eacute;rique matricielle appliqu&eacute;e &agrave;
+     * l'art de l'ing&eacute;nieur</i> (Masson, 1986), instead of representing
+     * the Householder transforms with u<sub>k</sub> unit vectors such that:
+     * <pre>
+     * H<sub>k</sub> = I - 2u<sub>k</sub>.u<sub>k</sub><sup>t</sup>
+     * </pre>
+     * we use <sub>k</sub> non-unit vectors such that:
+     * <pre>
+     * H<sub>k</sub> = I - beta<sub>k</sub>v<sub>k</sub>.v<sub>k</sub><sup>t</sup>
+     * </pre>
+     * where v<sub>k</sub> = a<sub>k</sub> - alpha<sub>k</sub> e<sub>k</sub>.
+     * The beta<sub>k</sub> coefficients are provided upon exit as recomputing
+     * them from the v<sub>k</sub> vectors would be costly.</p>
+     * <p>This decomposition handles rank deficient cases since the tranformations
+     * are performed in non-increasing columns norms order thanks to columns
+     * pivoting. The diagonal elements of the R matrix are therefore also in
+     * non-increasing absolute values order.</p>
+     *
+     * @param jacobian Weighted Jacobian matrix at the current point.
+     * @param solvedCols Number of solved point.
+     * @return data used in other methods of this class.
+     * @throws ConvergenceException if the decomposition cannot be performed.
+     */
+    private InternalData qrDecomposition(RealMatrix jacobian,
+                                         int solvedCols) throws ConvergenceException {
+        // Code in this class assumes that the weighted Jacobian is -(W^(1/2) J),
+        // hence the multiplication by -1.
+        final double[][] weightedJacobian = jacobian.scalarMultiply(-1).getData();
+
+        final int nR = weightedJacobian.length;
+        final int nC = weightedJacobian[0].length;
+
+        final int[] permutation = new int[nC];
+        final double[] diagR = new double[nC];
+        final double[] jacNorm = new double[nC];
+        final double[] beta = new double[nC];
+
+        // initializations
+        for (int k = 0; k < nC; ++k) {
+            permutation[k] = k;
+            double norm2 = 0;
+            for (int i = 0; i < nR; ++i) {
+                double akk = weightedJacobian[i][k];
+                norm2 += akk * akk;
+            }
+            jacNorm[k] = FastMath.sqrt(norm2);
+        }
+
+        // transform the matrix column after column
+        for (int k = 0; k < nC; ++k) {
+
+            // select the column with the greatest norm on active components
+            int nextColumn = -1;
+            double ak2 = Double.NEGATIVE_INFINITY;
+            for (int i = k; i < nC; ++i) {
+                double norm2 = 0;
+                for (int j = k; j < nR; ++j) {
+                    double aki = weightedJacobian[j][permutation[i]];
+                    norm2 += aki * aki;
+                }
+                if (Double.isInfinite(norm2) || Double.isNaN(norm2)) {
+                    throw new ConvergenceException(LocalizedFormats.UNABLE_TO_PERFORM_QR_DECOMPOSITION_ON_JACOBIAN,
+                                                   nR, nC);
+                }
+                if (norm2 > ak2) {
+                    nextColumn = i;
+                    ak2        = norm2;
+                }
+            }
+            if (ak2 <= qrRankingThreshold) {
+                return new InternalData(weightedJacobian, permutation, k, diagR, jacNorm, beta);
+            }
+            int pk = permutation[nextColumn];
+            permutation[nextColumn] = permutation[k];
+            permutation[k] = pk;
+
+            // choose alpha such that Hk.u = alpha ek
+            double akk = weightedJacobian[k][pk];
+            double alpha = (akk > 0) ? -FastMath.sqrt(ak2) : FastMath.sqrt(ak2);
+            double betak = 1.0 / (ak2 - akk * alpha);
+            beta[pk] = betak;
+
+            // transform the current column
+            diagR[pk] = alpha;
+            weightedJacobian[k][pk] -= alpha;
+
+            // transform the remaining columns
+            for (int dk = nC - 1 - k; dk > 0; --dk) {
+                double gamma = 0;
+                for (int j = k; j < nR; ++j) {
+                    gamma += weightedJacobian[j][pk] * weightedJacobian[j][permutation[k + dk]];
+                }
+                gamma *= betak;
+                for (int j = k; j < nR; ++j) {
+                    weightedJacobian[j][permutation[k + dk]] -= gamma * weightedJacobian[j][pk];
+                }
+            }
+        }
+
+        return new InternalData(weightedJacobian, permutation, solvedCols, diagR, jacNorm, beta);
+    }
+
+    /**
+     * Compute the product Qt.y for some Q.R. decomposition.
+     *
+     * @param y vector to multiply (will be overwritten with the result)
+     * @param internalData Data.
+     */
+    private void qTy(double[] y,
+                     InternalData internalData) {
+        final double[][] weightedJacobian = internalData.weightedJacobian;
+        final int[] permutation = internalData.permutation;
+        final double[] beta = internalData.beta;
+
+        final int nR = weightedJacobian.length;
+        final int nC = weightedJacobian[0].length;
+
+        for (int k = 0; k < nC; ++k) {
+            int pk = permutation[k];
+            double gamma = 0;
+            for (int i = k; i < nR; ++i) {
+                gamma += weightedJacobian[i][pk] * y[i];
+            }
+            gamma *= beta[pk];
+            for (int i = k; i < nR; ++i) {
+                y[i] -= gamma * weightedJacobian[i][pk];
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/fitting/leastsquares/MultivariateJacobianFunction.java b/src/main/java/org/apache/commons/math3/fitting/leastsquares/MultivariateJacobianFunction.java
new file mode 100644
index 0000000..e673855
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fitting/leastsquares/MultivariateJacobianFunction.java
@@ -0,0 +1,39 @@
+/*
+ * 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.math3.fitting.leastsquares;
+
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.linear.RealVector;
+import org.apache.commons.math3.util.Pair;
+
+/**
+ * A interface for functions that compute a vector of values and can compute their
+ * derivatives (Jacobian).
+ *
+ * @since 3.3
+ */
+public interface MultivariateJacobianFunction {
+
+    /**
+     * Compute the function value and its Jacobian.
+     *
+     * @param point the abscissae
+     * @return the values and their Jacobian of this vector valued function.
+     */
+    Pair<RealVector, RealMatrix> value(RealVector point);
+
+}
diff --git a/src/main/java/org/apache/commons/math3/fitting/leastsquares/OptimumImpl.java b/src/main/java/org/apache/commons/math3/fitting/leastsquares/OptimumImpl.java
new file mode 100644
index 0000000..698f86c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fitting/leastsquares/OptimumImpl.java
@@ -0,0 +1,97 @@
+/*
+ * 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.math3.fitting.leastsquares;
+
+import org.apache.commons.math3.fitting.leastsquares.LeastSquaresOptimizer.Optimum;
+import org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem.Evaluation;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.linear.RealVector;
+
+/**
+ * A pedantic implementation of {@link Optimum}.
+ *
+ * @since 3.3
+ */
+class OptimumImpl implements Optimum {
+
+    /** abscissa and ordinate */
+    private final Evaluation value;
+    /** number of evaluations to compute this optimum */
+    private final int evaluations;
+    /** number of iterations to compute this optimum */
+    private final int iterations;
+
+    /**
+     * Construct an optimum from an evaluation and the values of the counters.
+     *
+     * @param value       the function value
+     * @param evaluations number of times the function was evaluated
+     * @param iterations  number of iterations of the algorithm
+     */
+    OptimumImpl(final Evaluation value, final int evaluations, final int iterations) {
+        this.value = value;
+        this.evaluations = evaluations;
+        this.iterations = iterations;
+    }
+
+    /* auto-generated implementations */
+
+    /** {@inheritDoc} */
+    public int getEvaluations() {
+        return evaluations;
+    }
+
+    /** {@inheritDoc} */
+    public int getIterations() {
+        return iterations;
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix getCovariances(double threshold) {
+        return value.getCovariances(threshold);
+    }
+
+    /** {@inheritDoc} */
+    public RealVector getSigma(double covarianceSingularityThreshold) {
+        return value.getSigma(covarianceSingularityThreshold);
+    }
+
+    /** {@inheritDoc} */
+    public double getRMS() {
+        return value.getRMS();
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix getJacobian() {
+        return value.getJacobian();
+    }
+
+    /** {@inheritDoc} */
+    public double getCost() {
+        return value.getCost();
+    }
+
+    /** {@inheritDoc} */
+    public RealVector getResiduals() {
+        return value.getResiduals();
+    }
+
+    /** {@inheritDoc} */
+    public RealVector getPoint() {
+        return value.getPoint();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/fitting/leastsquares/ParameterValidator.java b/src/main/java/org/apache/commons/math3/fitting/leastsquares/ParameterValidator.java
new file mode 100644
index 0000000..d5b8529
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fitting/leastsquares/ParameterValidator.java
@@ -0,0 +1,34 @@
+/*
+ * 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.math3.fitting.leastsquares;
+
+import org.apache.commons.math3.linear.RealVector;
+
+/**
+ * Interface for validating a set of model parameters.
+ *
+ * @since 3.4
+ */
+public interface ParameterValidator {
+    /**
+     * Validates the set of parameters.
+     *
+     * @param params Input parameters.
+     * @return the validated values.
+     */
+    RealVector validate(RealVector params);
+}
diff --git a/src/main/java/org/apache/commons/math3/fitting/leastsquares/ValueAndJacobianFunction.java b/src/main/java/org/apache/commons/math3/fitting/leastsquares/ValueAndJacobianFunction.java
new file mode 100644
index 0000000..180e328
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fitting/leastsquares/ValueAndJacobianFunction.java
@@ -0,0 +1,44 @@
+/*
+ * 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.math3.fitting.leastsquares;
+
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.linear.RealVector;
+
+/**
+ * A interface for functions that compute a vector of values and can compute their
+ * derivatives (Jacobian).
+ *
+ * @since 3.4
+ */
+public interface ValueAndJacobianFunction extends MultivariateJacobianFunction {
+    /**
+     * Compute the value.
+     *
+     * @param params Point.
+     * @return the value at the given point.
+     */
+    RealVector computeValue(final double[] params);
+
+    /**
+     * Compute the Jacobian.
+     *
+     * @param params Point.
+     * @return the Jacobian at the given point.
+     */
+    RealMatrix computeJacobian(final double[] params);
+}
diff --git a/src/main/java/org/apache/commons/math3/fitting/leastsquares/package-info.java b/src/main/java/org/apache/commons/math3/fitting/leastsquares/package-info.java
new file mode 100644
index 0000000..98623b5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fitting/leastsquares/package-info.java
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+/**
+ * This package provides algorithms that minimize the residuals
+ * between observations and model values.
+ * The {@link org.apache.commons.math3.fitting.leastsquares.LeastSquaresOptimizer
+ * least-squares optimizers} minimize the distance (called
+ * <em>cost</em> or <em>&chi;<sup>2</sup></em>) between model and
+ * observations.
+ *
+ * <br/>
+ * Algorithms in this category need access to a <em>problem</em>
+ * (represented by a {@link org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem
+ * LeastSquaresProblem}).
+ * Such a model predicts a set of values which the algorithm tries to match
+ * with a set of given set of observed values.
+ * <br/>
+ * The problem can be created progressively using a {@link
+ * org.apache.commons.math3.fitting.leastsquares.LeastSquaresBuilder builder} or it can
+ * be created at once using a {@link org.apache.commons.math3.fitting.leastsquares.LeastSquaresFactory
+ * factory}.
+ * @since 3.3
+ */
+package org.apache.commons.math3.fitting.leastsquares;
diff --git a/src/main/java/org/apache/commons/math3/fitting/package-info.java b/src/main/java/org/apache/commons/math3/fitting/package-info.java
new file mode 100644
index 0000000..af00a6a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fitting/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+/**
+ * Classes to perform curve fitting.
+ *
+ * <p>Curve fitting is a special case of a least-squares problem where the parameters are the
+ * coefficients of a function \( f \) whose graph \( y = f(x) \) should pass through sample points,
+ * and were the objective function is the squared sum of the residuals \( f(x_i) - y_i \) for
+ * observed points \( (x_i, y_i) \).
+ */
+package org.apache.commons.math3.fitting;
diff --git a/src/main/java/org/apache/commons/math3/fraction/AbstractFormat.java b/src/main/java/org/apache/commons/math3/fraction/AbstractFormat.java
new file mode 100644
index 0000000..c17d127
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fraction/AbstractFormat.java
@@ -0,0 +1,216 @@
+/*
+ * 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.math3.fraction;
+
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+import java.io.Serializable;
+import java.text.FieldPosition;
+import java.text.NumberFormat;
+import java.text.ParsePosition;
+import java.util.Locale;
+
+/**
+ * Common part shared by both {@link FractionFormat} and {@link BigFractionFormat}.
+ *
+ * @since 2.0
+ */
+public abstract class AbstractFormat extends NumberFormat implements Serializable {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -6981118387974191891L;
+
+    /** The format used for the denominator. */
+    private NumberFormat denominatorFormat;
+
+    /** The format used for the numerator. */
+    private NumberFormat numeratorFormat;
+
+    /**
+     * Create an improper formatting instance with the default number format for the numerator and
+     * denominator.
+     */
+    protected AbstractFormat() {
+        this(getDefaultNumberFormat());
+    }
+
+    /**
+     * Create an improper formatting instance with a custom number format for both the numerator and
+     * denominator.
+     *
+     * @param format the custom format for both the numerator and denominator.
+     */
+    protected AbstractFormat(final NumberFormat format) {
+        this(format, (NumberFormat) format.clone());
+    }
+
+    /**
+     * Create an improper formatting instance with a custom number format for the numerator and a
+     * custom number format for the denominator.
+     *
+     * @param numeratorFormat the custom format for the numerator.
+     * @param denominatorFormat the custom format for the denominator.
+     */
+    protected AbstractFormat(
+            final NumberFormat numeratorFormat, final NumberFormat denominatorFormat) {
+        this.numeratorFormat = numeratorFormat;
+        this.denominatorFormat = denominatorFormat;
+    }
+
+    /**
+     * Create a default number format. The default number format is based on {@link
+     * NumberFormat#getNumberInstance(java.util.Locale)}. The only customization is the maximum
+     * number of BigFraction digits, which is set to 0.
+     *
+     * @return the default number format.
+     */
+    protected static NumberFormat getDefaultNumberFormat() {
+        return getDefaultNumberFormat(Locale.getDefault());
+    }
+
+    /**
+     * Create a default number format. The default number format is based on {@link
+     * NumberFormat#getNumberInstance(java.util.Locale)}. The only customization is the maximum
+     * number of BigFraction digits, which is set to 0.
+     *
+     * @param locale the specific locale used by the format.
+     * @return the default number format specific to the given locale.
+     */
+    protected static NumberFormat getDefaultNumberFormat(final Locale locale) {
+        final NumberFormat nf = NumberFormat.getNumberInstance(locale);
+        nf.setMaximumFractionDigits(0);
+        nf.setParseIntegerOnly(true);
+        return nf;
+    }
+
+    /**
+     * Access the denominator format.
+     *
+     * @return the denominator format.
+     */
+    public NumberFormat getDenominatorFormat() {
+        return denominatorFormat;
+    }
+
+    /**
+     * Access the numerator format.
+     *
+     * @return the numerator format.
+     */
+    public NumberFormat getNumeratorFormat() {
+        return numeratorFormat;
+    }
+
+    /**
+     * Modify the denominator format.
+     *
+     * @param format the new denominator format value.
+     * @throws NullArgumentException if {@code format} is {@code null}.
+     */
+    public void setDenominatorFormat(final NumberFormat format) {
+        if (format == null) {
+            throw new NullArgumentException(LocalizedFormats.DENOMINATOR_FORMAT);
+        }
+        this.denominatorFormat = format;
+    }
+
+    /**
+     * Modify the numerator format.
+     *
+     * @param format the new numerator format value.
+     * @throws NullArgumentException if {@code format} is {@code null}.
+     */
+    public void setNumeratorFormat(final NumberFormat format) {
+        if (format == null) {
+            throw new NullArgumentException(LocalizedFormats.NUMERATOR_FORMAT);
+        }
+        this.numeratorFormat = format;
+    }
+
+    /**
+     * Parses <code>source</code> until a non-whitespace character is found.
+     *
+     * @param source the string to parse
+     * @param pos input/output parsing parameter. On output, <code>pos</code> holds the index of the
+     *     next non-whitespace character.
+     */
+    protected static void parseAndIgnoreWhitespace(final String source, final ParsePosition pos) {
+        parseNextCharacter(source, pos);
+        pos.setIndex(pos.getIndex() - 1);
+    }
+
+    /**
+     * Parses <code>source</code> until a non-whitespace character is found.
+     *
+     * @param source the string to parse
+     * @param pos input/output parsing parameter.
+     * @return the first non-whitespace character.
+     */
+    protected static char parseNextCharacter(final String source, final ParsePosition pos) {
+        int index = pos.getIndex();
+        final int n = source.length();
+        char ret = 0;
+
+        if (index < n) {
+            char c;
+            do {
+                c = source.charAt(index++);
+            } while (Character.isWhitespace(c) && index < n);
+            pos.setIndex(index);
+
+            if (index < n) {
+                ret = c;
+            }
+        }
+
+        return ret;
+    }
+
+    /**
+     * Formats a double value as a fraction and appends the result to a StringBuffer.
+     *
+     * @param value the double value to format
+     * @param buffer StringBuffer to append to
+     * @param position On input: an alignment field, if desired. On output: the offsets of the
+     *     alignment field
+     * @return a reference to the appended buffer
+     * @see #format(Object, StringBuffer, FieldPosition)
+     */
+    @Override
+    public StringBuffer format(
+            final double value, final StringBuffer buffer, final FieldPosition position) {
+        return format(Double.valueOf(value), buffer, position);
+    }
+
+    /**
+     * Formats a long value as a fraction and appends the result to a StringBuffer.
+     *
+     * @param value the long value to format
+     * @param buffer StringBuffer to append to
+     * @param position On input: an alignment field, if desired. On output: the offsets of the
+     *     alignment field
+     * @return a reference to the appended buffer
+     * @see #format(Object, StringBuffer, FieldPosition)
+     */
+    @Override
+    public StringBuffer format(
+            final long value, final StringBuffer buffer, final FieldPosition position) {
+        return format(Long.valueOf(value), buffer, position);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/fraction/BigFraction.java b/src/main/java/org/apache/commons/math3/fraction/BigFraction.java
new file mode 100644
index 0000000..56616b3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fraction/BigFraction.java
@@ -0,0 +1,1065 @@
+/*
+ * 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.math3.fraction;
+
+import org.apache.commons.math3.FieldElement;
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.ZeroException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.ArithmeticUtils;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+/**
+ * Representation of a rational number without any overflow. This class is immutable.
+ *
+ * @since 2.0
+ */
+public class BigFraction extends Number
+        implements FieldElement<BigFraction>, Comparable<BigFraction>, Serializable {
+
+    /** A fraction representing "2 / 1". */
+    public static final BigFraction TWO = new BigFraction(2);
+
+    /** A fraction representing "1". */
+    public static final BigFraction ONE = new BigFraction(1);
+
+    /** A fraction representing "0". */
+    public static final BigFraction ZERO = new BigFraction(0);
+
+    /** A fraction representing "-1 / 1". */
+    public static final BigFraction MINUS_ONE = new BigFraction(-1);
+
+    /** A fraction representing "4/5". */
+    public static final BigFraction FOUR_FIFTHS = new BigFraction(4, 5);
+
+    /** A fraction representing "1/5". */
+    public static final BigFraction ONE_FIFTH = new BigFraction(1, 5);
+
+    /** A fraction representing "1/2". */
+    public static final BigFraction ONE_HALF = new BigFraction(1, 2);
+
+    /** A fraction representing "1/4". */
+    public static final BigFraction ONE_QUARTER = new BigFraction(1, 4);
+
+    /** A fraction representing "1/3". */
+    public static final BigFraction ONE_THIRD = new BigFraction(1, 3);
+
+    /** A fraction representing "3/5". */
+    public static final BigFraction THREE_FIFTHS = new BigFraction(3, 5);
+
+    /** A fraction representing "3/4". */
+    public static final BigFraction THREE_QUARTERS = new BigFraction(3, 4);
+
+    /** A fraction representing "2/5". */
+    public static final BigFraction TWO_FIFTHS = new BigFraction(2, 5);
+
+    /** A fraction representing "2/4". */
+    public static final BigFraction TWO_QUARTERS = new BigFraction(2, 4);
+
+    /** A fraction representing "2/3". */
+    public static final BigFraction TWO_THIRDS = new BigFraction(2, 3);
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -5630213147331578515L;
+
+    /** <code>BigInteger</code> representation of 100. */
+    private static final BigInteger ONE_HUNDRED = BigInteger.valueOf(100);
+
+    /** The numerator. */
+    private final BigInteger numerator;
+
+    /** The denominator. */
+    private final BigInteger denominator;
+
+    /**
+     * Create a {@link BigFraction} equivalent to the passed {@code BigInteger}, ie "num / 1".
+     *
+     * @param num the numerator.
+     */
+    public BigFraction(final BigInteger num) {
+        this(num, BigInteger.ONE);
+    }
+
+    /**
+     * Create a {@link BigFraction} given the numerator and denominator as {@code BigInteger}. The
+     * {@link BigFraction} is reduced to lowest terms.
+     *
+     * @param num the numerator, must not be {@code null}.
+     * @param den the denominator, must not be {@code null}.
+     * @throws ZeroException if the denominator is zero.
+     * @throws NullArgumentException if either of the arguments is null
+     */
+    public BigFraction(BigInteger num, BigInteger den) {
+        MathUtils.checkNotNull(num, LocalizedFormats.NUMERATOR);
+        MathUtils.checkNotNull(den, LocalizedFormats.DENOMINATOR);
+        if (den.signum() == 0) {
+            throw new ZeroException(LocalizedFormats.ZERO_DENOMINATOR);
+        }
+        if (num.signum() == 0) {
+            numerator = BigInteger.ZERO;
+            denominator = BigInteger.ONE;
+        } else {
+
+            // reduce numerator and denominator by greatest common denominator
+            final BigInteger gcd = num.gcd(den);
+            if (BigInteger.ONE.compareTo(gcd) < 0) {
+                num = num.divide(gcd);
+                den = den.divide(gcd);
+            }
+
+            // move sign to numerator
+            if (den.signum() == -1) {
+                num = num.negate();
+                den = den.negate();
+            }
+
+            // store the values in the final fields
+            numerator = num;
+            denominator = den;
+        }
+    }
+
+    /**
+     * Create a fraction given the double value.
+     *
+     * <p>This constructor behaves <em>differently</em> from {@link #BigFraction(double, double,
+     * int)}. It converts the double value exactly, considering its internal bits representation.
+     * This works for all values except NaN and infinities and does not requires any loop or
+     * convergence threshold.
+     *
+     * <p>Since this conversion is exact and since double numbers are sometimes approximated, the
+     * fraction created may seem strange in some cases. For example, calling <code>
+     * new BigFraction(1.0 / 3.0)</code> does <em>not</em> create the fraction 1/3, but the fraction
+     * 6004799503160661 / 18014398509481984 because the double number passed to the constructor is
+     * not exactly 1/3 (this number cannot be stored exactly in IEEE754).
+     *
+     * @see #BigFraction(double, double, int)
+     * @param value the double value to convert to a fraction.
+     * @exception MathIllegalArgumentException if value is NaN or infinite
+     */
+    public BigFraction(final double value) throws MathIllegalArgumentException {
+        if (Double.isNaN(value)) {
+            throw new MathIllegalArgumentException(LocalizedFormats.NAN_VALUE_CONVERSION);
+        }
+        if (Double.isInfinite(value)) {
+            throw new MathIllegalArgumentException(LocalizedFormats.INFINITE_VALUE_CONVERSION);
+        }
+
+        // compute m and k such that value = m * 2^k
+        final long bits = Double.doubleToLongBits(value);
+        final long sign = bits & 0x8000000000000000L;
+        final long exponent = bits & 0x7ff0000000000000L;
+        long m = bits & 0x000fffffffffffffL;
+        if (exponent != 0) {
+            // this was a normalized number, add the implicit most significant bit
+            m |= 0x0010000000000000L;
+        }
+        if (sign != 0) {
+            m = -m;
+        }
+        int k = ((int) (exponent >> 52)) - 1075;
+        while (((m & 0x001ffffffffffffeL) != 0) && ((m & 0x1) == 0)) {
+            m >>= 1;
+            ++k;
+        }
+
+        if (k < 0) {
+            numerator = BigInteger.valueOf(m);
+            denominator = BigInteger.ZERO.flipBit(-k);
+        } else {
+            numerator = BigInteger.valueOf(m).multiply(BigInteger.ZERO.flipBit(k));
+            denominator = BigInteger.ONE;
+        }
+    }
+
+    /**
+     * Create a fraction given the double value and maximum error allowed.
+     *
+     * <p>References:
+     *
+     * <ul>
+     *   <li><a href="http://mathworld.wolfram.com/ContinuedFraction.html">Continued Fraction</a>
+     *       equations (11) and (22)-(26)
+     * </ul>
+     *
+     * @param value the double value to convert to a fraction.
+     * @param epsilon maximum error allowed. The resulting fraction is within <code>epsilon</code>
+     *     of <code>value</code>, in absolute terms.
+     * @param maxIterations maximum number of convergents.
+     * @throws FractionConversionException if the continued fraction failed to converge.
+     * @see #BigFraction(double)
+     */
+    public BigFraction(final double value, final double epsilon, final int maxIterations)
+            throws FractionConversionException {
+        this(value, epsilon, Integer.MAX_VALUE, maxIterations);
+    }
+
+    /**
+     * Create a fraction given the double value and either the maximum error allowed or the maximum
+     * number of denominator digits.
+     *
+     * <p>NOTE: This constructor is called with EITHER - a valid epsilon value and the
+     * maxDenominator set to Integer.MAX_VALUE (that way the maxDenominator has no effect). OR - a
+     * valid maxDenominator value and the epsilon value set to zero (that way epsilon only has
+     * effect if there is an exact match before the maxDenominator value is reached).
+     *
+     * <p>It has been done this way so that the same code can be (re)used for both scenarios.
+     * However this could be confusing to users if it were part of the public API and this
+     * constructor should therefore remain PRIVATE. See JIRA issue ticket MATH-181 for more details:
+     *
+     * <p>https://issues.apache.org/jira/browse/MATH-181
+     *
+     * @param value the double value to convert to a fraction.
+     * @param epsilon maximum error allowed. The resulting fraction is within <code>epsilon</code>
+     *     of <code>value</code>, in absolute terms.
+     * @param maxDenominator maximum denominator value allowed.
+     * @param maxIterations maximum number of convergents.
+     * @throws FractionConversionException if the continued fraction failed to converge.
+     */
+    private BigFraction(
+            final double value, final double epsilon, final int maxDenominator, int maxIterations)
+            throws FractionConversionException {
+        long overflow = Integer.MAX_VALUE;
+        double r0 = value;
+        long a0 = (long) FastMath.floor(r0);
+
+        if (FastMath.abs(a0) > overflow) {
+            throw new FractionConversionException(value, a0, 1l);
+        }
+
+        // check for (almost) integer arguments, which should not go
+        // to iterations.
+        if (FastMath.abs(a0 - value) < epsilon) {
+            numerator = BigInteger.valueOf(a0);
+            denominator = BigInteger.ONE;
+            return;
+        }
+
+        long p0 = 1;
+        long q0 = 0;
+        long p1 = a0;
+        long q1 = 1;
+
+        long p2 = 0;
+        long q2 = 1;
+
+        int n = 0;
+        boolean stop = false;
+        do {
+            ++n;
+            final double r1 = 1.0 / (r0 - a0);
+            final long a1 = (long) FastMath.floor(r1);
+            p2 = (a1 * p1) + p0;
+            q2 = (a1 * q1) + q0;
+            if ((p2 > overflow) || (q2 > overflow)) {
+                // in maxDenominator mode, if the last fraction was very close to the actual value
+                // q2 may overflow in the next iteration; in this case return the last one.
+                if (epsilon == 0.0 && FastMath.abs(q1) < maxDenominator) {
+                    break;
+                }
+                throw new FractionConversionException(value, p2, q2);
+            }
+
+            final double convergent = (double) p2 / (double) q2;
+            if ((n < maxIterations)
+                    && (FastMath.abs(convergent - value) > epsilon)
+                    && (q2 < maxDenominator)) {
+                p0 = p1;
+                p1 = p2;
+                q0 = q1;
+                q1 = q2;
+                a0 = a1;
+                r0 = r1;
+            } else {
+                stop = true;
+            }
+        } while (!stop);
+
+        if (n >= maxIterations) {
+            throw new FractionConversionException(value, maxIterations);
+        }
+
+        if (q2 < maxDenominator) {
+            numerator = BigInteger.valueOf(p2);
+            denominator = BigInteger.valueOf(q2);
+        } else {
+            numerator = BigInteger.valueOf(p1);
+            denominator = BigInteger.valueOf(q1);
+        }
+    }
+
+    /**
+     * Create a fraction given the double value and maximum denominator.
+     *
+     * <p>References:
+     *
+     * <ul>
+     *   <li><a href="http://mathworld.wolfram.com/ContinuedFraction.html">Continued Fraction</a>
+     *       equations (11) and (22)-(26)
+     * </ul>
+     *
+     * @param value the double value to convert to a fraction.
+     * @param maxDenominator The maximum allowed value for denominator.
+     * @throws FractionConversionException if the continued fraction failed to converge.
+     */
+    public BigFraction(final double value, final int maxDenominator)
+            throws FractionConversionException {
+        this(value, 0, maxDenominator, 100);
+    }
+
+    /**
+     * Create a {@link BigFraction} equivalent to the passed {@code int}, ie "num / 1".
+     *
+     * @param num the numerator.
+     */
+    public BigFraction(final int num) {
+        this(BigInteger.valueOf(num), BigInteger.ONE);
+    }
+
+    /**
+     * Create a {@link BigFraction} given the numerator and denominator as simple {@code int}. The
+     * {@link BigFraction} is reduced to lowest terms.
+     *
+     * @param num the numerator.
+     * @param den the denominator.
+     */
+    public BigFraction(final int num, final int den) {
+        this(BigInteger.valueOf(num), BigInteger.valueOf(den));
+    }
+
+    /**
+     * Create a {@link BigFraction} equivalent to the passed long, ie "num / 1".
+     *
+     * @param num the numerator.
+     */
+    public BigFraction(final long num) {
+        this(BigInteger.valueOf(num), BigInteger.ONE);
+    }
+
+    /**
+     * Create a {@link BigFraction} given the numerator and denominator as simple {@code long}. The
+     * {@link BigFraction} is reduced to lowest terms.
+     *
+     * @param num the numerator.
+     * @param den the denominator.
+     */
+    public BigFraction(final long num, final long den) {
+        this(BigInteger.valueOf(num), BigInteger.valueOf(den));
+    }
+
+    /**
+     * Creates a <code>BigFraction</code> instance with the 2 parts of a fraction Y/Z.
+     *
+     * <p>Any negative signs are resolved to be on the numerator.
+     *
+     * @param numerator the numerator, for example the three in 'three sevenths'.
+     * @param denominator the denominator, for example the seven in 'three sevenths'.
+     * @return a new fraction instance, with the numerator and denominator reduced.
+     * @throws ArithmeticException if the denominator is <code>zero</code>.
+     */
+    public static BigFraction getReducedFraction(final int numerator, final int denominator) {
+        if (numerator == 0) {
+            return ZERO; // normalize zero.
+        }
+
+        return new BigFraction(numerator, denominator);
+    }
+
+    /**
+     * Returns the absolute value of this {@link BigFraction}.
+     *
+     * @return the absolute value as a {@link BigFraction}.
+     */
+    public BigFraction abs() {
+        return (numerator.signum() == 1) ? this : negate();
+    }
+
+    /**
+     * Adds the value of this fraction to the passed {@link BigInteger}, returning the result in
+     * reduced form.
+     *
+     * @param bg the {@link BigInteger} to add, must'nt be <code>null</code>.
+     * @return a <code>BigFraction</code> instance with the resulting values.
+     * @throws NullArgumentException if the {@link BigInteger} is <code>null</code>.
+     */
+    public BigFraction add(final BigInteger bg) throws NullArgumentException {
+        MathUtils.checkNotNull(bg);
+
+        if (numerator.signum() == 0) {
+            return new BigFraction(bg);
+        }
+        if (bg.signum() == 0) {
+            return this;
+        }
+
+        return new BigFraction(numerator.add(denominator.multiply(bg)), denominator);
+    }
+
+    /**
+     * Adds the value of this fraction to the passed {@code integer}, returning the result in
+     * reduced form.
+     *
+     * @param i the {@code integer} to add.
+     * @return a <code>BigFraction</code> instance with the resulting values.
+     */
+    public BigFraction add(final int i) {
+        return add(BigInteger.valueOf(i));
+    }
+
+    /**
+     * Adds the value of this fraction to the passed {@code long}, returning the result in reduced
+     * form.
+     *
+     * @param l the {@code long} to add.
+     * @return a <code>BigFraction</code> instance with the resulting values.
+     */
+    public BigFraction add(final long l) {
+        return add(BigInteger.valueOf(l));
+    }
+
+    /**
+     * Adds the value of this fraction to another, returning the result in reduced form.
+     *
+     * @param fraction the {@link BigFraction} to add, must not be <code>null</code>.
+     * @return a {@link BigFraction} instance with the resulting values.
+     * @throws NullArgumentException if the {@link BigFraction} is {@code null}.
+     */
+    public BigFraction add(final BigFraction fraction) {
+        if (fraction == null) {
+            throw new NullArgumentException(LocalizedFormats.FRACTION);
+        }
+        if (fraction.numerator.signum() == 0) {
+            return this;
+        }
+        if (numerator.signum() == 0) {
+            return fraction;
+        }
+
+        BigInteger num = null;
+        BigInteger den = null;
+
+        if (denominator.equals(fraction.denominator)) {
+            num = numerator.add(fraction.numerator);
+            den = denominator;
+        } else {
+            num =
+                    (numerator.multiply(fraction.denominator))
+                            .add((fraction.numerator).multiply(denominator));
+            den = denominator.multiply(fraction.denominator);
+        }
+
+        if (num.signum() == 0) {
+            return ZERO;
+        }
+
+        return new BigFraction(num, den);
+    }
+
+    /**
+     * Gets the fraction as a <code>BigDecimal</code>. This calculates the fraction as the numerator
+     * divided by denominator.
+     *
+     * @return the fraction as a <code>BigDecimal</code>.
+     * @throws ArithmeticException if the exact quotient does not have a terminating decimal
+     *     expansion.
+     * @see BigDecimal
+     */
+    public BigDecimal bigDecimalValue() {
+        return new BigDecimal(numerator).divide(new BigDecimal(denominator));
+    }
+
+    /**
+     * Gets the fraction as a <code>BigDecimal</code> following the passed rounding mode. This
+     * calculates the fraction as the numerator divided by denominator.
+     *
+     * @param roundingMode rounding mode to apply. see {@link BigDecimal} constants.
+     * @return the fraction as a <code>BigDecimal</code>.
+     * @throws IllegalArgumentException if {@code roundingMode} does not represent a valid rounding
+     *     mode.
+     * @see BigDecimal
+     */
+    public BigDecimal bigDecimalValue(final int roundingMode) {
+        return new BigDecimal(numerator).divide(new BigDecimal(denominator), roundingMode);
+    }
+
+    /**
+     * Gets the fraction as a <code>BigDecimal</code> following the passed scale and rounding mode.
+     * This calculates the fraction as the numerator divided by denominator.
+     *
+     * @param scale scale of the <code>BigDecimal</code> quotient to be returned. see {@link
+     *     BigDecimal} for more information.
+     * @param roundingMode rounding mode to apply. see {@link BigDecimal} constants.
+     * @return the fraction as a <code>BigDecimal</code>.
+     * @see BigDecimal
+     */
+    public BigDecimal bigDecimalValue(final int scale, final int roundingMode) {
+        return new BigDecimal(numerator).divide(new BigDecimal(denominator), scale, roundingMode);
+    }
+
+    /**
+     * Compares this object to another based on size.
+     *
+     * @param object the object to compare to, must not be <code>null</code>.
+     * @return -1 if this is less than {@code object}, +1 if this is greater than {@code object}, 0
+     *     if they are equal.
+     * @see java.lang.Comparable#compareTo(java.lang.Object)
+     */
+    public int compareTo(final BigFraction object) {
+        int lhsSigNum = numerator.signum();
+        int rhsSigNum = object.numerator.signum();
+
+        if (lhsSigNum != rhsSigNum) {
+            return (lhsSigNum > rhsSigNum) ? 1 : -1;
+        }
+        if (lhsSigNum == 0) {
+            return 0;
+        }
+
+        BigInteger nOd = numerator.multiply(object.denominator);
+        BigInteger dOn = denominator.multiply(object.numerator);
+        return nOd.compareTo(dOn);
+    }
+
+    /**
+     * Divide the value of this fraction by the passed {@code BigInteger}, ie {@code this * 1 / bg},
+     * returning the result in reduced form.
+     *
+     * @param bg the {@code BigInteger} to divide by, must not be {@code null}
+     * @return a {@link BigFraction} instance with the resulting values
+     * @throws NullArgumentException if the {@code BigInteger} is {@code null}
+     * @throws MathArithmeticException if the fraction to divide by is zero
+     */
+    public BigFraction divide(final BigInteger bg) {
+        if (bg == null) {
+            throw new NullArgumentException(LocalizedFormats.FRACTION);
+        }
+        if (bg.signum() == 0) {
+            throw new MathArithmeticException(LocalizedFormats.ZERO_DENOMINATOR);
+        }
+        if (numerator.signum() == 0) {
+            return ZERO;
+        }
+        return new BigFraction(numerator, denominator.multiply(bg));
+    }
+
+    /**
+     * Divide the value of this fraction by the passed {@code int}, ie {@code this * 1 / i},
+     * returning the result in reduced form.
+     *
+     * @param i the {@code int} to divide by
+     * @return a {@link BigFraction} instance with the resulting values
+     * @throws MathArithmeticException if the fraction to divide by is zero
+     */
+    public BigFraction divide(final int i) {
+        return divide(BigInteger.valueOf(i));
+    }
+
+    /**
+     * Divide the value of this fraction by the passed {@code long}, ie {@code this * 1 / l},
+     * returning the result in reduced form.
+     *
+     * @param l the {@code long} to divide by
+     * @return a {@link BigFraction} instance with the resulting values
+     * @throws MathArithmeticException if the fraction to divide by is zero
+     */
+    public BigFraction divide(final long l) {
+        return divide(BigInteger.valueOf(l));
+    }
+
+    /**
+     * Divide the value of this fraction by another, returning the result in reduced form.
+     *
+     * @param fraction Fraction to divide by, must not be {@code null}.
+     * @return a {@link BigFraction} instance with the resulting values.
+     * @throws NullArgumentException if the {@code fraction} is {@code null}.
+     * @throws MathArithmeticException if the fraction to divide by is zero
+     */
+    public BigFraction divide(final BigFraction fraction) {
+        if (fraction == null) {
+            throw new NullArgumentException(LocalizedFormats.FRACTION);
+        }
+        if (fraction.numerator.signum() == 0) {
+            throw new MathArithmeticException(LocalizedFormats.ZERO_DENOMINATOR);
+        }
+        if (numerator.signum() == 0) {
+            return ZERO;
+        }
+
+        return multiply(fraction.reciprocal());
+    }
+
+    /**
+     * Gets the fraction as a {@code double}. This calculates the fraction as the numerator divided
+     * by denominator.
+     *
+     * @return the fraction as a {@code double}
+     * @see java.lang.Number#doubleValue()
+     */
+    @Override
+    public double doubleValue() {
+        double result = numerator.doubleValue() / denominator.doubleValue();
+        if (Double.isNaN(result)) {
+            // Numerator and/or denominator must be out of range:
+            // Calculate how far to shift them to put them in range.
+            int shift =
+                    FastMath.max(numerator.bitLength(), denominator.bitLength())
+                            - FastMath.getExponent(Double.MAX_VALUE);
+            result =
+                    numerator.shiftRight(shift).doubleValue()
+                            / denominator.shiftRight(shift).doubleValue();
+        }
+        return result;
+    }
+
+    /**
+     * Test for the equality of two fractions. If the lowest term numerator and denominators are the
+     * same for both fractions, the two fractions are considered to be equal.
+     *
+     * @param other fraction to test for equality to this fraction, can be <code>null</code>.
+     * @return true if two fractions are equal, false if object is <code>null</code>, not an
+     *     instance of {@link BigFraction}, or not equal to this fraction instance.
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(final Object other) {
+        boolean ret = false;
+
+        if (this == other) {
+            ret = true;
+        } else if (other instanceof BigFraction) {
+            BigFraction rhs = ((BigFraction) other).reduce();
+            BigFraction thisOne = this.reduce();
+            ret =
+                    thisOne.numerator.equals(rhs.numerator)
+                            && thisOne.denominator.equals(rhs.denominator);
+        }
+
+        return ret;
+    }
+
+    /**
+     * Gets the fraction as a {@code float}. This calculates the fraction as the numerator divided
+     * by denominator.
+     *
+     * @return the fraction as a {@code float}.
+     * @see java.lang.Number#floatValue()
+     */
+    @Override
+    public float floatValue() {
+        float result = numerator.floatValue() / denominator.floatValue();
+        if (Double.isNaN(result)) {
+            // Numerator and/or denominator must be out of range:
+            // Calculate how far to shift them to put them in range.
+            int shift =
+                    FastMath.max(numerator.bitLength(), denominator.bitLength())
+                            - FastMath.getExponent(Float.MAX_VALUE);
+            result =
+                    numerator.shiftRight(shift).floatValue()
+                            / denominator.shiftRight(shift).floatValue();
+        }
+        return result;
+    }
+
+    /**
+     * Access the denominator as a <code>BigInteger</code>.
+     *
+     * @return the denominator as a <code>BigInteger</code>.
+     */
+    public BigInteger getDenominator() {
+        return denominator;
+    }
+
+    /**
+     * Access the denominator as a {@code int}.
+     *
+     * @return the denominator as a {@code int}.
+     */
+    public int getDenominatorAsInt() {
+        return denominator.intValue();
+    }
+
+    /**
+     * Access the denominator as a {@code long}.
+     *
+     * @return the denominator as a {@code long}.
+     */
+    public long getDenominatorAsLong() {
+        return denominator.longValue();
+    }
+
+    /**
+     * Access the numerator as a <code>BigInteger</code>.
+     *
+     * @return the numerator as a <code>BigInteger</code>.
+     */
+    public BigInteger getNumerator() {
+        return numerator;
+    }
+
+    /**
+     * Access the numerator as a {@code int}.
+     *
+     * @return the numerator as a {@code int}.
+     */
+    public int getNumeratorAsInt() {
+        return numerator.intValue();
+    }
+
+    /**
+     * Access the numerator as a {@code long}.
+     *
+     * @return the numerator as a {@code long}.
+     */
+    public long getNumeratorAsLong() {
+        return numerator.longValue();
+    }
+
+    /**
+     * Gets a hashCode for the fraction.
+     *
+     * @return a hash code value for this object.
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        return 37 * (37 * 17 + numerator.hashCode()) + denominator.hashCode();
+    }
+
+    /**
+     * Gets the fraction as an {@code int}. This returns the whole number part of the fraction.
+     *
+     * @return the whole number fraction part.
+     * @see java.lang.Number#intValue()
+     */
+    @Override
+    public int intValue() {
+        return numerator.divide(denominator).intValue();
+    }
+
+    /**
+     * Gets the fraction as a {@code long}. This returns the whole number part of the fraction.
+     *
+     * @return the whole number fraction part.
+     * @see java.lang.Number#longValue()
+     */
+    @Override
+    public long longValue() {
+        return numerator.divide(denominator).longValue();
+    }
+
+    /**
+     * Multiplies the value of this fraction by the passed <code>BigInteger</code>, returning the
+     * result in reduced form.
+     *
+     * @param bg the {@code BigInteger} to multiply by.
+     * @return a {@code BigFraction} instance with the resulting values.
+     * @throws NullArgumentException if {@code bg} is {@code null}.
+     */
+    public BigFraction multiply(final BigInteger bg) {
+        if (bg == null) {
+            throw new NullArgumentException();
+        }
+        if (numerator.signum() == 0 || bg.signum() == 0) {
+            return ZERO;
+        }
+        return new BigFraction(bg.multiply(numerator), denominator);
+    }
+
+    /**
+     * Multiply the value of this fraction by the passed {@code int}, returning the result in
+     * reduced form.
+     *
+     * @param i the {@code int} to multiply by.
+     * @return a {@link BigFraction} instance with the resulting values.
+     */
+    public BigFraction multiply(final int i) {
+        if (i == 0 || numerator.signum() == 0) {
+            return ZERO;
+        }
+
+        return multiply(BigInteger.valueOf(i));
+    }
+
+    /**
+     * Multiply the value of this fraction by the passed {@code long}, returning the result in
+     * reduced form.
+     *
+     * @param l the {@code long} to multiply by.
+     * @return a {@link BigFraction} instance with the resulting values.
+     */
+    public BigFraction multiply(final long l) {
+        if (l == 0 || numerator.signum() == 0) {
+            return ZERO;
+        }
+
+        return multiply(BigInteger.valueOf(l));
+    }
+
+    /**
+     * Multiplies the value of this fraction by another, returning the result in reduced form.
+     *
+     * @param fraction Fraction to multiply by, must not be {@code null}.
+     * @return a {@link BigFraction} instance with the resulting values.
+     * @throws NullArgumentException if {@code fraction} is {@code null}.
+     */
+    public BigFraction multiply(final BigFraction fraction) {
+        if (fraction == null) {
+            throw new NullArgumentException(LocalizedFormats.FRACTION);
+        }
+        if (numerator.signum() == 0 || fraction.numerator.signum() == 0) {
+            return ZERO;
+        }
+        return new BigFraction(
+                numerator.multiply(fraction.numerator), denominator.multiply(fraction.denominator));
+    }
+
+    /**
+     * Return the additive inverse of this fraction, returning the result in reduced form.
+     *
+     * @return the negation of this fraction.
+     */
+    public BigFraction negate() {
+        return new BigFraction(numerator.negate(), denominator);
+    }
+
+    /**
+     * Gets the fraction percentage as a {@code double}. This calculates the fraction as the
+     * numerator divided by denominator multiplied by 100.
+     *
+     * @return the fraction percentage as a {@code double}.
+     */
+    public double percentageValue() {
+        return multiply(ONE_HUNDRED).doubleValue();
+    }
+
+    /**
+     * Returns a {@code BigFraction} whose value is {@code (this<sup>exponent</sup>)}, returning the
+     * result in reduced form.
+     *
+     * @param exponent exponent to which this {@code BigFraction} is to be raised.
+     * @return <tt>this<sup>exponent</sup></tt>.
+     */
+    public BigFraction pow(final int exponent) {
+        if (exponent == 0) {
+            return ONE;
+        }
+        if (numerator.signum() == 0) {
+            return this;
+        }
+
+        if (exponent < 0) {
+            return new BigFraction(denominator.pow(-exponent), numerator.pow(-exponent));
+        }
+        return new BigFraction(numerator.pow(exponent), denominator.pow(exponent));
+    }
+
+    /**
+     * Returns a <code>BigFraction</code> whose value is <tt>(this<sup>exponent</sup>)</tt>,
+     * returning the result in reduced form.
+     *
+     * @param exponent exponent to which this <code>BigFraction</code> is to be raised.
+     * @return <tt>this<sup>exponent</sup></tt> as a <code>BigFraction</code>.
+     */
+    public BigFraction pow(final long exponent) {
+        if (exponent == 0) {
+            return ONE;
+        }
+        if (numerator.signum() == 0) {
+            return this;
+        }
+
+        if (exponent < 0) {
+            return new BigFraction(
+                    ArithmeticUtils.pow(denominator, -exponent),
+                    ArithmeticUtils.pow(numerator, -exponent));
+        }
+        return new BigFraction(
+                ArithmeticUtils.pow(numerator, exponent),
+                ArithmeticUtils.pow(denominator, exponent));
+    }
+
+    /**
+     * Returns a <code>BigFraction</code> whose value is <tt>(this<sup>exponent</sup>)</tt>,
+     * returning the result in reduced form.
+     *
+     * @param exponent exponent to which this <code>BigFraction</code> is to be raised.
+     * @return <tt>this<sup>exponent</sup></tt> as a <code>BigFraction</code>.
+     */
+    public BigFraction pow(final BigInteger exponent) {
+        if (exponent.signum() == 0) {
+            return ONE;
+        }
+        if (numerator.signum() == 0) {
+            return this;
+        }
+
+        if (exponent.signum() == -1) {
+            final BigInteger eNeg = exponent.negate();
+            return new BigFraction(
+                    ArithmeticUtils.pow(denominator, eNeg), ArithmeticUtils.pow(numerator, eNeg));
+        }
+        return new BigFraction(
+                ArithmeticUtils.pow(numerator, exponent),
+                ArithmeticUtils.pow(denominator, exponent));
+    }
+
+    /**
+     * Returns a <code>double</code> whose value is <tt>(this<sup>exponent</sup>)</tt>, returning
+     * the result in reduced form.
+     *
+     * @param exponent exponent to which this <code>BigFraction</code> is to be raised.
+     * @return <tt>this<sup>exponent</sup></tt>.
+     */
+    public double pow(final double exponent) {
+        return FastMath.pow(numerator.doubleValue(), exponent)
+                / FastMath.pow(denominator.doubleValue(), exponent);
+    }
+
+    /**
+     * Return the multiplicative inverse of this fraction.
+     *
+     * @return the reciprocal fraction.
+     */
+    public BigFraction reciprocal() {
+        return new BigFraction(denominator, numerator);
+    }
+
+    /**
+     * Reduce this <code>BigFraction</code> to its lowest terms.
+     *
+     * @return the reduced <code>BigFraction</code>. It doesn't change anything if the fraction can
+     *     be reduced.
+     */
+    public BigFraction reduce() {
+        final BigInteger gcd = numerator.gcd(denominator);
+
+        if (BigInteger.ONE.compareTo(gcd) < 0) {
+            return new BigFraction(numerator.divide(gcd), denominator.divide(gcd));
+        } else {
+            return this;
+        }
+    }
+
+    /**
+     * Subtracts the value of an {@link BigInteger} from the value of this {@code BigFraction},
+     * returning the result in reduced form.
+     *
+     * @param bg the {@link BigInteger} to subtract, cannot be {@code null}.
+     * @return a {@code BigFraction} instance with the resulting values.
+     * @throws NullArgumentException if the {@link BigInteger} is {@code null}.
+     */
+    public BigFraction subtract(final BigInteger bg) {
+        if (bg == null) {
+            throw new NullArgumentException();
+        }
+        if (bg.signum() == 0) {
+            return this;
+        }
+        if (numerator.signum() == 0) {
+            return new BigFraction(bg.negate());
+        }
+
+        return new BigFraction(numerator.subtract(denominator.multiply(bg)), denominator);
+    }
+
+    /**
+     * Subtracts the value of an {@code integer} from the value of this {@code BigFraction},
+     * returning the result in reduced form.
+     *
+     * @param i the {@code integer} to subtract.
+     * @return a {@code BigFraction} instance with the resulting values.
+     */
+    public BigFraction subtract(final int i) {
+        return subtract(BigInteger.valueOf(i));
+    }
+
+    /**
+     * Subtracts the value of a {@code long} from the value of this {@code BigFraction}, returning
+     * the result in reduced form.
+     *
+     * @param l the {@code long} to subtract.
+     * @return a {@code BigFraction} instance with the resulting values.
+     */
+    public BigFraction subtract(final long l) {
+        return subtract(BigInteger.valueOf(l));
+    }
+
+    /**
+     * Subtracts the value of another fraction from the value of this one, returning the result in
+     * reduced form.
+     *
+     * @param fraction {@link BigFraction} to subtract, must not be {@code null}.
+     * @return a {@link BigFraction} instance with the resulting values
+     * @throws NullArgumentException if the {@code fraction} is {@code null}.
+     */
+    public BigFraction subtract(final BigFraction fraction) {
+        if (fraction == null) {
+            throw new NullArgumentException(LocalizedFormats.FRACTION);
+        }
+        if (fraction.numerator.signum() == 0) {
+            return this;
+        }
+        if (numerator.signum() == 0) {
+            return fraction.negate();
+        }
+
+        BigInteger num = null;
+        BigInteger den = null;
+        if (denominator.equals(fraction.denominator)) {
+            num = numerator.subtract(fraction.numerator);
+            den = denominator;
+        } else {
+            num =
+                    (numerator.multiply(fraction.denominator))
+                            .subtract((fraction.numerator).multiply(denominator));
+            den = denominator.multiply(fraction.denominator);
+        }
+        return new BigFraction(num, den);
+    }
+
+    /**
+     * Returns the <code>String</code> representing this fraction, ie "num / dem" or just "num" if
+     * the denominator is one.
+     *
+     * @return a string representation of the fraction.
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        String str = null;
+        if (BigInteger.ONE.equals(denominator)) {
+            str = numerator.toString();
+        } else if (BigInteger.ZERO.equals(numerator)) {
+            str = "0";
+        } else {
+            str = numerator + " / " + denominator;
+        }
+        return str;
+    }
+
+    /** {@inheritDoc} */
+    public BigFractionField getField() {
+        return BigFractionField.getInstance();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/fraction/BigFractionField.java b/src/main/java/org/apache/commons/math3/fraction/BigFractionField.java
new file mode 100644
index 0000000..763cea1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fraction/BigFractionField.java
@@ -0,0 +1,87 @@
+/*
+ * 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.math3.fraction;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.FieldElement;
+
+import java.io.Serializable;
+
+/**
+ * Representation of the fractional numbers without any overflow field.
+ *
+ * <p>This class is a singleton.
+ *
+ * @see Fraction
+ * @since 2.0
+ */
+public class BigFractionField implements Field<BigFraction>, Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -1699294557189741703L;
+
+    /** Private constructor for the singleton. */
+    private BigFractionField() {}
+
+    /**
+     * Get the unique instance.
+     *
+     * @return the unique instance
+     */
+    public static BigFractionField getInstance() {
+        return LazyHolder.INSTANCE;
+    }
+
+    /** {@inheritDoc} */
+    public BigFraction getOne() {
+        return BigFraction.ONE;
+    }
+
+    /** {@inheritDoc} */
+    public BigFraction getZero() {
+        return BigFraction.ZERO;
+    }
+
+    /** {@inheritDoc} */
+    public Class<? extends FieldElement<BigFraction>> getRuntimeClass() {
+        return BigFraction.class;
+    }
+
+    // CHECKSTYLE: stop HideUtilityClassConstructor
+    /**
+     * Holder for the instance.
+     *
+     * <p>We use here the Initialization On Demand Holder Idiom.
+     */
+    private static class LazyHolder {
+        /** Cached field instance. */
+        private static final BigFractionField INSTANCE = new BigFractionField();
+    }
+
+    // CHECKSTYLE: resume HideUtilityClassConstructor
+
+    /**
+     * Handle deserialization of the singleton.
+     *
+     * @return the singleton instance
+     */
+    private Object readResolve() {
+        // return the singleton instance
+        return LazyHolder.INSTANCE;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/fraction/BigFractionFormat.java b/src/main/java/org/apache/commons/math3/fraction/BigFractionFormat.java
new file mode 100644
index 0000000..aed9b26
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fraction/BigFractionFormat.java
@@ -0,0 +1,288 @@
+/*
+ * 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.math3.fraction;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.MathParseException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+import java.io.Serializable;
+import java.math.BigInteger;
+import java.text.FieldPosition;
+import java.text.NumberFormat;
+import java.text.ParsePosition;
+import java.util.Locale;
+
+/**
+ * Formats a BigFraction number in proper format or improper format.
+ *
+ * <p>The number format for each of the whole number, numerator and, denominator can be configured.
+ *
+ * @since 2.0
+ */
+public class BigFractionFormat extends AbstractFormat implements Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -2932167925527338976L;
+
+    /**
+     * Create an improper formatting instance with the default number format for the numerator and
+     * denominator.
+     */
+    public BigFractionFormat() {}
+
+    /**
+     * Create an improper formatting instance with a custom number format for both the numerator and
+     * denominator.
+     *
+     * @param format the custom format for both the numerator and denominator.
+     */
+    public BigFractionFormat(final NumberFormat format) {
+        super(format);
+    }
+
+    /**
+     * Create an improper formatting instance with a custom number format for the numerator and a
+     * custom number format for the denominator.
+     *
+     * @param numeratorFormat the custom format for the numerator.
+     * @param denominatorFormat the custom format for the denominator.
+     */
+    public BigFractionFormat(
+            final NumberFormat numeratorFormat, final NumberFormat denominatorFormat) {
+        super(numeratorFormat, denominatorFormat);
+    }
+
+    /**
+     * Get the set of locales for which complex formats are available. This is the same set as the
+     * {@link NumberFormat} set.
+     *
+     * @return available complex format locales.
+     */
+    public static Locale[] getAvailableLocales() {
+        return NumberFormat.getAvailableLocales();
+    }
+
+    /**
+     * This static method calls formatBigFraction() on a default instance of BigFractionFormat.
+     *
+     * @param f BigFraction object to format
+     * @return A formatted BigFraction in proper form.
+     */
+    public static String formatBigFraction(final BigFraction f) {
+        return getImproperInstance().format(f);
+    }
+
+    /**
+     * Returns the default complex format for the current locale.
+     *
+     * @return the default complex format.
+     */
+    public static BigFractionFormat getImproperInstance() {
+        return getImproperInstance(Locale.getDefault());
+    }
+
+    /**
+     * Returns the default complex format for the given locale.
+     *
+     * @param locale the specific locale used by the format.
+     * @return the complex format specific to the given locale.
+     */
+    public static BigFractionFormat getImproperInstance(final Locale locale) {
+        return new BigFractionFormat(getDefaultNumberFormat(locale));
+    }
+
+    /**
+     * Returns the default complex format for the current locale.
+     *
+     * @return the default complex format.
+     */
+    public static BigFractionFormat getProperInstance() {
+        return getProperInstance(Locale.getDefault());
+    }
+
+    /**
+     * Returns the default complex format for the given locale.
+     *
+     * @param locale the specific locale used by the format.
+     * @return the complex format specific to the given locale.
+     */
+    public static BigFractionFormat getProperInstance(final Locale locale) {
+        return new ProperBigFractionFormat(getDefaultNumberFormat(locale));
+    }
+
+    /**
+     * Formats a {@link BigFraction} object to produce a string. The BigFraction is output in
+     * improper format.
+     *
+     * @param BigFraction the object to format.
+     * @param toAppendTo where the text is to be appended
+     * @param pos On input: an alignment field, if desired. On output: the offsets of the alignment
+     *     field
+     * @return the value passed in as toAppendTo.
+     */
+    public StringBuffer format(
+            final BigFraction BigFraction, final StringBuffer toAppendTo, final FieldPosition pos) {
+
+        pos.setBeginIndex(0);
+        pos.setEndIndex(0);
+
+        getNumeratorFormat().format(BigFraction.getNumerator(), toAppendTo, pos);
+        toAppendTo.append(" / ");
+        getDenominatorFormat().format(BigFraction.getDenominator(), toAppendTo, pos);
+
+        return toAppendTo;
+    }
+
+    /**
+     * Formats an object and appends the result to a StringBuffer. <code>obj</code> must be either a
+     * {@link BigFraction} object or a {@link BigInteger} object or a {@link Number} object. Any
+     * other type of object will result in an {@link IllegalArgumentException} being thrown.
+     *
+     * @param obj the object to format.
+     * @param toAppendTo where the text is to be appended
+     * @param pos On input: an alignment field, if desired. On output: the offsets of the alignment
+     *     field
+     * @return the value passed in as toAppendTo.
+     * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer,
+     *     java.text.FieldPosition)
+     * @throws MathIllegalArgumentException if <code>obj</code> is not a valid type.
+     */
+    @Override
+    public StringBuffer format(
+            final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) {
+
+        final StringBuffer ret;
+        if (obj instanceof BigFraction) {
+            ret = format((BigFraction) obj, toAppendTo, pos);
+        } else if (obj instanceof BigInteger) {
+            ret = format(new BigFraction((BigInteger) obj), toAppendTo, pos);
+        } else if (obj instanceof Number) {
+            ret = format(new BigFraction(((Number) obj).doubleValue()), toAppendTo, pos);
+        } else {
+            throw new MathIllegalArgumentException(
+                    LocalizedFormats.CANNOT_FORMAT_OBJECT_TO_FRACTION);
+        }
+
+        return ret;
+    }
+
+    /**
+     * Parses a string to produce a {@link BigFraction} object.
+     *
+     * @param source the string to parse
+     * @return the parsed {@link BigFraction} object.
+     * @exception MathParseException if the beginning of the specified string cannot be parsed.
+     */
+    @Override
+    public BigFraction parse(final String source) throws MathParseException {
+        final ParsePosition parsePosition = new ParsePosition(0);
+        final BigFraction result = parse(source, parsePosition);
+        if (parsePosition.getIndex() == 0) {
+            throw new MathParseException(source, parsePosition.getErrorIndex(), BigFraction.class);
+        }
+        return result;
+    }
+
+    /**
+     * Parses a string to produce a {@link BigFraction} object. This method expects the string to be
+     * formatted as an improper BigFraction.
+     *
+     * @param source the string to parse
+     * @param pos input/output parsing parameter.
+     * @return the parsed {@link BigFraction} object.
+     */
+    @Override
+    public BigFraction parse(final String source, final ParsePosition pos) {
+        final int initialIndex = pos.getIndex();
+
+        // parse whitespace
+        parseAndIgnoreWhitespace(source, pos);
+
+        // parse numerator
+        final BigInteger num = parseNextBigInteger(source, pos);
+        if (num == null) {
+            // invalid integer number
+            // set index back to initial, error index should already be set
+            // character examined.
+            pos.setIndex(initialIndex);
+            return null;
+        }
+
+        // parse '/'
+        final int startIndex = pos.getIndex();
+        final char c = parseNextCharacter(source, pos);
+        switch (c) {
+            case 0:
+                // no '/'
+                // return num as a BigFraction
+                return new BigFraction(num);
+            case '/':
+                // found '/', continue parsing denominator
+                break;
+            default:
+                // invalid '/'
+                // set index back to initial, error index should be the last
+                // character examined.
+                pos.setIndex(initialIndex);
+                pos.setErrorIndex(startIndex);
+                return null;
+        }
+
+        // parse whitespace
+        parseAndIgnoreWhitespace(source, pos);
+
+        // parse denominator
+        final BigInteger den = parseNextBigInteger(source, pos);
+        if (den == null) {
+            // invalid integer number
+            // set index back to initial, error index should already be set
+            // character examined.
+            pos.setIndex(initialIndex);
+            return null;
+        }
+
+        return new BigFraction(num, den);
+    }
+
+    /**
+     * Parses a string to produce a <code>BigInteger</code>.
+     *
+     * @param source the string to parse
+     * @param pos input/output parsing parameter.
+     * @return a parsed <code>BigInteger</code> or null if string does not contain a BigInteger at
+     *     the specified position
+     */
+    protected BigInteger parseNextBigInteger(final String source, final ParsePosition pos) {
+
+        final int start = pos.getIndex();
+        int end = (source.charAt(start) == '-') ? (start + 1) : start;
+        while ((end < source.length()) && Character.isDigit(source.charAt(end))) {
+            ++end;
+        }
+
+        try {
+            BigInteger n = new BigInteger(source.substring(start, end));
+            pos.setIndex(end);
+            return n;
+        } catch (NumberFormatException nfe) {
+            pos.setErrorIndex(start);
+            return null;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/fraction/Fraction.java b/src/main/java/org/apache/commons/math3/fraction/Fraction.java
new file mode 100644
index 0000000..9b04e12
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fraction/Fraction.java
@@ -0,0 +1,669 @@
+/*
+ * 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.math3.fraction;
+
+import org.apache.commons.math3.FieldElement;
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.ArithmeticUtils;
+import org.apache.commons.math3.util.FastMath;
+
+import java.io.Serializable;
+import java.math.BigInteger;
+
+/**
+ * Representation of a rational number.
+ *
+ * <p>implements Serializable since 2.0
+ *
+ * @since 1.1
+ */
+public class Fraction extends Number
+        implements FieldElement<Fraction>, Comparable<Fraction>, Serializable {
+
+    /** A fraction representing "2 / 1". */
+    public static final Fraction TWO = new Fraction(2, 1);
+
+    /** A fraction representing "1". */
+    public static final Fraction ONE = new Fraction(1, 1);
+
+    /** A fraction representing "0". */
+    public static final Fraction ZERO = new Fraction(0, 1);
+
+    /** A fraction representing "4/5". */
+    public static final Fraction FOUR_FIFTHS = new Fraction(4, 5);
+
+    /** A fraction representing "1/5". */
+    public static final Fraction ONE_FIFTH = new Fraction(1, 5);
+
+    /** A fraction representing "1/2". */
+    public static final Fraction ONE_HALF = new Fraction(1, 2);
+
+    /** A fraction representing "1/4". */
+    public static final Fraction ONE_QUARTER = new Fraction(1, 4);
+
+    /** A fraction representing "1/3". */
+    public static final Fraction ONE_THIRD = new Fraction(1, 3);
+
+    /** A fraction representing "3/5". */
+    public static final Fraction THREE_FIFTHS = new Fraction(3, 5);
+
+    /** A fraction representing "3/4". */
+    public static final Fraction THREE_QUARTERS = new Fraction(3, 4);
+
+    /** A fraction representing "2/5". */
+    public static final Fraction TWO_FIFTHS = new Fraction(2, 5);
+
+    /** A fraction representing "2/4". */
+    public static final Fraction TWO_QUARTERS = new Fraction(2, 4);
+
+    /** A fraction representing "2/3". */
+    public static final Fraction TWO_THIRDS = new Fraction(2, 3);
+
+    /** A fraction representing "-1 / 1". */
+    public static final Fraction MINUS_ONE = new Fraction(-1, 1);
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 3698073679419233275L;
+
+    /** The default epsilon used for convergence. */
+    private static final double DEFAULT_EPSILON = 1e-5;
+
+    /** The denominator. */
+    private final int denominator;
+
+    /** The numerator. */
+    private final int numerator;
+
+    /**
+     * Create a fraction given the double value.
+     *
+     * @param value the double value to convert to a fraction.
+     * @throws FractionConversionException if the continued fraction failed to converge.
+     */
+    public Fraction(double value) throws FractionConversionException {
+        this(value, DEFAULT_EPSILON, 100);
+    }
+
+    /**
+     * Create a fraction given the double value and maximum error allowed.
+     *
+     * <p>References:
+     *
+     * <ul>
+     *   <li><a href="http://mathworld.wolfram.com/ContinuedFraction.html">Continued Fraction</a>
+     *       equations (11) and (22)-(26)
+     * </ul>
+     *
+     * @param value the double value to convert to a fraction.
+     * @param epsilon maximum error allowed. The resulting fraction is within {@code epsilon} of
+     *     {@code value}, in absolute terms.
+     * @param maxIterations maximum number of convergents
+     * @throws FractionConversionException if the continued fraction failed to converge.
+     */
+    public Fraction(double value, double epsilon, int maxIterations)
+            throws FractionConversionException {
+        this(value, epsilon, Integer.MAX_VALUE, maxIterations);
+    }
+
+    /**
+     * Create a fraction given the double value and maximum denominator.
+     *
+     * <p>References:
+     *
+     * <ul>
+     *   <li><a href="http://mathworld.wolfram.com/ContinuedFraction.html">Continued Fraction</a>
+     *       equations (11) and (22)-(26)
+     * </ul>
+     *
+     * @param value the double value to convert to a fraction.
+     * @param maxDenominator The maximum allowed value for denominator
+     * @throws FractionConversionException if the continued fraction failed to converge
+     */
+    public Fraction(double value, int maxDenominator) throws FractionConversionException {
+        this(value, 0, maxDenominator, 100);
+    }
+
+    /**
+     * Create a fraction given the double value and either the maximum error allowed or the maximum
+     * number of denominator digits.
+     *
+     * <p>NOTE: This constructor is called with EITHER - a valid epsilon value and the
+     * maxDenominator set to Integer.MAX_VALUE (that way the maxDenominator has no effect). OR - a
+     * valid maxDenominator value and the epsilon value set to zero (that way epsilon only has
+     * effect if there is an exact match before the maxDenominator value is reached).
+     *
+     * <p>It has been done this way so that the same code can be (re)used for both scenarios.
+     * However this could be confusing to users if it were part of the public API and this
+     * constructor should therefore remain PRIVATE. See JIRA issue ticket MATH-181 for more details:
+     *
+     * <p>https://issues.apache.org/jira/browse/MATH-181
+     *
+     * @param value the double value to convert to a fraction.
+     * @param epsilon maximum error allowed. The resulting fraction is within {@code epsilon} of
+     *     {@code value}, in absolute terms.
+     * @param maxDenominator maximum denominator value allowed.
+     * @param maxIterations maximum number of convergents
+     * @throws FractionConversionException if the continued fraction failed to converge.
+     */
+    private Fraction(double value, double epsilon, int maxDenominator, int maxIterations)
+            throws FractionConversionException {
+        long overflow = Integer.MAX_VALUE;
+        double r0 = value;
+        long a0 = (long) FastMath.floor(r0);
+        if (FastMath.abs(a0) > overflow) {
+            throw new FractionConversionException(value, a0, 1l);
+        }
+
+        // check for (almost) integer arguments, which should not go to iterations.
+        if (FastMath.abs(a0 - value) < epsilon) {
+            this.numerator = (int) a0;
+            this.denominator = 1;
+            return;
+        }
+
+        long p0 = 1;
+        long q0 = 0;
+        long p1 = a0;
+        long q1 = 1;
+
+        long p2 = 0;
+        long q2 = 1;
+
+        int n = 0;
+        boolean stop = false;
+        do {
+            ++n;
+            double r1 = 1.0 / (r0 - a0);
+            long a1 = (long) FastMath.floor(r1);
+            p2 = (a1 * p1) + p0;
+            q2 = (a1 * q1) + q0;
+
+            if ((FastMath.abs(p2) > overflow) || (FastMath.abs(q2) > overflow)) {
+                // in maxDenominator mode, if the last fraction was very close to the actual value
+                // q2 may overflow in the next iteration; in this case return the last one.
+                if (epsilon == 0.0 && FastMath.abs(q1) < maxDenominator) {
+                    break;
+                }
+                throw new FractionConversionException(value, p2, q2);
+            }
+
+            double convergent = (double) p2 / (double) q2;
+            if (n < maxIterations
+                    && FastMath.abs(convergent - value) > epsilon
+                    && q2 < maxDenominator) {
+                p0 = p1;
+                p1 = p2;
+                q0 = q1;
+                q1 = q2;
+                a0 = a1;
+                r0 = r1;
+            } else {
+                stop = true;
+            }
+        } while (!stop);
+
+        if (n >= maxIterations) {
+            throw new FractionConversionException(value, maxIterations);
+        }
+
+        if (q2 < maxDenominator) {
+            this.numerator = (int) p2;
+            this.denominator = (int) q2;
+        } else {
+            this.numerator = (int) p1;
+            this.denominator = (int) q1;
+        }
+    }
+
+    /**
+     * Create a fraction from an int. The fraction is num / 1.
+     *
+     * @param num the numerator.
+     */
+    public Fraction(int num) {
+        this(num, 1);
+    }
+
+    /**
+     * Create a fraction given the numerator and denominator. The fraction is reduced to lowest
+     * terms.
+     *
+     * @param num the numerator.
+     * @param den the denominator.
+     * @throws MathArithmeticException if the denominator is {@code zero}
+     */
+    public Fraction(int num, int den) {
+        if (den == 0) {
+            throw new MathArithmeticException(
+                    LocalizedFormats.ZERO_DENOMINATOR_IN_FRACTION, num, den);
+        }
+        if (den < 0) {
+            if (num == Integer.MIN_VALUE || den == Integer.MIN_VALUE) {
+                throw new MathArithmeticException(LocalizedFormats.OVERFLOW_IN_FRACTION, num, den);
+            }
+            num = -num;
+            den = -den;
+        }
+        // reduce numerator and denominator by greatest common denominator.
+        final int d = ArithmeticUtils.gcd(num, den);
+        if (d > 1) {
+            num /= d;
+            den /= d;
+        }
+
+        // move sign to numerator.
+        if (den < 0) {
+            num = -num;
+            den = -den;
+        }
+        this.numerator = num;
+        this.denominator = den;
+    }
+
+    /**
+     * Returns the absolute value of this fraction.
+     *
+     * @return the absolute value.
+     */
+    public Fraction abs() {
+        Fraction ret;
+        if (numerator >= 0) {
+            ret = this;
+        } else {
+            ret = negate();
+        }
+        return ret;
+    }
+
+    /**
+     * Compares this object to another based on size.
+     *
+     * @param object the object to compare to
+     * @return -1 if this is less than {@code object}, +1 if this is greater than {@code object}, 0
+     *     if they are equal.
+     */
+    public int compareTo(Fraction object) {
+        long nOd = ((long) numerator) * object.denominator;
+        long dOn = ((long) denominator) * object.numerator;
+        return (nOd < dOn) ? -1 : ((nOd > dOn) ? +1 : 0);
+    }
+
+    /**
+     * Gets the fraction as a {@code double}. This calculates the fraction as the numerator divided
+     * by denominator.
+     *
+     * @return the fraction as a {@code double}
+     */
+    @Override
+    public double doubleValue() {
+        return (double) numerator / (double) denominator;
+    }
+
+    /**
+     * Test for the equality of two fractions. If the lowest term numerator and denominators are the
+     * same for both fractions, the two fractions are considered to be equal.
+     *
+     * @param other fraction to test for equality to this fraction
+     * @return true if two fractions are equal, false if object is {@code null}, not an instance of
+     *     {@link Fraction}, or not equal to this fraction instance.
+     */
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+        if (other instanceof Fraction) {
+            // since fractions are always in lowest terms, numerators and
+            // denominators can be compared directly for equality.
+            Fraction rhs = (Fraction) other;
+            return (numerator == rhs.numerator) && (denominator == rhs.denominator);
+        }
+        return false;
+    }
+
+    /**
+     * Gets the fraction as a {@code float}. This calculates the fraction as the numerator divided
+     * by denominator.
+     *
+     * @return the fraction as a {@code float}
+     */
+    @Override
+    public float floatValue() {
+        return (float) doubleValue();
+    }
+
+    /**
+     * Access the denominator.
+     *
+     * @return the denominator.
+     */
+    public int getDenominator() {
+        return denominator;
+    }
+
+    /**
+     * Access the numerator.
+     *
+     * @return the numerator.
+     */
+    public int getNumerator() {
+        return numerator;
+    }
+
+    /**
+     * Gets a hashCode for the fraction.
+     *
+     * @return a hash code value for this object
+     */
+    @Override
+    public int hashCode() {
+        return 37 * (37 * 17 + numerator) + denominator;
+    }
+
+    /**
+     * Gets the fraction as an {@code int}. This returns the whole number part of the fraction.
+     *
+     * @return the whole number fraction part
+     */
+    @Override
+    public int intValue() {
+        return (int) doubleValue();
+    }
+
+    /**
+     * Gets the fraction as a {@code long}. This returns the whole number part of the fraction.
+     *
+     * @return the whole number fraction part
+     */
+    @Override
+    public long longValue() {
+        return (long) doubleValue();
+    }
+
+    /**
+     * Return the additive inverse of this fraction.
+     *
+     * @return the negation of this fraction.
+     */
+    public Fraction negate() {
+        if (numerator == Integer.MIN_VALUE) {
+            throw new MathArithmeticException(
+                    LocalizedFormats.OVERFLOW_IN_FRACTION, numerator, denominator);
+        }
+        return new Fraction(-numerator, denominator);
+    }
+
+    /**
+     * Return the multiplicative inverse of this fraction.
+     *
+     * @return the reciprocal fraction
+     */
+    public Fraction reciprocal() {
+        return new Fraction(denominator, numerator);
+    }
+
+    /**
+     * Adds the value of this fraction to another, returning the result in reduced form. The
+     * algorithm follows Knuth, 4.5.1.
+     *
+     * @param fraction the fraction to add, must not be {@code null}
+     * @return a {@code Fraction} instance with the resulting values
+     * @throws NullArgumentException if the fraction is {@code null}
+     * @throws MathArithmeticException if the resulting numerator or denominator exceeds {@code
+     *     Integer.MAX_VALUE}
+     */
+    public Fraction add(Fraction fraction) {
+        return addSub(fraction, true /* add */);
+    }
+
+    /**
+     * Add an integer to the fraction.
+     *
+     * @param i the {@code integer} to add.
+     * @return this + i
+     */
+    public Fraction add(final int i) {
+        return new Fraction(numerator + i * denominator, denominator);
+    }
+
+    /**
+     * Subtracts the value of another fraction from the value of this one, returning the result in
+     * reduced form.
+     *
+     * @param fraction the fraction to subtract, must not be {@code null}
+     * @return a {@code Fraction} instance with the resulting values
+     * @throws NullArgumentException if the fraction is {@code null}
+     * @throws MathArithmeticException if the resulting numerator or denominator cannot be
+     *     represented in an {@code int}.
+     */
+    public Fraction subtract(Fraction fraction) {
+        return addSub(fraction, false /* subtract */);
+    }
+
+    /**
+     * Subtract an integer from the fraction.
+     *
+     * @param i the {@code integer} to subtract.
+     * @return this - i
+     */
+    public Fraction subtract(final int i) {
+        return new Fraction(numerator - i * denominator, denominator);
+    }
+
+    /**
+     * Implement add and subtract using algorithm described in Knuth 4.5.1.
+     *
+     * @param fraction the fraction to subtract, must not be {@code null}
+     * @param isAdd true to add, false to subtract
+     * @return a {@code Fraction} instance with the resulting values
+     * @throws NullArgumentException if the fraction is {@code null}
+     * @throws MathArithmeticException if the resulting numerator or denominator cannot be
+     *     represented in an {@code int}.
+     */
+    private Fraction addSub(Fraction fraction, boolean isAdd) {
+        if (fraction == null) {
+            throw new NullArgumentException(LocalizedFormats.FRACTION);
+        }
+        // zero is identity for addition.
+        if (numerator == 0) {
+            return isAdd ? fraction : fraction.negate();
+        }
+        if (fraction.numerator == 0) {
+            return this;
+        }
+        // if denominators are randomly distributed, d1 will be 1 about 61%
+        // of the time.
+        int d1 = ArithmeticUtils.gcd(denominator, fraction.denominator);
+        if (d1 == 1) {
+            // result is ( (u*v' +/- u'v) / u'v')
+            int uvp = ArithmeticUtils.mulAndCheck(numerator, fraction.denominator);
+            int upv = ArithmeticUtils.mulAndCheck(fraction.numerator, denominator);
+            return new Fraction(
+                    isAdd
+                            ? ArithmeticUtils.addAndCheck(uvp, upv)
+                            : ArithmeticUtils.subAndCheck(uvp, upv),
+                    ArithmeticUtils.mulAndCheck(denominator, fraction.denominator));
+        }
+        // the quantity 't' requires 65 bits of precision; see knuth 4.5.1
+        // exercise 7.  we're going to use a BigInteger.
+        // t = u(v'/d1) +/- v(u'/d1)
+        BigInteger uvp =
+                BigInteger.valueOf(numerator)
+                        .multiply(BigInteger.valueOf(fraction.denominator / d1));
+        BigInteger upv =
+                BigInteger.valueOf(fraction.numerator)
+                        .multiply(BigInteger.valueOf(denominator / d1));
+        BigInteger t = isAdd ? uvp.add(upv) : uvp.subtract(upv);
+        // but d2 doesn't need extra precision because
+        // d2 = gcd(t,d1) = gcd(t mod d1, d1)
+        int tmodd1 = t.mod(BigInteger.valueOf(d1)).intValue();
+        int d2 = (tmodd1 == 0) ? d1 : ArithmeticUtils.gcd(tmodd1, d1);
+
+        // result is (t/d2) / (u'/d1)(v'/d2)
+        BigInteger w = t.divide(BigInteger.valueOf(d2));
+        if (w.bitLength() > 31) {
+            throw new MathArithmeticException(
+                    LocalizedFormats.NUMERATOR_OVERFLOW_AFTER_MULTIPLY, w);
+        }
+        return new Fraction(
+                w.intValue(),
+                ArithmeticUtils.mulAndCheck(denominator / d1, fraction.denominator / d2));
+    }
+
+    /**
+     * Multiplies the value of this fraction by another, returning the result in reduced form.
+     *
+     * @param fraction the fraction to multiply by, must not be {@code null}
+     * @return a {@code Fraction} instance with the resulting values
+     * @throws NullArgumentException if the fraction is {@code null}
+     * @throws MathArithmeticException if the resulting numerator or denominator exceeds {@code
+     *     Integer.MAX_VALUE}
+     */
+    public Fraction multiply(Fraction fraction) {
+        if (fraction == null) {
+            throw new NullArgumentException(LocalizedFormats.FRACTION);
+        }
+        if (numerator == 0 || fraction.numerator == 0) {
+            return ZERO;
+        }
+        // knuth 4.5.1
+        // make sure we don't overflow unless the result *must* overflow.
+        int d1 = ArithmeticUtils.gcd(numerator, fraction.denominator);
+        int d2 = ArithmeticUtils.gcd(fraction.numerator, denominator);
+        return getReducedFraction(
+                ArithmeticUtils.mulAndCheck(numerator / d1, fraction.numerator / d2),
+                ArithmeticUtils.mulAndCheck(denominator / d2, fraction.denominator / d1));
+    }
+
+    /**
+     * Multiply the fraction by an integer.
+     *
+     * @param i the {@code integer} to multiply by.
+     * @return this * i
+     */
+    public Fraction multiply(final int i) {
+        return multiply(new Fraction(i));
+    }
+
+    /**
+     * Divide the value of this fraction by another.
+     *
+     * @param fraction the fraction to divide by, must not be {@code null}
+     * @return a {@code Fraction} instance with the resulting values
+     * @throws IllegalArgumentException if the fraction is {@code null}
+     * @throws MathArithmeticException if the fraction to divide by is zero
+     * @throws MathArithmeticException if the resulting numerator or denominator exceeds {@code
+     *     Integer.MAX_VALUE}
+     */
+    public Fraction divide(Fraction fraction) {
+        if (fraction == null) {
+            throw new NullArgumentException(LocalizedFormats.FRACTION);
+        }
+        if (fraction.numerator == 0) {
+            throw new MathArithmeticException(
+                    LocalizedFormats.ZERO_FRACTION_TO_DIVIDE_BY,
+                    fraction.numerator,
+                    fraction.denominator);
+        }
+        return multiply(fraction.reciprocal());
+    }
+
+    /**
+     * Divide the fraction by an integer.
+     *
+     * @param i the {@code integer} to divide by.
+     * @return this * i
+     */
+    public Fraction divide(final int i) {
+        return divide(new Fraction(i));
+    }
+
+    /**
+     * Gets the fraction percentage as a {@code double}. This calculates the fraction as the
+     * numerator divided by denominator multiplied by 100.
+     *
+     * @return the fraction percentage as a {@code double}.
+     */
+    public double percentageValue() {
+        return 100 * doubleValue();
+    }
+
+    /**
+     * Creates a {@code Fraction} instance with the 2 parts of a fraction Y/Z.
+     *
+     * <p>Any negative signs are resolved to be on the numerator.
+     *
+     * @param numerator the numerator, for example the three in 'three sevenths'
+     * @param denominator the denominator, for example the seven in 'three sevenths'
+     * @return a new fraction instance, with the numerator and denominator reduced
+     * @throws MathArithmeticException if the denominator is {@code zero}
+     */
+    public static Fraction getReducedFraction(int numerator, int denominator) {
+        if (denominator == 0) {
+            throw new MathArithmeticException(
+                    LocalizedFormats.ZERO_DENOMINATOR_IN_FRACTION, numerator, denominator);
+        }
+        if (numerator == 0) {
+            return ZERO; // normalize zero.
+        }
+        // allow 2^k/-2^31 as a valid fraction (where k>0)
+        if (denominator == Integer.MIN_VALUE && (numerator & 1) == 0) {
+            numerator /= 2;
+            denominator /= 2;
+        }
+        if (denominator < 0) {
+            if (numerator == Integer.MIN_VALUE || denominator == Integer.MIN_VALUE) {
+                throw new MathArithmeticException(
+                        LocalizedFormats.OVERFLOW_IN_FRACTION, numerator, denominator);
+            }
+            numerator = -numerator;
+            denominator = -denominator;
+        }
+        // simplify fraction.
+        int gcd = ArithmeticUtils.gcd(numerator, denominator);
+        numerator /= gcd;
+        denominator /= gcd;
+        return new Fraction(numerator, denominator);
+    }
+
+    /**
+     * Returns the {@code String} representing this fraction, ie "num / dem" or just "num" if the
+     * denominator is one.
+     *
+     * @return a string representation of the fraction.
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        String str = null;
+        if (denominator == 1) {
+            str = Integer.toString(numerator);
+        } else if (numerator == 0) {
+            str = "0";
+        } else {
+            str = numerator + " / " + denominator;
+        }
+        return str;
+    }
+
+    /** {@inheritDoc} */
+    public FractionField getField() {
+        return FractionField.getInstance();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/fraction/FractionConversionException.java b/src/main/java/org/apache/commons/math3/fraction/FractionConversionException.java
new file mode 100644
index 0000000..cc34cae
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fraction/FractionConversionException.java
@@ -0,0 +1,56 @@
+/*
+ * 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.math3.fraction;
+
+import org.apache.commons.math3.exception.ConvergenceException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Error thrown when a double value cannot be converted to a fraction in the allowed number of
+ * iterations.
+ *
+ * @since 1.2
+ */
+public class FractionConversionException extends ConvergenceException {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -4661812640132576263L;
+
+    /**
+     * Constructs an exception with specified formatted detail message. Message formatting is
+     * delegated to {@link java.text.MessageFormat}.
+     *
+     * @param value double value to convert
+     * @param maxIterations maximal number of iterations allowed
+     */
+    public FractionConversionException(double value, int maxIterations) {
+        super(LocalizedFormats.FAILED_FRACTION_CONVERSION, value, maxIterations);
+    }
+
+    /**
+     * Constructs an exception with specified formatted detail message. Message formatting is
+     * delegated to {@link java.text.MessageFormat}.
+     *
+     * @param value double value to convert
+     * @param p current numerator
+     * @param q current denominator
+     */
+    public FractionConversionException(double value, long p, long q) {
+        super(LocalizedFormats.FRACTION_CONVERSION_OVERFLOW, value, p, q);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/fraction/FractionField.java b/src/main/java/org/apache/commons/math3/fraction/FractionField.java
new file mode 100644
index 0000000..bc00716
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fraction/FractionField.java
@@ -0,0 +1,87 @@
+/*
+ * 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.math3.fraction;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.FieldElement;
+
+import java.io.Serializable;
+
+/**
+ * Representation of the fractional numbers field.
+ *
+ * <p>This class is a singleton.
+ *
+ * @see Fraction
+ * @since 2.0
+ */
+public class FractionField implements Field<Fraction>, Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -1257768487499119313L;
+
+    /** Private constructor for the singleton. */
+    private FractionField() {}
+
+    /**
+     * Get the unique instance.
+     *
+     * @return the unique instance
+     */
+    public static FractionField getInstance() {
+        return LazyHolder.INSTANCE;
+    }
+
+    /** {@inheritDoc} */
+    public Fraction getOne() {
+        return Fraction.ONE;
+    }
+
+    /** {@inheritDoc} */
+    public Fraction getZero() {
+        return Fraction.ZERO;
+    }
+
+    /** {@inheritDoc} */
+    public Class<? extends FieldElement<Fraction>> getRuntimeClass() {
+        return Fraction.class;
+    }
+
+    // CHECKSTYLE: stop HideUtilityClassConstructor
+    /**
+     * Holder for the instance.
+     *
+     * <p>We use here the Initialization On Demand Holder Idiom.
+     */
+    private static class LazyHolder {
+        /** Cached field instance. */
+        private static final FractionField INSTANCE = new FractionField();
+    }
+
+    // CHECKSTYLE: resume HideUtilityClassConstructor
+
+    /**
+     * Handle deserialization of the singleton.
+     *
+     * @return the singleton instance
+     */
+    private Object readResolve() {
+        // return the singleton instance
+        return LazyHolder.INSTANCE;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/fraction/FractionFormat.java b/src/main/java/org/apache/commons/math3/fraction/FractionFormat.java
new file mode 100644
index 0000000..181de7e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fraction/FractionFormat.java
@@ -0,0 +1,270 @@
+/*
+ * 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.math3.fraction;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.MathParseException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+import java.text.FieldPosition;
+import java.text.NumberFormat;
+import java.text.ParsePosition;
+import java.util.Locale;
+
+/**
+ * Formats a Fraction number in proper format or improper format. The number format for each of the
+ * whole number, numerator and, denominator can be configured.
+ *
+ * @since 1.1
+ */
+public class FractionFormat extends AbstractFormat {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 3008655719530972611L;
+
+    /**
+     * Create an improper formatting instance with the default number format for the numerator and
+     * denominator.
+     */
+    public FractionFormat() {}
+
+    /**
+     * Create an improper formatting instance with a custom number format for both the numerator and
+     * denominator.
+     *
+     * @param format the custom format for both the numerator and denominator.
+     */
+    public FractionFormat(final NumberFormat format) {
+        super(format);
+    }
+
+    /**
+     * Create an improper formatting instance with a custom number format for the numerator and a
+     * custom number format for the denominator.
+     *
+     * @param numeratorFormat the custom format for the numerator.
+     * @param denominatorFormat the custom format for the denominator.
+     */
+    public FractionFormat(
+            final NumberFormat numeratorFormat, final NumberFormat denominatorFormat) {
+        super(numeratorFormat, denominatorFormat);
+    }
+
+    /**
+     * Get the set of locales for which complex formats are available. This is the same set as the
+     * {@link NumberFormat} set.
+     *
+     * @return available complex format locales.
+     */
+    public static Locale[] getAvailableLocales() {
+        return NumberFormat.getAvailableLocales();
+    }
+
+    /**
+     * This static method calls formatFraction() on a default instance of FractionFormat.
+     *
+     * @param f Fraction object to format
+     * @return a formatted fraction in proper form.
+     */
+    public static String formatFraction(Fraction f) {
+        return getImproperInstance().format(f);
+    }
+
+    /**
+     * Returns the default complex format for the current locale.
+     *
+     * @return the default complex format.
+     */
+    public static FractionFormat getImproperInstance() {
+        return getImproperInstance(Locale.getDefault());
+    }
+
+    /**
+     * Returns the default complex format for the given locale.
+     *
+     * @param locale the specific locale used by the format.
+     * @return the complex format specific to the given locale.
+     */
+    public static FractionFormat getImproperInstance(final Locale locale) {
+        return new FractionFormat(getDefaultNumberFormat(locale));
+    }
+
+    /**
+     * Returns the default complex format for the current locale.
+     *
+     * @return the default complex format.
+     */
+    public static FractionFormat getProperInstance() {
+        return getProperInstance(Locale.getDefault());
+    }
+
+    /**
+     * Returns the default complex format for the given locale.
+     *
+     * @param locale the specific locale used by the format.
+     * @return the complex format specific to the given locale.
+     */
+    public static FractionFormat getProperInstance(final Locale locale) {
+        return new ProperFractionFormat(getDefaultNumberFormat(locale));
+    }
+
+    /**
+     * Create a default number format. The default number format is based on {@link
+     * NumberFormat#getNumberInstance(java.util.Locale)} with the only customizing is the maximum
+     * number of fraction digits, which is set to 0.
+     *
+     * @return the default number format.
+     */
+    protected static NumberFormat getDefaultNumberFormat() {
+        return getDefaultNumberFormat(Locale.getDefault());
+    }
+
+    /**
+     * Formats a {@link Fraction} object to produce a string. The fraction is output in improper
+     * format.
+     *
+     * @param fraction the object to format.
+     * @param toAppendTo where the text is to be appended
+     * @param pos On input: an alignment field, if desired. On output: the offsets of the alignment
+     *     field
+     * @return the value passed in as toAppendTo.
+     */
+    public StringBuffer format(
+            final Fraction fraction, final StringBuffer toAppendTo, final FieldPosition pos) {
+
+        pos.setBeginIndex(0);
+        pos.setEndIndex(0);
+
+        getNumeratorFormat().format(fraction.getNumerator(), toAppendTo, pos);
+        toAppendTo.append(" / ");
+        getDenominatorFormat().format(fraction.getDenominator(), toAppendTo, pos);
+
+        return toAppendTo;
+    }
+
+    /**
+     * Formats an object and appends the result to a StringBuffer. <code>obj</code> must be either a
+     * {@link Fraction} object or a {@link Number} object. Any other type of object will result in
+     * an {@link IllegalArgumentException} being thrown.
+     *
+     * @param obj the object to format.
+     * @param toAppendTo where the text is to be appended
+     * @param pos On input: an alignment field, if desired. On output: the offsets of the alignment
+     *     field
+     * @return the value passed in as toAppendTo.
+     * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer,
+     *     java.text.FieldPosition)
+     * @throws FractionConversionException if the number cannot be converted to a fraction
+     * @throws MathIllegalArgumentException if <code>obj</code> is not a valid type.
+     */
+    @Override
+    public StringBuffer format(
+            final Object obj, final StringBuffer toAppendTo, final FieldPosition pos)
+            throws FractionConversionException, MathIllegalArgumentException {
+        StringBuffer ret = null;
+
+        if (obj instanceof Fraction) {
+            ret = format((Fraction) obj, toAppendTo, pos);
+        } else if (obj instanceof Number) {
+            ret = format(new Fraction(((Number) obj).doubleValue()), toAppendTo, pos);
+        } else {
+            throw new MathIllegalArgumentException(
+                    LocalizedFormats.CANNOT_FORMAT_OBJECT_TO_FRACTION);
+        }
+
+        return ret;
+    }
+
+    /**
+     * Parses a string to produce a {@link Fraction} object.
+     *
+     * @param source the string to parse
+     * @return the parsed {@link Fraction} object.
+     * @exception MathParseException if the beginning of the specified string cannot be parsed.
+     */
+    @Override
+    public Fraction parse(final String source) throws MathParseException {
+        final ParsePosition parsePosition = new ParsePosition(0);
+        final Fraction result = parse(source, parsePosition);
+        if (parsePosition.getIndex() == 0) {
+            throw new MathParseException(source, parsePosition.getErrorIndex(), Fraction.class);
+        }
+        return result;
+    }
+
+    /**
+     * Parses a string to produce a {@link Fraction} object. This method expects the string to be
+     * formatted as an improper fraction.
+     *
+     * @param source the string to parse
+     * @param pos input/output parsing parameter.
+     * @return the parsed {@link Fraction} object.
+     */
+    @Override
+    public Fraction parse(final String source, final ParsePosition pos) {
+        final int initialIndex = pos.getIndex();
+
+        // parse whitespace
+        parseAndIgnoreWhitespace(source, pos);
+
+        // parse numerator
+        final Number num = getNumeratorFormat().parse(source, pos);
+        if (num == null) {
+            // invalid integer number
+            // set index back to initial, error index should already be set
+            // character examined.
+            pos.setIndex(initialIndex);
+            return null;
+        }
+
+        // parse '/'
+        final int startIndex = pos.getIndex();
+        final char c = parseNextCharacter(source, pos);
+        switch (c) {
+            case 0:
+                // no '/'
+                // return num as a fraction
+                return new Fraction(num.intValue(), 1);
+            case '/':
+                // found '/', continue parsing denominator
+                break;
+            default:
+                // invalid '/'
+                // set index back to initial, error index should be the last
+                // character examined.
+                pos.setIndex(initialIndex);
+                pos.setErrorIndex(startIndex);
+                return null;
+        }
+
+        // parse whitespace
+        parseAndIgnoreWhitespace(source, pos);
+
+        // parse denominator
+        final Number den = getDenominatorFormat().parse(source, pos);
+        if (den == null) {
+            // invalid integer number
+            // set index back to initial, error index should already be set
+            // character examined.
+            pos.setIndex(initialIndex);
+            return null;
+        }
+
+        return new Fraction(num.intValue(), den.intValue());
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/fraction/ProperBigFractionFormat.java b/src/main/java/org/apache/commons/math3/fraction/ProperBigFractionFormat.java
new file mode 100644
index 0000000..6e034b5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fraction/ProperBigFractionFormat.java
@@ -0,0 +1,239 @@
+/*
+ * 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.math3.fraction;
+
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+import java.math.BigInteger;
+import java.text.FieldPosition;
+import java.text.NumberFormat;
+import java.text.ParsePosition;
+
+/**
+ * Formats a BigFraction number in proper format. The number format for each of the whole number,
+ * numerator and, denominator can be configured.
+ *
+ * <p>Minus signs are only allowed in the whole number part - i.e., "-3 1/2" is legitimate and
+ * denotes -7/2, but "-3 -1/2" is invalid and will result in a <code>ParseException</code>.
+ *
+ * @since 1.1
+ */
+public class ProperBigFractionFormat extends BigFractionFormat {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -6337346779577272307L;
+
+    /** The format used for the whole number. */
+    private NumberFormat wholeFormat;
+
+    /**
+     * Create a proper formatting instance with the default number format for the whole, numerator,
+     * and denominator.
+     */
+    public ProperBigFractionFormat() {
+        this(getDefaultNumberFormat());
+    }
+
+    /**
+     * Create a proper formatting instance with a custom number format for the whole, numerator, and
+     * denominator.
+     *
+     * @param format the custom format for the whole, numerator, and denominator.
+     */
+    public ProperBigFractionFormat(final NumberFormat format) {
+        this(format, (NumberFormat) format.clone(), (NumberFormat) format.clone());
+    }
+
+    /**
+     * Create a proper formatting instance with a custom number format for each of the whole,
+     * numerator, and denominator.
+     *
+     * @param wholeFormat the custom format for the whole.
+     * @param numeratorFormat the custom format for the numerator.
+     * @param denominatorFormat the custom format for the denominator.
+     */
+    public ProperBigFractionFormat(
+            final NumberFormat wholeFormat,
+            final NumberFormat numeratorFormat,
+            final NumberFormat denominatorFormat) {
+        super(numeratorFormat, denominatorFormat);
+        setWholeFormat(wholeFormat);
+    }
+
+    /**
+     * Formats a {@link BigFraction} object to produce a string. The BigFraction is output in proper
+     * format.
+     *
+     * @param fraction the object to format.
+     * @param toAppendTo where the text is to be appended
+     * @param pos On input: an alignment field, if desired. On output: the offsets of the alignment
+     *     field
+     * @return the value passed in as toAppendTo.
+     */
+    @Override
+    public StringBuffer format(
+            final BigFraction fraction, final StringBuffer toAppendTo, final FieldPosition pos) {
+
+        pos.setBeginIndex(0);
+        pos.setEndIndex(0);
+
+        BigInteger num = fraction.getNumerator();
+        BigInteger den = fraction.getDenominator();
+        BigInteger whole = num.divide(den);
+        num = num.remainder(den);
+
+        if (!BigInteger.ZERO.equals(whole)) {
+            getWholeFormat().format(whole, toAppendTo, pos);
+            toAppendTo.append(' ');
+            if (num.compareTo(BigInteger.ZERO) < 0) {
+                num = num.negate();
+            }
+        }
+        getNumeratorFormat().format(num, toAppendTo, pos);
+        toAppendTo.append(" / ");
+        getDenominatorFormat().format(den, toAppendTo, pos);
+
+        return toAppendTo;
+    }
+
+    /**
+     * Access the whole format.
+     *
+     * @return the whole format.
+     */
+    public NumberFormat getWholeFormat() {
+        return wholeFormat;
+    }
+
+    /**
+     * Parses a string to produce a {@link BigFraction} object. This method expects the string to be
+     * formatted as a proper BigFraction.
+     *
+     * <p>Minus signs are only allowed in the whole number part - i.e., "-3 1/2" is legitimate and
+     * denotes -7/2, but "-3 -1/2" is invalid and will result in a <code>ParseException</code>.
+     *
+     * @param source the string to parse
+     * @param pos input/ouput parsing parameter.
+     * @return the parsed {@link BigFraction} object.
+     */
+    @Override
+    public BigFraction parse(final String source, final ParsePosition pos) {
+        // try to parse improper BigFraction
+        BigFraction ret = super.parse(source, pos);
+        if (ret != null) {
+            return ret;
+        }
+
+        final int initialIndex = pos.getIndex();
+
+        // parse whitespace
+        parseAndIgnoreWhitespace(source, pos);
+
+        // parse whole
+        BigInteger whole = parseNextBigInteger(source, pos);
+        if (whole == null) {
+            // invalid integer number
+            // set index back to initial, error index should already be set
+            // character examined.
+            pos.setIndex(initialIndex);
+            return null;
+        }
+
+        // parse whitespace
+        parseAndIgnoreWhitespace(source, pos);
+
+        // parse numerator
+        BigInteger num = parseNextBigInteger(source, pos);
+        if (num == null) {
+            // invalid integer number
+            // set index back to initial, error index should already be set
+            // character examined.
+            pos.setIndex(initialIndex);
+            return null;
+        }
+
+        if (num.compareTo(BigInteger.ZERO) < 0) {
+            // minus signs should be leading, invalid expression
+            pos.setIndex(initialIndex);
+            return null;
+        }
+
+        // parse '/'
+        final int startIndex = pos.getIndex();
+        final char c = parseNextCharacter(source, pos);
+        switch (c) {
+            case 0:
+                // no '/'
+                // return num as a BigFraction
+                return new BigFraction(num);
+            case '/':
+                // found '/', continue parsing denominator
+                break;
+            default:
+                // invalid '/'
+                // set index back to initial, error index should be the last
+                // character examined.
+                pos.setIndex(initialIndex);
+                pos.setErrorIndex(startIndex);
+                return null;
+        }
+
+        // parse whitespace
+        parseAndIgnoreWhitespace(source, pos);
+
+        // parse denominator
+        final BigInteger den = parseNextBigInteger(source, pos);
+        if (den == null) {
+            // invalid integer number
+            // set index back to initial, error index should already be set
+            // character examined.
+            pos.setIndex(initialIndex);
+            return null;
+        }
+
+        if (den.compareTo(BigInteger.ZERO) < 0) {
+            // minus signs must be leading, invalid
+            pos.setIndex(initialIndex);
+            return null;
+        }
+
+        boolean wholeIsNeg = whole.compareTo(BigInteger.ZERO) < 0;
+        if (wholeIsNeg) {
+            whole = whole.negate();
+        }
+        num = whole.multiply(den).add(num);
+        if (wholeIsNeg) {
+            num = num.negate();
+        }
+
+        return new BigFraction(num, den);
+    }
+
+    /**
+     * Modify the whole format.
+     *
+     * @param format The new whole format value.
+     * @throws NullArgumentException if {@code format} is {@code null}.
+     */
+    public void setWholeFormat(final NumberFormat format) {
+        if (format == null) {
+            throw new NullArgumentException(LocalizedFormats.WHOLE_FORMAT);
+        }
+        this.wholeFormat = format;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/fraction/ProperFractionFormat.java b/src/main/java/org/apache/commons/math3/fraction/ProperFractionFormat.java
new file mode 100644
index 0000000..3f74f16
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fraction/ProperFractionFormat.java
@@ -0,0 +1,231 @@
+/*
+ * 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.math3.fraction;
+
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+
+import java.text.FieldPosition;
+import java.text.NumberFormat;
+import java.text.ParsePosition;
+
+/**
+ * Formats a Fraction number in proper format. The number format for each of the whole number,
+ * numerator and, denominator can be configured.
+ *
+ * <p>Minus signs are only allowed in the whole number part - i.e., "-3 1/2" is legitimate and
+ * denotes -7/2, but "-3 -1/2" is invalid and will result in a <code>ParseException</code>.
+ *
+ * @since 1.1
+ */
+public class ProperFractionFormat extends FractionFormat {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 760934726031766749L;
+
+    /** The format used for the whole number. */
+    private NumberFormat wholeFormat;
+
+    /**
+     * Create a proper formatting instance with the default number format for the whole, numerator,
+     * and denominator.
+     */
+    public ProperFractionFormat() {
+        this(getDefaultNumberFormat());
+    }
+
+    /**
+     * Create a proper formatting instance with a custom number format for the whole, numerator, and
+     * denominator.
+     *
+     * @param format the custom format for the whole, numerator, and denominator.
+     */
+    public ProperFractionFormat(NumberFormat format) {
+        this(format, (NumberFormat) format.clone(), (NumberFormat) format.clone());
+    }
+
+    /**
+     * Create a proper formatting instance with a custom number format for each of the whole,
+     * numerator, and denominator.
+     *
+     * @param wholeFormat the custom format for the whole.
+     * @param numeratorFormat the custom format for the numerator.
+     * @param denominatorFormat the custom format for the denominator.
+     */
+    public ProperFractionFormat(
+            NumberFormat wholeFormat,
+            NumberFormat numeratorFormat,
+            NumberFormat denominatorFormat) {
+        super(numeratorFormat, denominatorFormat);
+        setWholeFormat(wholeFormat);
+    }
+
+    /**
+     * Formats a {@link Fraction} object to produce a string. The fraction is output in proper
+     * format.
+     *
+     * @param fraction the object to format.
+     * @param toAppendTo where the text is to be appended
+     * @param pos On input: an alignment field, if desired. On output: the offsets of the alignment
+     *     field
+     * @return the value passed in as toAppendTo.
+     */
+    @Override
+    public StringBuffer format(Fraction fraction, StringBuffer toAppendTo, FieldPosition pos) {
+
+        pos.setBeginIndex(0);
+        pos.setEndIndex(0);
+
+        int num = fraction.getNumerator();
+        int den = fraction.getDenominator();
+        int whole = num / den;
+        num %= den;
+
+        if (whole != 0) {
+            getWholeFormat().format(whole, toAppendTo, pos);
+            toAppendTo.append(' ');
+            num = FastMath.abs(num);
+        }
+        getNumeratorFormat().format(num, toAppendTo, pos);
+        toAppendTo.append(" / ");
+        getDenominatorFormat().format(den, toAppendTo, pos);
+
+        return toAppendTo;
+    }
+
+    /**
+     * Access the whole format.
+     *
+     * @return the whole format.
+     */
+    public NumberFormat getWholeFormat() {
+        return wholeFormat;
+    }
+
+    /**
+     * Parses a string to produce a {@link Fraction} object. This method expects the string to be
+     * formatted as a proper fraction.
+     *
+     * <p>Minus signs are only allowed in the whole number part - i.e., "-3 1/2" is legitimate and
+     * denotes -7/2, but "-3 -1/2" is invalid and will result in a <code>ParseException</code>.
+     *
+     * @param source the string to parse
+     * @param pos input/ouput parsing parameter.
+     * @return the parsed {@link Fraction} object.
+     */
+    @Override
+    public Fraction parse(String source, ParsePosition pos) {
+        // try to parse improper fraction
+        Fraction ret = super.parse(source, pos);
+        if (ret != null) {
+            return ret;
+        }
+
+        int initialIndex = pos.getIndex();
+
+        // parse whitespace
+        parseAndIgnoreWhitespace(source, pos);
+
+        // parse whole
+        Number whole = getWholeFormat().parse(source, pos);
+        if (whole == null) {
+            // invalid integer number
+            // set index back to initial, error index should already be set
+            // character examined.
+            pos.setIndex(initialIndex);
+            return null;
+        }
+
+        // parse whitespace
+        parseAndIgnoreWhitespace(source, pos);
+
+        // parse numerator
+        Number num = getNumeratorFormat().parse(source, pos);
+        if (num == null) {
+            // invalid integer number
+            // set index back to initial, error index should already be set
+            // character examined.
+            pos.setIndex(initialIndex);
+            return null;
+        }
+
+        if (num.intValue() < 0) {
+            // minus signs should be leading, invalid expression
+            pos.setIndex(initialIndex);
+            return null;
+        }
+
+        // parse '/'
+        int startIndex = pos.getIndex();
+        char c = parseNextCharacter(source, pos);
+        switch (c) {
+            case 0:
+                // no '/'
+                // return num as a fraction
+                return new Fraction(num.intValue(), 1);
+            case '/':
+                // found '/', continue parsing denominator
+                break;
+            default:
+                // invalid '/'
+                // set index back to initial, error index should be the last
+                // character examined.
+                pos.setIndex(initialIndex);
+                pos.setErrorIndex(startIndex);
+                return null;
+        }
+
+        // parse whitespace
+        parseAndIgnoreWhitespace(source, pos);
+
+        // parse denominator
+        Number den = getDenominatorFormat().parse(source, pos);
+        if (den == null) {
+            // invalid integer number
+            // set index back to initial, error index should already be set
+            // character examined.
+            pos.setIndex(initialIndex);
+            return null;
+        }
+
+        if (den.intValue() < 0) {
+            // minus signs must be leading, invalid
+            pos.setIndex(initialIndex);
+            return null;
+        }
+
+        int w = whole.intValue();
+        int n = num.intValue();
+        int d = den.intValue();
+        return new Fraction(((FastMath.abs(w) * d) + n) * MathUtils.copySign(1, w), d);
+    }
+
+    /**
+     * Modify the whole format.
+     *
+     * @param format The new whole format value.
+     * @throws NullArgumentException if {@code format} is {@code null}.
+     */
+    public void setWholeFormat(NumberFormat format) {
+        if (format == null) {
+            throw new NullArgumentException(LocalizedFormats.WHOLE_FORMAT);
+        }
+        this.wholeFormat = format;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/fraction/package-info.java b/src/main/java/org/apache/commons/math3/fraction/package-info.java
new file mode 100644
index 0000000..9d05869
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fraction/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+/** Fraction number type and fraction number formatting. */
+package org.apache.commons.math3.fraction;
diff --git a/src/main/java/org/apache/commons/math3/genetics/AbstractListChromosome.java b/src/main/java/org/apache/commons/math3/genetics/AbstractListChromosome.java
new file mode 100644
index 0000000..60d09c7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/genetics/AbstractListChromosome.java
@@ -0,0 +1,119 @@
+/*
+ * 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.math3.genetics;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Chromosome represented by an immutable list of a fixed length.
+ *
+ * @param <T> type of the representation list
+ * @since 2.0
+ */
+public abstract class AbstractListChromosome<T> extends Chromosome {
+
+    /** List representing the chromosome */
+    private final List<T> representation;
+
+    /**
+     * Constructor, copying the input representation.
+     *
+     * @param representation inner representation of the chromosome
+     * @throws InvalidRepresentationException iff the <code>representation</code> can not represent
+     *     a valid chromosome
+     */
+    public AbstractListChromosome(final List<T> representation)
+            throws InvalidRepresentationException {
+        this(representation, true);
+    }
+
+    /**
+     * Constructor, copying the input representation.
+     *
+     * @param representation inner representation of the chromosome
+     * @throws InvalidRepresentationException iff the <code>representation</code> can not represent
+     *     a valid chromosome
+     */
+    public AbstractListChromosome(final T[] representation) throws InvalidRepresentationException {
+        this(Arrays.asList(representation));
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param representation inner representation of the chromosome
+     * @param copyList if {@code true}, the representation will be copied, otherwise it will be
+     *     referenced.
+     * @since 3.3
+     */
+    public AbstractListChromosome(final List<T> representation, final boolean copyList) {
+        checkValidity(representation);
+        this.representation =
+                Collections.unmodifiableList(
+                        copyList ? new ArrayList<T>(representation) : representation);
+    }
+
+    /**
+     * Asserts that <code>representation</code> can represent a valid chromosome.
+     *
+     * @param chromosomeRepresentation representation of the chromosome
+     * @throws InvalidRepresentationException iff the <code>representation</code> can not represent
+     *     a valid chromosome
+     */
+    protected abstract void checkValidity(List<T> chromosomeRepresentation)
+            throws InvalidRepresentationException;
+
+    /**
+     * Returns the (immutable) inner representation of the chromosome.
+     *
+     * @return the representation of the chromosome
+     */
+    protected List<T> getRepresentation() {
+        return representation;
+    }
+
+    /**
+     * Returns the length of the chromosome.
+     *
+     * @return the length of the chromosome
+     */
+    public int getLength() {
+        return getRepresentation().size();
+    }
+
+    /**
+     * Creates a new instance of the same class as <code>this</code> is, with a given <code>
+     * arrayRepresentation</code>. This is needed in crossover and mutation operators, where we need
+     * a new instance of the same class, but with different array representation.
+     *
+     * <p>Usually, this method just calls a constructor of the class.
+     *
+     * @param chromosomeRepresentation the inner array representation of the new chromosome.
+     * @return new instance extended from FixedLengthChromosome with the given arrayRepresentation
+     */
+    public abstract AbstractListChromosome<T> newFixedLengthChromosome(
+            final List<T> chromosomeRepresentation);
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return String.format("(f=%s %s)", getFitness(), getRepresentation());
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/genetics/BinaryChromosome.java b/src/main/java/org/apache/commons/math3/genetics/BinaryChromosome.java
new file mode 100644
index 0000000..6874ddc
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/genetics/BinaryChromosome.java
@@ -0,0 +1,100 @@
+/*
+ * 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.math3.genetics;
+
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Chromosome represented by a vector of 0s and 1s.
+ *
+ * @since 2.0
+ */
+public abstract class BinaryChromosome extends AbstractListChromosome<Integer> {
+
+    /**
+     * Constructor.
+     *
+     * @param representation list of {0,1} values representing the chromosome
+     * @throws InvalidRepresentationException iff the <code>representation</code> can not represent
+     *     a valid chromosome
+     */
+    public BinaryChromosome(List<Integer> representation) throws InvalidRepresentationException {
+        super(representation);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param representation array of {0,1} values representing the chromosome
+     * @throws InvalidRepresentationException iff the <code>representation</code> can not represent
+     *     a valid chromosome
+     */
+    public BinaryChromosome(Integer[] representation) throws InvalidRepresentationException {
+        super(representation);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void checkValidity(List<Integer> chromosomeRepresentation)
+            throws InvalidRepresentationException {
+        for (int i : chromosomeRepresentation) {
+            if (i < 0 || i > 1) {
+                throw new InvalidRepresentationException(LocalizedFormats.INVALID_BINARY_DIGIT, i);
+            }
+        }
+    }
+
+    /**
+     * Returns a representation of a random binary array of length <code>length</code>.
+     *
+     * @param length length of the array
+     * @return a random binary array of length <code>length</code>
+     */
+    public static List<Integer> randomBinaryRepresentation(int length) {
+        // random binary list
+        List<Integer> rList = new ArrayList<Integer>(length);
+        for (int j = 0; j < length; j++) {
+            rList.add(GeneticAlgorithm.getRandomGenerator().nextInt(2));
+        }
+        return rList;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected boolean isSame(Chromosome another) {
+        // type check
+        if (!(another instanceof BinaryChromosome)) {
+            return false;
+        }
+        BinaryChromosome anotherBc = (BinaryChromosome) another;
+        // size check
+        if (getLength() != anotherBc.getLength()) {
+            return false;
+        }
+
+        for (int i = 0; i < getRepresentation().size(); i++) {
+            if (!(getRepresentation().get(i).equals(anotherBc.getRepresentation().get(i)))) {
+                return false;
+            }
+        }
+        // all is ok
+        return true;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/genetics/BinaryMutation.java b/src/main/java/org/apache/commons/math3/genetics/BinaryMutation.java
new file mode 100644
index 0000000..9372b13
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/genetics/BinaryMutation.java
@@ -0,0 +1,56 @@
+/*
+ * 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.math3.genetics;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Mutation for {@link BinaryChromosome}s. Randomly changes one gene.
+ *
+ * @since 2.0
+ */
+public class BinaryMutation implements MutationPolicy {
+
+    /**
+     * Mutate the given chromosome. Randomly changes one gene.
+     *
+     * @param original the original chromosome.
+     * @return the mutated chromosome.
+     * @throws MathIllegalArgumentException if <code>original</code> is not an instance of {@link
+     *     BinaryChromosome}.
+     */
+    public Chromosome mutate(Chromosome original) throws MathIllegalArgumentException {
+        if (!(original instanceof BinaryChromosome)) {
+            throw new MathIllegalArgumentException(LocalizedFormats.INVALID_BINARY_CHROMOSOME);
+        }
+
+        BinaryChromosome origChrom = (BinaryChromosome) original;
+        List<Integer> newRepr = new ArrayList<Integer>(origChrom.getRepresentation());
+
+        // randomly select a gene
+        int geneIndex = GeneticAlgorithm.getRandomGenerator().nextInt(origChrom.getLength());
+        // and change it
+        newRepr.set(geneIndex, origChrom.getRepresentation().get(geneIndex) == 0 ? 1 : 0);
+
+        Chromosome newChrom = origChrom.newFixedLengthChromosome(newRepr);
+        return newChrom;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/genetics/Chromosome.java b/src/main/java/org/apache/commons/math3/genetics/Chromosome.java
new file mode 100644
index 0000000..532a726
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/genetics/Chromosome.java
@@ -0,0 +1,108 @@
+/*
+ * 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.math3.genetics;
+
+/**
+ * Individual in a population. Chromosomes are compared based on their fitness.
+ *
+ * <p>The chromosomes are IMMUTABLE, and so their fitness is also immutable and therefore it can be
+ * cached.
+ *
+ * @since 2.0
+ */
+public abstract class Chromosome implements Comparable<Chromosome>, Fitness {
+    /** Value assigned when no fitness has been computed yet. */
+    private static final double NO_FITNESS = Double.NEGATIVE_INFINITY;
+
+    /** Cached value of the fitness of this chromosome. */
+    private double fitness = NO_FITNESS;
+
+    /**
+     * Access the fitness of this chromosome. The bigger the fitness, the better the chromosome.
+     *
+     * <p>Computation of fitness is usually very time-consuming task, therefore the fitness is
+     * cached.
+     *
+     * @return the fitness
+     */
+    public double getFitness() {
+        if (this.fitness == NO_FITNESS) {
+            // no cache - compute the fitness
+            this.fitness = fitness();
+        }
+        return this.fitness;
+    }
+
+    /**
+     * Compares two chromosomes based on their fitness. The bigger the fitness, the better the
+     * chromosome.
+     *
+     * @param another another chromosome to compare
+     * @return
+     *     <ul>
+     *       <li>-1 if <code>another</code> is better than <code>this</code>
+     *       <li>1 if <code>another</code> is worse than <code>this</code>
+     *       <li>0 if the two chromosomes have the same fitness
+     *     </ul>
+     */
+    public int compareTo(final Chromosome another) {
+        return Double.compare(getFitness(), another.getFitness());
+    }
+
+    /**
+     * Returns <code>true</code> iff <code>another</code> has the same representation and therefore
+     * the same fitness. By default, it returns false -- override it in your implementation if you
+     * need it.
+     *
+     * @param another chromosome to compare
+     * @return true if <code>another</code> is equivalent to this chromosome
+     */
+    protected boolean isSame(final Chromosome another) {
+        return false;
+    }
+
+    /**
+     * Searches the <code>population</code> for another chromosome with the same representation. If
+     * such chromosome is found, it is returned, if no such chromosome exists, returns <code>null
+     * </code>.
+     *
+     * @param population Population to search
+     * @return Chromosome with the same representation, or <code>null</code> if no such chromosome
+     *     exists.
+     */
+    protected Chromosome findSameChromosome(final Population population) {
+        for (Chromosome anotherChr : population) {
+            if (this.isSame(anotherChr)) {
+                return anotherChr;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Searches the population for a chromosome representing the same solution, and if it finds one,
+     * updates the fitness to its value.
+     *
+     * @param population Population to search
+     */
+    public void searchForFitnessUpdate(final Population population) {
+        Chromosome sameChromosome = findSameChromosome(population);
+        if (sameChromosome != null) {
+            fitness = sameChromosome.getFitness();
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/genetics/ChromosomePair.java b/src/main/java/org/apache/commons/math3/genetics/ChromosomePair.java
new file mode 100644
index 0000000..e47c1b7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/genetics/ChromosomePair.java
@@ -0,0 +1,66 @@
+/*
+ * 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.math3.genetics;
+
+/**
+ * A pair of {@link Chromosome} objects.
+ *
+ * @since 2.0
+ */
+public class ChromosomePair {
+    /** the first chromosome in the pair. */
+    private final Chromosome first;
+
+    /** the second chromosome in the pair. */
+    private final Chromosome second;
+
+    /**
+     * Create a chromosome pair.
+     *
+     * @param c1 the first chromosome.
+     * @param c2 the second chromosome.
+     */
+    public ChromosomePair(final Chromosome c1, final Chromosome c2) {
+        super();
+        first = c1;
+        second = c2;
+    }
+
+    /**
+     * Access the first chromosome.
+     *
+     * @return the first chromosome.
+     */
+    public Chromosome getFirst() {
+        return first;
+    }
+
+    /**
+     * Access the second chromosome.
+     *
+     * @return the second chromosome.
+     */
+    public Chromosome getSecond() {
+        return second;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return String.format("(%s,%s)", getFirst(), getSecond());
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/genetics/CrossoverPolicy.java b/src/main/java/org/apache/commons/math3/genetics/CrossoverPolicy.java
new file mode 100644
index 0000000..d959044
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/genetics/CrossoverPolicy.java
@@ -0,0 +1,40 @@
+/*
+ * 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.math3.genetics;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+
+/**
+ * Policy used to create a pair of new chromosomes by performing a crossover operation on a source
+ * pair of chromosomes.
+ *
+ * @since 2.0
+ */
+public interface CrossoverPolicy {
+
+    /**
+     * Perform a crossover operation on the given chromosomes.
+     *
+     * @param first the first chromosome.
+     * @param second the second chromosome.
+     * @return the pair of new chromosomes that resulted from the crossover.
+     * @throws MathIllegalArgumentException if the given chromosomes are not compatible with this
+     *     {@link CrossoverPolicy}
+     */
+    ChromosomePair crossover(Chromosome first, Chromosome second)
+            throws MathIllegalArgumentException;
+}
diff --git a/src/main/java/org/apache/commons/math3/genetics/CycleCrossover.java b/src/main/java/org/apache/commons/math3/genetics/CycleCrossover.java
new file mode 100644
index 0000000..e07e47c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/genetics/CycleCrossover.java
@@ -0,0 +1,185 @@
+/*
+ * 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.math3.genetics;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Cycle Crossover [CX] builds offspring from <b>ordered</b> chromosomes by identifying cycles
+ * between two parent chromosomes. To form the children, the cycles are copied from the respective
+ * parents.
+ *
+ * <p>To form a cycle the following procedure is applied:
+ *
+ * <ol>
+ *   <li>start with the first gene of parent 1
+ *   <li>look at the gene at the same position of parent 2
+ *   <li>go to the position with the same gene in parent 1
+ *   <li>add this gene index to the cycle
+ *   <li>repeat the steps 2-5 until we arrive at the starting gene of this cycle
+ * </ol>
+ *
+ * The indices that form a cycle are then used to form the children in alternating order, i.e. in
+ * cycle 1, the genes of parent 1 are copied to child 1, while in cycle 2 the genes of parent 1 are
+ * copied to child 2, and so forth ... Example (zero-start cycle):
+ *
+ * <pre>
+ * p1 = (8 4 7 3 6 2 5 1 9 0)    X   c1 = (8 1 2 3 4 5 6 7 9 0)
+ * p2 = (0 1 2 3 4 5 6 7 8 9)    X   c2 = (0 4 7 3 6 2 5 1 8 9)
+ *
+ * cycle 1: 8 0 9
+ * cycle 2: 4 1 7 2 5 6
+ * cycle 3: 3
+ * </pre>
+ *
+ * This policy works only on {@link AbstractListChromosome}, and therefore it is parameterized by T.
+ * Moreover, the chromosomes must have same lengths.
+ *
+ * @see <a
+ *     href="http://www.rubicite.com/Tutorials/GeneticAlgorithms/CrossoverOperators/CycleCrossoverOperator.aspx">
+ *     Cycle Crossover Operator</a>
+ * @param <T> generic type of the {@link AbstractListChromosome}s for crossover
+ * @since 3.1
+ */
+public class CycleCrossover<T> implements CrossoverPolicy {
+
+    /** If the start index shall be chosen randomly. */
+    private final boolean randomStart;
+
+    /** Creates a new {@link CycleCrossover} policy. */
+    public CycleCrossover() {
+        this(false);
+    }
+
+    /**
+     * Creates a new {@link CycleCrossover} policy using the given {@code randomStart} behavior.
+     *
+     * @param randomStart whether the start index shall be chosen randomly or be set to 0
+     */
+    public CycleCrossover(final boolean randomStart) {
+        this.randomStart = randomStart;
+    }
+
+    /**
+     * Returns whether the starting index is chosen randomly or set to zero.
+     *
+     * @return {@code true} if the starting index is chosen randomly, {@code false} otherwise
+     */
+    public boolean isRandomStart() {
+        return randomStart;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws MathIllegalArgumentException if the chromosomes are not an instance of {@link
+     *     AbstractListChromosome}
+     * @throws DimensionMismatchException if the length of the two chromosomes is different
+     */
+    @SuppressWarnings("unchecked")
+    public ChromosomePair crossover(final Chromosome first, final Chromosome second)
+            throws DimensionMismatchException, MathIllegalArgumentException {
+
+        if (!(first instanceof AbstractListChromosome<?>
+                && second instanceof AbstractListChromosome<?>)) {
+            throw new MathIllegalArgumentException(
+                    LocalizedFormats.INVALID_FIXED_LENGTH_CHROMOSOME);
+        }
+        return mate((AbstractListChromosome<T>) first, (AbstractListChromosome<T>) second);
+    }
+
+    /**
+     * Helper for {@link #crossover(Chromosome, Chromosome)}. Performs the actual crossover.
+     *
+     * @param first the first chromosome
+     * @param second the second chromosome
+     * @return the pair of new chromosomes that resulted from the crossover
+     * @throws DimensionMismatchException if the length of the two chromosomes is different
+     */
+    protected ChromosomePair mate(
+            final AbstractListChromosome<T> first, final AbstractListChromosome<T> second)
+            throws DimensionMismatchException {
+
+        final int length = first.getLength();
+        if (length != second.getLength()) {
+            throw new DimensionMismatchException(second.getLength(), length);
+        }
+
+        // array representations of the parents
+        final List<T> parent1Rep = first.getRepresentation();
+        final List<T> parent2Rep = second.getRepresentation();
+        // and of the children: do a crossover copy to simplify the later processing
+        final List<T> child1Rep = new ArrayList<T>(second.getRepresentation());
+        final List<T> child2Rep = new ArrayList<T>(first.getRepresentation());
+
+        // the set of all visited indices so far
+        final Set<Integer> visitedIndices = new HashSet<Integer>(length);
+        // the indices of the current cycle
+        final List<Integer> indices = new ArrayList<Integer>(length);
+
+        // determine the starting index
+        int idx = randomStart ? GeneticAlgorithm.getRandomGenerator().nextInt(length) : 0;
+        int cycle = 1;
+
+        while (visitedIndices.size() < length) {
+            indices.add(idx);
+
+            T item = parent2Rep.get(idx);
+            idx = parent1Rep.indexOf(item);
+
+            while (idx != indices.get(0)) {
+                // add that index to the cycle indices
+                indices.add(idx);
+                // get the item in the second parent at that index
+                item = parent2Rep.get(idx);
+                // get the index of that item in the first parent
+                idx = parent1Rep.indexOf(item);
+            }
+
+            // for even cycles: swap the child elements on the indices found in this cycle
+            if (cycle++ % 2 != 0) {
+                for (int i : indices) {
+                    T tmp = child1Rep.get(i);
+                    child1Rep.set(i, child2Rep.get(i));
+                    child2Rep.set(i, tmp);
+                }
+            }
+
+            visitedIndices.addAll(indices);
+            // find next starting index: last one + 1 until we find an unvisited index
+            idx = (indices.get(0) + 1) % length;
+            while (visitedIndices.contains(idx) && visitedIndices.size() < length) {
+                idx++;
+                if (idx >= length) {
+                    idx = 0;
+                }
+            }
+            indices.clear();
+        }
+
+        return new ChromosomePair(
+                first.newFixedLengthChromosome(child1Rep),
+                second.newFixedLengthChromosome(child2Rep));
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/genetics/ElitisticListPopulation.java b/src/main/java/org/apache/commons/math3/genetics/ElitisticListPopulation.java
new file mode 100644
index 0000000..0e7009d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/genetics/ElitisticListPopulation.java
@@ -0,0 +1,125 @@
+/*
+ * 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.math3.genetics;
+
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.FastMath;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Population of chromosomes which uses elitism (certain percentage of the best chromosomes is
+ * directly copied to the next generation).
+ *
+ * @since 2.0
+ */
+public class ElitisticListPopulation extends ListPopulation {
+
+    /** percentage of chromosomes copied to the next generation */
+    private double elitismRate = 0.9;
+
+    /**
+     * Creates a new {@link ElitisticListPopulation} instance.
+     *
+     * @param chromosomes list of chromosomes in the population
+     * @param populationLimit maximal size of the population
+     * @param elitismRate how many best chromosomes will be directly transferred to the next
+     *     generation [in %]
+     * @throws NullArgumentException if the list of chromosomes is {@code null}
+     * @throws NotPositiveException if the population limit is not a positive number (&lt; 1)
+     * @throws NumberIsTooLargeException if the list of chromosomes exceeds the population limit
+     * @throws OutOfRangeException if the elitism rate is outside the [0, 1] range
+     */
+    public ElitisticListPopulation(
+            final List<Chromosome> chromosomes, final int populationLimit, final double elitismRate)
+            throws NullArgumentException,
+                    NotPositiveException,
+                    NumberIsTooLargeException,
+                    OutOfRangeException {
+
+        super(chromosomes, populationLimit);
+        setElitismRate(elitismRate);
+    }
+
+    /**
+     * Creates a new {@link ElitisticListPopulation} instance and initializes its inner chromosome
+     * list.
+     *
+     * @param populationLimit maximal size of the population
+     * @param elitismRate how many best chromosomes will be directly transferred to the next
+     *     generation [in %]
+     * @throws NotPositiveException if the population limit is not a positive number (&lt; 1)
+     * @throws OutOfRangeException if the elitism rate is outside the [0, 1] range
+     */
+    public ElitisticListPopulation(final int populationLimit, final double elitismRate)
+            throws NotPositiveException, OutOfRangeException {
+
+        super(populationLimit);
+        setElitismRate(elitismRate);
+    }
+
+    /**
+     * Start the population for the next generation. The <code>{@link #elitismRate}</code> percents
+     * of the best chromosomes are directly copied to the next generation.
+     *
+     * @return the beginnings of the next generation.
+     */
+    public Population nextGeneration() {
+        // initialize a new generation with the same parameters
+        ElitisticListPopulation nextGeneration =
+                new ElitisticListPopulation(getPopulationLimit(), getElitismRate());
+
+        final List<Chromosome> oldChromosomes = getChromosomeList();
+        Collections.sort(oldChromosomes);
+
+        // index of the last "not good enough" chromosome
+        int boundIndex = (int) FastMath.ceil((1.0 - getElitismRate()) * oldChromosomes.size());
+        for (int i = boundIndex; i < oldChromosomes.size(); i++) {
+            nextGeneration.addChromosome(oldChromosomes.get(i));
+        }
+        return nextGeneration;
+    }
+
+    /**
+     * Sets the elitism rate, i.e. how many best chromosomes will be directly transferred to the
+     * next generation [in %].
+     *
+     * @param elitismRate how many best chromosomes will be directly transferred to the next
+     *     generation [in %]
+     * @throws OutOfRangeException if the elitism rate is outside the [0, 1] range
+     */
+    public void setElitismRate(final double elitismRate) throws OutOfRangeException {
+        if (elitismRate < 0 || elitismRate > 1) {
+            throw new OutOfRangeException(LocalizedFormats.ELITISM_RATE, elitismRate, 0, 1);
+        }
+        this.elitismRate = elitismRate;
+    }
+
+    /**
+     * Access the elitism rate.
+     *
+     * @return the elitism rate
+     */
+    public double getElitismRate() {
+        return this.elitismRate;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/genetics/Fitness.java b/src/main/java/org/apache/commons/math3/genetics/Fitness.java
new file mode 100644
index 0000000..66f8d56
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/genetics/Fitness.java
@@ -0,0 +1,32 @@
+/*
+ * 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.math3.genetics;
+
+/**
+ * Fitness of a chromosome.
+ *
+ * @since 2.0
+ */
+public interface Fitness {
+
+    /**
+     * Compute the fitness. This is usually very time-consuming, so the value should be cached.
+     *
+     * @return fitness
+     */
+    double fitness();
+}
diff --git a/src/main/java/org/apache/commons/math3/genetics/FixedElapsedTime.java b/src/main/java/org/apache/commons/math3/genetics/FixedElapsedTime.java
new file mode 100644
index 0000000..af63094
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/genetics/FixedElapsedTime.java
@@ -0,0 +1,78 @@
+/*
+ * 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.math3.genetics;
+
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Stops after a fixed amount of time has elapsed.
+ *
+ * <p>The first time {@link #isSatisfied(Population)} is invoked, the end time of the evolution is
+ * determined based on the provided <code>maxTime</code> value. Once the elapsed time reaches the
+ * configured <code>maxTime</code> value, {@link #isSatisfied(Population)} returns true.
+ *
+ * @since 3.1
+ */
+public class FixedElapsedTime implements StoppingCondition {
+    /** Maximum allowed time period (in nanoseconds). */
+    private final long maxTimePeriod;
+
+    /** The predetermined termination time (stopping condition). */
+    private long endTime = -1;
+
+    /**
+     * Create a new {@link FixedElapsedTime} instance.
+     *
+     * @param maxTime maximum number of seconds generations are allowed to evolve
+     * @throws NumberIsTooSmallException if the provided time is &lt; 0
+     */
+    public FixedElapsedTime(final long maxTime) throws NumberIsTooSmallException {
+        this(maxTime, TimeUnit.SECONDS);
+    }
+
+    /**
+     * Create a new {@link FixedElapsedTime} instance.
+     *
+     * @param maxTime maximum time generations are allowed to evolve
+     * @param unit {@link TimeUnit} of the maxTime argument
+     * @throws NumberIsTooSmallException if the provided time is &lt; 0
+     */
+    public FixedElapsedTime(final long maxTime, final TimeUnit unit)
+            throws NumberIsTooSmallException {
+        if (maxTime < 0) {
+            throw new NumberIsTooSmallException(maxTime, 0, true);
+        }
+        maxTimePeriod = unit.toNanos(maxTime);
+    }
+
+    /**
+     * Determine whether or not the maximum allowed time has passed. The termination time is
+     * determined after the first generation.
+     *
+     * @param population ignored (no impact on result)
+     * @return <code>true</code> IFF the maximum allowed time period has elapsed
+     */
+    public boolean isSatisfied(final Population population) {
+        if (endTime < 0) {
+            endTime = System.nanoTime() + maxTimePeriod;
+        }
+
+        return System.nanoTime() >= endTime;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/genetics/FixedGenerationCount.java b/src/main/java/org/apache/commons/math3/genetics/FixedGenerationCount.java
new file mode 100644
index 0000000..d7d2088
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/genetics/FixedGenerationCount.java
@@ -0,0 +1,71 @@
+/*
+ * 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.math3.genetics;
+
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+
+/**
+ * Stops after a fixed number of generations. Each time {@link #isSatisfied(Population)} is invoked,
+ * a generation counter is incremented. Once the counter reaches the configured <code>maxGenerations
+ * </code> value, {@link #isSatisfied(Population)} returns true.
+ *
+ * @since 2.0
+ */
+public class FixedGenerationCount implements StoppingCondition {
+    /** Number of generations that have passed */
+    private int numGenerations = 0;
+
+    /** Maximum number of generations (stopping criteria) */
+    private final int maxGenerations;
+
+    /**
+     * Create a new FixedGenerationCount instance.
+     *
+     * @param maxGenerations number of generations to evolve
+     * @throws NumberIsTooSmallException if the number of generations is &lt; 1
+     */
+    public FixedGenerationCount(final int maxGenerations) throws NumberIsTooSmallException {
+        if (maxGenerations <= 0) {
+            throw new NumberIsTooSmallException(maxGenerations, 1, true);
+        }
+        this.maxGenerations = maxGenerations;
+    }
+
+    /**
+     * Determine whether or not the given number of generations have passed. Increments the number
+     * of generations counter if the maximum has not been reached.
+     *
+     * @param population ignored (no impact on result)
+     * @return <code>true</code> IFF the maximum number of generations has been exceeded
+     */
+    public boolean isSatisfied(final Population population) {
+        if (this.numGenerations < this.maxGenerations) {
+            numGenerations++;
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Returns the number of generations that have already passed.
+     *
+     * @return the number of generations that have passed
+     */
+    public int getNumGenerations() {
+        return numGenerations;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/genetics/GeneticAlgorithm.java b/src/main/java/org/apache/commons/math3/genetics/GeneticAlgorithm.java
new file mode 100644
index 0000000..e0f1127
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/genetics/GeneticAlgorithm.java
@@ -0,0 +1,240 @@
+/*
+ * 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.math3.genetics;
+
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.JDKRandomGenerator;
+import org.apache.commons.math3.random.RandomGenerator;
+
+/**
+ * Implementation of a genetic algorithm. All factors that govern the operation of the algorithm can
+ * be configured for a specific problem.
+ *
+ * @since 2.0
+ */
+public class GeneticAlgorithm {
+
+    /**
+     * Static random number generator shared by GA implementation classes. Set the randomGenerator
+     * seed to get reproducible results. Use {@link #setRandomGenerator(RandomGenerator)} to supply
+     * an alternative to the default JDK-provided PRNG.
+     */
+    // @GuardedBy("this")
+    private static RandomGenerator randomGenerator = new JDKRandomGenerator();
+
+    /** the crossover policy used by the algorithm. */
+    private final CrossoverPolicy crossoverPolicy;
+
+    /** the rate of crossover for the algorithm. */
+    private final double crossoverRate;
+
+    /** the mutation policy used by the algorithm. */
+    private final MutationPolicy mutationPolicy;
+
+    /** the rate of mutation for the algorithm. */
+    private final double mutationRate;
+
+    /** the selection policy used by the algorithm. */
+    private final SelectionPolicy selectionPolicy;
+
+    /** the number of generations evolved to reach {@link StoppingCondition} in the last run. */
+    private int generationsEvolved = 0;
+
+    /**
+     * Create a new genetic algorithm.
+     *
+     * @param crossoverPolicy The {@link CrossoverPolicy}
+     * @param crossoverRate The crossover rate as a percentage (0-1 inclusive)
+     * @param mutationPolicy The {@link MutationPolicy}
+     * @param mutationRate The mutation rate as a percentage (0-1 inclusive)
+     * @param selectionPolicy The {@link SelectionPolicy}
+     * @throws OutOfRangeException if the crossover or mutation rate is outside the [0, 1] range
+     */
+    public GeneticAlgorithm(
+            final CrossoverPolicy crossoverPolicy,
+            final double crossoverRate,
+            final MutationPolicy mutationPolicy,
+            final double mutationRate,
+            final SelectionPolicy selectionPolicy)
+            throws OutOfRangeException {
+
+        if (crossoverRate < 0 || crossoverRate > 1) {
+            throw new OutOfRangeException(LocalizedFormats.CROSSOVER_RATE, crossoverRate, 0, 1);
+        }
+        if (mutationRate < 0 || mutationRate > 1) {
+            throw new OutOfRangeException(LocalizedFormats.MUTATION_RATE, mutationRate, 0, 1);
+        }
+        this.crossoverPolicy = crossoverPolicy;
+        this.crossoverRate = crossoverRate;
+        this.mutationPolicy = mutationPolicy;
+        this.mutationRate = mutationRate;
+        this.selectionPolicy = selectionPolicy;
+    }
+
+    /**
+     * Set the (static) random generator.
+     *
+     * @param random random generator
+     */
+    public static synchronized void setRandomGenerator(final RandomGenerator random) {
+        randomGenerator = random;
+    }
+
+    /**
+     * Returns the (static) random generator.
+     *
+     * @return the static random generator shared by GA implementation classes
+     */
+    public static synchronized RandomGenerator getRandomGenerator() {
+        return randomGenerator;
+    }
+
+    /**
+     * Evolve the given population. Evolution stops when the stopping condition is satisfied.
+     * Updates the {@link #getGenerationsEvolved() generationsEvolved} property with the number of
+     * generations evolved before the StoppingCondition is satisfied.
+     *
+     * @param initial the initial, seed population.
+     * @param condition the stopping condition used to stop evolution.
+     * @return the population that satisfies the stopping condition.
+     */
+    public Population evolve(final Population initial, final StoppingCondition condition) {
+        Population current = initial;
+        generationsEvolved = 0;
+        while (!condition.isSatisfied(current)) {
+            current = nextGeneration(current);
+            generationsEvolved++;
+        }
+        return current;
+    }
+
+    /**
+     * Evolve the given population into the next generation.
+     *
+     * <p>
+     *
+     * <ol>
+     *   <li>Get nextGeneration population to fill from <code>current</code> generation, using its
+     *       nextGeneration method
+     *   <li>Loop until new generation is filled:
+     *       <ul>
+     *         <li>Apply configured SelectionPolicy to select a pair of parents from <code>current
+     *             </code>
+     *         <li>With probability = {@link #getCrossoverRate()}, apply configured {@link
+     *             CrossoverPolicy} to parents
+     *         <li>With probability = {@link #getMutationRate()}, apply configured {@link
+     *             MutationPolicy} to each of the offspring
+     *         <li>Add offspring individually to nextGeneration, space permitting
+     *       </ul>
+     *   <li>Return nextGeneration
+     * </ol>
+     *
+     * @param current the current population.
+     * @return the population for the next generation.
+     */
+    public Population nextGeneration(final Population current) {
+        Population nextGeneration = current.nextGeneration();
+
+        RandomGenerator randGen = getRandomGenerator();
+
+        while (nextGeneration.getPopulationSize() < nextGeneration.getPopulationLimit()) {
+            // select parent chromosomes
+            ChromosomePair pair = getSelectionPolicy().select(current);
+
+            // crossover?
+            if (randGen.nextDouble() < getCrossoverRate()) {
+                // apply crossover policy to create two offspring
+                pair = getCrossoverPolicy().crossover(pair.getFirst(), pair.getSecond());
+            }
+
+            // mutation?
+            if (randGen.nextDouble() < getMutationRate()) {
+                // apply mutation policy to the chromosomes
+                pair =
+                        new ChromosomePair(
+                                getMutationPolicy().mutate(pair.getFirst()),
+                                getMutationPolicy().mutate(pair.getSecond()));
+            }
+
+            // add the first chromosome to the population
+            nextGeneration.addChromosome(pair.getFirst());
+            // is there still a place for the second chromosome?
+            if (nextGeneration.getPopulationSize() < nextGeneration.getPopulationLimit()) {
+                // add the second chromosome to the population
+                nextGeneration.addChromosome(pair.getSecond());
+            }
+        }
+
+        return nextGeneration;
+    }
+
+    /**
+     * Returns the crossover policy.
+     *
+     * @return crossover policy
+     */
+    public CrossoverPolicy getCrossoverPolicy() {
+        return crossoverPolicy;
+    }
+
+    /**
+     * Returns the crossover rate.
+     *
+     * @return crossover rate
+     */
+    public double getCrossoverRate() {
+        return crossoverRate;
+    }
+
+    /**
+     * Returns the mutation policy.
+     *
+     * @return mutation policy
+     */
+    public MutationPolicy getMutationPolicy() {
+        return mutationPolicy;
+    }
+
+    /**
+     * Returns the mutation rate.
+     *
+     * @return mutation rate
+     */
+    public double getMutationRate() {
+        return mutationRate;
+    }
+
+    /**
+     * Returns the selection policy.
+     *
+     * @return selection policy
+     */
+    public SelectionPolicy getSelectionPolicy() {
+        return selectionPolicy;
+    }
+
+    /**
+     * Returns the number of generations evolved to reach {@link StoppingCondition} in the last run.
+     *
+     * @return number of generations evolved
+     * @since 2.1
+     */
+    public int getGenerationsEvolved() {
+        return generationsEvolved;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/genetics/InvalidRepresentationException.java b/src/main/java/org/apache/commons/math3/genetics/InvalidRepresentationException.java
new file mode 100644
index 0000000..33bc90c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/genetics/InvalidRepresentationException.java
@@ -0,0 +1,41 @@
+/*
+ * 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.math3.genetics;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.Localizable;
+
+/**
+ * Exception indicating that the representation of a chromosome is not valid.
+ *
+ * @since 2.0
+ */
+public class InvalidRepresentationException extends MathIllegalArgumentException {
+
+    /** Serialization version id */
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Construct an InvalidRepresentationException with a specialized message.
+     *
+     * @param pattern Message pattern.
+     * @param args Arguments.
+     */
+    public InvalidRepresentationException(Localizable pattern, Object... args) {
+        super(pattern, args);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/genetics/ListPopulation.java b/src/main/java/org/apache/commons/math3/genetics/ListPopulation.java
new file mode 100644
index 0000000..b2023d6
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/genetics/ListPopulation.java
@@ -0,0 +1,245 @@
+/*
+ * 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.math3.genetics;
+
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Population of chromosomes represented by a {@link List}.
+ *
+ * @since 2.0
+ */
+public abstract class ListPopulation implements Population {
+
+    /** List of chromosomes */
+    private List<Chromosome> chromosomes;
+
+    /** maximal size of the population */
+    private int populationLimit;
+
+    /**
+     * Creates a new ListPopulation instance and initializes its inner chromosome list.
+     *
+     * @param populationLimit maximal size of the population
+     * @throws NotPositiveException if the population limit is not a positive number (&lt; 1)
+     */
+    public ListPopulation(final int populationLimit) throws NotPositiveException {
+        this(Collections.<Chromosome>emptyList(), populationLimit);
+    }
+
+    /**
+     * Creates a new ListPopulation instance.
+     *
+     * <p>Note: the chromosomes of the specified list are added to the population.
+     *
+     * @param chromosomes list of chromosomes to be added to the population
+     * @param populationLimit maximal size of the population
+     * @throws NullArgumentException if the list of chromosomes is {@code null}
+     * @throws NotPositiveException if the population limit is not a positive number (&lt; 1)
+     * @throws NumberIsTooLargeException if the list of chromosomes exceeds the population limit
+     */
+    public ListPopulation(final List<Chromosome> chromosomes, final int populationLimit)
+            throws NullArgumentException, NotPositiveException, NumberIsTooLargeException {
+
+        if (chromosomes == null) {
+            throw new NullArgumentException();
+        }
+        if (populationLimit <= 0) {
+            throw new NotPositiveException(
+                    LocalizedFormats.POPULATION_LIMIT_NOT_POSITIVE, populationLimit);
+        }
+        if (chromosomes.size() > populationLimit) {
+            throw new NumberIsTooLargeException(
+                    LocalizedFormats.LIST_OF_CHROMOSOMES_BIGGER_THAN_POPULATION_SIZE,
+                    chromosomes.size(),
+                    populationLimit,
+                    false);
+        }
+        this.populationLimit = populationLimit;
+        this.chromosomes = new ArrayList<Chromosome>(populationLimit);
+        this.chromosomes.addAll(chromosomes);
+    }
+
+    /**
+     * Sets the list of chromosomes.
+     *
+     * <p>Note: this method removes all existing chromosomes in the population and adds all
+     * chromosomes of the specified list to the population.
+     *
+     * @param chromosomes the list of chromosomes
+     * @throws NullArgumentException if the list of chromosomes is {@code null}
+     * @throws NumberIsTooLargeException if the list of chromosomes exceeds the population limit
+     * @deprecated use {@link #addChromosomes(Collection)} instead
+     */
+    @Deprecated
+    public void setChromosomes(final List<Chromosome> chromosomes)
+            throws NullArgumentException, NumberIsTooLargeException {
+
+        if (chromosomes == null) {
+            throw new NullArgumentException();
+        }
+        if (chromosomes.size() > populationLimit) {
+            throw new NumberIsTooLargeException(
+                    LocalizedFormats.LIST_OF_CHROMOSOMES_BIGGER_THAN_POPULATION_SIZE,
+                    chromosomes.size(),
+                    populationLimit,
+                    false);
+        }
+        this.chromosomes.clear();
+        this.chromosomes.addAll(chromosomes);
+    }
+
+    /**
+     * Add a {@link Collection} of chromosomes to this {@link Population}.
+     *
+     * @param chromosomeColl a {@link Collection} of chromosomes
+     * @throws NumberIsTooLargeException if the population would exceed the population limit when
+     *     adding this chromosome
+     * @since 3.1
+     */
+    public void addChromosomes(final Collection<Chromosome> chromosomeColl)
+            throws NumberIsTooLargeException {
+        if (chromosomes.size() + chromosomeColl.size() > populationLimit) {
+            throw new NumberIsTooLargeException(
+                    LocalizedFormats.LIST_OF_CHROMOSOMES_BIGGER_THAN_POPULATION_SIZE,
+                    chromosomes.size(),
+                    populationLimit,
+                    false);
+        }
+        this.chromosomes.addAll(chromosomeColl);
+    }
+
+    /**
+     * Returns an unmodifiable list of the chromosomes in this population.
+     *
+     * @return the unmodifiable list of chromosomes
+     */
+    public List<Chromosome> getChromosomes() {
+        return Collections.unmodifiableList(chromosomes);
+    }
+
+    /**
+     * Access the list of chromosomes.
+     *
+     * @return the list of chromosomes
+     * @since 3.1
+     */
+    protected List<Chromosome> getChromosomeList() {
+        return chromosomes;
+    }
+
+    /**
+     * Add the given chromosome to the population.
+     *
+     * @param chromosome the chromosome to add.
+     * @throws NumberIsTooLargeException if the population would exceed the {@code populationLimit}
+     *     after adding this chromosome
+     */
+    public void addChromosome(final Chromosome chromosome) throws NumberIsTooLargeException {
+        if (chromosomes.size() >= populationLimit) {
+            throw new NumberIsTooLargeException(
+                    LocalizedFormats.LIST_OF_CHROMOSOMES_BIGGER_THAN_POPULATION_SIZE,
+                    chromosomes.size(),
+                    populationLimit,
+                    false);
+        }
+        this.chromosomes.add(chromosome);
+    }
+
+    /**
+     * Access the fittest chromosome in this population.
+     *
+     * @return the fittest chromosome.
+     */
+    public Chromosome getFittestChromosome() {
+        // best so far
+        Chromosome bestChromosome = this.chromosomes.get(0);
+        for (Chromosome chromosome : this.chromosomes) {
+            if (chromosome.compareTo(bestChromosome) > 0) {
+                // better chromosome found
+                bestChromosome = chromosome;
+            }
+        }
+        return bestChromosome;
+    }
+
+    /**
+     * Access the maximum population size.
+     *
+     * @return the maximum population size.
+     */
+    public int getPopulationLimit() {
+        return this.populationLimit;
+    }
+
+    /**
+     * Sets the maximal population size.
+     *
+     * @param populationLimit maximal population size.
+     * @throws NotPositiveException if the population limit is not a positive number (&lt; 1)
+     * @throws NumberIsTooSmallException if the new population size is smaller than the current
+     *     number of chromosomes in the population
+     */
+    public void setPopulationLimit(final int populationLimit)
+            throws NotPositiveException, NumberIsTooSmallException {
+        if (populationLimit <= 0) {
+            throw new NotPositiveException(
+                    LocalizedFormats.POPULATION_LIMIT_NOT_POSITIVE, populationLimit);
+        }
+        if (populationLimit < chromosomes.size()) {
+            throw new NumberIsTooSmallException(populationLimit, chromosomes.size(), true);
+        }
+        this.populationLimit = populationLimit;
+    }
+
+    /**
+     * Access the current population size.
+     *
+     * @return the current population size.
+     */
+    public int getPopulationSize() {
+        return this.chromosomes.size();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return this.chromosomes.toString();
+    }
+
+    /**
+     * Returns an iterator over the unmodifiable list of chromosomes.
+     *
+     * <p>Any call to {@link Iterator#remove()} will result in a {@link
+     * UnsupportedOperationException}.
+     *
+     * @return chromosome iterator
+     */
+    public Iterator<Chromosome> iterator() {
+        return getChromosomes().iterator();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/genetics/MutationPolicy.java b/src/main/java/org/apache/commons/math3/genetics/MutationPolicy.java
new file mode 100644
index 0000000..ded2ec6
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/genetics/MutationPolicy.java
@@ -0,0 +1,37 @@
+/*
+ * 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.math3.genetics;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+
+/**
+ * Algorithm used to mutate a chromosome.
+ *
+ * @since 2.0
+ */
+public interface MutationPolicy {
+
+    /**
+     * Mutate the given chromosome.
+     *
+     * @param original the original chromosome.
+     * @return the mutated chromosome.
+     * @throws MathIllegalArgumentException if the given chromosome is not compatible with this
+     *     {@link MutationPolicy}
+     */
+    Chromosome mutate(Chromosome original) throws MathIllegalArgumentException;
+}
diff --git a/src/main/java/org/apache/commons/math3/genetics/NPointCrossover.java b/src/main/java/org/apache/commons/math3/genetics/NPointCrossover.java
new file mode 100644
index 0000000..f78997e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/genetics/NPointCrossover.java
@@ -0,0 +1,187 @@
+/*
+ * 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.math3.genetics;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * N-point crossover policy. For each iteration a random crossover point is selected and the first
+ * part from each parent is copied to the corresponding child, and the second parts are copied
+ * crosswise.
+ *
+ * <p>Example (2-point crossover):
+ *
+ * <pre>
+ * -C- denotes a crossover point
+ *           -C-       -C-                         -C-        -C-
+ * p1 = (1 0  | 1 0 0 1 | 0 1 1)    X    p2 = (0 1  | 1 0 1 0  | 1 1 1)
+ *      \----/ \-------/ \-----/              \----/ \--------/ \-----/
+ *        ||      (*)       ||                  ||      (**)       ||
+ *        VV      (**)      VV                  VV      (*)        VV
+ *      /----\ /--------\ /-----\             /----\ /--------\ /-----\
+ * c1 = (1 0  | 1 0 1 0  | 0 1 1)    X   c2 = (0 1  | 1 0 0 1  | 0 1 1)
+ * </pre>
+ *
+ * This policy works only on {@link AbstractListChromosome}, and therefore it is parameterized by T.
+ * Moreover, the chromosomes must have same lengths.
+ *
+ * @param <T> generic type of the {@link AbstractListChromosome}s for crossover
+ * @since 3.1
+ */
+public class NPointCrossover<T> implements CrossoverPolicy {
+
+    /** The number of crossover points. */
+    private final int crossoverPoints;
+
+    /**
+     * Creates a new {@link NPointCrossover} policy using the given number of points.
+     *
+     * <p><b>Note</b>: the number of crossover points must be &lt; <code>chromosome length - 1
+     * </code>. This condition can only be checked at runtime, as the chromosome length is not known
+     * in advance.
+     *
+     * @param crossoverPoints the number of crossover points
+     * @throws NotStrictlyPositiveException if the number of {@code crossoverPoints} is not strictly
+     *     positive
+     */
+    public NPointCrossover(final int crossoverPoints) throws NotStrictlyPositiveException {
+        if (crossoverPoints <= 0) {
+            throw new NotStrictlyPositiveException(crossoverPoints);
+        }
+        this.crossoverPoints = crossoverPoints;
+    }
+
+    /**
+     * Returns the number of crossover points used by this {@link CrossoverPolicy}.
+     *
+     * @return the number of crossover points
+     */
+    public int getCrossoverPoints() {
+        return crossoverPoints;
+    }
+
+    /**
+     * Performs a N-point crossover. N random crossover points are selected and are used to divide
+     * the parent chromosomes into segments. The segments are copied in alternate order from the two
+     * parents to the corresponding child chromosomes.
+     *
+     * <p>Example (2-point crossover):
+     *
+     * <pre>
+     * -C- denotes a crossover point
+     *           -C-       -C-                         -C-        -C-
+     * p1 = (1 0  | 1 0 0 1 | 0 1 1)    X    p2 = (0 1  | 1 0 1 0  | 1 1 1)
+     *      \----/ \-------/ \-----/              \----/ \--------/ \-----/
+     *        ||      (*)       ||                  ||      (**)       ||
+     *        VV      (**)      VV                  VV      (*)        VV
+     *      /----\ /--------\ /-----\             /----\ /--------\ /-----\
+     * c1 = (1 0  | 1 0 1 0  | 0 1 1)    X   c2 = (0 1  | 1 0 0 1  | 0 1 1)
+     * </pre>
+     *
+     * @param first first parent (p1)
+     * @param second second parent (p2)
+     * @return pair of two children (c1,c2)
+     * @throws MathIllegalArgumentException iff one of the chromosomes is not an instance of {@link
+     *     AbstractListChromosome}
+     * @throws DimensionMismatchException if the length of the two chromosomes is different
+     */
+    @SuppressWarnings("unchecked") // OK because of instanceof checks
+    public ChromosomePair crossover(final Chromosome first, final Chromosome second)
+            throws DimensionMismatchException, MathIllegalArgumentException {
+
+        if (!(first instanceof AbstractListChromosome<?>
+                && second instanceof AbstractListChromosome<?>)) {
+            throw new MathIllegalArgumentException(
+                    LocalizedFormats.INVALID_FIXED_LENGTH_CHROMOSOME);
+        }
+        return mate((AbstractListChromosome<T>) first, (AbstractListChromosome<T>) second);
+    }
+
+    /**
+     * Helper for {@link #crossover(Chromosome, Chromosome)}. Performs the actual crossover.
+     *
+     * @param first the first chromosome
+     * @param second the second chromosome
+     * @return the pair of new chromosomes that resulted from the crossover
+     * @throws DimensionMismatchException if the length of the two chromosomes is different
+     * @throws NumberIsTooLargeException if the number of crossoverPoints is too large for the
+     *     actual chromosomes
+     */
+    private ChromosomePair mate(
+            final AbstractListChromosome<T> first, final AbstractListChromosome<T> second)
+            throws DimensionMismatchException, NumberIsTooLargeException {
+
+        final int length = first.getLength();
+        if (length != second.getLength()) {
+            throw new DimensionMismatchException(second.getLength(), length);
+        }
+        if (crossoverPoints >= length) {
+            throw new NumberIsTooLargeException(crossoverPoints, length, false);
+        }
+
+        // array representations of the parents
+        final List<T> parent1Rep = first.getRepresentation();
+        final List<T> parent2Rep = second.getRepresentation();
+        // and of the children
+        final List<T> child1Rep = new ArrayList<T>(length);
+        final List<T> child2Rep = new ArrayList<T>(length);
+
+        final RandomGenerator random = GeneticAlgorithm.getRandomGenerator();
+
+        List<T> c1 = child1Rep;
+        List<T> c2 = child2Rep;
+
+        int remainingPoints = crossoverPoints;
+        int lastIndex = 0;
+        for (int i = 0; i < crossoverPoints; i++, remainingPoints--) {
+            // select the next crossover point at random
+            final int crossoverIndex =
+                    1 + lastIndex + random.nextInt(length - lastIndex - remainingPoints);
+
+            // copy the current segment
+            for (int j = lastIndex; j < crossoverIndex; j++) {
+                c1.add(parent1Rep.get(j));
+                c2.add(parent2Rep.get(j));
+            }
+
+            // swap the children for the next segment
+            List<T> tmp = c1;
+            c1 = c2;
+            c2 = tmp;
+
+            lastIndex = crossoverIndex;
+        }
+
+        // copy the last segment
+        for (int j = lastIndex; j < length; j++) {
+            c1.add(parent1Rep.get(j));
+            c2.add(parent2Rep.get(j));
+        }
+
+        return new ChromosomePair(
+                first.newFixedLengthChromosome(child1Rep),
+                second.newFixedLengthChromosome(child2Rep));
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/genetics/OnePointCrossover.java b/src/main/java/org/apache/commons/math3/genetics/OnePointCrossover.java
new file mode 100644
index 0000000..9514771
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/genetics/OnePointCrossover.java
@@ -0,0 +1,128 @@
+/*
+ * 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.math3.genetics;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * One point crossover policy. A random crossover point is selected and the first part from each
+ * parent is copied to the corresponding child, and the second parts are copied crosswise.
+ *
+ * <p>Example:
+ *
+ * <pre>
+ * -C- denotes a crossover point
+ *                   -C-                                 -C-
+ * p1 = (1 0 1 0 0 1  | 0 1 1)    X    p2 = (0 1 1 0 1 0  | 1 1 1)
+ *      \------------/ \-----/              \------------/ \-----/
+ *            ||         (*)                       ||        (**)
+ *            VV         (**)                      VV        (*)
+ *      /------------\ /-----\              /------------\ /-----\
+ * c1 = (1 0 1 0 0 1  | 1 1 1)    X    c2 = (0 1 1 0 1 0  | 0 1 1)
+ * </pre>
+ *
+ * This policy works only on {@link AbstractListChromosome}, and therefore it is parameterized by T.
+ * Moreover, the chromosomes must have same lengths.
+ *
+ * @param <T> generic type of the {@link AbstractListChromosome}s for crossover
+ * @since 2.0
+ */
+public class OnePointCrossover<T> implements CrossoverPolicy {
+
+    /**
+     * Performs one point crossover. A random crossover point is selected and the first part from
+     * each parent is copied to the corresponding child, and the second parts are copied crosswise.
+     *
+     * <p>Example:
+     *
+     * <pre>
+     * -C- denotes a crossover point
+     *                   -C-                                 -C-
+     * p1 = (1 0 1 0 0 1  | 0 1 1)    X    p2 = (0 1 1 0 1 0  | 1 1 1)
+     *      \------------/ \-----/              \------------/ \-----/
+     *            ||         (*)                       ||        (**)
+     *            VV         (**)                      VV        (*)
+     *      /------------\ /-----\              /------------\ /-----\
+     * c1 = (1 0 1 0 0 1  | 1 1 1)    X    c2 = (0 1 1 0 1 0  | 0 1 1)
+     * </pre>
+     *
+     * @param first first parent (p1)
+     * @param second second parent (p2)
+     * @return pair of two children (c1,c2)
+     * @throws MathIllegalArgumentException iff one of the chromosomes is not an instance of {@link
+     *     AbstractListChromosome}
+     * @throws DimensionMismatchException if the length of the two chromosomes is different
+     */
+    @SuppressWarnings("unchecked") // OK because of instanceof checks
+    public ChromosomePair crossover(final Chromosome first, final Chromosome second)
+            throws DimensionMismatchException, MathIllegalArgumentException {
+
+        if (!(first instanceof AbstractListChromosome<?>
+                && second instanceof AbstractListChromosome<?>)) {
+            throw new MathIllegalArgumentException(
+                    LocalizedFormats.INVALID_FIXED_LENGTH_CHROMOSOME);
+        }
+        return crossover((AbstractListChromosome<T>) first, (AbstractListChromosome<T>) second);
+    }
+
+    /**
+     * Helper for {@link #crossover(Chromosome, Chromosome)}. Performs the actual crossover.
+     *
+     * @param first the first chromosome.
+     * @param second the second chromosome.
+     * @return the pair of new chromosomes that resulted from the crossover.
+     * @throws DimensionMismatchException if the length of the two chromosomes is different
+     */
+    private ChromosomePair crossover(
+            final AbstractListChromosome<T> first, final AbstractListChromosome<T> second)
+            throws DimensionMismatchException {
+        final int length = first.getLength();
+        if (length != second.getLength()) {
+            throw new DimensionMismatchException(second.getLength(), length);
+        }
+
+        // array representations of the parents
+        final List<T> parent1Rep = first.getRepresentation();
+        final List<T> parent2Rep = second.getRepresentation();
+        // and of the children
+        final List<T> child1Rep = new ArrayList<T>(length);
+        final List<T> child2Rep = new ArrayList<T>(length);
+
+        // select a crossover point at random (0 and length makes no sense)
+        final int crossoverIndex = 1 + (GeneticAlgorithm.getRandomGenerator().nextInt(length - 2));
+
+        // copy the first part
+        for (int i = 0; i < crossoverIndex; i++) {
+            child1Rep.add(parent1Rep.get(i));
+            child2Rep.add(parent2Rep.get(i));
+        }
+        // and switch the second part
+        for (int i = crossoverIndex; i < length; i++) {
+            child1Rep.add(parent2Rep.get(i));
+            child2Rep.add(parent1Rep.get(i));
+        }
+
+        return new ChromosomePair(
+                first.newFixedLengthChromosome(child1Rep),
+                second.newFixedLengthChromosome(child2Rep));
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/genetics/OrderedCrossover.java b/src/main/java/org/apache/commons/math3/genetics/OrderedCrossover.java
new file mode 100644
index 0000000..eeb62ed
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/genetics/OrderedCrossover.java
@@ -0,0 +1,154 @@
+/*
+ * 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.math3.genetics;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.util.FastMath;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Order 1 Crossover [OX1] builds offspring from <b>ordered</b> chromosomes by copying a consecutive
+ * slice from one parent, and filling up the remaining genes from the other parent as they appear.
+ *
+ * <p>This policy works by applying the following rules:
+ *
+ * <ol>
+ *   <li>select a random slice of consecutive genes from parent 1
+ *   <li>copy the slice to child 1 and mark out the genes in parent 2
+ *   <li>starting from the right side of the slice, copy genes from parent 2 as they appear to child
+ *       1 if they are not yet marked out.
+ * </ol>
+ *
+ * <p>Example (random sublist from index 3 to 7, underlined):
+ *
+ * <pre>
+ * p1 = (8 4 7 3 6 2 5 1 9 0)   X   c1 = (0 4 7 3 6 2 5 1 8 9)
+ *             ---------                        ---------
+ * p2 = (0 1 2 3 4 5 6 7 8 9)   X   c2 = (8 1 2 3 4 5 6 7 9 0)
+ * </pre>
+ *
+ * <p>This policy works only on {@link AbstractListChromosome}, and therefore it is parameterized by
+ * T. Moreover, the chromosomes must have same lengths.
+ *
+ * @see <a
+ *     href="http://www.rubicite.com/Tutorials/GeneticAlgorithms/CrossoverOperators/Order1CrossoverOperator.aspx">
+ *     Order 1 Crossover Operator</a>
+ * @param <T> generic type of the {@link AbstractListChromosome}s for crossover
+ * @since 3.1
+ */
+public class OrderedCrossover<T> implements CrossoverPolicy {
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws MathIllegalArgumentException iff one of the chromosomes is not an instance of {@link
+     *     AbstractListChromosome}
+     * @throws DimensionMismatchException if the length of the two chromosomes is different
+     */
+    @SuppressWarnings("unchecked")
+    public ChromosomePair crossover(final Chromosome first, final Chromosome second)
+            throws DimensionMismatchException, MathIllegalArgumentException {
+
+        if (!(first instanceof AbstractListChromosome<?>
+                && second instanceof AbstractListChromosome<?>)) {
+            throw new MathIllegalArgumentException(
+                    LocalizedFormats.INVALID_FIXED_LENGTH_CHROMOSOME);
+        }
+        return mate((AbstractListChromosome<T>) first, (AbstractListChromosome<T>) second);
+    }
+
+    /**
+     * Helper for {@link #crossover(Chromosome, Chromosome)}. Performs the actual crossover.
+     *
+     * @param first the first chromosome
+     * @param second the second chromosome
+     * @return the pair of new chromosomes that resulted from the crossover
+     * @throws DimensionMismatchException if the length of the two chromosomes is different
+     */
+    protected ChromosomePair mate(
+            final AbstractListChromosome<T> first, final AbstractListChromosome<T> second)
+            throws DimensionMismatchException {
+
+        final int length = first.getLength();
+        if (length != second.getLength()) {
+            throw new DimensionMismatchException(second.getLength(), length);
+        }
+
+        // array representations of the parents
+        final List<T> parent1Rep = first.getRepresentation();
+        final List<T> parent2Rep = second.getRepresentation();
+        // and of the children
+        final List<T> child1 = new ArrayList<T>(length);
+        final List<T> child2 = new ArrayList<T>(length);
+        // sets of already inserted items for quick access
+        final Set<T> child1Set = new HashSet<T>(length);
+        final Set<T> child2Set = new HashSet<T>(length);
+
+        final RandomGenerator random = GeneticAlgorithm.getRandomGenerator();
+        // choose random points, making sure that lb < ub.
+        int a = random.nextInt(length);
+        int b;
+        do {
+            b = random.nextInt(length);
+        } while (a == b);
+        // determine the lower and upper bounds
+        final int lb = FastMath.min(a, b);
+        final int ub = FastMath.max(a, b);
+
+        // add the subLists that are between lb and ub
+        child1.addAll(parent1Rep.subList(lb, ub + 1));
+        child1Set.addAll(child1);
+        child2.addAll(parent2Rep.subList(lb, ub + 1));
+        child2Set.addAll(child2);
+
+        // iterate over every item in the parents
+        for (int i = 1; i <= length; i++) {
+            final int idx = (ub + i) % length;
+
+            // retrieve the current item in each parent
+            final T item1 = parent1Rep.get(idx);
+            final T item2 = parent2Rep.get(idx);
+
+            // if the first child already contains the item in the second parent add it
+            if (!child1Set.contains(item2)) {
+                child1.add(item2);
+                child1Set.add(item2);
+            }
+
+            // if the second child already contains the item in the first parent add it
+            if (!child2Set.contains(item1)) {
+                child2.add(item1);
+                child2Set.add(item1);
+            }
+        }
+
+        // rotate so that the original slice is in the same place as in the parents.
+        Collections.rotate(child1, lb);
+        Collections.rotate(child2, lb);
+
+        return new ChromosomePair(
+                first.newFixedLengthChromosome(child1), second.newFixedLengthChromosome(child2));
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/genetics/PermutationChromosome.java b/src/main/java/org/apache/commons/math3/genetics/PermutationChromosome.java
new file mode 100644
index 0000000..0857733
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/genetics/PermutationChromosome.java
@@ -0,0 +1,38 @@
+/*
+ * 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.math3.genetics;
+
+import java.util.List;
+
+/**
+ * Interface indicating that the chromosome represents a permutation of objects.
+ *
+ * @param <T> type of the permuted objects
+ * @since 2.0
+ */
+public interface PermutationChromosome<T> {
+
+    /**
+     * Permutes the <code>sequence</code> of objects of type T according to the permutation this
+     * chromosome represents. For example, if this chromosome represents a permutation (3,0,1,2),
+     * and the unpermuted sequence is (a,b,c,d), this yields (d,a,b,c).
+     *
+     * @param sequence the unpermuted (original) sequence of objects
+     * @return permutation of <code>sequence</code> represented by this permutation
+     */
+    List<T> decode(List<T> sequence);
+}
diff --git a/src/main/java/org/apache/commons/math3/genetics/Population.java b/src/main/java/org/apache/commons/math3/genetics/Population.java
new file mode 100644
index 0000000..25b6c2a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/genetics/Population.java
@@ -0,0 +1,63 @@
+/*
+ * 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.math3.genetics;
+
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+
+/**
+ * A collection of chromosomes that facilitates generational evolution.
+ *
+ * @since 2.0
+ */
+public interface Population extends Iterable<Chromosome> {
+    /**
+     * Access the current population size.
+     *
+     * @return the current population size.
+     */
+    int getPopulationSize();
+
+    /**
+     * Access the maximum population size.
+     *
+     * @return the maximum population size.
+     */
+    int getPopulationLimit();
+
+    /**
+     * Start the population for the next generation.
+     *
+     * @return the beginnings of the next generation.
+     */
+    Population nextGeneration();
+
+    /**
+     * Add the given chromosome to the population.
+     *
+     * @param chromosome the chromosome to add.
+     * @throws NumberIsTooLargeException if the population would exceed the population limit when
+     *     adding this chromosome
+     */
+    void addChromosome(Chromosome chromosome) throws NumberIsTooLargeException;
+
+    /**
+     * Access the fittest chromosome in this population.
+     *
+     * @return the fittest chromosome.
+     */
+    Chromosome getFittestChromosome();
+}
diff --git a/src/main/java/org/apache/commons/math3/genetics/RandomKey.java b/src/main/java/org/apache/commons/math3/genetics/RandomKey.java
new file mode 100644
index 0000000..b33eaec
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/genetics/RandomKey.java
@@ -0,0 +1,296 @@
+/*
+ * 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.math3.genetics;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Random Key chromosome is used for permutation representation. It is a vector of a fixed length of
+ * real numbers in [0,1] interval. The index of the i-th smallest value in the vector represents an
+ * i-th member of the permutation.
+ *
+ * <p>For example, the random key [0.2, 0.3, 0.8, 0.1] corresponds to the permutation of indices
+ * (3,0,1,2). If the original (unpermuted) sequence would be (a,b,c,d), this would mean the sequence
+ * (d,a,b,c).
+ *
+ * <p>With this representation, common operators like n-point crossover can be used, because any
+ * such chromosome represents a valid permutation.
+ *
+ * <p>Since the chromosome (and thus its arrayRepresentation) is immutable, the array representation
+ * is sorted only once in the constructor.
+ *
+ * <p>For details, see:
+ *
+ * <ul>
+ *   <li>Bean, J.C.: Genetic algorithms and random keys for sequencing and optimization. ORSA
+ *       Journal on Computing 6 (1994) 154-160
+ *   <li>Rothlauf, F.: Representations for Genetic and Evolutionary Algorithms. Volume 104 of
+ *       Studies in Fuzziness and Soft Computing. Physica-Verlag, Heidelberg (2002)
+ * </ul>
+ *
+ * @param <T> type of the permuted objects
+ * @since 2.0
+ */
+public abstract class RandomKey<T> extends AbstractListChromosome<Double>
+        implements PermutationChromosome<T> {
+
+    /** Cache of sorted representation (unmodifiable). */
+    private final List<Double> sortedRepresentation;
+
+    /** Base sequence [0,1,...,n-1], permuted according to the representation (unmodifiable). */
+    private final List<Integer> baseSeqPermutation;
+
+    /**
+     * Constructor.
+     *
+     * @param representation list of [0,1] values representing the permutation
+     * @throws InvalidRepresentationException iff the <code>representation</code> can not represent
+     *     a valid chromosome
+     */
+    public RandomKey(final List<Double> representation) throws InvalidRepresentationException {
+        super(representation);
+        // store the sorted representation
+        List<Double> sortedRepr = new ArrayList<Double>(getRepresentation());
+        Collections.sort(sortedRepr);
+        sortedRepresentation = Collections.unmodifiableList(sortedRepr);
+        // store the permutation of [0,1,...,n-1] list for toString() and isSame() methods
+        baseSeqPermutation =
+                Collections.unmodifiableList(
+                        decodeGeneric(
+                                baseSequence(getLength()),
+                                getRepresentation(),
+                                sortedRepresentation));
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param representation array of [0,1] values representing the permutation
+     * @throws InvalidRepresentationException iff the <code>representation</code> can not represent
+     *     a valid chromosome
+     */
+    public RandomKey(final Double[] representation) throws InvalidRepresentationException {
+        this(Arrays.asList(representation));
+    }
+
+    /** {@inheritDoc} */
+    public List<T> decode(final List<T> sequence) {
+        return decodeGeneric(sequence, getRepresentation(), sortedRepresentation);
+    }
+
+    /**
+     * Decodes a permutation represented by <code>representation</code> and returns a (generic) list
+     * with the permuted values.
+     *
+     * @param <S> generic type of the sequence values
+     * @param sequence the unpermuted sequence
+     * @param representation representation of the permutation ([0,1] vector)
+     * @param sortedRepr sorted <code>representation</code>
+     * @return list with the sequence values permuted according to the representation
+     * @throws DimensionMismatchException iff the length of the <code>sequence</code>, <code>
+     *     representation</code> or <code>sortedRepr</code> lists are not equal
+     */
+    private static <S> List<S> decodeGeneric(
+            final List<S> sequence, List<Double> representation, final List<Double> sortedRepr)
+            throws DimensionMismatchException {
+
+        int l = sequence.size();
+
+        // the size of the three lists must be equal
+        if (representation.size() != l) {
+            throw new DimensionMismatchException(representation.size(), l);
+        }
+        if (sortedRepr.size() != l) {
+            throw new DimensionMismatchException(sortedRepr.size(), l);
+        }
+
+        // do not modify the original representation
+        List<Double> reprCopy = new ArrayList<Double>(representation);
+
+        // now find the indices in the original repr and use them for permuting
+        List<S> res = new ArrayList<S>(l);
+        for (int i = 0; i < l; i++) {
+            int index = reprCopy.indexOf(sortedRepr.get(i));
+            res.add(sequence.get(index));
+            reprCopy.set(index, null);
+        }
+        return res;
+    }
+
+    /**
+     * Returns <code>true</code> iff <code>another</code> is a RandomKey and encodes the same
+     * permutation.
+     *
+     * @param another chromosome to compare
+     * @return true iff chromosomes encode the same permutation
+     */
+    @Override
+    protected boolean isSame(final Chromosome another) {
+        // type check
+        if (!(another instanceof RandomKey<?>)) {
+            return false;
+        }
+        RandomKey<?> anotherRk = (RandomKey<?>) another;
+        // size check
+        if (getLength() != anotherRk.getLength()) {
+            return false;
+        }
+
+        // two different representations can still encode the same permutation
+        // the ordering is what counts
+        List<Integer> thisPerm = this.baseSeqPermutation;
+        List<Integer> anotherPerm = anotherRk.baseSeqPermutation;
+
+        for (int i = 0; i < getLength(); i++) {
+            if (thisPerm.get(i) != anotherPerm.get(i)) {
+                return false;
+            }
+        }
+        // the permutations are the same
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void checkValidity(final List<Double> chromosomeRepresentation)
+            throws InvalidRepresentationException {
+
+        for (double val : chromosomeRepresentation) {
+            if (val < 0 || val > 1) {
+                throw new InvalidRepresentationException(
+                        LocalizedFormats.OUT_OF_RANGE_SIMPLE, val, 0, 1);
+            }
+        }
+    }
+
+    /**
+     * Generates a representation corresponding to a random permutation of length l which can be
+     * passed to the RandomKey constructor.
+     *
+     * @param l length of the permutation
+     * @return representation of a random permutation
+     */
+    public static final List<Double> randomPermutation(final int l) {
+        List<Double> repr = new ArrayList<Double>(l);
+        for (int i = 0; i < l; i++) {
+            repr.add(GeneticAlgorithm.getRandomGenerator().nextDouble());
+        }
+        return repr;
+    }
+
+    /**
+     * Generates a representation corresponding to an identity permutation of length l which can be
+     * passed to the RandomKey constructor.
+     *
+     * @param l length of the permutation
+     * @return representation of an identity permutation
+     */
+    public static final List<Double> identityPermutation(final int l) {
+        List<Double> repr = new ArrayList<Double>(l);
+        for (int i = 0; i < l; i++) {
+            repr.add((double) i / l);
+        }
+        return repr;
+    }
+
+    /**
+     * Generates a representation of a permutation corresponding to the <code>data</code> sorted by
+     * <code>comparator</code>. The <code>data</code> is not modified during the process.
+     *
+     * <p>This is useful if you want to inject some permutations to the initial population.
+     *
+     * @param <S> type of the data
+     * @param data list of data determining the order
+     * @param comparator how the data will be compared
+     * @return list representation of the permutation corresponding to the parameters
+     */
+    public static <S> List<Double> comparatorPermutation(
+            final List<S> data, final Comparator<S> comparator) {
+        List<S> sortedData = new ArrayList<S>(data);
+        Collections.sort(sortedData, comparator);
+
+        return inducedPermutation(data, sortedData);
+    }
+
+    /**
+     * Generates a representation of a permutation corresponding to a permutation which yields
+     * <code>permutedData</code> when applied to <code>originalData</code>.
+     *
+     * <p>This method can be viewed as an inverse to {@link #decode(List)}.
+     *
+     * @param <S> type of the data
+     * @param originalData the original, unpermuted data
+     * @param permutedData the data, somehow permuted
+     * @return representation of a permutation corresponding to the permutation <code>
+     *     originalData -> permutedData</code>
+     * @throws DimensionMismatchException iff the length of <code>originalData</code> and <code>
+     *     permutedData</code> lists are not equal
+     * @throws MathIllegalArgumentException iff the <code>permutedData</code> and <code>originalData
+     *     </code> lists contain different data
+     */
+    public static <S> List<Double> inducedPermutation(
+            final List<S> originalData, final List<S> permutedData)
+            throws DimensionMismatchException, MathIllegalArgumentException {
+
+        if (originalData.size() != permutedData.size()) {
+            throw new DimensionMismatchException(permutedData.size(), originalData.size());
+        }
+        int l = originalData.size();
+
+        List<S> origDataCopy = new ArrayList<S>(originalData);
+
+        Double[] res = new Double[l];
+        for (int i = 0; i < l; i++) {
+            int index = origDataCopy.indexOf(permutedData.get(i));
+            if (index == -1) {
+                throw new MathIllegalArgumentException(
+                        LocalizedFormats.DIFFERENT_ORIG_AND_PERMUTED_DATA);
+            }
+            res[index] = (double) i / l;
+            origDataCopy.set(index, null);
+        }
+        return Arrays.asList(res);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return String.format("(f=%s pi=(%s))", getFitness(), baseSeqPermutation);
+    }
+
+    /**
+     * Helper for constructor. Generates a list of natural numbers (0,1,...,l-1).
+     *
+     * @param l length of list to generate
+     * @return list of integers from 0 to l-1
+     */
+    private static List<Integer> baseSequence(final int l) {
+        List<Integer> baseSequence = new ArrayList<Integer>(l);
+        for (int i = 0; i < l; i++) {
+            baseSequence.add(i);
+        }
+        return baseSequence;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/genetics/RandomKeyMutation.java b/src/main/java/org/apache/commons/math3/genetics/RandomKeyMutation.java
new file mode 100644
index 0000000..6d1512a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/genetics/RandomKeyMutation.java
@@ -0,0 +1,55 @@
+/*
+ * 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.math3.genetics;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Mutation operator for {@link RandomKey}s. Changes a randomly chosen element of the array
+ * representation to a random value uniformly distributed in [0,1].
+ *
+ * @since 2.0
+ */
+public class RandomKeyMutation implements MutationPolicy {
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws MathIllegalArgumentException if <code>original</code> is not a {@link RandomKey}
+     *     instance
+     */
+    public Chromosome mutate(final Chromosome original) throws MathIllegalArgumentException {
+        if (!(original instanceof RandomKey<?>)) {
+            throw new MathIllegalArgumentException(
+                    LocalizedFormats.RANDOMKEY_MUTATION_WRONG_CLASS,
+                    original.getClass().getSimpleName());
+        }
+
+        RandomKey<?> originalRk = (RandomKey<?>) original;
+        List<Double> repr = originalRk.getRepresentation();
+        int rInd = GeneticAlgorithm.getRandomGenerator().nextInt(repr.size());
+
+        List<Double> newRepr = new ArrayList<Double>(repr);
+        newRepr.set(rInd, GeneticAlgorithm.getRandomGenerator().nextDouble());
+
+        return originalRk.newFixedLengthChromosome(newRepr);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/genetics/SelectionPolicy.java b/src/main/java/org/apache/commons/math3/genetics/SelectionPolicy.java
new file mode 100644
index 0000000..c19c922
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/genetics/SelectionPolicy.java
@@ -0,0 +1,36 @@
+/*
+ * 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.math3.genetics;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+
+/**
+ * Algorithm used to select a chromosome pair from a population.
+ *
+ * @since 2.0
+ */
+public interface SelectionPolicy {
+    /**
+     * Select two chromosomes from the population.
+     *
+     * @param population the population from which the chromosomes are choosen.
+     * @return the selected chromosomes.
+     * @throws MathIllegalArgumentException if the population is not compatible with this {@link
+     *     SelectionPolicy}
+     */
+    ChromosomePair select(Population population) throws MathIllegalArgumentException;
+}
diff --git a/src/main/java/org/apache/commons/math3/genetics/StoppingCondition.java b/src/main/java/org/apache/commons/math3/genetics/StoppingCondition.java
new file mode 100644
index 0000000..06eb2ad
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/genetics/StoppingCondition.java
@@ -0,0 +1,33 @@
+/*
+ * 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.math3.genetics;
+
+/**
+ * Algorithm used to determine when to stop evolution.
+ *
+ * @since 2.0
+ */
+public interface StoppingCondition {
+    /**
+     * Determine whether or not the given population satisfies the stopping condition.
+     *
+     * @param population the population to test.
+     * @return <code>true</code> if this stopping condition is met by the given population, <code>
+     *     false</code> otherwise.
+     */
+    boolean isSatisfied(Population population);
+}
diff --git a/src/main/java/org/apache/commons/math3/genetics/TournamentSelection.java b/src/main/java/org/apache/commons/math3/genetics/TournamentSelection.java
new file mode 100644
index 0000000..2a9d9d7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/genetics/TournamentSelection.java
@@ -0,0 +1,119 @@
+/*
+ * 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.math3.genetics;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tournament selection scheme. Each of the two selected chromosomes is selected based on n-ary
+ * tournament -- this is done by drawing {@link #arity} random chromosomes without replacement from
+ * the population, and then selecting the fittest chromosome among them.
+ *
+ * @since 2.0
+ */
+public class TournamentSelection implements SelectionPolicy {
+
+    /** number of chromosomes included in the tournament selections */
+    private int arity;
+
+    /**
+     * Creates a new TournamentSelection instance.
+     *
+     * @param arity how many chromosomes will be drawn to the tournament
+     */
+    public TournamentSelection(final int arity) {
+        this.arity = arity;
+    }
+
+    /**
+     * Select two chromosomes from the population. Each of the two selected chromosomes is selected
+     * based on n-ary tournament -- this is done by drawing {@link #arity} random chromosomes
+     * without replacement from the population, and then selecting the fittest chromosome among
+     * them.
+     *
+     * @param population the population from which the chromosomes are chosen.
+     * @return the selected chromosomes.
+     * @throws MathIllegalArgumentException if the tournament arity is bigger than the population
+     *     size
+     */
+    public ChromosomePair select(final Population population) throws MathIllegalArgumentException {
+        return new ChromosomePair(
+                tournament((ListPopulation) population), tournament((ListPopulation) population));
+    }
+
+    /**
+     * Helper for {@link #select(Population)}. Draw {@link #arity} random chromosomes without
+     * replacement from the population, and then select the fittest chromosome among them.
+     *
+     * @param population the population from which the chromosomes are chosen.
+     * @return the selected chromosome.
+     * @throws MathIllegalArgumentException if the tournament arity is bigger than the population
+     *     size
+     */
+    private Chromosome tournament(final ListPopulation population)
+            throws MathIllegalArgumentException {
+        if (population.getPopulationSize() < this.arity) {
+            throw new MathIllegalArgumentException(
+                    LocalizedFormats.TOO_LARGE_TOURNAMENT_ARITY,
+                    arity,
+                    population.getPopulationSize());
+        }
+        // auxiliary population
+        ListPopulation tournamentPopulation =
+                new ListPopulation(this.arity) {
+                    /** {@inheritDoc} */
+                    public Population nextGeneration() {
+                        // not useful here
+                        return null;
+                    }
+                };
+
+        // create a copy of the chromosome list
+        List<Chromosome> chromosomes = new ArrayList<Chromosome>(population.getChromosomes());
+        for (int i = 0; i < this.arity; i++) {
+            // select a random individual and add it to the tournament
+            int rind = GeneticAlgorithm.getRandomGenerator().nextInt(chromosomes.size());
+            tournamentPopulation.addChromosome(chromosomes.get(rind));
+            // do not select it again
+            chromosomes.remove(rind);
+        }
+        // the winner takes it all
+        return tournamentPopulation.getFittestChromosome();
+    }
+
+    /**
+     * Gets the arity (number of chromosomes drawn to the tournament).
+     *
+     * @return arity of the tournament
+     */
+    public int getArity() {
+        return arity;
+    }
+
+    /**
+     * Sets the arity (number of chromosomes drawn to the tournament).
+     *
+     * @param arity arity of the tournament
+     */
+    public void setArity(final int arity) {
+        this.arity = arity;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/genetics/UniformCrossover.java b/src/main/java/org/apache/commons/math3/genetics/UniformCrossover.java
new file mode 100644
index 0000000..c11450a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/genetics/UniformCrossover.java
@@ -0,0 +1,140 @@
+/*
+ * 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.math3.genetics;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Perform Uniform Crossover [UX] on the specified chromosomes. A fixed mixing ratio is used to
+ * combine genes from the first and second parents, e.g. using a ratio of 0.5 would result in
+ * approximately 50% of genes coming from each parent. This is typically a poor method of crossover,
+ * but empirical evidence suggests that it is more exploratory and results in a larger part of the
+ * problem space being searched.
+ *
+ * <p>This crossover policy evaluates each gene of the parent chromosomes by chosing a uniform
+ * random number {@code p} in the range [0, 1]. If {@code p} &lt; {@code ratio}, the parent genes
+ * are swapped. This means with a ratio of 0.7, 30% of the genes from the first parent and 70% from
+ * the second parent will be selected for the first offspring (and vice versa for the second
+ * offspring).
+ *
+ * <p>This policy works only on {@link AbstractListChromosome}, and therefore it is parameterized by
+ * T. Moreover, the chromosomes must have same lengths.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Crossover_%28genetic_algorithm%29">Crossover
+ *     techniques (Wikipedia)</a>
+ * @see <a
+ *     href="http://www.obitko.com/tutorials/genetic-algorithms/crossover-mutation.php">Crossover
+ *     (Obitko.com)</a>
+ * @see <a href="http://www.tomaszgwiazda.com/uniformX.htm">Uniform crossover</a>
+ * @param <T> generic type of the {@link AbstractListChromosome}s for crossover
+ * @since 3.1
+ */
+public class UniformCrossover<T> implements CrossoverPolicy {
+
+    /** The mixing ratio. */
+    private final double ratio;
+
+    /**
+     * Creates a new {@link UniformCrossover} policy using the given mixing ratio.
+     *
+     * @param ratio the mixing ratio
+     * @throws OutOfRangeException if the mixing ratio is outside the [0, 1] range
+     */
+    public UniformCrossover(final double ratio) throws OutOfRangeException {
+        if (ratio < 0.0d || ratio > 1.0d) {
+            throw new OutOfRangeException(LocalizedFormats.CROSSOVER_RATE, ratio, 0.0d, 1.0d);
+        }
+        this.ratio = ratio;
+    }
+
+    /**
+     * Returns the mixing ratio used by this {@link CrossoverPolicy}.
+     *
+     * @return the mixing ratio
+     */
+    public double getRatio() {
+        return ratio;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws MathIllegalArgumentException iff one of the chromosomes is not an instance of {@link
+     *     AbstractListChromosome}
+     * @throws DimensionMismatchException if the length of the two chromosomes is different
+     */
+    @SuppressWarnings("unchecked")
+    public ChromosomePair crossover(final Chromosome first, final Chromosome second)
+            throws DimensionMismatchException, MathIllegalArgumentException {
+
+        if (!(first instanceof AbstractListChromosome<?>
+                && second instanceof AbstractListChromosome<?>)) {
+            throw new MathIllegalArgumentException(
+                    LocalizedFormats.INVALID_FIXED_LENGTH_CHROMOSOME);
+        }
+        return mate((AbstractListChromosome<T>) first, (AbstractListChromosome<T>) second);
+    }
+
+    /**
+     * Helper for {@link #crossover(Chromosome, Chromosome)}. Performs the actual crossover.
+     *
+     * @param first the first chromosome
+     * @param second the second chromosome
+     * @return the pair of new chromosomes that resulted from the crossover
+     * @throws DimensionMismatchException if the length of the two chromosomes is different
+     */
+    private ChromosomePair mate(
+            final AbstractListChromosome<T> first, final AbstractListChromosome<T> second)
+            throws DimensionMismatchException {
+        final int length = first.getLength();
+        if (length != second.getLength()) {
+            throw new DimensionMismatchException(second.getLength(), length);
+        }
+
+        // array representations of the parents
+        final List<T> parent1Rep = first.getRepresentation();
+        final List<T> parent2Rep = second.getRepresentation();
+        // and of the children
+        final List<T> child1Rep = new ArrayList<T>(length);
+        final List<T> child2Rep = new ArrayList<T>(length);
+
+        final RandomGenerator random = GeneticAlgorithm.getRandomGenerator();
+
+        for (int index = 0; index < length; index++) {
+
+            if (random.nextDouble() < ratio) {
+                // swap the bits -> take other parent
+                child1Rep.add(parent2Rep.get(index));
+                child2Rep.add(parent1Rep.get(index));
+            } else {
+                child1Rep.add(parent1Rep.get(index));
+                child2Rep.add(parent2Rep.get(index));
+            }
+        }
+
+        return new ChromosomePair(
+                first.newFixedLengthChromosome(child1Rep),
+                second.newFixedLengthChromosome(child2Rep));
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/genetics/package-info.java b/src/main/java/org/apache/commons/math3/genetics/package-info.java
new file mode 100644
index 0000000..32109e8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/genetics/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+/** This package provides Genetic Algorithms components and implementations. */
+package org.apache.commons.math3.genetics;
diff --git a/src/main/java/org/apache/commons/math3/geometry/Point.java b/src/main/java/org/apache/commons/math3/geometry/Point.java
new file mode 100644
index 0000000..49f290a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/Point.java
@@ -0,0 +1,52 @@
+/*
+ * 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.math3.geometry;
+
+import java.io.Serializable;
+
+/**
+ * This interface represents a generic geometrical point.
+ *
+ * @param <S> Type of the space.
+ * @see Space
+ * @see Vector
+ * @since 3.3
+ */
+public interface Point<S extends Space> extends Serializable {
+
+    /**
+     * Get the space to which the point belongs.
+     *
+     * @return containing space
+     */
+    Space getSpace();
+
+    /**
+     * Returns true if any coordinate of this point is NaN; false otherwise
+     *
+     * @return true if any coordinate of this point is NaN; false otherwise
+     */
+    boolean isNaN();
+
+    /**
+     * Compute the distance between the instance and another point.
+     *
+     * @param p second point
+     * @return the distance between the instance and p
+     */
+    double distance(Point<S> p);
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/Space.java b/src/main/java/org/apache/commons/math3/geometry/Space.java
new file mode 100644
index 0000000..7b563c6
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/Space.java
@@ -0,0 +1,47 @@
+/*
+ * 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.math3.geometry;
+
+import org.apache.commons.math3.exception.MathUnsupportedOperationException;
+
+import java.io.Serializable;
+
+/**
+ * This interface represents a generic space, with affine and vectorial counterparts.
+ *
+ * @see Vector
+ * @since 3.0
+ */
+public interface Space extends Serializable {
+
+    /**
+     * Get the dimension of the space.
+     *
+     * @return dimension of the space
+     */
+    int getDimension();
+
+    /**
+     * Get the n-1 dimension subspace of this space.
+     *
+     * @return n-1 dimension sub-space of this space
+     * @see #getDimension()
+     * @exception MathUnsupportedOperationException for dimension-1 spaces which do not have
+     *     sub-spaces
+     */
+    Space getSubSpace() throws MathUnsupportedOperationException;
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/Vector.java b/src/main/java/org/apache/commons/math3/geometry/Vector.java
new file mode 100644
index 0000000..92ad04a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/Vector.java
@@ -0,0 +1,194 @@
+/*
+ * 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.math3.geometry;
+
+import org.apache.commons.math3.exception.MathArithmeticException;
+
+import java.text.NumberFormat;
+
+/**
+ * This interface represents a generic vector in a vectorial space or a point in an affine space.
+ *
+ * @param <S> Type of the space.
+ * @see Space
+ * @see Point
+ * @since 3.0
+ */
+public interface Vector<S extends Space> extends Point<S> {
+
+    /**
+     * Get the null vector of the vectorial space or origin point of the affine space.
+     *
+     * @return null vector of the vectorial space or origin point of the affine space
+     */
+    Vector<S> getZero();
+
+    /**
+     * Get the L<sub>1</sub> norm for the vector.
+     *
+     * @return L<sub>1</sub> norm for the vector
+     */
+    double getNorm1();
+
+    /**
+     * Get the L<sub>2</sub> norm for the vector.
+     *
+     * @return Euclidean norm for the vector
+     */
+    double getNorm();
+
+    /**
+     * Get the square of the norm for the vector.
+     *
+     * @return square of the Euclidean norm for the vector
+     */
+    double getNormSq();
+
+    /**
+     * Get the L<sub>&infin;</sub> norm for the vector.
+     *
+     * @return L<sub>&infin;</sub> norm for the vector
+     */
+    double getNormInf();
+
+    /**
+     * Add a vector to the instance.
+     *
+     * @param v vector to add
+     * @return a new vector
+     */
+    Vector<S> add(Vector<S> v);
+
+    /**
+     * Add a scaled vector to the instance.
+     *
+     * @param factor scale factor to apply to v before adding it
+     * @param v vector to add
+     * @return a new vector
+     */
+    Vector<S> add(double factor, Vector<S> v);
+
+    /**
+     * Subtract a vector from the instance.
+     *
+     * @param v vector to subtract
+     * @return a new vector
+     */
+    Vector<S> subtract(Vector<S> v);
+
+    /**
+     * Subtract a scaled vector from the instance.
+     *
+     * @param factor scale factor to apply to v before subtracting it
+     * @param v vector to subtract
+     * @return a new vector
+     */
+    Vector<S> subtract(double factor, Vector<S> v);
+
+    /**
+     * Get the opposite of the instance.
+     *
+     * @return a new vector which is opposite to the instance
+     */
+    Vector<S> negate();
+
+    /**
+     * Get a normalized vector aligned with the instance.
+     *
+     * @return a new normalized vector
+     * @exception MathArithmeticException if the norm is zero
+     */
+    Vector<S> normalize() throws MathArithmeticException;
+
+    /**
+     * Multiply the instance by a scalar.
+     *
+     * @param a scalar
+     * @return a new vector
+     */
+    Vector<S> scalarMultiply(double a);
+
+    /**
+     * Returns true if any coordinate of this vector is infinite and none are NaN; false otherwise
+     *
+     * @return true if any coordinate of this vector is infinite and none are NaN; false otherwise
+     */
+    boolean isInfinite();
+
+    /**
+     * Compute the distance between the instance and another vector according to the L<sub>1</sub>
+     * norm.
+     *
+     * <p>Calling this method is equivalent to calling: <code>q.subtract(p).getNorm1()</code> except
+     * that no intermediate vector is built
+     *
+     * @param v second vector
+     * @return the distance between the instance and p according to the L<sub>1</sub> norm
+     */
+    double distance1(Vector<S> v);
+
+    /**
+     * Compute the distance between the instance and another vector according to the L<sub>2</sub>
+     * norm.
+     *
+     * <p>Calling this method is equivalent to calling: <code>q.subtract(p).getNorm()</code> except
+     * that no intermediate vector is built
+     *
+     * @param v second vector
+     * @return the distance between the instance and p according to the L<sub>2</sub> norm
+     */
+    double distance(Vector<S> v);
+
+    /**
+     * Compute the distance between the instance and another vector according to the
+     * L<sub>&infin;</sub> norm.
+     *
+     * <p>Calling this method is equivalent to calling: <code>q.subtract(p).getNormInf()</code>
+     * except that no intermediate vector is built
+     *
+     * @param v second vector
+     * @return the distance between the instance and p according to the L<sub>&infin;</sub> norm
+     */
+    double distanceInf(Vector<S> v);
+
+    /**
+     * Compute the square of the distance between the instance and another vector.
+     *
+     * <p>Calling this method is equivalent to calling: <code>q.subtract(p).getNormSq()</code>
+     * except that no intermediate vector is built
+     *
+     * @param v second vector
+     * @return the square of the distance between the instance and p
+     */
+    double distanceSq(Vector<S> v);
+
+    /**
+     * Compute the dot-product of the instance and another vector.
+     *
+     * @param v second vector
+     * @return the dot product this.v
+     */
+    double dotProduct(Vector<S> v);
+
+    /**
+     * Get a string representation of this vector.
+     *
+     * @param format the custom format for components
+     * @return a string representation of this vector
+     */
+    String toString(final NumberFormat format);
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/VectorFormat.java b/src/main/java/org/apache/commons/math3/geometry/VectorFormat.java
new file mode 100644
index 0000000..7c6d0c5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/VectorFormat.java
@@ -0,0 +1,307 @@
+/*
+ * 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.math3.geometry;
+
+import org.apache.commons.math3.exception.MathParseException;
+import org.apache.commons.math3.util.CompositeFormat;
+
+import java.text.FieldPosition;
+import java.text.NumberFormat;
+import java.text.ParsePosition;
+import java.util.Locale;
+
+/**
+ * Formats a vector in components list format "{x; y; ...}".
+ *
+ * <p>The prefix and suffix "{" and "}" and the separator "; " can be replaced by any user-defined
+ * strings. The number format for components can be configured.
+ *
+ * <p>White space is ignored at parse time, even if it is in the prefix, suffix or separator
+ * specifications. So even if the default separator does include a space character that is used at
+ * format time, both input string "{1;1;1}" and " { 1 ; 1 ; 1 } " will be parsed without error and
+ * the same vector will be returned. In the second case, however, the parse position after parsing
+ * will be just after the closing curly brace, i.e. just before the trailing space.
+ *
+ * <p><b>Note:</b> using "," as a separator may interfere with the grouping separator of the default
+ * {@link NumberFormat} for the current locale. Thus it is advised to use a {@link NumberFormat}
+ * instance with disabled grouping in such a case.
+ *
+ * @param <S> Type of the space.
+ * @since 3.0
+ */
+public abstract class VectorFormat<S extends Space> {
+
+    /** The default prefix: "{". */
+    public static final String DEFAULT_PREFIX = "{";
+
+    /** The default suffix: "}". */
+    public static final String DEFAULT_SUFFIX = "}";
+
+    /** The default separator: ", ". */
+    public static final String DEFAULT_SEPARATOR = "; ";
+
+    /** Prefix. */
+    private final String prefix;
+
+    /** Suffix. */
+    private final String suffix;
+
+    /** Separator. */
+    private final String separator;
+
+    /** Trimmed prefix. */
+    private final String trimmedPrefix;
+
+    /** Trimmed suffix. */
+    private final String trimmedSuffix;
+
+    /** Trimmed separator. */
+    private final String trimmedSeparator;
+
+    /** The format used for components. */
+    private final NumberFormat format;
+
+    /**
+     * Create an instance with default settings.
+     *
+     * <p>The instance uses the default prefix, suffix and separator: "{", "}", and "; " and the
+     * default number format for components.
+     */
+    protected VectorFormat() {
+        this(
+                DEFAULT_PREFIX,
+                DEFAULT_SUFFIX,
+                DEFAULT_SEPARATOR,
+                CompositeFormat.getDefaultNumberFormat());
+    }
+
+    /**
+     * Create an instance with a custom number format for components.
+     *
+     * @param format the custom format for components.
+     */
+    protected VectorFormat(final NumberFormat format) {
+        this(DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_SEPARATOR, format);
+    }
+
+    /**
+     * Create an instance with custom prefix, suffix and separator.
+     *
+     * @param prefix prefix to use instead of the default "{"
+     * @param suffix suffix to use instead of the default "}"
+     * @param separator separator to use instead of the default "; "
+     */
+    protected VectorFormat(final String prefix, final String suffix, final String separator) {
+        this(prefix, suffix, separator, CompositeFormat.getDefaultNumberFormat());
+    }
+
+    /**
+     * Create an instance with custom prefix, suffix, separator and format for components.
+     *
+     * @param prefix prefix to use instead of the default "{"
+     * @param suffix suffix to use instead of the default "}"
+     * @param separator separator to use instead of the default "; "
+     * @param format the custom format for components.
+     */
+    protected VectorFormat(
+            final String prefix,
+            final String suffix,
+            final String separator,
+            final NumberFormat format) {
+        this.prefix = prefix;
+        this.suffix = suffix;
+        this.separator = separator;
+        trimmedPrefix = prefix.trim();
+        trimmedSuffix = suffix.trim();
+        trimmedSeparator = separator.trim();
+        this.format = format;
+    }
+
+    /**
+     * Get the set of locales for which point/vector formats are available.
+     *
+     * <p>This is the same set as the {@link NumberFormat} set.
+     *
+     * @return available point/vector format locales.
+     */
+    public static Locale[] getAvailableLocales() {
+        return NumberFormat.getAvailableLocales();
+    }
+
+    /**
+     * Get the format prefix.
+     *
+     * @return format prefix.
+     */
+    public String getPrefix() {
+        return prefix;
+    }
+
+    /**
+     * Get the format suffix.
+     *
+     * @return format suffix.
+     */
+    public String getSuffix() {
+        return suffix;
+    }
+
+    /**
+     * Get the format separator between components.
+     *
+     * @return format separator.
+     */
+    public String getSeparator() {
+        return separator;
+    }
+
+    /**
+     * Get the components format.
+     *
+     * @return components format.
+     */
+    public NumberFormat getFormat() {
+        return format;
+    }
+
+    /**
+     * Formats a {@link Vector} object to produce a string.
+     *
+     * @param vector the object to format.
+     * @return a formatted string.
+     */
+    public String format(Vector<S> vector) {
+        return format(vector, new StringBuffer(), new FieldPosition(0)).toString();
+    }
+
+    /**
+     * Formats a {@link Vector} object to produce a string.
+     *
+     * @param vector the object to format.
+     * @param toAppendTo where the text is to be appended
+     * @param pos On input: an alignment field, if desired. On output: the offsets of the alignment
+     *     field
+     * @return the value passed in as toAppendTo.
+     */
+    public abstract StringBuffer format(
+            Vector<S> vector, StringBuffer toAppendTo, FieldPosition pos);
+
+    /**
+     * Formats the coordinates of a {@link Vector} to produce a string.
+     *
+     * @param toAppendTo where the text is to be appended
+     * @param pos On input: an alignment field, if desired. On output: the offsets of the alignment
+     *     field
+     * @param coordinates coordinates of the object to format.
+     * @return the value passed in as toAppendTo.
+     */
+    protected StringBuffer format(
+            StringBuffer toAppendTo, FieldPosition pos, double... coordinates) {
+
+        pos.setBeginIndex(0);
+        pos.setEndIndex(0);
+
+        // format prefix
+        toAppendTo.append(prefix);
+
+        // format components
+        for (int i = 0; i < coordinates.length; ++i) {
+            if (i > 0) {
+                toAppendTo.append(separator);
+            }
+            CompositeFormat.formatDouble(coordinates[i], format, toAppendTo, pos);
+        }
+
+        // format suffix
+        toAppendTo.append(suffix);
+
+        return toAppendTo;
+    }
+
+    /**
+     * Parses a string to produce a {@link Vector} object.
+     *
+     * @param source the string to parse
+     * @return the parsed {@link Vector} object.
+     * @throws MathParseException if the beginning of the specified string cannot be parsed.
+     */
+    public abstract Vector<S> parse(String source) throws MathParseException;
+
+    /**
+     * Parses a string to produce a {@link Vector} object.
+     *
+     * @param source the string to parse
+     * @param pos input/output parsing parameter.
+     * @return the parsed {@link Vector} object.
+     */
+    public abstract Vector<S> parse(String source, ParsePosition pos);
+
+    /**
+     * Parses a string to produce an array of coordinates.
+     *
+     * @param dimension dimension of the space
+     * @param source the string to parse
+     * @param pos input/output parsing parameter.
+     * @return coordinates array.
+     */
+    protected double[] parseCoordinates(int dimension, String source, ParsePosition pos) {
+
+        int initialIndex = pos.getIndex();
+        double[] coordinates = new double[dimension];
+
+        // parse prefix
+        CompositeFormat.parseAndIgnoreWhitespace(source, pos);
+        if (!CompositeFormat.parseFixedstring(source, trimmedPrefix, pos)) {
+            return null;
+        }
+
+        for (int i = 0; i < dimension; ++i) {
+
+            // skip whitespace
+            CompositeFormat.parseAndIgnoreWhitespace(source, pos);
+
+            // parse separator
+            if (i > 0 && !CompositeFormat.parseFixedstring(source, trimmedSeparator, pos)) {
+                return null;
+            }
+
+            // skip whitespace
+            CompositeFormat.parseAndIgnoreWhitespace(source, pos);
+
+            // parse coordinate
+            Number c = CompositeFormat.parseNumber(source, format, pos);
+            if (c == null) {
+                // invalid coordinate
+                // set index back to initial, error index should already be set
+                pos.setIndex(initialIndex);
+                return null;
+            }
+
+            // store coordinate
+            coordinates[i] = c.doubleValue();
+        }
+
+        // parse suffix
+        CompositeFormat.parseAndIgnoreWhitespace(source, pos);
+        if (!CompositeFormat.parseFixedstring(source, trimmedSuffix, pos)) {
+            return null;
+        }
+
+        return coordinates;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/enclosing/Encloser.java b/src/main/java/org/apache/commons/math3/geometry/enclosing/Encloser.java
new file mode 100644
index 0000000..9b2588a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/enclosing/Encloser.java
@@ -0,0 +1,36 @@
+/*
+ * 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.math3.geometry.enclosing;
+
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.Space;
+
+/** Interface for algorithms computing enclosing balls.
+ * @param <S> Space type.
+ * @param <P> Point type.
+ * @see EnclosingBall
+ * @since 3.3
+ */
+public interface Encloser<S extends Space, P extends Point<S>> {
+
+    /** Find a ball enclosing a list of points.
+     * @param points points to enclose
+     * @return enclosing ball
+     */
+    EnclosingBall<S, P> enclose(Iterable<P> points);
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/enclosing/EnclosingBall.java b/src/main/java/org/apache/commons/math3/geometry/enclosing/EnclosingBall.java
new file mode 100644
index 0000000..eedbd46
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/enclosing/EnclosingBall.java
@@ -0,0 +1,103 @@
+/*
+ * 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.math3.geometry.enclosing;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.Space;
+
+/** This class represents a ball enclosing some points.
+ * @param <S> Space type.
+ * @param <P> Point type.
+ * @see Space
+ * @see Point
+ * @see Encloser
+ * @since 3.3
+ */
+public class EnclosingBall<S extends Space, P extends Point<S>> implements Serializable {
+
+    /** Serializable UID. */
+    private static final long serialVersionUID = 20140126L;
+
+    /** Center of the ball. */
+    private final P center;
+
+    /** Radius of the ball. */
+    private final double radius;
+
+    /** Support points used to define the ball. */
+    private final P[] support;
+
+    /** Simple constructor.
+     * @param center center of the ball
+     * @param radius radius of the ball
+     * @param support support points used to define the ball
+     */
+    public EnclosingBall(final P center, final double radius, final P ... support) {
+        this.center  = center;
+        this.radius  = radius;
+        this.support = support.clone();
+    }
+
+    /** Get the center of the ball.
+     * @return center of the ball
+     */
+    public P getCenter() {
+        return center;
+    }
+
+    /** Get the radius of the ball.
+     * @return radius of the ball (can be negative if the ball is empty)
+     */
+    public double getRadius() {
+        return radius;
+    }
+
+    /** Get the support points used to define the ball.
+     * @return support points used to define the ball
+     */
+    public P[] getSupport() {
+        return support.clone();
+    }
+
+    /** Get the number of support points used to define the ball.
+     * @return number of support points used to define the ball
+     */
+    public int getSupportSize() {
+        return support.length;
+    }
+
+    /** Check if a point is within the ball or at boundary.
+     * @param point point to test
+     * @return true if the point is within the ball or at boundary
+     */
+    public boolean contains(final P point) {
+        return point.distance(center) <= radius;
+    }
+
+    /** Check if a point is within an enlarged ball or at boundary.
+     * @param point point to test
+     * @param margin margin to consider
+     * @return true if the point is within the ball enlarged
+     * by the margin or at boundary
+     */
+    public boolean contains(final P point, final double margin) {
+        return point.distance(center) <= radius + margin;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/enclosing/SupportBallGenerator.java b/src/main/java/org/apache/commons/math3/geometry/enclosing/SupportBallGenerator.java
new file mode 100644
index 0000000..3a0f875
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/enclosing/SupportBallGenerator.java
@@ -0,0 +1,42 @@
+/*
+ * 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.math3.geometry.enclosing;
+
+import java.util.List;
+
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.Space;
+
+/** Interface for generating balls based on support points.
+ * <p>
+ * This generator is used in the {@link WelzlEncloser Emo Welzl} algorithm
+ * and its derivatives.
+ * </p>
+ * @param <S> Space type.
+ * @param <P> Point type.
+ * @see EnclosingBall
+ * @since 3.3
+ */
+public interface SupportBallGenerator<S extends Space, P extends Point<S>> {
+
+    /** Create a ball whose boundary lies on prescribed support points.
+     * @param support support points (may be empty)
+     * @return ball whose boundary lies on the prescribed support points
+     */
+    EnclosingBall<S, P> ballOnSupport(List<P> support);
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/enclosing/WelzlEncloser.java b/src/main/java/org/apache/commons/math3/geometry/enclosing/WelzlEncloser.java
new file mode 100644
index 0000000..987e7d9
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/enclosing/WelzlEncloser.java
@@ -0,0 +1,181 @@
+/*
+ * 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.math3.geometry.enclosing;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.math3.exception.MathInternalError;
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.Space;
+
+/** Class implementing Emo Welzl algorithm to find the smallest enclosing ball in linear time.
+ * <p>
+ * The class implements the algorithm described in paper <a
+ * href="http://www.inf.ethz.ch/personal/emo/PublFiles/SmallEnclDisk_LNCS555_91.pdf">Smallest
+ * Enclosing Disks (Balls and Ellipsoids)</a> by Emo Welzl, Lecture Notes in Computer Science
+ * 555 (1991) 359-370. The pivoting improvement published in the paper <a
+ * href="http://www.inf.ethz.ch/personal/gaertner/texts/own_work/esa99_final.pdf">Fast and
+ * Robust Smallest Enclosing Balls</a>, by Bernd Gärtner and further modified in
+ * paper <a
+ * href=http://www.idt.mdh.se/kurser/ct3340/ht12/MINICONFERENCE/FinalPapers/ircse12_submission_30.pdf">
+ * Efficient Computation of Smallest Enclosing Balls in Three Dimensions</a> by Linus Källberg
+ * to avoid performing local copies of data have been included.
+ * </p>
+ * @param <S> Space type.
+ * @param <P> Point type.
+ * @since 3.3
+ */
+public class WelzlEncloser<S extends Space, P extends Point<S>> implements Encloser<S, P> {
+
+    /** Tolerance below which points are consider to be identical. */
+    private final double tolerance;
+
+    /** Generator for balls on support. */
+    private final SupportBallGenerator<S, P> generator;
+
+    /** Simple constructor.
+     * @param tolerance below which points are consider to be identical
+     * @param generator generator for balls on support
+     */
+    public WelzlEncloser(final double tolerance, final SupportBallGenerator<S, P> generator) {
+        this.tolerance = tolerance;
+        this.generator = generator;
+    }
+
+    /** {@inheritDoc} */
+    public EnclosingBall<S, P> enclose(final Iterable<P> points) {
+
+        if (points == null || !points.iterator().hasNext()) {
+            // return an empty ball
+            return generator.ballOnSupport(new ArrayList<P>());
+        }
+
+        // Emo Welzl algorithm with Bernd Gärtner and Linus Källberg improvements
+        return pivotingBall(points);
+
+    }
+
+    /** Compute enclosing ball using Gärtner's pivoting heuristic.
+     * @param points points to be enclosed
+     * @return enclosing ball
+     */
+    private EnclosingBall<S, P> pivotingBall(final Iterable<P> points) {
+
+        final P first = points.iterator().next();
+        final List<P> extreme = new ArrayList<P>(first.getSpace().getDimension() + 1);
+        final List<P> support = new ArrayList<P>(first.getSpace().getDimension() + 1);
+
+        // start with only first point selected as a candidate support
+        extreme.add(first);
+        EnclosingBall<S, P> ball = moveToFrontBall(extreme, extreme.size(), support);
+
+        while (true) {
+
+            // select the point farthest to current ball
+            final P farthest = selectFarthest(points, ball);
+
+            if (ball.contains(farthest, tolerance)) {
+                // we have found a ball containing all points
+                return ball;
+            }
+
+            // recurse search, restricted to the small subset containing support and farthest point
+            support.clear();
+            support.add(farthest);
+            EnclosingBall<S, P> savedBall = ball;
+            ball = moveToFrontBall(extreme, extreme.size(), support);
+            if (ball.getRadius() < savedBall.getRadius()) {
+                // this should never happen
+                throw new MathInternalError();
+            }
+
+            // it was an interesting point, move it to the front
+            // according to Gärtner's heuristic
+            extreme.add(0, farthest);
+
+            // prune the least interesting points
+            extreme.subList(ball.getSupportSize(), extreme.size()).clear();
+
+
+        }
+    }
+
+    /** Compute enclosing ball using Welzl's move to front heuristic.
+     * @param extreme subset of extreme points
+     * @param nbExtreme number of extreme points to consider
+     * @param support points that must belong to the ball support
+     * @return enclosing ball, for the extreme subset only
+     */
+    private EnclosingBall<S, P> moveToFrontBall(final List<P> extreme, final int nbExtreme,
+                                                final List<P> support) {
+
+        // create a new ball on the prescribed support
+        EnclosingBall<S, P> ball = generator.ballOnSupport(support);
+
+        if (ball.getSupportSize() <= ball.getCenter().getSpace().getDimension()) {
+
+            for (int i = 0; i < nbExtreme; ++i) {
+                final P pi = extreme.get(i);
+                if (!ball.contains(pi, tolerance)) {
+
+                    // we have found an outside point,
+                    // enlarge the ball by adding it to the support
+                    support.add(pi);
+                    ball = moveToFrontBall(extreme, i, support);
+                    support.remove(support.size() - 1);
+
+                    // it was an interesting point, move it to the front
+                    // according to Welzl's heuristic
+                    for (int j = i; j > 0; --j) {
+                        extreme.set(j, extreme.get(j - 1));
+                    }
+                    extreme.set(0, pi);
+
+                }
+            }
+
+        }
+
+        return ball;
+
+    }
+
+    /** Select the point farthest to the current ball.
+     * @param points points to be enclosed
+     * @param ball current ball
+     * @return farthest point
+     */
+    public P selectFarthest(final Iterable<P> points, final EnclosingBall<S, P> ball) {
+
+        final P center = ball.getCenter();
+        P farthest   = null;
+        double dMax  = -1.0;
+
+        for (final P point : points) {
+            final double d = point.distance(center);
+            if (d > dMax) {
+                farthest = point;
+                dMax     = d;
+            }
+        }
+
+        return farthest;
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/enclosing/package-info.java b/src/main/java/org/apache/commons/math3/geometry/enclosing/package-info.java
new file mode 100644
index 0000000..20462a1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/enclosing/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * <p>
+ * This package provides interfaces and classes related to the smallest enclosing ball problem.
+ * </p>
+ *
+ */
+package org.apache.commons.math3.geometry.enclosing;
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/oned/Euclidean1D.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/oned/Euclidean1D.java
new file mode 100644
index 0000000..14d130d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/oned/Euclidean1D.java
@@ -0,0 +1,100 @@
+/*
+ * 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.math3.geometry.euclidean.oned;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.exception.MathUnsupportedOperationException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.geometry.Space;
+
+/**
+ * This class implements a one-dimensional space.
+ * @since 3.0
+ */
+public class Euclidean1D implements Serializable, Space {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -1178039568877797126L;
+
+    /** Private constructor for the singleton.
+     */
+    private Euclidean1D() {
+    }
+
+    /** Get the unique instance.
+     * @return the unique instance
+     */
+    public static Euclidean1D getInstance() {
+        return LazyHolder.INSTANCE;
+    }
+
+    /** {@inheritDoc} */
+    public int getDimension() {
+        return 1;
+    }
+
+    /** {@inheritDoc}
+     * <p>
+     * As the 1-dimension Euclidean space does not have proper sub-spaces,
+     * this method always throws a {@link NoSubSpaceException}
+     * </p>
+     * @return nothing
+     * @throws NoSubSpaceException in all cases
+     */
+    public Space getSubSpace() throws NoSubSpaceException {
+        throw new NoSubSpaceException();
+    }
+
+    // CHECKSTYLE: stop HideUtilityClassConstructor
+    /** Holder for the instance.
+     * <p>We use here the Initialization On Demand Holder Idiom.</p>
+     */
+    private static class LazyHolder {
+        /** Cached field instance. */
+        private static final Euclidean1D INSTANCE = new Euclidean1D();
+    }
+    // CHECKSTYLE: resume HideUtilityClassConstructor
+
+    /** Handle deserialization of the singleton.
+     * @return the singleton instance
+     */
+    private Object readResolve() {
+        // return the singleton instance
+        return LazyHolder.INSTANCE;
+    }
+
+    /** Specialized exception for inexistent sub-space.
+     * <p>
+     * This exception is thrown when attempting to get the sub-space of a one-dimensional space
+     * </p>
+     */
+    public static class NoSubSpaceException extends MathUnsupportedOperationException {
+
+        /** Serializable UID. */
+        private static final long serialVersionUID = 20140225L;
+
+        /** Simple constructor.
+         */
+        public NoSubSpaceException() {
+            super(LocalizedFormats.NOT_SUPPORTED_IN_DIMENSION_N, 1);
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/oned/Interval.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/oned/Interval.java
new file mode 100644
index 0000000..ca15231
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/oned/Interval.java
@@ -0,0 +1,135 @@
+/*
+ * 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.math3.geometry.euclidean.oned;
+
+import org.apache.commons.math3.geometry.partitioning.Region.Location;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+
+/** This class represents a 1D interval.
+ * @see IntervalsSet
+ * @since 3.0
+ */
+public class Interval {
+
+    /** The lower bound of the interval. */
+    private final double lower;
+
+    /** The upper bound of the interval. */
+    private final double upper;
+
+    /** Simple constructor.
+     * @param lower lower bound of the interval
+     * @param upper upper bound of the interval
+     */
+    public Interval(final double lower, final double upper) {
+        if (upper < lower) {
+            throw new NumberIsTooSmallException(LocalizedFormats.ENDPOINTS_NOT_AN_INTERVAL,
+                                                upper, lower, true);
+        }
+        this.lower = lower;
+        this.upper = upper;
+    }
+
+    /** Get the lower bound of the interval.
+     * @return lower bound of the interval
+     * @since 3.1
+     */
+    public double getInf() {
+        return lower;
+    }
+
+    /** Get the lower bound of the interval.
+     * @return lower bound of the interval
+     * @deprecated as of 3.1, replaced by {@link #getInf()}
+     */
+    @Deprecated
+    public double getLower() {
+        return getInf();
+    }
+
+    /** Get the upper bound of the interval.
+     * @return upper bound of the interval
+     * @since 3.1
+     */
+    public double getSup() {
+        return upper;
+    }
+
+    /** Get the upper bound of the interval.
+     * @return upper bound of the interval
+     * @deprecated as of 3.1, replaced by {@link #getSup()}
+     */
+    @Deprecated
+    public double getUpper() {
+        return getSup();
+    }
+
+    /** Get the size of the interval.
+     * @return size of the interval
+     * @since 3.1
+     */
+    public double getSize() {
+        return upper - lower;
+    }
+
+    /** Get the length of the interval.
+     * @return length of the interval
+     * @deprecated as of 3.1, replaced by {@link #getSize()}
+     */
+    @Deprecated
+    public double getLength() {
+        return getSize();
+    }
+
+    /** Get the barycenter of the interval.
+     * @return barycenter of the interval
+     * @since 3.1
+     */
+    public double getBarycenter() {
+        return 0.5 * (lower + upper);
+    }
+
+    /** Get the midpoint of the interval.
+     * @return midpoint of the interval
+     * @deprecated as of 3.1, replaced by {@link #getBarycenter()}
+     */
+    @Deprecated
+    public double getMidPoint() {
+        return getBarycenter();
+    }
+
+    /** Check a point with respect to the interval.
+     * @param point point to check
+     * @param tolerance tolerance below which points are considered to
+     * belong to the boundary
+     * @return a code representing the point status: either {@link
+     * Location#INSIDE}, {@link Location#OUTSIDE} or {@link Location#BOUNDARY}
+     * @since 3.1
+     */
+    public Location checkPoint(final double point, final double tolerance) {
+        if (point < lower - tolerance || point > upper + tolerance) {
+            return Location.OUTSIDE;
+        } else if (point > lower + tolerance && point < upper - tolerance) {
+            return Location.INSIDE;
+        } else {
+            return Location.BOUNDARY;
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/oned/IntervalsSet.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/oned/IntervalsSet.java
new file mode 100644
index 0000000..5ce7edb
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/oned/IntervalsSet.java
@@ -0,0 +1,686 @@
+/*
+ * 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.math3.geometry.euclidean.oned;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.partitioning.AbstractRegion;
+import org.apache.commons.math3.geometry.partitioning.BSPTree;
+import org.apache.commons.math3.geometry.partitioning.BoundaryProjection;
+import org.apache.commons.math3.geometry.partitioning.SubHyperplane;
+import org.apache.commons.math3.util.Precision;
+
+/** This class represents a 1D region: a set of intervals.
+ * @since 3.0
+ */
+public class IntervalsSet extends AbstractRegion<Euclidean1D, Euclidean1D> implements Iterable<double[]> {
+
+    /** Default value for tolerance. */
+    private static final double DEFAULT_TOLERANCE = 1.0e-10;
+
+    /** Build an intervals set representing the whole real line.
+     * @param tolerance tolerance below which points are considered identical.
+     * @since 3.3
+     */
+    public IntervalsSet(final double tolerance) {
+        super(tolerance);
+    }
+
+    /** Build an intervals set corresponding to a single interval.
+     * @param lower lower bound of the interval, must be lesser or equal
+     * to {@code upper} (may be {@code Double.NEGATIVE_INFINITY})
+     * @param upper upper bound of the interval, must be greater or equal
+     * to {@code lower} (may be {@code Double.POSITIVE_INFINITY})
+     * @param tolerance tolerance below which points are considered identical.
+     * @since 3.3
+     */
+    public IntervalsSet(final double lower, final double upper, final double tolerance) {
+        super(buildTree(lower, upper, tolerance), tolerance);
+    }
+
+    /** Build an intervals set from an inside/outside BSP tree.
+     * <p>The leaf nodes of the BSP tree <em>must</em> have a
+     * {@code Boolean} attribute representing the inside status of
+     * the corresponding cell (true for inside cells, false for outside
+     * cells). In order to avoid building too many small objects, it is
+     * recommended to use the predefined constants
+     * {@code Boolean.TRUE} and {@code Boolean.FALSE}</p>
+     * @param tree inside/outside BSP tree representing the intervals set
+     * @param tolerance tolerance below which points are considered identical.
+     * @since 3.3
+     */
+    public IntervalsSet(final BSPTree<Euclidean1D> tree, final double tolerance) {
+        super(tree, tolerance);
+    }
+
+    /** Build an intervals set from a Boundary REPresentation (B-rep).
+     * <p>The boundary is provided as a collection of {@link
+     * SubHyperplane sub-hyperplanes}. Each sub-hyperplane has the
+     * interior part of the region on its minus side and the exterior on
+     * its plus side.</p>
+     * <p>The boundary elements can be in any order, and can form
+     * several non-connected sets (like for example polygons with holes
+     * or a set of disjoints polyhedrons considered as a whole). In
+     * fact, the elements do not even need to be connected together
+     * (their topological connections are not used here). However, if the
+     * boundary does not really separate an inside open from an outside
+     * open (open having here its topological meaning), then subsequent
+     * calls to the {@link
+     * org.apache.commons.math3.geometry.partitioning.Region#checkPoint(org.apache.commons.math3.geometry.Point)
+     * checkPoint} method will not be meaningful anymore.</p>
+     * <p>If the boundary is empty, the region will represent the whole
+     * space.</p>
+     * @param boundary collection of boundary elements
+     * @param tolerance tolerance below which points are considered identical.
+     * @since 3.3
+     */
+    public IntervalsSet(final Collection<SubHyperplane<Euclidean1D>> boundary,
+                        final double tolerance) {
+        super(boundary, tolerance);
+    }
+
+    /** Build an intervals set representing the whole real line.
+     * @deprecated as of 3.1 replaced with {@link #IntervalsSet(double)}
+     */
+    @Deprecated
+    public IntervalsSet() {
+        this(DEFAULT_TOLERANCE);
+    }
+
+    /** Build an intervals set corresponding to a single interval.
+     * @param lower lower bound of the interval, must be lesser or equal
+     * to {@code upper} (may be {@code Double.NEGATIVE_INFINITY})
+     * @param upper upper bound of the interval, must be greater or equal
+     * to {@code lower} (may be {@code Double.POSITIVE_INFINITY})
+     * @deprecated as of 3.3 replaced with {@link #IntervalsSet(double, double, double)}
+     */
+    @Deprecated
+    public IntervalsSet(final double lower, final double upper) {
+        this(lower, upper, DEFAULT_TOLERANCE);
+    }
+
+    /** Build an intervals set from an inside/outside BSP tree.
+     * <p>The leaf nodes of the BSP tree <em>must</em> have a
+     * {@code Boolean} attribute representing the inside status of
+     * the corresponding cell (true for inside cells, false for outside
+     * cells). In order to avoid building too many small objects, it is
+     * recommended to use the predefined constants
+     * {@code Boolean.TRUE} and {@code Boolean.FALSE}</p>
+     * @param tree inside/outside BSP tree representing the intervals set
+     * @deprecated as of 3.3, replaced with {@link #IntervalsSet(BSPTree, double)}
+     */
+    @Deprecated
+    public IntervalsSet(final BSPTree<Euclidean1D> tree) {
+        this(tree, DEFAULT_TOLERANCE);
+    }
+
+    /** Build an intervals set from a Boundary REPresentation (B-rep).
+     * <p>The boundary is provided as a collection of {@link
+     * SubHyperplane sub-hyperplanes}. Each sub-hyperplane has the
+     * interior part of the region on its minus side and the exterior on
+     * its plus side.</p>
+     * <p>The boundary elements can be in any order, and can form
+     * several non-connected sets (like for example polygons with holes
+     * or a set of disjoints polyhedrons considered as a whole). In
+     * fact, the elements do not even need to be connected together
+     * (their topological connections are not used here). However, if the
+     * boundary does not really separate an inside open from an outside
+     * open (open having here its topological meaning), then subsequent
+     * calls to the {@link
+     * org.apache.commons.math3.geometry.partitioning.Region#checkPoint(org.apache.commons.math3.geometry.Point)
+     * checkPoint} method will not be meaningful anymore.</p>
+     * <p>If the boundary is empty, the region will represent the whole
+     * space.</p>
+     * @param boundary collection of boundary elements
+     * @deprecated as of 3.3, replaced with {@link #IntervalsSet(Collection, double)}
+     */
+    @Deprecated
+    public IntervalsSet(final Collection<SubHyperplane<Euclidean1D>> boundary) {
+        this(boundary, DEFAULT_TOLERANCE);
+    }
+
+    /** Build an inside/outside tree representing a single interval.
+     * @param lower lower bound of the interval, must be lesser or equal
+     * to {@code upper} (may be {@code Double.NEGATIVE_INFINITY})
+     * @param upper upper bound of the interval, must be greater or equal
+     * to {@code lower} (may be {@code Double.POSITIVE_INFINITY})
+     * @param tolerance tolerance below which points are considered identical.
+     * @return the built tree
+     */
+    private static BSPTree<Euclidean1D> buildTree(final double lower, final double upper,
+                                                  final double tolerance) {
+        if (Double.isInfinite(lower) && (lower < 0)) {
+            if (Double.isInfinite(upper) && (upper > 0)) {
+                // the tree must cover the whole real line
+                return new BSPTree<Euclidean1D>(Boolean.TRUE);
+            }
+            // the tree must be open on the negative infinity side
+            final SubHyperplane<Euclidean1D> upperCut =
+                new OrientedPoint(new Vector1D(upper), true, tolerance).wholeHyperplane();
+            return new BSPTree<Euclidean1D>(upperCut,
+                               new BSPTree<Euclidean1D>(Boolean.FALSE),
+                               new BSPTree<Euclidean1D>(Boolean.TRUE),
+                               null);
+        }
+        final SubHyperplane<Euclidean1D> lowerCut =
+            new OrientedPoint(new Vector1D(lower), false, tolerance).wholeHyperplane();
+        if (Double.isInfinite(upper) && (upper > 0)) {
+            // the tree must be open on the positive infinity side
+            return new BSPTree<Euclidean1D>(lowerCut,
+                                            new BSPTree<Euclidean1D>(Boolean.FALSE),
+                                            new BSPTree<Euclidean1D>(Boolean.TRUE),
+                                            null);
+        }
+
+        // the tree must be bounded on the two sides
+        final SubHyperplane<Euclidean1D> upperCut =
+            new OrientedPoint(new Vector1D(upper), true, tolerance).wholeHyperplane();
+        return new BSPTree<Euclidean1D>(lowerCut,
+                                        new BSPTree<Euclidean1D>(Boolean.FALSE),
+                                        new BSPTree<Euclidean1D>(upperCut,
+                                                                 new BSPTree<Euclidean1D>(Boolean.FALSE),
+                                                                 new BSPTree<Euclidean1D>(Boolean.TRUE),
+                                                                 null),
+                                        null);
+
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public IntervalsSet buildNew(final BSPTree<Euclidean1D> tree) {
+        return new IntervalsSet(tree, getTolerance());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void computeGeometricalProperties() {
+        if (getTree(false).getCut() == null) {
+            setBarycenter((Point<Euclidean1D>) Vector1D.NaN);
+            setSize(((Boolean) getTree(false).getAttribute()) ? Double.POSITIVE_INFINITY : 0);
+        } else {
+            double size = 0.0;
+            double sum = 0.0;
+            for (final Interval interval : asList()) {
+                size += interval.getSize();
+                sum  += interval.getSize() * interval.getBarycenter();
+            }
+            setSize(size);
+            if (Double.isInfinite(size)) {
+                setBarycenter((Point<Euclidean1D>) Vector1D.NaN);
+            } else if (size >= Precision.SAFE_MIN) {
+                setBarycenter((Point<Euclidean1D>) new Vector1D(sum / size));
+            } else {
+                setBarycenter((Point<Euclidean1D>) ((OrientedPoint) getTree(false).getCut().getHyperplane()).getLocation());
+            }
+        }
+    }
+
+    /** Get the lowest value belonging to the instance.
+     * @return lowest value belonging to the instance
+     * ({@code Double.NEGATIVE_INFINITY} if the instance doesn't
+     * have any low bound, {@code Double.POSITIVE_INFINITY} if the
+     * instance is empty)
+     */
+    public double getInf() {
+        BSPTree<Euclidean1D> node = getTree(false);
+        double  inf  = Double.POSITIVE_INFINITY;
+        while (node.getCut() != null) {
+            final OrientedPoint op = (OrientedPoint) node.getCut().getHyperplane();
+            inf  = op.getLocation().getX();
+            node = op.isDirect() ? node.getMinus() : node.getPlus();
+        }
+        return ((Boolean) node.getAttribute()) ? Double.NEGATIVE_INFINITY : inf;
+    }
+
+    /** Get the highest value belonging to the instance.
+     * @return highest value belonging to the instance
+     * ({@code Double.POSITIVE_INFINITY} if the instance doesn't
+     * have any high bound, {@code Double.NEGATIVE_INFINITY} if the
+     * instance is empty)
+     */
+    public double getSup() {
+        BSPTree<Euclidean1D> node = getTree(false);
+        double  sup  = Double.NEGATIVE_INFINITY;
+        while (node.getCut() != null) {
+            final OrientedPoint op = (OrientedPoint) node.getCut().getHyperplane();
+            sup  = op.getLocation().getX();
+            node = op.isDirect() ? node.getPlus() : node.getMinus();
+        }
+        return ((Boolean) node.getAttribute()) ? Double.POSITIVE_INFINITY : sup;
+    }
+
+    /** {@inheritDoc}
+     * @since 3.3
+     */
+    @Override
+    public BoundaryProjection<Euclidean1D> projectToBoundary(final Point<Euclidean1D> point) {
+
+        // get position of test point
+        final double x = ((Vector1D) point).getX();
+
+        double previous = Double.NEGATIVE_INFINITY;
+        for (final double[] a : this) {
+            if (x < a[0]) {
+                // the test point lies between the previous and the current intervals
+                // offset will be positive
+                final double previousOffset = x - previous;
+                final double currentOffset  = a[0] - x;
+                if (previousOffset < currentOffset) {
+                    return new BoundaryProjection<Euclidean1D>(point, finiteOrNullPoint(previous), previousOffset);
+                } else {
+                    return new BoundaryProjection<Euclidean1D>(point, finiteOrNullPoint(a[0]), currentOffset);
+                }
+            } else if (x <= a[1]) {
+                // the test point lies within the current interval
+                // offset will be negative
+                final double offset0 = a[0] - x;
+                final double offset1 = x - a[1];
+                if (offset0 < offset1) {
+                    return new BoundaryProjection<Euclidean1D>(point, finiteOrNullPoint(a[1]), offset1);
+                } else {
+                    return new BoundaryProjection<Euclidean1D>(point, finiteOrNullPoint(a[0]), offset0);
+                }
+            }
+            previous = a[1];
+        }
+
+        // the test point if past the last sub-interval
+        return new BoundaryProjection<Euclidean1D>(point, finiteOrNullPoint(previous), x - previous);
+
+    }
+
+    /** Build a finite point.
+     * @param x abscissa of the point
+     * @return a new point for finite abscissa, null otherwise
+     */
+    private Vector1D finiteOrNullPoint(final double x) {
+        return Double.isInfinite(x) ? null : new Vector1D(x);
+    }
+
+    /** Build an ordered list of intervals representing the instance.
+     * <p>This method builds this intervals set as an ordered list of
+     * {@link Interval Interval} elements. If the intervals set has no
+     * lower limit, the first interval will have its low bound equal to
+     * {@code Double.NEGATIVE_INFINITY}. If the intervals set has
+     * no upper limit, the last interval will have its upper bound equal
+     * to {@code Double.POSITIVE_INFINITY}. An empty tree will
+     * build an empty list while a tree representing the whole real line
+     * will build a one element list with both bounds being
+     * infinite.</p>
+     * @return a new ordered list containing {@link Interval Interval}
+     * elements
+     */
+    public List<Interval> asList() {
+        final List<Interval> list = new ArrayList<Interval>();
+        for (final double[] a : this) {
+            list.add(new Interval(a[0], a[1]));
+        }
+        return list;
+    }
+
+    /** Get the first leaf node of a tree.
+     * @param root tree root
+     * @return first leaf node
+     */
+    private BSPTree<Euclidean1D> getFirstLeaf(final BSPTree<Euclidean1D> root) {
+
+        if (root.getCut() == null) {
+            return root;
+        }
+
+        // find the smallest internal node
+        BSPTree<Euclidean1D> smallest = null;
+        for (BSPTree<Euclidean1D> n = root; n != null; n = previousInternalNode(n)) {
+            smallest = n;
+        }
+
+        return leafBefore(smallest);
+
+    }
+
+    /** Get the node corresponding to the first interval boundary.
+     * @return smallest internal node,
+     * or null if there are no internal nodes (i.e. the set is either empty or covers the real line)
+     */
+    private BSPTree<Euclidean1D> getFirstIntervalBoundary() {
+
+        // start search at the tree root
+        BSPTree<Euclidean1D> node = getTree(false);
+        if (node.getCut() == null) {
+            return null;
+        }
+
+        // walk tree until we find the smallest internal node
+        node = getFirstLeaf(node).getParent();
+
+        // walk tree until we find an interval boundary
+        while (node != null && !(isIntervalStart(node) || isIntervalEnd(node))) {
+            node = nextInternalNode(node);
+        }
+
+        return node;
+
+    }
+
+    /** Check if an internal node corresponds to the start abscissa of an interval.
+     * @param node internal node to check
+     * @return true if the node corresponds to the start abscissa of an interval
+     */
+    private boolean isIntervalStart(final BSPTree<Euclidean1D> node) {
+
+        if ((Boolean) leafBefore(node).getAttribute()) {
+            // it has an inside cell before it, it may end an interval but not start it
+            return false;
+        }
+
+        if (!(Boolean) leafAfter(node).getAttribute()) {
+            // it has an outside cell after it, it is a dummy cut away from real intervals
+            return false;
+        }
+
+        // the cell has an outside before and an inside after it
+        // it is the start of an interval
+        return true;
+
+    }
+
+    /** Check if an internal node corresponds to the end abscissa of an interval.
+     * @param node internal node to check
+     * @return true if the node corresponds to the end abscissa of an interval
+     */
+    private boolean isIntervalEnd(final BSPTree<Euclidean1D> node) {
+
+        if (!(Boolean) leafBefore(node).getAttribute()) {
+            // it has an outside cell before it, it may start an interval but not end it
+            return false;
+        }
+
+        if ((Boolean) leafAfter(node).getAttribute()) {
+            // it has an inside cell after it, it is a dummy cut in the middle of an interval
+            return false;
+        }
+
+        // the cell has an inside before and an outside after it
+        // it is the end of an interval
+        return true;
+
+    }
+
+    /** Get the next internal node.
+     * @param node current internal node
+     * @return next internal node in ascending order, or null
+     * if this is the last internal node
+     */
+    private BSPTree<Euclidean1D> nextInternalNode(BSPTree<Euclidean1D> node) {
+
+        if (childAfter(node).getCut() != null) {
+            // the next node is in the sub-tree
+            return leafAfter(node).getParent();
+        }
+
+        // there is nothing left deeper in the tree, we backtrack
+        while (isAfterParent(node)) {
+            node = node.getParent();
+        }
+        return node.getParent();
+
+    }
+
+    /** Get the previous internal node.
+     * @param node current internal node
+     * @return previous internal node in ascending order, or null
+     * if this is the first internal node
+     */
+    private BSPTree<Euclidean1D> previousInternalNode(BSPTree<Euclidean1D> node) {
+
+        if (childBefore(node).getCut() != null) {
+            // the next node is in the sub-tree
+            return leafBefore(node).getParent();
+        }
+
+        // there is nothing left deeper in the tree, we backtrack
+        while (isBeforeParent(node)) {
+            node = node.getParent();
+        }
+        return node.getParent();
+
+    }
+
+    /** Find the leaf node just before an internal node.
+     * @param node internal node at which the sub-tree starts
+     * @return leaf node just before the internal node
+     */
+    private BSPTree<Euclidean1D> leafBefore(BSPTree<Euclidean1D> node) {
+
+        node = childBefore(node);
+        while (node.getCut() != null) {
+            node = childAfter(node);
+        }
+
+        return node;
+
+    }
+
+    /** Find the leaf node just after an internal node.
+     * @param node internal node at which the sub-tree starts
+     * @return leaf node just after the internal node
+     */
+    private BSPTree<Euclidean1D> leafAfter(BSPTree<Euclidean1D> node) {
+
+        node = childAfter(node);
+        while (node.getCut() != null) {
+            node = childBefore(node);
+        }
+
+        return node;
+
+    }
+
+    /** Check if a node is the child before its parent in ascending order.
+     * @param node child node considered
+     * @return true is the node has a parent end is before it in ascending order
+     */
+    private boolean isBeforeParent(final BSPTree<Euclidean1D> node) {
+        final BSPTree<Euclidean1D> parent = node.getParent();
+        if (parent == null) {
+            return false;
+        } else {
+            return node == childBefore(parent);
+        }
+    }
+
+    /** Check if a node is the child after its parent in ascending order.
+     * @param node child node considered
+     * @return true is the node has a parent end is after it in ascending order
+     */
+    private boolean isAfterParent(final BSPTree<Euclidean1D> node) {
+        final BSPTree<Euclidean1D> parent = node.getParent();
+        if (parent == null) {
+            return false;
+        } else {
+            return node == childAfter(parent);
+        }
+    }
+
+    /** Find the child node just before an internal node.
+     * @param node internal node at which the sub-tree starts
+     * @return child node just before the internal node
+     */
+    private BSPTree<Euclidean1D> childBefore(BSPTree<Euclidean1D> node) {
+        if (isDirect(node)) {
+            // smaller abscissas are on minus side, larger abscissas are on plus side
+            return node.getMinus();
+        } else {
+            // smaller abscissas are on plus side, larger abscissas are on minus side
+            return node.getPlus();
+        }
+    }
+
+    /** Find the child node just after an internal node.
+     * @param node internal node at which the sub-tree starts
+     * @return child node just after the internal node
+     */
+    private BSPTree<Euclidean1D> childAfter(BSPTree<Euclidean1D> node) {
+        if (isDirect(node)) {
+            // smaller abscissas are on minus side, larger abscissas are on plus side
+            return node.getPlus();
+        } else {
+            // smaller abscissas are on plus side, larger abscissas are on minus side
+            return node.getMinus();
+        }
+    }
+
+    /** Check if an internal node has a direct oriented point.
+     * @param node internal node to check
+     * @return true if the oriented point is direct
+     */
+    private boolean isDirect(final BSPTree<Euclidean1D> node) {
+        return ((OrientedPoint) node.getCut().getHyperplane()).isDirect();
+    }
+
+    /** Get the abscissa of an internal node.
+     * @param node internal node to check
+     * @return abscissa
+     */
+    private double getAngle(final BSPTree<Euclidean1D> node) {
+        return ((OrientedPoint) node.getCut().getHyperplane()).getLocation().getX();
+    }
+
+    /** {@inheritDoc}
+     * <p>
+     * The iterator returns the limit values of sub-intervals in ascending order.
+     * </p>
+     * <p>
+     * The iterator does <em>not</em> support the optional {@code remove} operation.
+     * </p>
+     * @since 3.3
+     */
+    public Iterator<double[]> iterator() {
+        return new SubIntervalsIterator();
+    }
+
+    /** Local iterator for sub-intervals. */
+    private class SubIntervalsIterator implements Iterator<double[]> {
+
+        /** Current node. */
+        private BSPTree<Euclidean1D> current;
+
+        /** Sub-interval no yet returned. */
+        private double[] pending;
+
+        /** Simple constructor.
+         */
+        SubIntervalsIterator() {
+
+            current = getFirstIntervalBoundary();
+
+            if (current == null) {
+                // all the leaf tree nodes share the same inside/outside status
+                if ((Boolean) getFirstLeaf(getTree(false)).getAttribute()) {
+                    // it is an inside node, it represents the full real line
+                    pending = new double[] {
+                        Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY
+                    };
+                } else {
+                    pending = null;
+                }
+            } else if (isIntervalEnd(current)) {
+                // the first boundary is an interval end,
+                // so the first interval starts at infinity
+                pending = new double[] {
+                    Double.NEGATIVE_INFINITY, getAngle(current)
+                };
+            } else {
+                selectPending();
+            }
+        }
+
+        /** Walk the tree to select the pending sub-interval.
+         */
+        private void selectPending() {
+
+            // look for the start of the interval
+            BSPTree<Euclidean1D> start = current;
+            while (start != null && !isIntervalStart(start)) {
+                start = nextInternalNode(start);
+            }
+
+            if (start == null) {
+                // we have exhausted the iterator
+                current = null;
+                pending = null;
+                return;
+            }
+
+            // look for the end of the interval
+            BSPTree<Euclidean1D> end = start;
+            while (end != null && !isIntervalEnd(end)) {
+                end = nextInternalNode(end);
+            }
+
+            if (end != null) {
+
+                // we have identified the interval
+                pending = new double[] {
+                    getAngle(start), getAngle(end)
+                };
+
+                // prepare search for next interval
+                current = end;
+
+            } else {
+
+                // the final interval is open toward infinity
+                pending = new double[] {
+                    getAngle(start), Double.POSITIVE_INFINITY
+                };
+
+                // there won't be any other intervals
+                current = null;
+
+            }
+
+        }
+
+        /** {@inheritDoc} */
+        public boolean hasNext() {
+            return pending != null;
+        }
+
+        /** {@inheritDoc} */
+        public double[] next() {
+            if (pending == null) {
+                throw new NoSuchElementException();
+            }
+            final double[] next = pending;
+            selectPending();
+            return next;
+        }
+
+        /** {@inheritDoc} */
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/oned/OrientedPoint.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/oned/OrientedPoint.java
new file mode 100644
index 0000000..512bf5d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/oned/OrientedPoint.java
@@ -0,0 +1,153 @@
+/*
+ * 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.math3.geometry.euclidean.oned;
+
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.Vector;
+import org.apache.commons.math3.geometry.partitioning.Hyperplane;
+
+/** This class represents a 1D oriented hyperplane.
+ * <p>An hyperplane in 1D is a simple point, its orientation being a
+ * boolean.</p>
+ * <p>Instances of this class are guaranteed to be immutable.</p>
+ * @since 3.0
+ */
+public class OrientedPoint implements Hyperplane<Euclidean1D> {
+
+    /** Default value for tolerance. */
+    private static final double DEFAULT_TOLERANCE = 1.0e-10;
+
+    /** Vector location. */
+    private Vector1D location;
+
+    /** Orientation. */
+    private boolean direct;
+
+    /** Tolerance below which points are considered to belong to the hyperplane. */
+    private final double tolerance;
+
+    /** Simple constructor.
+     * @param location location of the hyperplane
+     * @param direct if true, the plus side of the hyperplane is towards
+     * abscissas greater than {@code location}
+     * @param tolerance tolerance below which points are considered to belong to the hyperplane
+     * @since 3.3
+     */
+    public OrientedPoint(final Vector1D location, final boolean direct, final double tolerance) {
+        this.location  = location;
+        this.direct    = direct;
+        this.tolerance = tolerance;
+    }
+
+    /** Simple constructor.
+     * @param location location of the hyperplane
+     * @param direct if true, the plus side of the hyperplane is towards
+     * abscissas greater than {@code location}
+     * @deprecated as of 3.3, replaced with {@link #OrientedPoint(Vector1D, boolean, double)}
+     */
+    @Deprecated
+    public OrientedPoint(final Vector1D location, final boolean direct) {
+        this(location, direct, DEFAULT_TOLERANCE);
+    }
+
+    /** Copy the instance.
+     * <p>Since instances are immutable, this method directly returns
+     * the instance.</p>
+     * @return the instance itself
+     */
+    public OrientedPoint copySelf() {
+        return this;
+    }
+
+    /** Get the offset (oriented distance) of a vector.
+     * @param vector vector to check
+     * @return offset of the vector
+     */
+    public double getOffset(Vector<Euclidean1D> vector) {
+        return getOffset((Point<Euclidean1D>) vector);
+    }
+
+    /** {@inheritDoc} */
+    public double getOffset(final Point<Euclidean1D> point) {
+        final double delta = ((Vector1D) point).getX() - location.getX();
+        return direct ? delta : -delta;
+    }
+
+    /** Build a region covering the whole hyperplane.
+     * <p>Since this class represent zero dimension spaces which does
+     * not have lower dimension sub-spaces, this method returns a dummy
+     * implementation of a {@link
+     * org.apache.commons.math3.geometry.partitioning.SubHyperplane SubHyperplane}.
+     * This implementation is only used to allow the {@link
+     * org.apache.commons.math3.geometry.partitioning.SubHyperplane
+     * SubHyperplane} class implementation to work properly, it should
+     * <em>not</em> be used otherwise.</p>
+     * @return a dummy sub hyperplane
+     */
+    public SubOrientedPoint wholeHyperplane() {
+        return new SubOrientedPoint(this, null);
+    }
+
+    /** Build a region covering the whole space.
+     * @return a region containing the instance (really an {@link
+     * IntervalsSet IntervalsSet} instance)
+     */
+    public IntervalsSet wholeSpace() {
+        return new IntervalsSet(tolerance);
+    }
+
+    /** {@inheritDoc} */
+    public boolean sameOrientationAs(final Hyperplane<Euclidean1D> other) {
+        return !(direct ^ ((OrientedPoint) other).direct);
+    }
+
+    /** {@inheritDoc}
+     * @since 3.3
+     */
+    public Point<Euclidean1D> project(Point<Euclidean1D> point) {
+        return location;
+    }
+
+    /** {@inheritDoc}
+     * @since 3.3
+     */
+    public double getTolerance() {
+        return tolerance;
+    }
+
+    /** Get the hyperplane location on the real line.
+     * @return the hyperplane location
+     */
+    public Vector1D getLocation() {
+        return location;
+    }
+
+    /** Check if the hyperplane orientation is direct.
+     * @return true if the plus side of the hyperplane is towards
+     * abscissae greater than hyperplane location
+     */
+    public boolean isDirect() {
+        return direct;
+    }
+
+    /** Revert the instance.
+     */
+    public void revertSelf() {
+        direct = !direct;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/oned/SubOrientedPoint.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/oned/SubOrientedPoint.java
new file mode 100644
index 0000000..a0288bb
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/oned/SubOrientedPoint.java
@@ -0,0 +1,72 @@
+/*
+ * 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.math3.geometry.euclidean.oned;
+
+import org.apache.commons.math3.geometry.partitioning.AbstractSubHyperplane;
+import org.apache.commons.math3.geometry.partitioning.Hyperplane;
+import org.apache.commons.math3.geometry.partitioning.Region;
+
+/** This class represents sub-hyperplane for {@link OrientedPoint}.
+ * <p>An hyperplane in 1D is a simple point, its orientation being a
+ * boolean.</p>
+ * <p>Instances of this class are guaranteed to be immutable.</p>
+ * @since 3.0
+ */
+public class SubOrientedPoint extends AbstractSubHyperplane<Euclidean1D, Euclidean1D> {
+
+    /** Simple constructor.
+     * @param hyperplane underlying hyperplane
+     * @param remainingRegion remaining region of the hyperplane
+     */
+    public SubOrientedPoint(final Hyperplane<Euclidean1D> hyperplane,
+                            final Region<Euclidean1D> remainingRegion) {
+        super(hyperplane, remainingRegion);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getSize() {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isEmpty() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected AbstractSubHyperplane<Euclidean1D, Euclidean1D> buildNew(final Hyperplane<Euclidean1D> hyperplane,
+                                                                       final Region<Euclidean1D> remainingRegion) {
+        return new SubOrientedPoint(hyperplane, remainingRegion);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public SplitSubHyperplane<Euclidean1D> split(final Hyperplane<Euclidean1D> hyperplane) {
+        final double global = hyperplane.getOffset(((OrientedPoint) getHyperplane()).getLocation());
+        if (global < -1.0e-10) {
+            return new SplitSubHyperplane<Euclidean1D>(null, this);
+        } else if (global > 1.0e-10) {
+            return new SplitSubHyperplane<Euclidean1D>(this, null);
+        } else {
+            return new SplitSubHyperplane<Euclidean1D>(null, null);
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/oned/Vector1D.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/oned/Vector1D.java
new file mode 100644
index 0000000..1ec7a4e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/oned/Vector1D.java
@@ -0,0 +1,356 @@
+/*
+ * 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.math3.geometry.euclidean.oned;
+
+import java.text.NumberFormat;
+
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.Space;
+import org.apache.commons.math3.geometry.Vector;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+
+/** This class represents a 1D vector.
+ * <p>Instances of this class are guaranteed to be immutable.</p>
+ * @since 3.0
+ */
+public class Vector1D implements Vector<Euclidean1D> {
+
+    /** Origin (coordinates: 0). */
+    public static final Vector1D ZERO = new Vector1D(0.0);
+
+    /** Unit (coordinates: 1). */
+    public static final Vector1D ONE  = new Vector1D(1.0);
+
+    // CHECKSTYLE: stop ConstantName
+    /** A vector with all coordinates set to NaN. */
+    public static final Vector1D NaN = new Vector1D(Double.NaN);
+    // CHECKSTYLE: resume ConstantName
+
+    /** A vector with all coordinates set to positive infinity. */
+    public static final Vector1D POSITIVE_INFINITY =
+        new Vector1D(Double.POSITIVE_INFINITY);
+
+    /** A vector with all coordinates set to negative infinity. */
+    public static final Vector1D NEGATIVE_INFINITY =
+        new Vector1D(Double.NEGATIVE_INFINITY);
+
+    /** Serializable UID. */
+    private static final long serialVersionUID = 7556674948671647925L;
+
+    /** Abscissa. */
+    private final double x;
+
+    /** Simple constructor.
+     * Build a vector from its coordinates
+     * @param x abscissa
+     * @see #getX()
+     */
+    public Vector1D(double x) {
+        this.x = x;
+    }
+
+    /** Multiplicative constructor
+     * Build a vector from another one and a scale factor.
+     * The vector built will be a * u
+     * @param a scale factor
+     * @param u base (unscaled) vector
+     */
+    public Vector1D(double a, Vector1D u) {
+        this.x = a * u.x;
+    }
+
+    /** Linear constructor
+     * Build a vector from two other ones and corresponding scale factors.
+     * The vector built will be a1 * u1 + a2 * u2
+     * @param a1 first scale factor
+     * @param u1 first base (unscaled) vector
+     * @param a2 second scale factor
+     * @param u2 second base (unscaled) vector
+     */
+    public Vector1D(double a1, Vector1D u1, double a2, Vector1D u2) {
+        this.x = a1 * u1.x + a2 * u2.x;
+    }
+
+    /** Linear constructor
+     * Build a vector from three other ones and corresponding scale factors.
+     * The vector built will be a1 * u1 + a2 * u2 + a3 * u3
+     * @param a1 first scale factor
+     * @param u1 first base (unscaled) vector
+     * @param a2 second scale factor
+     * @param u2 second base (unscaled) vector
+     * @param a3 third scale factor
+     * @param u3 third base (unscaled) vector
+     */
+    public Vector1D(double a1, Vector1D u1, double a2, Vector1D u2,
+                   double a3, Vector1D u3) {
+        this.x = a1 * u1.x + a2 * u2.x + a3 * u3.x;
+    }
+
+    /** Linear constructor
+     * Build a vector from four other ones and corresponding scale factors.
+     * The vector built will be a1 * u1 + a2 * u2 + a3 * u3 + a4 * u4
+     * @param a1 first scale factor
+     * @param u1 first base (unscaled) vector
+     * @param a2 second scale factor
+     * @param u2 second base (unscaled) vector
+     * @param a3 third scale factor
+     * @param u3 third base (unscaled) vector
+     * @param a4 fourth scale factor
+     * @param u4 fourth base (unscaled) vector
+     */
+    public Vector1D(double a1, Vector1D u1, double a2, Vector1D u2,
+                   double a3, Vector1D u3, double a4, Vector1D u4) {
+        this.x = a1 * u1.x + a2 * u2.x + a3 * u3.x + a4 * u4.x;
+    }
+
+    /** Get the abscissa of the vector.
+     * @return abscissa of the vector
+     * @see #Vector1D(double)
+     */
+    public double getX() {
+        return x;
+    }
+
+    /** {@inheritDoc} */
+    public Space getSpace() {
+        return Euclidean1D.getInstance();
+    }
+
+    /** {@inheritDoc} */
+    public Vector1D getZero() {
+        return ZERO;
+    }
+
+    /** {@inheritDoc} */
+    public double getNorm1() {
+        return FastMath.abs(x);
+    }
+
+    /** {@inheritDoc} */
+    public double getNorm() {
+        return FastMath.abs(x);
+    }
+
+    /** {@inheritDoc} */
+    public double getNormSq() {
+        return x * x;
+    }
+
+    /** {@inheritDoc} */
+    public double getNormInf() {
+        return FastMath.abs(x);
+    }
+
+    /** {@inheritDoc} */
+    public Vector1D add(Vector<Euclidean1D> v) {
+        Vector1D v1 = (Vector1D) v;
+        return new Vector1D(x + v1.getX());
+    }
+
+    /** {@inheritDoc} */
+    public Vector1D add(double factor, Vector<Euclidean1D> v) {
+        Vector1D v1 = (Vector1D) v;
+        return new Vector1D(x + factor * v1.getX());
+    }
+
+    /** {@inheritDoc} */
+    public Vector1D subtract(Vector<Euclidean1D> p) {
+        Vector1D p3 = (Vector1D) p;
+        return new Vector1D(x - p3.x);
+    }
+
+    /** {@inheritDoc} */
+    public Vector1D subtract(double factor, Vector<Euclidean1D> v) {
+        Vector1D v1 = (Vector1D) v;
+        return new Vector1D(x - factor * v1.getX());
+    }
+
+    /** {@inheritDoc} */
+    public Vector1D normalize() throws MathArithmeticException {
+        double s = getNorm();
+        if (s == 0) {
+            throw new MathArithmeticException(LocalizedFormats.CANNOT_NORMALIZE_A_ZERO_NORM_VECTOR);
+        }
+        return scalarMultiply(1 / s);
+    }
+    /** {@inheritDoc} */
+    public Vector1D negate() {
+        return new Vector1D(-x);
+    }
+
+    /** {@inheritDoc} */
+    public Vector1D scalarMultiply(double a) {
+        return new Vector1D(a * x);
+    }
+
+    /** {@inheritDoc} */
+    public boolean isNaN() {
+        return Double.isNaN(x);
+    }
+
+    /** {@inheritDoc} */
+    public boolean isInfinite() {
+        return !isNaN() && Double.isInfinite(x);
+    }
+
+    /** {@inheritDoc} */
+    public double distance1(Vector<Euclidean1D> p) {
+        Vector1D p3 = (Vector1D) p;
+        final double dx = FastMath.abs(p3.x - x);
+        return dx;
+    }
+
+    /** {@inheritDoc}
+     * @deprecated as of 3.3, replaced with {@link #distance(Point)}
+     */
+    @Deprecated
+    public double distance(Vector<Euclidean1D> p) {
+        return distance((Point<Euclidean1D>) p);
+    }
+
+    /** {@inheritDoc} */
+    public double distance(Point<Euclidean1D> p) {
+        Vector1D p3 = (Vector1D) p;
+        final double dx = p3.x - x;
+        return FastMath.abs(dx);
+    }
+
+    /** {@inheritDoc} */
+    public double distanceInf(Vector<Euclidean1D> p) {
+        Vector1D p3 = (Vector1D) p;
+        final double dx = FastMath.abs(p3.x - x);
+        return dx;
+    }
+
+    /** {@inheritDoc} */
+    public double distanceSq(Vector<Euclidean1D> p) {
+        Vector1D p3 = (Vector1D) p;
+        final double dx = p3.x - x;
+        return dx * dx;
+    }
+
+    /** {@inheritDoc} */
+    public double dotProduct(final Vector<Euclidean1D> v) {
+        final Vector1D v1 = (Vector1D) v;
+        return x * v1.x;
+    }
+
+    /** Compute the distance between two vectors according to the L<sub>2</sub> norm.
+     * <p>Calling this method is equivalent to calling:
+     * <code>p1.subtract(p2).getNorm()</code> except that no intermediate
+     * vector is built</p>
+     * @param p1 first vector
+     * @param p2 second vector
+     * @return the distance between p1 and p2 according to the L<sub>2</sub> norm
+     */
+    public static double distance(Vector1D p1, Vector1D p2) {
+        return p1.distance(p2);
+    }
+
+    /** Compute the distance between two vectors according to the L<sub>&infin;</sub> norm.
+     * <p>Calling this method is equivalent to calling:
+     * <code>p1.subtract(p2).getNormInf()</code> except that no intermediate
+     * vector is built</p>
+     * @param p1 first vector
+     * @param p2 second vector
+     * @return the distance between p1 and p2 according to the L<sub>&infin;</sub> norm
+     */
+    public static double distanceInf(Vector1D p1, Vector1D p2) {
+        return p1.distanceInf(p2);
+    }
+
+    /** Compute the square of the distance between two vectors.
+     * <p>Calling this method is equivalent to calling:
+     * <code>p1.subtract(p2).getNormSq()</code> except that no intermediate
+     * vector is built</p>
+     * @param p1 first vector
+     * @param p2 second vector
+     * @return the square of the distance between p1 and p2
+     */
+    public static double distanceSq(Vector1D p1, Vector1D p2) {
+        return p1.distanceSq(p2);
+    }
+
+    /**
+     * Test for the equality of two 1D vectors.
+     * <p>
+     * If all coordinates of two 1D vectors are exactly the same, and none are
+     * <code>Double.NaN</code>, the two 1D vectors are considered to be equal.
+     * </p>
+     * <p>
+     * <code>NaN</code> coordinates are considered to affect globally the vector
+     * and be equals to each other - i.e, if either (or all) coordinates of the
+     * 1D vector are equal to <code>Double.NaN</code>, the 1D vector is equal to
+     * {@link #NaN}.
+     * </p>
+     *
+     * @param other Object to test for equality to this
+     * @return true if two 1D vector objects are equal, false if
+     *         object is null, not an instance of Vector1D, or
+     *         not equal to this Vector1D instance
+     *
+     */
+    @Override
+    public boolean equals(Object other) {
+
+        if (this == other) {
+            return true;
+        }
+
+        if (other instanceof Vector1D) {
+            final Vector1D rhs = (Vector1D)other;
+            if (rhs.isNaN()) {
+                return this.isNaN();
+            }
+
+            return x == rhs.x;
+        }
+        return false;
+    }
+
+    /**
+     * Get a hashCode for the 1D vector.
+     * <p>
+     * All NaN values have the same hash code.</p>
+     *
+     * @return a hash code value for this object
+     */
+    @Override
+    public int hashCode() {
+        if (isNaN()) {
+            return 7785;
+        }
+        return 997 * MathUtils.hash(x);
+    }
+
+    /** Get a string representation of this vector.
+     * @return a string representation of this vector
+     */
+    @Override
+    public String toString() {
+        return Vector1DFormat.getInstance().format(this);
+    }
+
+    /** {@inheritDoc} */
+    public String toString(final NumberFormat format) {
+        return new Vector1DFormat(format).format(this);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/oned/Vector1DFormat.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/oned/Vector1DFormat.java
new file mode 100644
index 0000000..27f1905
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/oned/Vector1DFormat.java
@@ -0,0 +1,135 @@
+/*
+ * 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.math3.geometry.euclidean.oned;
+
+import java.text.FieldPosition;
+import java.text.NumberFormat;
+import java.text.ParsePosition;
+import java.util.Locale;
+
+import org.apache.commons.math3.exception.MathParseException;
+import org.apache.commons.math3.geometry.Vector;
+import org.apache.commons.math3.geometry.VectorFormat;
+import org.apache.commons.math3.util.CompositeFormat;
+
+/**
+ * Formats a 1D vector in components list format "{x}".
+ * <p>The prefix and suffix "{" and "}" can be replaced by
+ * any user-defined strings. The number format for components can be configured.</p>
+ * <p>White space is ignored at parse time, even if it is in the prefix, suffix
+ * or separator specifications. So even if the default separator does include a space
+ * character that is used at format time, both input string "{1}" and
+ * " { 1 } " will be parsed without error and the same vector will be
+ * returned. In the second case, however, the parse position after parsing will be
+ * just after the closing curly brace, i.e. just before the trailing space.</p>
+ * <p><b>Note:</b> using "," as a separator may interfere with the grouping separator
+ * of the default {@link NumberFormat} for the current locale. Thus it is advised
+ * to use a {@link NumberFormat} instance with disabled grouping in such a case.</p>
+ *
+ * @since 3.0
+ */
+public class Vector1DFormat extends VectorFormat<Euclidean1D> {
+
+    /**
+     * Create an instance with default settings.
+     * <p>The instance uses the default prefix, suffix and separator:
+     * "{", "}", and "; " and the default number format for components.</p>
+     */
+    public Vector1DFormat() {
+        super(DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_SEPARATOR,
+              CompositeFormat.getDefaultNumberFormat());
+    }
+
+    /**
+     * Create an instance with a custom number format for components.
+     * @param format the custom format for components.
+     */
+    public Vector1DFormat(final NumberFormat format) {
+        super(DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_SEPARATOR, format);
+    }
+
+    /**
+     * Create an instance with custom prefix, suffix and separator.
+     * @param prefix prefix to use instead of the default "{"
+     * @param suffix suffix to use instead of the default "}"
+     */
+    public Vector1DFormat(final String prefix, final String suffix) {
+        super(prefix, suffix, DEFAULT_SEPARATOR, CompositeFormat.getDefaultNumberFormat());
+    }
+
+    /**
+     * Create an instance with custom prefix, suffix, separator and format
+     * for components.
+     * @param prefix prefix to use instead of the default "{"
+     * @param suffix suffix to use instead of the default "}"
+     * @param format the custom format for components.
+     */
+    public Vector1DFormat(final String prefix, final String suffix,
+                         final NumberFormat format) {
+        super(prefix, suffix, DEFAULT_SEPARATOR, format);
+    }
+
+    /**
+     * Returns the default 1D vector format for the current locale.
+     * @return the default 1D vector format.
+     */
+    public static Vector1DFormat getInstance() {
+        return getInstance(Locale.getDefault());
+    }
+
+    /**
+     * Returns the default 1D vector format for the given locale.
+     * @param locale the specific locale used by the format.
+     * @return the 1D vector format specific to the given locale.
+     */
+    public static Vector1DFormat getInstance(final Locale locale) {
+        return new Vector1DFormat(CompositeFormat.getDefaultNumberFormat(locale));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public StringBuffer format(final Vector<Euclidean1D> vector, final StringBuffer toAppendTo,
+                               final FieldPosition pos) {
+        final Vector1D p1 = (Vector1D) vector;
+        return format(toAppendTo, pos, p1.getX());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Vector1D parse(final String source) throws MathParseException {
+        ParsePosition parsePosition = new ParsePosition(0);
+        Vector1D result = parse(source, parsePosition);
+        if (parsePosition.getIndex() == 0) {
+            throw new MathParseException(source,
+                                         parsePosition.getErrorIndex(),
+                                         Vector1D.class);
+        }
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Vector1D parse(final String source, final ParsePosition pos) {
+        final double[] coordinates = parseCoordinates(1, source, pos);
+        if (coordinates == null) {
+            return null;
+        }
+        return new Vector1D(coordinates[0]);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/oned/package-info.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/oned/package-info.java
new file mode 100644
index 0000000..0fa3788
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/oned/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * <p>
+ * This package provides basic 1D geometry components.
+ * </p>
+ *
+ */
+package org.apache.commons.math3.geometry.euclidean.oned;
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/CardanEulerSingularityException.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/CardanEulerSingularityException.java
new file mode 100644
index 0000000..728074d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/CardanEulerSingularityException.java
@@ -0,0 +1,44 @@
+/*
+ * 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.math3.geometry.euclidean.threed;
+
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/** This class represents exceptions thrown while extractiong Cardan
+ * or Euler angles from a rotation.
+
+ * @since 1.2
+ */
+public class CardanEulerSingularityException
+  extends MathIllegalStateException {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -1360952845582206770L;
+
+    /**
+     * Simple constructor.
+     * build an exception with a default message.
+     * @param isCardan if true, the rotation is related to Cardan angles,
+     * if false it is related to EulerAngles
+     */
+    public CardanEulerSingularityException(boolean isCardan) {
+        super(isCardan ? LocalizedFormats.CARDAN_ANGLES_SINGULARITY : LocalizedFormats.EULER_ANGLES_SINGULARITY);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/Euclidean3D.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/Euclidean3D.java
new file mode 100644
index 0000000..dc06936
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/Euclidean3D.java
@@ -0,0 +1,74 @@
+/*
+ * 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.math3.geometry.euclidean.threed;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.geometry.Space;
+import org.apache.commons.math3.geometry.euclidean.twod.Euclidean2D;
+
+/**
+ * This class implements a three-dimensional space.
+ * @since 3.0
+ */
+public class Euclidean3D implements Serializable, Space {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 6249091865814886817L;
+
+    /** Private constructor for the singleton.
+     */
+    private Euclidean3D() {
+    }
+
+    /** Get the unique instance.
+     * @return the unique instance
+     */
+    public static Euclidean3D getInstance() {
+        return LazyHolder.INSTANCE;
+    }
+
+    /** {@inheritDoc} */
+    public int getDimension() {
+        return 3;
+    }
+
+    /** {@inheritDoc} */
+    public Euclidean2D getSubSpace() {
+        return Euclidean2D.getInstance();
+    }
+
+    // CHECKSTYLE: stop HideUtilityClassConstructor
+    /** Holder for the instance.
+     * <p>We use here the Initialization On Demand Holder Idiom.</p>
+     */
+    private static class LazyHolder {
+        /** Cached field instance. */
+        private static final Euclidean3D INSTANCE = new Euclidean3D();
+    }
+    // CHECKSTYLE: resume HideUtilityClassConstructor
+
+    /** Handle deserialization of the singleton.
+     * @return the singleton instance
+     */
+    private Object readResolve() {
+        // return the singleton instance
+        return LazyHolder.INSTANCE;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/FieldRotation.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/FieldRotation.java
new file mode 100644
index 0000000..4e2278b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/FieldRotation.java
@@ -0,0 +1,1663 @@
+/*
+ * 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.math3.geometry.euclidean.threed;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * This class is a re-implementation of {@link Rotation} using {@link RealFieldElement}.
+ * <p>Instance of this class are guaranteed to be immutable.</p>
+ *
+ * @param <T> the type of the field elements
+ * @see FieldVector3D
+ * @see RotationOrder
+ * @since 3.2
+ */
+
+public class FieldRotation<T extends RealFieldElement<T>> implements Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 20130224l;
+
+    /** Scalar coordinate of the quaternion. */
+    private final T q0;
+
+    /** First coordinate of the vectorial part of the quaternion. */
+    private final T q1;
+
+    /** Second coordinate of the vectorial part of the quaternion. */
+    private final T q2;
+
+    /** Third coordinate of the vectorial part of the quaternion. */
+    private final T q3;
+
+    /** Build a rotation from the quaternion coordinates.
+     * <p>A rotation can be built from a <em>normalized</em> quaternion,
+     * i.e. a quaternion for which q<sub>0</sub><sup>2</sup> +
+     * q<sub>1</sub><sup>2</sup> + q<sub>2</sub><sup>2</sup> +
+     * q<sub>3</sub><sup>2</sup> = 1. If the quaternion is not normalized,
+     * the constructor can normalize it in a preprocessing step.</p>
+     * <p>Note that some conventions put the scalar part of the quaternion
+     * as the 4<sup>th</sup> component and the vector part as the first three
+     * components. This is <em>not</em> our convention. We put the scalar part
+     * as the first component.</p>
+     * @param q0 scalar part of the quaternion
+     * @param q1 first coordinate of the vectorial part of the quaternion
+     * @param q2 second coordinate of the vectorial part of the quaternion
+     * @param q3 third coordinate of the vectorial part of the quaternion
+     * @param needsNormalization if true, the coordinates are considered
+     * not to be normalized, a normalization preprocessing step is performed
+     * before using them
+     */
+    public FieldRotation(final T q0, final T q1, final T q2, final T q3, final boolean needsNormalization) {
+
+        if (needsNormalization) {
+            // normalization preprocessing
+            final T inv =
+                    q0.multiply(q0).add(q1.multiply(q1)).add(q2.multiply(q2)).add(q3.multiply(q3)).sqrt().reciprocal();
+            this.q0 = inv.multiply(q0);
+            this.q1 = inv.multiply(q1);
+            this.q2 = inv.multiply(q2);
+            this.q3 = inv.multiply(q3);
+        } else {
+            this.q0 = q0;
+            this.q1 = q1;
+            this.q2 = q2;
+            this.q3 = q3;
+        }
+
+    }
+
+    /** Build a rotation from an axis and an angle.
+     * <p>We use the convention that angles are oriented according to
+     * the effect of the rotation on vectors around the axis. That means
+     * that if (i, j, k) is a direct frame and if we first provide +k as
+     * the axis and &pi;/2 as the angle to this constructor, and then
+     * {@link #applyTo(FieldVector3D) apply} the instance to +i, we will get
+     * +j.</p>
+     * <p>Another way to represent our convention is to say that a rotation
+     * of angle &theta; about the unit vector (x, y, z) is the same as the
+     * rotation build from quaternion components { cos(-&theta;/2),
+     * x * sin(-&theta;/2), y * sin(-&theta;/2), z * sin(-&theta;/2) }.
+     * Note the minus sign on the angle!</p>
+     * <p>On the one hand this convention is consistent with a vectorial
+     * perspective (moving vectors in fixed frames), on the other hand it
+     * is different from conventions with a frame perspective (fixed vectors
+     * viewed from different frames) like the ones used for example in spacecraft
+     * attitude community or in the graphics community.</p>
+     * @param axis axis around which to rotate
+     * @param angle rotation angle.
+     * @exception MathIllegalArgumentException if the axis norm is zero
+     * @deprecated as of 3.6, replaced with {@link
+     * #FieldRotation(FieldVector3D, RealFieldElement, RotationConvention)}
+     */
+    @Deprecated
+    public FieldRotation(final FieldVector3D<T> axis, final T angle)
+        throws MathIllegalArgumentException {
+        this(axis, angle, RotationConvention.VECTOR_OPERATOR);
+    }
+
+    /** Build a rotation from an axis and an angle.
+     * <p>We use the convention that angles are oriented according to
+     * the effect of the rotation on vectors around the axis. That means
+     * that if (i, j, k) is a direct frame and if we first provide +k as
+     * the axis and &pi;/2 as the angle to this constructor, and then
+     * {@link #applyTo(FieldVector3D) apply} the instance to +i, we will get
+     * +j.</p>
+     * <p>Another way to represent our convention is to say that a rotation
+     * of angle &theta; about the unit vector (x, y, z) is the same as the
+     * rotation build from quaternion components { cos(-&theta;/2),
+     * x * sin(-&theta;/2), y * sin(-&theta;/2), z * sin(-&theta;/2) }.
+     * Note the minus sign on the angle!</p>
+     * <p>On the one hand this convention is consistent with a vectorial
+     * perspective (moving vectors in fixed frames), on the other hand it
+     * is different from conventions with a frame perspective (fixed vectors
+     * viewed from different frames) like the ones used for example in spacecraft
+     * attitude community or in the graphics community.</p>
+     * @param axis axis around which to rotate
+     * @param angle rotation angle.
+     * @param convention convention to use for the semantics of the angle
+     * @exception MathIllegalArgumentException if the axis norm is zero
+     * @since 3.6
+     */
+    public FieldRotation(final FieldVector3D<T> axis, final T angle, final RotationConvention convention)
+        throws MathIllegalArgumentException {
+
+        final T norm = axis.getNorm();
+        if (norm.getReal() == 0) {
+            throw new MathIllegalArgumentException(LocalizedFormats.ZERO_NORM_FOR_ROTATION_AXIS);
+        }
+
+        final T halfAngle = angle.multiply(convention == RotationConvention.VECTOR_OPERATOR ? -0.5 : 0.5);
+        final T coeff = halfAngle.sin().divide(norm);
+
+        q0 = halfAngle.cos();
+        q1 = coeff.multiply(axis.getX());
+        q2 = coeff.multiply(axis.getY());
+        q3 = coeff.multiply(axis.getZ());
+
+    }
+
+    /** Build a rotation from a 3X3 matrix.
+
+     * <p>Rotation matrices are orthogonal matrices, i.e. unit matrices
+     * (which are matrices for which m.m<sup>T</sup> = I) with real
+     * coefficients. The module of the determinant of unit matrices is
+     * 1, among the orthogonal 3X3 matrices, only the ones having a
+     * positive determinant (+1) are rotation matrices.</p>
+
+     * <p>When a rotation is defined by a matrix with truncated values
+     * (typically when it is extracted from a technical sheet where only
+     * four to five significant digits are available), the matrix is not
+     * orthogonal anymore. This constructor handles this case
+     * transparently by using a copy of the given matrix and applying a
+     * correction to the copy in order to perfect its orthogonality. If
+     * the Frobenius norm of the correction needed is above the given
+     * threshold, then the matrix is considered to be too far from a
+     * true rotation matrix and an exception is thrown.<p>
+
+     * @param m rotation matrix
+     * @param threshold convergence threshold for the iterative
+     * orthogonality correction (convergence is reached when the
+     * difference between two steps of the Frobenius norm of the
+     * correction is below this threshold)
+
+     * @exception NotARotationMatrixException if the matrix is not a 3X3
+     * matrix, or if it cannot be transformed into an orthogonal matrix
+     * with the given threshold, or if the determinant of the resulting
+     * orthogonal matrix is negative
+
+     */
+    public FieldRotation(final T[][] m, final double threshold)
+        throws NotARotationMatrixException {
+
+        // dimension check
+        if ((m.length != 3) || (m[0].length != 3) ||
+                (m[1].length != 3) || (m[2].length != 3)) {
+            throw new NotARotationMatrixException(
+                                                  LocalizedFormats.ROTATION_MATRIX_DIMENSIONS,
+                                                  m.length, m[0].length);
+        }
+
+        // compute a "close" orthogonal matrix
+        final T[][] ort = orthogonalizeMatrix(m, threshold);
+
+        // check the sign of the determinant
+        final T d0 = ort[1][1].multiply(ort[2][2]).subtract(ort[2][1].multiply(ort[1][2]));
+        final T d1 = ort[0][1].multiply(ort[2][2]).subtract(ort[2][1].multiply(ort[0][2]));
+        final T d2 = ort[0][1].multiply(ort[1][2]).subtract(ort[1][1].multiply(ort[0][2]));
+        final T det =
+                ort[0][0].multiply(d0).subtract(ort[1][0].multiply(d1)).add(ort[2][0].multiply(d2));
+        if (det.getReal() < 0.0) {
+            throw new NotARotationMatrixException(
+                                                  LocalizedFormats.CLOSEST_ORTHOGONAL_MATRIX_HAS_NEGATIVE_DETERMINANT,
+                                                  det);
+        }
+
+        final T[] quat = mat2quat(ort);
+        q0 = quat[0];
+        q1 = quat[1];
+        q2 = quat[2];
+        q3 = quat[3];
+
+    }
+
+    /** Build the rotation that transforms a pair of vectors into another pair.
+
+     * <p>Except for possible scale factors, if the instance were applied to
+     * the pair (u<sub>1</sub>, u<sub>2</sub>) it will produce the pair
+     * (v<sub>1</sub>, v<sub>2</sub>).</p>
+
+     * <p>If the angular separation between u<sub>1</sub> and u<sub>2</sub> is
+     * not the same as the angular separation between v<sub>1</sub> and
+     * v<sub>2</sub>, then a corrected v'<sub>2</sub> will be used rather than
+     * v<sub>2</sub>, the corrected vector will be in the (&pm;v<sub>1</sub>,
+     * +v<sub>2</sub>) half-plane.</p>
+
+     * @param u1 first vector of the origin pair
+     * @param u2 second vector of the origin pair
+     * @param v1 desired image of u1 by the rotation
+     * @param v2 desired image of u2 by the rotation
+     * @exception MathArithmeticException if the norm of one of the vectors is zero,
+     * or if one of the pair is degenerated (i.e. the vectors of the pair are collinear)
+     */
+    public FieldRotation(FieldVector3D<T> u1, FieldVector3D<T> u2, FieldVector3D<T> v1, FieldVector3D<T> v2)
+        throws MathArithmeticException {
+
+        // build orthonormalized base from u1, u2
+        // this fails when vectors are null or collinear, which is forbidden to define a rotation
+        final FieldVector3D<T> u3 = FieldVector3D.crossProduct(u1, u2).normalize();
+        u2 = FieldVector3D.crossProduct(u3, u1).normalize();
+        u1 = u1.normalize();
+
+        // build an orthonormalized base from v1, v2
+        // this fails when vectors are null or collinear, which is forbidden to define a rotation
+        final FieldVector3D<T> v3 = FieldVector3D.crossProduct(v1, v2).normalize();
+        v2 = FieldVector3D.crossProduct(v3, v1).normalize();
+        v1 = v1.normalize();
+
+        // buid a matrix transforming the first base into the second one
+        final T[][] array = MathArrays.buildArray(u1.getX().getField(), 3, 3);
+        array[0][0] = u1.getX().multiply(v1.getX()).add(u2.getX().multiply(v2.getX())).add(u3.getX().multiply(v3.getX()));
+        array[0][1] = u1.getY().multiply(v1.getX()).add(u2.getY().multiply(v2.getX())).add(u3.getY().multiply(v3.getX()));
+        array[0][2] = u1.getZ().multiply(v1.getX()).add(u2.getZ().multiply(v2.getX())).add(u3.getZ().multiply(v3.getX()));
+        array[1][0] = u1.getX().multiply(v1.getY()).add(u2.getX().multiply(v2.getY())).add(u3.getX().multiply(v3.getY()));
+        array[1][1] = u1.getY().multiply(v1.getY()).add(u2.getY().multiply(v2.getY())).add(u3.getY().multiply(v3.getY()));
+        array[1][2] = u1.getZ().multiply(v1.getY()).add(u2.getZ().multiply(v2.getY())).add(u3.getZ().multiply(v3.getY()));
+        array[2][0] = u1.getX().multiply(v1.getZ()).add(u2.getX().multiply(v2.getZ())).add(u3.getX().multiply(v3.getZ()));
+        array[2][1] = u1.getY().multiply(v1.getZ()).add(u2.getY().multiply(v2.getZ())).add(u3.getY().multiply(v3.getZ()));
+        array[2][2] = u1.getZ().multiply(v1.getZ()).add(u2.getZ().multiply(v2.getZ())).add(u3.getZ().multiply(v3.getZ()));
+
+        T[] quat = mat2quat(array);
+        q0 = quat[0];
+        q1 = quat[1];
+        q2 = quat[2];
+        q3 = quat[3];
+
+    }
+
+    /** Build one of the rotations that transform one vector into another one.
+
+     * <p>Except for a possible scale factor, if the instance were
+     * applied to the vector u it will produce the vector v. There is an
+     * infinite number of such rotations, this constructor choose the
+     * one with the smallest associated angle (i.e. the one whose axis
+     * is orthogonal to the (u, v) plane). If u and v are collinear, an
+     * arbitrary rotation axis is chosen.</p>
+
+     * @param u origin vector
+     * @param v desired image of u by the rotation
+     * @exception MathArithmeticException if the norm of one of the vectors is zero
+     */
+    public FieldRotation(final FieldVector3D<T> u, final FieldVector3D<T> v) throws MathArithmeticException {
+
+        final T normProduct = u.getNorm().multiply(v.getNorm());
+        if (normProduct.getReal() == 0) {
+            throw new MathArithmeticException(LocalizedFormats.ZERO_NORM_FOR_ROTATION_DEFINING_VECTOR);
+        }
+
+        final T dot = FieldVector3D.dotProduct(u, v);
+
+        if (dot.getReal() < ((2.0e-15 - 1.0) * normProduct.getReal())) {
+            // special case u = -v: we select a PI angle rotation around
+            // an arbitrary vector orthogonal to u
+            final FieldVector3D<T> w = u.orthogonal();
+            q0 = normProduct.getField().getZero();
+            q1 = w.getX().negate();
+            q2 = w.getY().negate();
+            q3 = w.getZ().negate();
+        } else {
+            // general case: (u, v) defines a plane, we select
+            // the shortest possible rotation: axis orthogonal to this plane
+            q0 = dot.divide(normProduct).add(1.0).multiply(0.5).sqrt();
+            final T coeff = q0.multiply(normProduct).multiply(2.0).reciprocal();
+            final FieldVector3D<T> q = FieldVector3D.crossProduct(v, u);
+            q1 = coeff.multiply(q.getX());
+            q2 = coeff.multiply(q.getY());
+            q3 = coeff.multiply(q.getZ());
+        }
+
+    }
+
+    /** Build a rotation from three Cardan or Euler elementary rotations.
+
+     * <p>Cardan rotations are three successive rotations around the
+     * canonical axes X, Y and Z, each axis being used once. There are
+     * 6 such sets of rotations (XYZ, XZY, YXZ, YZX, ZXY and ZYX). Euler
+     * rotations are three successive rotations around the canonical
+     * axes X, Y and Z, the first and last rotations being around the
+     * same axis. There are 6 such sets of rotations (XYX, XZX, YXY,
+     * YZY, ZXZ and ZYZ), the most popular one being ZXZ.</p>
+     * <p>Beware that many people routinely use the term Euler angles even
+     * for what really are Cardan angles (this confusion is especially
+     * widespread in the aerospace business where Roll, Pitch and Yaw angles
+     * are often wrongly tagged as Euler angles).</p>
+
+     * @param order order of rotations to use
+     * @param alpha1 angle of the first elementary rotation
+     * @param alpha2 angle of the second elementary rotation
+     * @param alpha3 angle of the third elementary rotation
+     * @deprecated as of 3.6, replaced with {@link
+     * #FieldRotation(RotationOrder, RotationConvention,
+     * RealFieldElement, RealFieldElement, RealFieldElement)}
+     */
+    @Deprecated
+    public FieldRotation(final RotationOrder order, final T alpha1, final T alpha2, final T alpha3) {
+        this(order, RotationConvention.VECTOR_OPERATOR, alpha1, alpha2, alpha3);
+    }
+
+    /** Build a rotation from three Cardan or Euler elementary rotations.
+
+     * <p>Cardan rotations are three successive rotations around the
+     * canonical axes X, Y and Z, each axis being used once. There are
+     * 6 such sets of rotations (XYZ, XZY, YXZ, YZX, ZXY and ZYX). Euler
+     * rotations are three successive rotations around the canonical
+     * axes X, Y and Z, the first and last rotations being around the
+     * same axis. There are 6 such sets of rotations (XYX, XZX, YXY,
+     * YZY, ZXZ and ZYZ), the most popular one being ZXZ.</p>
+     * <p>Beware that many people routinely use the term Euler angles even
+     * for what really are Cardan angles (this confusion is especially
+     * widespread in the aerospace business where Roll, Pitch and Yaw angles
+     * are often wrongly tagged as Euler angles).</p>
+
+     * @param order order of rotations to compose, from left to right
+     * (i.e. we will use {@code r1.compose(r2.compose(r3, convention), convention)})
+     * @param convention convention to use for the semantics of the angle
+     * @param alpha1 angle of the first elementary rotation
+     * @param alpha2 angle of the second elementary rotation
+     * @param alpha3 angle of the third elementary rotation
+     * @since 3.6
+     */
+    public FieldRotation(final RotationOrder order, final RotationConvention convention,
+                         final T alpha1, final T alpha2, final T alpha3) {
+        final T one = alpha1.getField().getOne();
+        final FieldRotation<T> r1 = new FieldRotation<T>(new FieldVector3D<T>(one, order.getA1()), alpha1, convention);
+        final FieldRotation<T> r2 = new FieldRotation<T>(new FieldVector3D<T>(one, order.getA2()), alpha2, convention);
+        final FieldRotation<T> r3 = new FieldRotation<T>(new FieldVector3D<T>(one, order.getA3()), alpha3, convention);
+        final FieldRotation<T> composed = r1.compose(r2.compose(r3, convention), convention);
+        q0 = composed.q0;
+        q1 = composed.q1;
+        q2 = composed.q2;
+        q3 = composed.q3;
+    }
+
+    /** Convert an orthogonal rotation matrix to a quaternion.
+     * @param ort orthogonal rotation matrix
+     * @return quaternion corresponding to the matrix
+     */
+    private T[] mat2quat(final T[][] ort) {
+
+        final T[] quat = MathArrays.buildArray(ort[0][0].getField(), 4);
+
+        // There are different ways to compute the quaternions elements
+        // from the matrix. They all involve computing one element from
+        // the diagonal of the matrix, and computing the three other ones
+        // using a formula involving a division by the first element,
+        // which unfortunately can be zero. Since the norm of the
+        // quaternion is 1, we know at least one element has an absolute
+        // value greater or equal to 0.5, so it is always possible to
+        // select the right formula and avoid division by zero and even
+        // numerical inaccuracy. Checking the elements in turn and using
+        // the first one greater than 0.45 is safe (this leads to a simple
+        // test since qi = 0.45 implies 4 qi^2 - 1 = -0.19)
+        T s = ort[0][0].add(ort[1][1]).add(ort[2][2]);
+        if (s.getReal() > -0.19) {
+            // compute q0 and deduce q1, q2 and q3
+            quat[0] = s.add(1.0).sqrt().multiply(0.5);
+            T inv = quat[0].reciprocal().multiply(0.25);
+            quat[1] = inv.multiply(ort[1][2].subtract(ort[2][1]));
+            quat[2] = inv.multiply(ort[2][0].subtract(ort[0][2]));
+            quat[3] = inv.multiply(ort[0][1].subtract(ort[1][0]));
+        } else {
+            s = ort[0][0].subtract(ort[1][1]).subtract(ort[2][2]);
+            if (s.getReal() > -0.19) {
+                // compute q1 and deduce q0, q2 and q3
+                quat[1] = s.add(1.0).sqrt().multiply(0.5);
+                T inv = quat[1].reciprocal().multiply(0.25);
+                quat[0] = inv.multiply(ort[1][2].subtract(ort[2][1]));
+                quat[2] = inv.multiply(ort[0][1].add(ort[1][0]));
+                quat[3] = inv.multiply(ort[0][2].add(ort[2][0]));
+            } else {
+                s = ort[1][1].subtract(ort[0][0]).subtract(ort[2][2]);
+                if (s.getReal() > -0.19) {
+                    // compute q2 and deduce q0, q1 and q3
+                    quat[2] = s.add(1.0).sqrt().multiply(0.5);
+                    T inv = quat[2].reciprocal().multiply(0.25);
+                    quat[0] = inv.multiply(ort[2][0].subtract(ort[0][2]));
+                    quat[1] = inv.multiply(ort[0][1].add(ort[1][0]));
+                    quat[3] = inv.multiply(ort[2][1].add(ort[1][2]));
+                } else {
+                    // compute q3 and deduce q0, q1 and q2
+                    s = ort[2][2].subtract(ort[0][0]).subtract(ort[1][1]);
+                    quat[3] = s.add(1.0).sqrt().multiply(0.5);
+                    T inv = quat[3].reciprocal().multiply(0.25);
+                    quat[0] = inv.multiply(ort[0][1].subtract(ort[1][0]));
+                    quat[1] = inv.multiply(ort[0][2].add(ort[2][0]));
+                    quat[2] = inv.multiply(ort[2][1].add(ort[1][2]));
+                }
+            }
+        }
+
+        return quat;
+
+    }
+
+    /** Revert a rotation.
+     * Build a rotation which reverse the effect of another
+     * rotation. This means that if r(u) = v, then r.revert(v) = u. The
+     * instance is not changed.
+     * @return a new rotation whose effect is the reverse of the effect
+     * of the instance
+     */
+    public FieldRotation<T> revert() {
+        return new FieldRotation<T>(q0.negate(), q1, q2, q3, false);
+    }
+
+    /** Get the scalar coordinate of the quaternion.
+     * @return scalar coordinate of the quaternion
+     */
+    public T getQ0() {
+        return q0;
+    }
+
+    /** Get the first coordinate of the vectorial part of the quaternion.
+     * @return first coordinate of the vectorial part of the quaternion
+     */
+    public T getQ1() {
+        return q1;
+    }
+
+    /** Get the second coordinate of the vectorial part of the quaternion.
+     * @return second coordinate of the vectorial part of the quaternion
+     */
+    public T getQ2() {
+        return q2;
+    }
+
+    /** Get the third coordinate of the vectorial part of the quaternion.
+     * @return third coordinate of the vectorial part of the quaternion
+     */
+    public T getQ3() {
+        return q3;
+    }
+
+    /** Get the normalized axis of the rotation.
+     * @return normalized axis of the rotation
+     * @see #FieldRotation(FieldVector3D, RealFieldElement)
+     * @deprecated as of 3.6, replaced with {@link #getAxis(RotationConvention)}
+     */
+    @Deprecated
+    public FieldVector3D<T> getAxis() {
+        return getAxis(RotationConvention.VECTOR_OPERATOR);
+    }
+
+    /** Get the normalized axis of the rotation.
+     * <p>
+     * Note that as {@link #getAngle()} always returns an angle
+     * between 0 and &pi;, changing the convention changes the
+     * direction of the axis, not the sign of the angle.
+     * </p>
+     * @param convention convention to use for the semantics of the angle
+     * @return normalized axis of the rotation
+     * @see #FieldRotation(FieldVector3D, RealFieldElement)
+     * @since 3.6
+     */
+    public FieldVector3D<T> getAxis(final RotationConvention convention) {
+        final T squaredSine = q1.multiply(q1).add(q2.multiply(q2)).add(q3.multiply(q3));
+        if (squaredSine.getReal() == 0) {
+            final Field<T> field = squaredSine.getField();
+            return new FieldVector3D<T>(convention == RotationConvention.VECTOR_OPERATOR ? field.getOne(): field.getOne().negate(),
+                                        field.getZero(),
+                                        field.getZero());
+        } else {
+            final double sgn = convention == RotationConvention.VECTOR_OPERATOR ? +1 : -1;
+            if (q0.getReal() < 0) {
+                T inverse = squaredSine.sqrt().reciprocal().multiply(sgn);
+                return new FieldVector3D<T>(q1.multiply(inverse), q2.multiply(inverse), q3.multiply(inverse));
+            }
+            final T inverse = squaredSine.sqrt().reciprocal().negate().multiply(sgn);
+            return new FieldVector3D<T>(q1.multiply(inverse), q2.multiply(inverse), q3.multiply(inverse));
+        }
+    }
+
+    /** Get the angle of the rotation.
+     * @return angle of the rotation (between 0 and &pi;)
+     * @see #FieldRotation(FieldVector3D, RealFieldElement)
+     */
+    public T getAngle() {
+        if ((q0.getReal() < -0.1) || (q0.getReal() > 0.1)) {
+            return q1.multiply(q1).add(q2.multiply(q2)).add(q3.multiply(q3)).sqrt().asin().multiply(2);
+        } else if (q0.getReal() < 0) {
+            return q0.negate().acos().multiply(2);
+        }
+        return q0.acos().multiply(2);
+    }
+
+    /** Get the Cardan or Euler angles corresponding to the instance.
+
+     * <p>The equations show that each rotation can be defined by two
+     * different values of the Cardan or Euler angles set. For example
+     * if Cardan angles are used, the rotation defined by the angles
+     * a<sub>1</sub>, a<sub>2</sub> and a<sub>3</sub> is the same as
+     * the rotation defined by the angles &pi; + a<sub>1</sub>, &pi;
+     * - a<sub>2</sub> and &pi; + a<sub>3</sub>. This method implements
+     * the following arbitrary choices:</p>
+     * <ul>
+     *   <li>for Cardan angles, the chosen set is the one for which the
+     *   second angle is between -&pi;/2 and &pi;/2 (i.e its cosine is
+     *   positive),</li>
+     *   <li>for Euler angles, the chosen set is the one for which the
+     *   second angle is between 0 and &pi; (i.e its sine is positive).</li>
+     * </ul>
+
+     * <p>Cardan and Euler angle have a very disappointing drawback: all
+     * of them have singularities. This means that if the instance is
+     * too close to the singularities corresponding to the given
+     * rotation order, it will be impossible to retrieve the angles. For
+     * Cardan angles, this is often called gimbal lock. There is
+     * <em>nothing</em> to do to prevent this, it is an intrinsic problem
+     * with Cardan and Euler representation (but not a problem with the
+     * rotation itself, which is perfectly well defined). For Cardan
+     * angles, singularities occur when the second angle is close to
+     * -&pi;/2 or +&pi;/2, for Euler angle singularities occur when the
+     * second angle is close to 0 or &pi;, this implies that the identity
+     * rotation is always singular for Euler angles!</p>
+
+     * @param order rotation order to use
+     * @return an array of three angles, in the order specified by the set
+     * @exception CardanEulerSingularityException if the rotation is
+     * singular with respect to the angles set specified
+     * @deprecated as of 3.6, replaced with {@link #getAngles(RotationOrder, RotationConvention)}
+     */
+    @Deprecated
+    public T[] getAngles(final RotationOrder order)
+        throws CardanEulerSingularityException {
+        return getAngles(order, RotationConvention.VECTOR_OPERATOR);
+    }
+
+    /** Get the Cardan or Euler angles corresponding to the instance.
+
+     * <p>The equations show that each rotation can be defined by two
+     * different values of the Cardan or Euler angles set. For example
+     * if Cardan angles are used, the rotation defined by the angles
+     * a<sub>1</sub>, a<sub>2</sub> and a<sub>3</sub> is the same as
+     * the rotation defined by the angles &pi; + a<sub>1</sub>, &pi;
+     * - a<sub>2</sub> and &pi; + a<sub>3</sub>. This method implements
+     * the following arbitrary choices:</p>
+     * <ul>
+     *   <li>for Cardan angles, the chosen set is the one for which the
+     *   second angle is between -&pi;/2 and &pi;/2 (i.e its cosine is
+     *   positive),</li>
+     *   <li>for Euler angles, the chosen set is the one for which the
+     *   second angle is between 0 and &pi; (i.e its sine is positive).</li>
+     * </ul>
+
+     * <p>Cardan and Euler angle have a very disappointing drawback: all
+     * of them have singularities. This means that if the instance is
+     * too close to the singularities corresponding to the given
+     * rotation order, it will be impossible to retrieve the angles. For
+     * Cardan angles, this is often called gimbal lock. There is
+     * <em>nothing</em> to do to prevent this, it is an intrinsic problem
+     * with Cardan and Euler representation (but not a problem with the
+     * rotation itself, which is perfectly well defined). For Cardan
+     * angles, singularities occur when the second angle is close to
+     * -&pi;/2 or +&pi;/2, for Euler angle singularities occur when the
+     * second angle is close to 0 or &pi;, this implies that the identity
+     * rotation is always singular for Euler angles!</p>
+
+     * @param order rotation order to use
+     * @param convention convention to use for the semantics of the angle
+     * @return an array of three angles, in the order specified by the set
+     * @exception CardanEulerSingularityException if the rotation is
+     * singular with respect to the angles set specified
+     * @since 3.6
+     */
+    public T[] getAngles(final RotationOrder order, RotationConvention convention)
+        throws CardanEulerSingularityException {
+
+        if (convention == RotationConvention.VECTOR_OPERATOR) {
+            if (order == RotationOrder.XYZ) {
+
+                // r (+K) coordinates are :
+                //  sin (theta), -cos (theta) sin (phi), cos (theta) cos (phi)
+                // (-r) (+I) coordinates are :
+                // cos (psi) cos (theta), -sin (psi) cos (theta), sin (theta)
+                final // and we can choose to have theta in the interval [-PI/2 ; +PI/2]
+                FieldVector3D<T> v1 = applyTo(vector(0, 0, 1));
+                final FieldVector3D<T> v2 = applyInverseTo(vector(1, 0, 0));
+                if  ((v2.getZ().getReal() < -0.9999999999) || (v2.getZ().getReal() > 0.9999999999)) {
+                    throw new CardanEulerSingularityException(true);
+                }
+                return buildArray(v1.getY().negate().atan2(v1.getZ()),
+                                  v2.getZ().asin(),
+                                  v2.getY().negate().atan2(v2.getX()));
+
+            } else if (order == RotationOrder.XZY) {
+
+                // r (+J) coordinates are :
+                // -sin (psi), cos (psi) cos (phi), cos (psi) sin (phi)
+                // (-r) (+I) coordinates are :
+                // cos (theta) cos (psi), -sin (psi), sin (theta) cos (psi)
+                // and we can choose to have psi in the interval [-PI/2 ; +PI/2]
+                final FieldVector3D<T> v1 = applyTo(vector(0, 1, 0));
+                final FieldVector3D<T> v2 = applyInverseTo(vector(1, 0, 0));
+                if ((v2.getY().getReal() < -0.9999999999) || (v2.getY().getReal() > 0.9999999999)) {
+                    throw new CardanEulerSingularityException(true);
+                }
+                return buildArray(v1.getZ().atan2(v1.getY()),
+                                  v2.getY().asin().negate(),
+                                  v2.getZ().atan2(v2.getX()));
+
+            } else if (order == RotationOrder.YXZ) {
+
+                // r (+K) coordinates are :
+                //  cos (phi) sin (theta), -sin (phi), cos (phi) cos (theta)
+                // (-r) (+J) coordinates are :
+                // sin (psi) cos (phi), cos (psi) cos (phi), -sin (phi)
+                // and we can choose to have phi in the interval [-PI/2 ; +PI/2]
+                final FieldVector3D<T> v1 = applyTo(vector(0, 0, 1));
+                final FieldVector3D<T> v2 = applyInverseTo(vector(0, 1, 0));
+                if ((v2.getZ().getReal() < -0.9999999999) || (v2.getZ().getReal() > 0.9999999999)) {
+                    throw new CardanEulerSingularityException(true);
+                }
+                return buildArray(v1.getX().atan2(v1.getZ()),
+                                  v2.getZ().asin().negate(),
+                                  v2.getX().atan2(v2.getY()));
+
+            } else if (order == RotationOrder.YZX) {
+
+                // r (+I) coordinates are :
+                // cos (psi) cos (theta), sin (psi), -cos (psi) sin (theta)
+                // (-r) (+J) coordinates are :
+                // sin (psi), cos (phi) cos (psi), -sin (phi) cos (psi)
+                // and we can choose to have psi in the interval [-PI/2 ; +PI/2]
+                final FieldVector3D<T> v1 = applyTo(vector(1, 0, 0));
+                final FieldVector3D<T> v2 = applyInverseTo(vector(0, 1, 0));
+                if ((v2.getX().getReal() < -0.9999999999) || (v2.getX().getReal() > 0.9999999999)) {
+                    throw new CardanEulerSingularityException(true);
+                }
+                return buildArray(v1.getZ().negate().atan2(v1.getX()),
+                                  v2.getX().asin(),
+                                  v2.getZ().negate().atan2(v2.getY()));
+
+            } else if (order == RotationOrder.ZXY) {
+
+                // r (+J) coordinates are :
+                // -cos (phi) sin (psi), cos (phi) cos (psi), sin (phi)
+                // (-r) (+K) coordinates are :
+                // -sin (theta) cos (phi), sin (phi), cos (theta) cos (phi)
+                // and we can choose to have phi in the interval [-PI/2 ; +PI/2]
+                final FieldVector3D<T> v1 = applyTo(vector(0, 1, 0));
+                final FieldVector3D<T> v2 = applyInverseTo(vector(0, 0, 1));
+                if ((v2.getY().getReal() < -0.9999999999) || (v2.getY().getReal() > 0.9999999999)) {
+                    throw new CardanEulerSingularityException(true);
+                }
+                return buildArray(v1.getX().negate().atan2(v1.getY()),
+                                  v2.getY().asin(),
+                                  v2.getX().negate().atan2(v2.getZ()));
+
+            } else if (order == RotationOrder.ZYX) {
+
+                // r (+I) coordinates are :
+                //  cos (theta) cos (psi), cos (theta) sin (psi), -sin (theta)
+                // (-r) (+K) coordinates are :
+                // -sin (theta), sin (phi) cos (theta), cos (phi) cos (theta)
+                // and we can choose to have theta in the interval [-PI/2 ; +PI/2]
+                final FieldVector3D<T> v1 = applyTo(vector(1, 0, 0));
+                final FieldVector3D<T> v2 = applyInverseTo(vector(0, 0, 1));
+                if ((v2.getX().getReal() < -0.9999999999) || (v2.getX().getReal() > 0.9999999999)) {
+                    throw new CardanEulerSingularityException(true);
+                }
+                return buildArray(v1.getY().atan2(v1.getX()),
+                                  v2.getX().asin().negate(),
+                                  v2.getY().atan2(v2.getZ()));
+
+            } else if (order == RotationOrder.XYX) {
+
+                // r (+I) coordinates are :
+                //  cos (theta), sin (phi1) sin (theta), -cos (phi1) sin (theta)
+                // (-r) (+I) coordinates are :
+                // cos (theta), sin (theta) sin (phi2), sin (theta) cos (phi2)
+                // and we can choose to have theta in the interval [0 ; PI]
+                final FieldVector3D<T> v1 = applyTo(vector(1, 0, 0));
+                final FieldVector3D<T> v2 = applyInverseTo(vector(1, 0, 0));
+                if ((v2.getX().getReal() < -0.9999999999) || (v2.getX().getReal() > 0.9999999999)) {
+                    throw new CardanEulerSingularityException(false);
+                }
+                return buildArray(v1.getY().atan2(v1.getZ().negate()),
+                                  v2.getX().acos(),
+                                  v2.getY().atan2(v2.getZ()));
+
+            } else if (order == RotationOrder.XZX) {
+
+                // r (+I) coordinates are :
+                //  cos (psi), cos (phi1) sin (psi), sin (phi1) sin (psi)
+                // (-r) (+I) coordinates are :
+                // cos (psi), -sin (psi) cos (phi2), sin (psi) sin (phi2)
+                // and we can choose to have psi in the interval [0 ; PI]
+                final FieldVector3D<T> v1 = applyTo(vector(1, 0, 0));
+                final FieldVector3D<T> v2 = applyInverseTo(vector(1, 0, 0));
+                if ((v2.getX().getReal() < -0.9999999999) || (v2.getX().getReal() > 0.9999999999)) {
+                    throw new CardanEulerSingularityException(false);
+                }
+                return buildArray(v1.getZ().atan2(v1.getY()),
+                                  v2.getX().acos(),
+                                  v2.getZ().atan2(v2.getY().negate()));
+
+            } else if (order == RotationOrder.YXY) {
+
+                // r (+J) coordinates are :
+                //  sin (theta1) sin (phi), cos (phi), cos (theta1) sin (phi)
+                // (-r) (+J) coordinates are :
+                // sin (phi) sin (theta2), cos (phi), -sin (phi) cos (theta2)
+                // and we can choose to have phi in the interval [0 ; PI]
+                final FieldVector3D<T> v1 = applyTo(vector(0, 1, 0));
+                final FieldVector3D<T> v2 = applyInverseTo(vector(0, 1, 0));
+                if ((v2.getY().getReal() < -0.9999999999) || (v2.getY().getReal() > 0.9999999999)) {
+                    throw new CardanEulerSingularityException(false);
+                }
+                return buildArray(v1.getX().atan2(v1.getZ()),
+                                  v2.getY().acos(),
+                                  v2.getX().atan2(v2.getZ().negate()));
+
+            } else if (order == RotationOrder.YZY) {
+
+                // r (+J) coordinates are :
+                //  -cos (theta1) sin (psi), cos (psi), sin (theta1) sin (psi)
+                // (-r) (+J) coordinates are :
+                // sin (psi) cos (theta2), cos (psi), sin (psi) sin (theta2)
+                // and we can choose to have psi in the interval [0 ; PI]
+                final FieldVector3D<T> v1 = applyTo(vector(0, 1, 0));
+                final FieldVector3D<T> v2 = applyInverseTo(vector(0, 1, 0));
+                if ((v2.getY().getReal() < -0.9999999999) || (v2.getY().getReal() > 0.9999999999)) {
+                    throw new CardanEulerSingularityException(false);
+                }
+                return buildArray(v1.getZ().atan2(v1.getX().negate()),
+                                  v2.getY().acos(),
+                                  v2.getZ().atan2(v2.getX()));
+
+            } else if (order == RotationOrder.ZXZ) {
+
+                // r (+K) coordinates are :
+                //  sin (psi1) sin (phi), -cos (psi1) sin (phi), cos (phi)
+                // (-r) (+K) coordinates are :
+                // sin (phi) sin (psi2), sin (phi) cos (psi2), cos (phi)
+                // and we can choose to have phi in the interval [0 ; PI]
+                final FieldVector3D<T> v1 = applyTo(vector(0, 0, 1));
+                final FieldVector3D<T> v2 = applyInverseTo(vector(0, 0, 1));
+                if ((v2.getZ().getReal() < -0.9999999999) || (v2.getZ().getReal() > 0.9999999999)) {
+                    throw new CardanEulerSingularityException(false);
+                }
+                return buildArray(v1.getX().atan2(v1.getY().negate()),
+                                  v2.getZ().acos(),
+                                  v2.getX().atan2(v2.getY()));
+
+            } else { // last possibility is ZYZ
+
+                // r (+K) coordinates are :
+                //  cos (psi1) sin (theta), sin (psi1) sin (theta), cos (theta)
+                // (-r) (+K) coordinates are :
+                // -sin (theta) cos (psi2), sin (theta) sin (psi2), cos (theta)
+                // and we can choose to have theta in the interval [0 ; PI]
+                final FieldVector3D<T> v1 = applyTo(vector(0, 0, 1));
+                final FieldVector3D<T> v2 = applyInverseTo(vector(0, 0, 1));
+                if ((v2.getZ().getReal() < -0.9999999999) || (v2.getZ().getReal() > 0.9999999999)) {
+                    throw new CardanEulerSingularityException(false);
+                }
+                return buildArray(v1.getY().atan2(v1.getX()),
+                                  v2.getZ().acos(),
+                                  v2.getY().atan2(v2.getX().negate()));
+
+            }
+        } else {
+            if (order == RotationOrder.XYZ) {
+
+                // r (Vector3D.plusI) coordinates are :
+                //  cos (theta) cos (psi), -cos (theta) sin (psi), sin (theta)
+                // (-r) (Vector3D.plusK) coordinates are :
+                // sin (theta), -sin (phi) cos (theta), cos (phi) cos (theta)
+                // and we can choose to have theta in the interval [-PI/2 ; +PI/2]
+                FieldVector3D<T> v1 = applyTo(Vector3D.PLUS_I);
+                FieldVector3D<T> v2 = applyInverseTo(Vector3D.PLUS_K);
+                if ((v2.getX().getReal() < -0.9999999999) || (v2.getX().getReal() > 0.9999999999)) {
+                    throw new CardanEulerSingularityException(true);
+                }
+                return buildArray(v2.getY().negate().atan2(v2.getZ()),
+                                  v2.getX().asin(),
+                                  v1.getY().negate().atan2(v1.getX()));
+
+            } else if (order == RotationOrder.XZY) {
+
+                // r (Vector3D.plusI) coordinates are :
+                // cos (psi) cos (theta), -sin (psi), cos (psi) sin (theta)
+                // (-r) (Vector3D.plusJ) coordinates are :
+                // -sin (psi), cos (phi) cos (psi), sin (phi) cos (psi)
+                // and we can choose to have psi in the interval [-PI/2 ; +PI/2]
+                FieldVector3D<T> v1 = applyTo(Vector3D.PLUS_I);
+                FieldVector3D<T> v2 = applyInverseTo(Vector3D.PLUS_J);
+                if ((v2.getX().getReal() < -0.9999999999) || (v2.getX().getReal() > 0.9999999999)) {
+                    throw new CardanEulerSingularityException(true);
+                }
+                return buildArray(v2.getZ().atan2(v2.getY()),
+                                  v2.getX().asin().negate(),
+                                  v1.getZ().atan2(v1.getX()));
+
+            } else if (order == RotationOrder.YXZ) {
+
+                // r (Vector3D.plusJ) coordinates are :
+                // cos (phi) sin (psi), cos (phi) cos (psi), -sin (phi)
+                // (-r) (Vector3D.plusK) coordinates are :
+                // sin (theta) cos (phi), -sin (phi), cos (theta) cos (phi)
+                // and we can choose to have phi in the interval [-PI/2 ; +PI/2]
+                FieldVector3D<T> v1 = applyTo(Vector3D.PLUS_J);
+                FieldVector3D<T> v2 = applyInverseTo(Vector3D.PLUS_K);
+                if ((v2.getY().getReal() < -0.9999999999) || (v2.getY().getReal() > 0.9999999999)) {
+                    throw new CardanEulerSingularityException(true);
+                }
+                return buildArray(v2.getX().atan2(v2.getZ()),
+                                  v2.getY().asin().negate(),
+                                  v1.getX().atan2(v1.getY()));
+
+            } else if (order == RotationOrder.YZX) {
+
+                // r (Vector3D.plusJ) coordinates are :
+                // sin (psi), cos (psi) cos (phi), -cos (psi) sin (phi)
+                // (-r) (Vector3D.plusI) coordinates are :
+                // cos (theta) cos (psi), sin (psi), -sin (theta) cos (psi)
+                // and we can choose to have psi in the interval [-PI/2 ; +PI/2]
+                FieldVector3D<T> v1 = applyTo(Vector3D.PLUS_J);
+                FieldVector3D<T> v2 = applyInverseTo(Vector3D.PLUS_I);
+                if ((v2.getY().getReal() < -0.9999999999) || (v2.getY().getReal() > 0.9999999999)) {
+                    throw new CardanEulerSingularityException(true);
+                }
+                return buildArray(v2.getZ().negate().atan2(v2.getX()),
+                                  v2.getY().asin(),
+                                  v1.getZ().negate().atan2(v1.getY()));
+
+            } else if (order == RotationOrder.ZXY) {
+
+                // r (Vector3D.plusK) coordinates are :
+                //  -cos (phi) sin (theta), sin (phi), cos (phi) cos (theta)
+                // (-r) (Vector3D.plusJ) coordinates are :
+                // -sin (psi) cos (phi), cos (psi) cos (phi), sin (phi)
+                // and we can choose to have phi in the interval [-PI/2 ; +PI/2]
+                FieldVector3D<T> v1 = applyTo(Vector3D.PLUS_K);
+                FieldVector3D<T> v2 = applyInverseTo(Vector3D.PLUS_J);
+                if ((v2.getZ().getReal() < -0.9999999999) || (v2.getZ().getReal() > 0.9999999999)) {
+                    throw new CardanEulerSingularityException(true);
+                }
+                return buildArray(v2.getX().negate().atan2(v2.getY()),
+                                  v2.getZ().asin(),
+                                  v1.getX().negate().atan2(v1.getZ()));
+
+            } else if (order == RotationOrder.ZYX) {
+
+                // r (Vector3D.plusK) coordinates are :
+                //  -sin (theta), cos (theta) sin (phi), cos (theta) cos (phi)
+                // (-r) (Vector3D.plusI) coordinates are :
+                // cos (psi) cos (theta), sin (psi) cos (theta), -sin (theta)
+                // and we can choose to have theta in the interval [-PI/2 ; +PI/2]
+                FieldVector3D<T> v1 = applyTo(Vector3D.PLUS_K);
+                FieldVector3D<T> v2 = applyInverseTo(Vector3D.PLUS_I);
+                if  ((v2.getZ().getReal() < -0.9999999999) || (v2.getZ().getReal() > 0.9999999999)) {
+                    throw new CardanEulerSingularityException(true);
+                }
+                return buildArray(v2.getY().atan2(v2.getX()),
+                                  v2.getZ().asin().negate(),
+                                  v1.getY().atan2(v1.getZ()));
+
+            } else if (order == RotationOrder.XYX) {
+
+                // r (Vector3D.plusI) coordinates are :
+                //  cos (theta), sin (phi2) sin (theta), cos (phi2) sin (theta)
+                // (-r) (Vector3D.plusI) coordinates are :
+                // cos (theta), sin (theta) sin (phi1), -sin (theta) cos (phi1)
+                // and we can choose to have theta in the interval [0 ; PI]
+                FieldVector3D<T> v1 = applyTo(Vector3D.PLUS_I);
+                FieldVector3D<T> v2 = applyInverseTo(Vector3D.PLUS_I);
+                if ((v2.getX().getReal() < -0.9999999999) || (v2.getX().getReal() > 0.9999999999)) {
+                    throw new CardanEulerSingularityException(false);
+                }
+                return buildArray(v2.getY().atan2(v2.getZ().negate()),
+                                  v2.getX().acos(),
+                                  v1.getY().atan2(v1.getZ()));
+
+            } else if (order == RotationOrder.XZX) {
+
+                // r (Vector3D.plusI) coordinates are :
+                //  cos (psi), -cos (phi2) sin (psi), sin (phi2) sin (psi)
+                // (-r) (Vector3D.plusI) coordinates are :
+                // cos (psi), sin (psi) cos (phi1), sin (psi) sin (phi1)
+                // and we can choose to have psi in the interval [0 ; PI]
+                FieldVector3D<T> v1 = applyTo(Vector3D.PLUS_I);
+                FieldVector3D<T> v2 = applyInverseTo(Vector3D.PLUS_I);
+                if ((v2.getX().getReal() < -0.9999999999) || (v2.getX().getReal() > 0.9999999999)) {
+                    throw new CardanEulerSingularityException(false);
+                }
+                return buildArray(v2.getZ().atan2(v2.getY()),
+                                  v2.getX().acos(),
+                                  v1.getZ().atan2(v1.getY().negate()));
+
+            } else if (order == RotationOrder.YXY) {
+
+                // r (Vector3D.plusJ) coordinates are :
+                // sin (phi) sin (theta2), cos (phi), -sin (phi) cos (theta2)
+                // (-r) (Vector3D.plusJ) coordinates are :
+                //  sin (theta1) sin (phi), cos (phi), cos (theta1) sin (phi)
+                // and we can choose to have phi in the interval [0 ; PI]
+                FieldVector3D<T> v1 = applyTo(Vector3D.PLUS_J);
+                FieldVector3D<T> v2 = applyInverseTo(Vector3D.PLUS_J);
+                if ((v2.getY().getReal() < -0.9999999999) || (v2.getY().getReal() > 0.9999999999)) {
+                    throw new CardanEulerSingularityException(false);
+                }
+                return buildArray(v2.getX().atan2(v2.getZ()),
+                                  v2.getY().acos(),
+                                  v1.getX().atan2(v1.getZ().negate()));
+
+            } else if (order == RotationOrder.YZY) {
+
+                // r (Vector3D.plusJ) coordinates are :
+                // sin (psi) cos (theta2), cos (psi), sin (psi) sin (theta2)
+                // (-r) (Vector3D.plusJ) coordinates are :
+                //  -cos (theta1) sin (psi), cos (psi), sin (theta1) sin (psi)
+                // and we can choose to have psi in the interval [0 ; PI]
+                FieldVector3D<T> v1 = applyTo(Vector3D.PLUS_J);
+                FieldVector3D<T> v2 = applyInverseTo(Vector3D.PLUS_J);
+                if ((v2.getY().getReal() < -0.9999999999) || (v2.getY().getReal() > 0.9999999999)) {
+                    throw new CardanEulerSingularityException(false);
+                }
+                return buildArray(v2.getZ().atan2(v2.getX().negate()),
+                                  v2.getY().acos(),
+                                  v1.getZ().atan2(v1.getX()));
+
+            } else if (order == RotationOrder.ZXZ) {
+
+                // r (Vector3D.plusK) coordinates are :
+                // sin (phi) sin (psi2), sin (phi) cos (psi2), cos (phi)
+                // (-r) (Vector3D.plusK) coordinates are :
+                //  sin (psi1) sin (phi), -cos (psi1) sin (phi), cos (phi)
+                // and we can choose to have phi in the interval [0 ; PI]
+                FieldVector3D<T> v1 = applyTo(Vector3D.PLUS_K);
+                FieldVector3D<T> v2 = applyInverseTo(Vector3D.PLUS_K);
+                if ((v2.getZ().getReal() < -0.9999999999) || (v2.getZ().getReal() > 0.9999999999)) {
+                    throw new CardanEulerSingularityException(false);
+                }
+                return buildArray(v2.getX().atan2(v2.getY().negate()),
+                                  v2.getZ().acos(),
+                                  v1.getX().atan2(v1.getY()));
+
+            } else { // last possibility is ZYZ
+
+                // r (Vector3D.plusK) coordinates are :
+                // -sin (theta) cos (psi2), sin (theta) sin (psi2), cos (theta)
+                // (-r) (Vector3D.plusK) coordinates are :
+                //  cos (psi1) sin (theta), sin (psi1) sin (theta), cos (theta)
+                // and we can choose to have theta in the interval [0 ; PI]
+                FieldVector3D<T> v1 = applyTo(Vector3D.PLUS_K);
+                FieldVector3D<T> v2 = applyInverseTo(Vector3D.PLUS_K);
+                if ((v2.getZ().getReal() < -0.9999999999) || (v2.getZ().getReal() > 0.9999999999)) {
+                    throw new CardanEulerSingularityException(false);
+                }
+                return buildArray(v2.getY().atan2(v2.getX()),
+                                  v2.getZ().acos(),
+                                  v1.getY().atan2(v1.getX().negate()));
+
+            }
+        }
+
+    }
+
+    /** Create a dimension 3 array.
+     * @param a0 first array element
+     * @param a1 second array element
+     * @param a2 third array element
+     * @return new array
+     */
+    private T[] buildArray(final T a0, final T a1, final T a2) {
+        final T[] array = MathArrays.buildArray(a0.getField(), 3);
+        array[0] = a0;
+        array[1] = a1;
+        array[2] = a2;
+        return array;
+    }
+
+    /** Create a constant vector.
+     * @param x abscissa
+     * @param y ordinate
+     * @param z height
+     * @return a constant vector
+     */
+    private FieldVector3D<T> vector(final double x, final double y, final double z) {
+        final T zero = q0.getField().getZero();
+        return new FieldVector3D<T>(zero.add(x), zero.add(y), zero.add(z));
+    }
+
+    /** Get the 3X3 matrix corresponding to the instance
+     * @return the matrix corresponding to the instance
+     */
+    public T[][] getMatrix() {
+
+        // products
+        final T q0q0  = q0.multiply(q0);
+        final T q0q1  = q0.multiply(q1);
+        final T q0q2  = q0.multiply(q2);
+        final T q0q3  = q0.multiply(q3);
+        final T q1q1  = q1.multiply(q1);
+        final T q1q2  = q1.multiply(q2);
+        final T q1q3  = q1.multiply(q3);
+        final T q2q2  = q2.multiply(q2);
+        final T q2q3  = q2.multiply(q3);
+        final T q3q3  = q3.multiply(q3);
+
+        // create the matrix
+        final T[][] m = MathArrays.buildArray(q0.getField(), 3, 3);
+
+        m [0][0] = q0q0.add(q1q1).multiply(2).subtract(1);
+        m [1][0] = q1q2.subtract(q0q3).multiply(2);
+        m [2][0] = q1q3.add(q0q2).multiply(2);
+
+        m [0][1] = q1q2.add(q0q3).multiply(2);
+        m [1][1] = q0q0.add(q2q2).multiply(2).subtract(1);
+        m [2][1] = q2q3.subtract(q0q1).multiply(2);
+
+        m [0][2] = q1q3.subtract(q0q2).multiply(2);
+        m [1][2] = q2q3.add(q0q1).multiply(2);
+        m [2][2] = q0q0.add(q3q3).multiply(2).subtract(1);
+
+        return m;
+
+    }
+
+    /** Convert to a constant vector without derivatives.
+     * @return a constant vector
+     */
+    public Rotation toRotation() {
+        return new Rotation(q0.getReal(), q1.getReal(), q2.getReal(), q3.getReal(), false);
+    }
+
+    /** Apply the rotation to a vector.
+     * @param u vector to apply the rotation to
+     * @return a new vector which is the image of u by the rotation
+     */
+    public FieldVector3D<T> applyTo(final FieldVector3D<T> u) {
+
+        final T x = u.getX();
+        final T y = u.getY();
+        final T z = u.getZ();
+
+        final T s = q1.multiply(x).add(q2.multiply(y)).add(q3.multiply(z));
+
+        return new FieldVector3D<T>(q0.multiply(x.multiply(q0).subtract(q2.multiply(z).subtract(q3.multiply(y)))).add(s.multiply(q1)).multiply(2).subtract(x),
+                                    q0.multiply(y.multiply(q0).subtract(q3.multiply(x).subtract(q1.multiply(z)))).add(s.multiply(q2)).multiply(2).subtract(y),
+                                    q0.multiply(z.multiply(q0).subtract(q1.multiply(y).subtract(q2.multiply(x)))).add(s.multiply(q3)).multiply(2).subtract(z));
+
+    }
+
+    /** Apply the rotation to a vector.
+     * @param u vector to apply the rotation to
+     * @return a new vector which is the image of u by the rotation
+     */
+    public FieldVector3D<T> applyTo(final Vector3D u) {
+
+        final double x = u.getX();
+        final double y = u.getY();
+        final double z = u.getZ();
+
+        final T s = q1.multiply(x).add(q2.multiply(y)).add(q3.multiply(z));
+
+        return new FieldVector3D<T>(q0.multiply(q0.multiply(x).subtract(q2.multiply(z).subtract(q3.multiply(y)))).add(s.multiply(q1)).multiply(2).subtract(x),
+                                    q0.multiply(q0.multiply(y).subtract(q3.multiply(x).subtract(q1.multiply(z)))).add(s.multiply(q2)).multiply(2).subtract(y),
+                                    q0.multiply(q0.multiply(z).subtract(q1.multiply(y).subtract(q2.multiply(x)))).add(s.multiply(q3)).multiply(2).subtract(z));
+
+    }
+
+    /** Apply the rotation to a vector stored in an array.
+     * @param in an array with three items which stores vector to rotate
+     * @param out an array with three items to put result to (it can be the same
+     * array as in)
+     */
+    public void applyTo(final T[] in, final T[] out) {
+
+        final T x = in[0];
+        final T y = in[1];
+        final T z = in[2];
+
+        final T s = q1.multiply(x).add(q2.multiply(y)).add(q3.multiply(z));
+
+        out[0] = q0.multiply(x.multiply(q0).subtract(q2.multiply(z).subtract(q3.multiply(y)))).add(s.multiply(q1)).multiply(2).subtract(x);
+        out[1] = q0.multiply(y.multiply(q0).subtract(q3.multiply(x).subtract(q1.multiply(z)))).add(s.multiply(q2)).multiply(2).subtract(y);
+        out[2] = q0.multiply(z.multiply(q0).subtract(q1.multiply(y).subtract(q2.multiply(x)))).add(s.multiply(q3)).multiply(2).subtract(z);
+
+    }
+
+    /** Apply the rotation to a vector stored in an array.
+     * @param in an array with three items which stores vector to rotate
+     * @param out an array with three items to put result to
+     */
+    public void applyTo(final double[] in, final T[] out) {
+
+        final double x = in[0];
+        final double y = in[1];
+        final double z = in[2];
+
+        final T s = q1.multiply(x).add(q2.multiply(y)).add(q3.multiply(z));
+
+        out[0] = q0.multiply(q0.multiply(x).subtract(q2.multiply(z).subtract(q3.multiply(y)))).add(s.multiply(q1)).multiply(2).subtract(x);
+        out[1] = q0.multiply(q0.multiply(y).subtract(q3.multiply(x).subtract(q1.multiply(z)))).add(s.multiply(q2)).multiply(2).subtract(y);
+        out[2] = q0.multiply(q0.multiply(z).subtract(q1.multiply(y).subtract(q2.multiply(x)))).add(s.multiply(q3)).multiply(2).subtract(z);
+
+    }
+
+    /** Apply a rotation to a vector.
+     * @param r rotation to apply
+     * @param u vector to apply the rotation to
+     * @param <T> the type of the field elements
+     * @return a new vector which is the image of u by the rotation
+     */
+    public static <T extends RealFieldElement<T>> FieldVector3D<T> applyTo(final Rotation r, final FieldVector3D<T> u) {
+
+        final T x = u.getX();
+        final T y = u.getY();
+        final T z = u.getZ();
+
+        final T s = x.multiply(r.getQ1()).add(y.multiply(r.getQ2())).add(z.multiply(r.getQ3()));
+
+        return new FieldVector3D<T>(x.multiply(r.getQ0()).subtract(z.multiply(r.getQ2()).subtract(y.multiply(r.getQ3()))).multiply(r.getQ0()).add(s.multiply(r.getQ1())).multiply(2).subtract(x),
+                                    y.multiply(r.getQ0()).subtract(x.multiply(r.getQ3()).subtract(z.multiply(r.getQ1()))).multiply(r.getQ0()).add(s.multiply(r.getQ2())).multiply(2).subtract(y),
+                                    z.multiply(r.getQ0()).subtract(y.multiply(r.getQ1()).subtract(x.multiply(r.getQ2()))).multiply(r.getQ0()).add(s.multiply(r.getQ3())).multiply(2).subtract(z));
+
+    }
+
+    /** Apply the inverse of the rotation to a vector.
+     * @param u vector to apply the inverse of the rotation to
+     * @return a new vector which such that u is its image by the rotation
+     */
+    public FieldVector3D<T> applyInverseTo(final FieldVector3D<T> u) {
+
+        final T x = u.getX();
+        final T y = u.getY();
+        final T z = u.getZ();
+
+        final T s  = q1.multiply(x).add(q2.multiply(y)).add(q3.multiply(z));
+        final T m0 = q0.negate();
+
+        return new FieldVector3D<T>(m0.multiply(x.multiply(m0).subtract(q2.multiply(z).subtract(q3.multiply(y)))).add(s.multiply(q1)).multiply(2).subtract(x),
+                                    m0.multiply(y.multiply(m0).subtract(q3.multiply(x).subtract(q1.multiply(z)))).add(s.multiply(q2)).multiply(2).subtract(y),
+                                    m0.multiply(z.multiply(m0).subtract(q1.multiply(y).subtract(q2.multiply(x)))).add(s.multiply(q3)).multiply(2).subtract(z));
+
+    }
+
+    /** Apply the inverse of the rotation to a vector.
+     * @param u vector to apply the inverse of the rotation to
+     * @return a new vector which such that u is its image by the rotation
+     */
+    public FieldVector3D<T> applyInverseTo(final Vector3D u) {
+
+        final double x = u.getX();
+        final double y = u.getY();
+        final double z = u.getZ();
+
+        final T s  = q1.multiply(x).add(q2.multiply(y)).add(q3.multiply(z));
+        final T m0 = q0.negate();
+
+        return new FieldVector3D<T>(m0.multiply(m0.multiply(x).subtract(q2.multiply(z).subtract(q3.multiply(y)))).add(s.multiply(q1)).multiply(2).subtract(x),
+                                    m0.multiply(m0.multiply(y).subtract(q3.multiply(x).subtract(q1.multiply(z)))).add(s.multiply(q2)).multiply(2).subtract(y),
+                                    m0.multiply(m0.multiply(z).subtract(q1.multiply(y).subtract(q2.multiply(x)))).add(s.multiply(q3)).multiply(2).subtract(z));
+
+    }
+
+    /** Apply the inverse of the rotation to a vector stored in an array.
+     * @param in an array with three items which stores vector to rotate
+     * @param out an array with three items to put result to (it can be the same
+     * array as in)
+     */
+    public void applyInverseTo(final T[] in, final T[] out) {
+
+        final T x = in[0];
+        final T y = in[1];
+        final T z = in[2];
+
+        final T s = q1.multiply(x).add(q2.multiply(y)).add(q3.multiply(z));
+        final T m0 = q0.negate();
+
+        out[0] = m0.multiply(x.multiply(m0).subtract(q2.multiply(z).subtract(q3.multiply(y)))).add(s.multiply(q1)).multiply(2).subtract(x);
+        out[1] = m0.multiply(y.multiply(m0).subtract(q3.multiply(x).subtract(q1.multiply(z)))).add(s.multiply(q2)).multiply(2).subtract(y);
+        out[2] = m0.multiply(z.multiply(m0).subtract(q1.multiply(y).subtract(q2.multiply(x)))).add(s.multiply(q3)).multiply(2).subtract(z);
+
+    }
+
+    /** Apply the inverse of the rotation to a vector stored in an array.
+     * @param in an array with three items which stores vector to rotate
+     * @param out an array with three items to put result to
+     */
+    public void applyInverseTo(final double[] in, final T[] out) {
+
+        final double x = in[0];
+        final double y = in[1];
+        final double z = in[2];
+
+        final T s = q1.multiply(x).add(q2.multiply(y)).add(q3.multiply(z));
+        final T m0 = q0.negate();
+
+        out[0] = m0.multiply(m0.multiply(x).subtract(q2.multiply(z).subtract(q3.multiply(y)))).add(s.multiply(q1)).multiply(2).subtract(x);
+        out[1] = m0.multiply(m0.multiply(y).subtract(q3.multiply(x).subtract(q1.multiply(z)))).add(s.multiply(q2)).multiply(2).subtract(y);
+        out[2] = m0.multiply(m0.multiply(z).subtract(q1.multiply(y).subtract(q2.multiply(x)))).add(s.multiply(q3)).multiply(2).subtract(z);
+
+    }
+
+    /** Apply the inverse of a rotation to a vector.
+     * @param r rotation to apply
+     * @param u vector to apply the inverse of the rotation to
+     * @param <T> the type of the field elements
+     * @return a new vector which such that u is its image by the rotation
+     */
+    public static <T extends RealFieldElement<T>> FieldVector3D<T> applyInverseTo(final Rotation r, final FieldVector3D<T> u) {
+
+        final T x = u.getX();
+        final T y = u.getY();
+        final T z = u.getZ();
+
+        final T s  = x.multiply(r.getQ1()).add(y.multiply(r.getQ2())).add(z.multiply(r.getQ3()));
+        final double m0 = -r.getQ0();
+
+        return new FieldVector3D<T>(x.multiply(m0).subtract(z.multiply(r.getQ2()).subtract(y.multiply(r.getQ3()))).multiply(m0).add(s.multiply(r.getQ1())).multiply(2).subtract(x),
+                                    y.multiply(m0).subtract(x.multiply(r.getQ3()).subtract(z.multiply(r.getQ1()))).multiply(m0).add(s.multiply(r.getQ2())).multiply(2).subtract(y),
+                                    z.multiply(m0).subtract(y.multiply(r.getQ1()).subtract(x.multiply(r.getQ2()))).multiply(m0).add(s.multiply(r.getQ3())).multiply(2).subtract(z));
+
+    }
+
+    /** Apply the instance to another rotation.
+     * <p>
+     * Calling this method is equivalent to call
+     * {@link #compose(FieldRotation, RotationConvention)
+     * compose(r, RotationConvention.VECTOR_OPERATOR)}.
+     * </p>
+     * @param r rotation to apply the rotation to
+     * @return a new rotation which is the composition of r by the instance
+     */
+    public FieldRotation<T> applyTo(final FieldRotation<T> r) {
+        return compose(r, RotationConvention.VECTOR_OPERATOR);
+    }
+
+    /** Compose the instance with another rotation.
+     * <p>
+     * If the semantics of the rotations composition corresponds to a
+     * {@link RotationConvention#VECTOR_OPERATOR vector operator} convention,
+     * applying the instance to a rotation is computing the composition
+     * in an order compliant with the following rule : let {@code u} be any
+     * vector and {@code v} its image by {@code r1} (i.e.
+     * {@code r1.applyTo(u) = v}). Let {@code w} be the image of {@code v} by
+     * rotation {@code r2} (i.e. {@code r2.applyTo(v) = w}). Then
+     * {@code w = comp.applyTo(u)}, where
+     * {@code comp = r2.compose(r1, RotationConvention.VECTOR_OPERATOR)}.
+     * </p>
+     * <p>
+     * If the semantics of the rotations composition corresponds to a
+     * {@link RotationConvention#FRAME_TRANSFORM frame transform} convention,
+     * the application order will be reversed. So keeping the exact same
+     * meaning of all {@code r1}, {@code r2}, {@code u}, {@code v}, {@code w}
+     * and  {@code comp} as above, {@code comp} could also be computed as
+     * {@code comp = r1.compose(r2, RotationConvention.FRAME_TRANSFORM)}.
+     * </p>
+     * @param r rotation to apply the rotation to
+     * @param convention convention to use for the semantics of the angle
+     * @return a new rotation which is the composition of r by the instance
+     */
+    public FieldRotation<T> compose(final FieldRotation<T> r, final RotationConvention convention) {
+        return convention == RotationConvention.VECTOR_OPERATOR ?
+                             composeInternal(r) : r.composeInternal(this);
+    }
+
+    /** Compose the instance with another rotation using vector operator convention.
+     * @param r rotation to apply the rotation to
+     * @return a new rotation which is the composition of r by the instance
+     * using vector operator convention
+     */
+    private FieldRotation<T> composeInternal(final FieldRotation<T> r) {
+        return new FieldRotation<T>(r.q0.multiply(q0).subtract(r.q1.multiply(q1).add(r.q2.multiply(q2)).add(r.q3.multiply(q3))),
+                                    r.q1.multiply(q0).add(r.q0.multiply(q1)).add(r.q2.multiply(q3).subtract(r.q3.multiply(q2))),
+                                    r.q2.multiply(q0).add(r.q0.multiply(q2)).add(r.q3.multiply(q1).subtract(r.q1.multiply(q3))),
+                                    r.q3.multiply(q0).add(r.q0.multiply(q3)).add(r.q1.multiply(q2).subtract(r.q2.multiply(q1))),
+                                    false);
+    }
+
+    /** Apply the instance to another rotation.
+     * <p>
+     * Calling this method is equivalent to call
+     * {@link #compose(Rotation, RotationConvention)
+     * compose(r, RotationConvention.VECTOR_OPERATOR)}.
+     * </p>
+     * @param r rotation to apply the rotation to
+     * @return a new rotation which is the composition of r by the instance
+     */
+    public FieldRotation<T> applyTo(final Rotation r) {
+        return compose(r, RotationConvention.VECTOR_OPERATOR);
+    }
+
+    /** Compose the instance with another rotation.
+     * <p>
+     * If the semantics of the rotations composition corresponds to a
+     * {@link RotationConvention#VECTOR_OPERATOR vector operator} convention,
+     * applying the instance to a rotation is computing the composition
+     * in an order compliant with the following rule : let {@code u} be any
+     * vector and {@code v} its image by {@code r1} (i.e.
+     * {@code r1.applyTo(u) = v}). Let {@code w} be the image of {@code v} by
+     * rotation {@code r2} (i.e. {@code r2.applyTo(v) = w}). Then
+     * {@code w = comp.applyTo(u)}, where
+     * {@code comp = r2.compose(r1, RotationConvention.VECTOR_OPERATOR)}.
+     * </p>
+     * <p>
+     * If the semantics of the rotations composition corresponds to a
+     * {@link RotationConvention#FRAME_TRANSFORM frame transform} convention,
+     * the application order will be reversed. So keeping the exact same
+     * meaning of all {@code r1}, {@code r2}, {@code u}, {@code v}, {@code w}
+     * and  {@code comp} as above, {@code comp} could also be computed as
+     * {@code comp = r1.compose(r2, RotationConvention.FRAME_TRANSFORM)}.
+     * </p>
+     * @param r rotation to apply the rotation to
+     * @param convention convention to use for the semantics of the angle
+     * @return a new rotation which is the composition of r by the instance
+     */
+    public FieldRotation<T> compose(final Rotation r, final RotationConvention convention) {
+        return convention == RotationConvention.VECTOR_OPERATOR ?
+                             composeInternal(r) : applyTo(r, this);
+    }
+
+    /** Compose the instance with another rotation using vector operator convention.
+     * @param r rotation to apply the rotation to
+     * @return a new rotation which is the composition of r by the instance
+     * using vector operator convention
+     */
+    private FieldRotation<T> composeInternal(final Rotation r) {
+        return new FieldRotation<T>(q0.multiply(r.getQ0()).subtract(q1.multiply(r.getQ1()).add(q2.multiply(r.getQ2())).add(q3.multiply(r.getQ3()))),
+                        q0.multiply(r.getQ1()).add(q1.multiply(r.getQ0())).add(q3.multiply(r.getQ2()).subtract(q2.multiply(r.getQ3()))),
+                        q0.multiply(r.getQ2()).add(q2.multiply(r.getQ0())).add(q1.multiply(r.getQ3()).subtract(q3.multiply(r.getQ1()))),
+                        q0.multiply(r.getQ3()).add(q3.multiply(r.getQ0())).add(q2.multiply(r.getQ1()).subtract(q1.multiply(r.getQ2()))),
+                        false);
+    }
+
+    /** Apply a rotation to another rotation.
+     * Applying a rotation to another rotation is computing the composition
+     * in an order compliant with the following rule : let u be any
+     * vector and v its image by rInner (i.e. rInner.applyTo(u) = v), let w be the image
+     * of v by rOuter (i.e. rOuter.applyTo(v) = w), then w = comp.applyTo(u),
+     * where comp = applyTo(rOuter, rInner).
+     * @param r1 rotation to apply
+     * @param rInner rotation to apply the rotation to
+     * @param <T> the type of the field elements
+     * @return a new rotation which is the composition of r by the instance
+     */
+    public static <T extends RealFieldElement<T>> FieldRotation<T> applyTo(final Rotation r1, final FieldRotation<T> rInner) {
+        return new FieldRotation<T>(rInner.q0.multiply(r1.getQ0()).subtract(rInner.q1.multiply(r1.getQ1()).add(rInner.q2.multiply(r1.getQ2())).add(rInner.q3.multiply(r1.getQ3()))),
+                                    rInner.q1.multiply(r1.getQ0()).add(rInner.q0.multiply(r1.getQ1())).add(rInner.q2.multiply(r1.getQ3()).subtract(rInner.q3.multiply(r1.getQ2()))),
+                                    rInner.q2.multiply(r1.getQ0()).add(rInner.q0.multiply(r1.getQ2())).add(rInner.q3.multiply(r1.getQ1()).subtract(rInner.q1.multiply(r1.getQ3()))),
+                                    rInner.q3.multiply(r1.getQ0()).add(rInner.q0.multiply(r1.getQ3())).add(rInner.q1.multiply(r1.getQ2()).subtract(rInner.q2.multiply(r1.getQ1()))),
+                                    false);
+    }
+
+    /** Apply the inverse of the instance to another rotation.
+     * <p>
+     * Calling this method is equivalent to call
+     * {@link #composeInverse(FieldRotation, RotationConvention)
+     * composeInverse(r, RotationConvention.VECTOR_OPERATOR)}.
+     * </p>
+     * @param r rotation to apply the rotation to
+     * @return a new rotation which is the composition of r by the inverse
+     * of the instance
+     */
+    public FieldRotation<T> applyInverseTo(final FieldRotation<T> r) {
+        return composeInverse(r, RotationConvention.VECTOR_OPERATOR);
+    }
+
+    /** Compose the inverse of the instance with another rotation.
+     * <p>
+     * If the semantics of the rotations composition corresponds to a
+     * {@link RotationConvention#VECTOR_OPERATOR vector operator} convention,
+     * applying the inverse of the instance to a rotation is computing
+     * the composition in an order compliant with the following rule :
+     * let {@code u} be any vector and {@code v} its image by {@code r1}
+     * (i.e. {@code r1.applyTo(u) = v}). Let {@code w} be the inverse image
+     * of {@code v} by {@code r2} (i.e. {@code r2.applyInverseTo(v) = w}).
+     * Then {@code w = comp.applyTo(u)}, where
+     * {@code comp = r2.composeInverse(r1)}.
+     * </p>
+     * <p>
+     * If the semantics of the rotations composition corresponds to a
+     * {@link RotationConvention#FRAME_TRANSFORM frame transform} convention,
+     * the application order will be reversed, which means it is the
+     * <em>innermost</em> rotation that will be reversed. So keeping the exact same
+     * meaning of all {@code r1}, {@code r2}, {@code u}, {@code v}, {@code w}
+     * and  {@code comp} as above, {@code comp} could also be computed as
+     * {@code comp = r1.revert().composeInverse(r2.revert(), RotationConvention.FRAME_TRANSFORM)}.
+     * </p>
+     * @param r rotation to apply the rotation to
+     * @param convention convention to use for the semantics of the angle
+     * @return a new rotation which is the composition of r by the inverse
+     * of the instance
+     */
+    public FieldRotation<T> composeInverse(final FieldRotation<T> r, final RotationConvention convention) {
+        return convention == RotationConvention.VECTOR_OPERATOR ?
+                             composeInverseInternal(r) : r.composeInternal(revert());
+    }
+
+    /** Compose the inverse of the instance with another rotation
+     * using vector operator convention.
+     * @param r rotation to apply the rotation to
+     * @return a new rotation which is the composition of r by the inverse
+     * of the instance using vector operator convention
+     */
+    private FieldRotation<T> composeInverseInternal(FieldRotation<T> r) {
+        return new FieldRotation<T>(r.q0.multiply(q0).add(r.q1.multiply(q1).add(r.q2.multiply(q2)).add(r.q3.multiply(q3))).negate(),
+                                    r.q0.multiply(q1).add(r.q2.multiply(q3).subtract(r.q3.multiply(q2))).subtract(r.q1.multiply(q0)),
+                                    r.q0.multiply(q2).add(r.q3.multiply(q1).subtract(r.q1.multiply(q3))).subtract(r.q2.multiply(q0)),
+                                    r.q0.multiply(q3).add(r.q1.multiply(q2).subtract(r.q2.multiply(q1))).subtract(r.q3.multiply(q0)),
+                                    false);
+    }
+
+    /** Apply the inverse of the instance to another rotation.
+     * <p>
+     * Calling this method is equivalent to call
+     * {@link #composeInverse(Rotation, RotationConvention)
+     * composeInverse(r, RotationConvention.VECTOR_OPERATOR)}.
+     * </p>
+     * @param r rotation to apply the rotation to
+     * @return a new rotation which is the composition of r by the inverse
+     * of the instance
+     */
+    public FieldRotation<T> applyInverseTo(final Rotation r) {
+        return composeInverse(r, RotationConvention.VECTOR_OPERATOR);
+    }
+
+    /** Compose the inverse of the instance with another rotation.
+     * <p>
+     * If the semantics of the rotations composition corresponds to a
+     * {@link RotationConvention#VECTOR_OPERATOR vector operator} convention,
+     * applying the inverse of the instance to a rotation is computing
+     * the composition in an order compliant with the following rule :
+     * let {@code u} be any vector and {@code v} its image by {@code r1}
+     * (i.e. {@code r1.applyTo(u) = v}). Let {@code w} be the inverse image
+     * of {@code v} by {@code r2} (i.e. {@code r2.applyInverseTo(v) = w}).
+     * Then {@code w = comp.applyTo(u)}, where
+     * {@code comp = r2.composeInverse(r1)}.
+     * </p>
+     * <p>
+     * If the semantics of the rotations composition corresponds to a
+     * {@link RotationConvention#FRAME_TRANSFORM frame transform} convention,
+     * the application order will be reversed, which means it is the
+     * <em>innermost</em> rotation that will be reversed. So keeping the exact same
+     * meaning of all {@code r1}, {@code r2}, {@code u}, {@code v}, {@code w}
+     * and  {@code comp} as above, {@code comp} could also be computed as
+     * {@code comp = r1.revert().composeInverse(r2.revert(), RotationConvention.FRAME_TRANSFORM)}.
+     * </p>
+     * @param r rotation to apply the rotation to
+     * @param convention convention to use for the semantics of the angle
+     * @return a new rotation which is the composition of r by the inverse
+     * of the instance
+     */
+    public FieldRotation<T> composeInverse(final Rotation r, final RotationConvention convention) {
+        return convention == RotationConvention.VECTOR_OPERATOR ?
+                             composeInverseInternal(r) : applyTo(r, revert());
+    }
+
+    /** Compose the inverse of the instance with another rotation
+     * using vector operator convention.
+     * @param r rotation to apply the rotation to
+     * @return a new rotation which is the composition of r by the inverse
+     * of the instance using vector operator convention
+     */
+    private FieldRotation<T> composeInverseInternal(Rotation r) {
+        return new FieldRotation<T>(q0.multiply(r.getQ0()).add(q1.multiply(r.getQ1()).add(q2.multiply(r.getQ2())).add(q3.multiply(r.getQ3()))).negate(),
+                                    q1.multiply(r.getQ0()).add(q3.multiply(r.getQ2()).subtract(q2.multiply(r.getQ3()))).subtract(q0.multiply(r.getQ1())),
+                                    q2.multiply(r.getQ0()).add(q1.multiply(r.getQ3()).subtract(q3.multiply(r.getQ1()))).subtract(q0.multiply(r.getQ2())),
+                                    q3.multiply(r.getQ0()).add(q2.multiply(r.getQ1()).subtract(q1.multiply(r.getQ2()))).subtract(q0.multiply(r.getQ3())),
+                                    false);
+    }
+
+    /** Apply the inverse of a rotation to another rotation.
+     * Applying the inverse of a rotation to another rotation is computing
+     * the composition in an order compliant with the following rule :
+     * let u be any vector and v its image by rInner (i.e. rInner.applyTo(u) = v),
+     * let w be the inverse image of v by rOuter
+     * (i.e. rOuter.applyInverseTo(v) = w), then w = comp.applyTo(u), where
+     * comp = applyInverseTo(rOuter, rInner).
+     * @param rOuter rotation to apply the rotation to
+     * @param rInner rotation to apply the rotation to
+     * @param <T> the type of the field elements
+     * @return a new rotation which is the composition of r by the inverse
+     * of the instance
+     */
+    public static <T extends RealFieldElement<T>> FieldRotation<T> applyInverseTo(final Rotation rOuter, final FieldRotation<T> rInner) {
+        return new FieldRotation<T>(rInner.q0.multiply(rOuter.getQ0()).add(rInner.q1.multiply(rOuter.getQ1()).add(rInner.q2.multiply(rOuter.getQ2())).add(rInner.q3.multiply(rOuter.getQ3()))).negate(),
+                                    rInner.q0.multiply(rOuter.getQ1()).add(rInner.q2.multiply(rOuter.getQ3()).subtract(rInner.q3.multiply(rOuter.getQ2()))).subtract(rInner.q1.multiply(rOuter.getQ0())),
+                                    rInner.q0.multiply(rOuter.getQ2()).add(rInner.q3.multiply(rOuter.getQ1()).subtract(rInner.q1.multiply(rOuter.getQ3()))).subtract(rInner.q2.multiply(rOuter.getQ0())),
+                                    rInner.q0.multiply(rOuter.getQ3()).add(rInner.q1.multiply(rOuter.getQ2()).subtract(rInner.q2.multiply(rOuter.getQ1()))).subtract(rInner.q3.multiply(rOuter.getQ0())),
+                                    false);
+    }
+
+    /** Perfect orthogonality on a 3X3 matrix.
+     * @param m initial matrix (not exactly orthogonal)
+     * @param threshold convergence threshold for the iterative
+     * orthogonality correction (convergence is reached when the
+     * difference between two steps of the Frobenius norm of the
+     * correction is below this threshold)
+     * @return an orthogonal matrix close to m
+     * @exception NotARotationMatrixException if the matrix cannot be
+     * orthogonalized with the given threshold after 10 iterations
+     */
+    private T[][] orthogonalizeMatrix(final T[][] m, final double threshold)
+        throws NotARotationMatrixException {
+
+        T x00 = m[0][0];
+        T x01 = m[0][1];
+        T x02 = m[0][2];
+        T x10 = m[1][0];
+        T x11 = m[1][1];
+        T x12 = m[1][2];
+        T x20 = m[2][0];
+        T x21 = m[2][1];
+        T x22 = m[2][2];
+        double fn = 0;
+        double fn1;
+
+        final T[][] o = MathArrays.buildArray(m[0][0].getField(), 3, 3);
+
+        // iterative correction: Xn+1 = Xn - 0.5 * (Xn.Mt.Xn - M)
+        int i = 0;
+        while (++i < 11) {
+
+            // Mt.Xn
+            final T mx00 = m[0][0].multiply(x00).add(m[1][0].multiply(x10)).add(m[2][0].multiply(x20));
+            final T mx10 = m[0][1].multiply(x00).add(m[1][1].multiply(x10)).add(m[2][1].multiply(x20));
+            final T mx20 = m[0][2].multiply(x00).add(m[1][2].multiply(x10)).add(m[2][2].multiply(x20));
+            final T mx01 = m[0][0].multiply(x01).add(m[1][0].multiply(x11)).add(m[2][0].multiply(x21));
+            final T mx11 = m[0][1].multiply(x01).add(m[1][1].multiply(x11)).add(m[2][1].multiply(x21));
+            final T mx21 = m[0][2].multiply(x01).add(m[1][2].multiply(x11)).add(m[2][2].multiply(x21));
+            final T mx02 = m[0][0].multiply(x02).add(m[1][0].multiply(x12)).add(m[2][0].multiply(x22));
+            final T mx12 = m[0][1].multiply(x02).add(m[1][1].multiply(x12)).add(m[2][1].multiply(x22));
+            final T mx22 = m[0][2].multiply(x02).add(m[1][2].multiply(x12)).add(m[2][2].multiply(x22));
+
+            // Xn+1
+            o[0][0] = x00.subtract(x00.multiply(mx00).add(x01.multiply(mx10)).add(x02.multiply(mx20)).subtract(m[0][0]).multiply(0.5));
+            o[0][1] = x01.subtract(x00.multiply(mx01).add(x01.multiply(mx11)).add(x02.multiply(mx21)).subtract(m[0][1]).multiply(0.5));
+            o[0][2] = x02.subtract(x00.multiply(mx02).add(x01.multiply(mx12)).add(x02.multiply(mx22)).subtract(m[0][2]).multiply(0.5));
+            o[1][0] = x10.subtract(x10.multiply(mx00).add(x11.multiply(mx10)).add(x12.multiply(mx20)).subtract(m[1][0]).multiply(0.5));
+            o[1][1] = x11.subtract(x10.multiply(mx01).add(x11.multiply(mx11)).add(x12.multiply(mx21)).subtract(m[1][1]).multiply(0.5));
+            o[1][2] = x12.subtract(x10.multiply(mx02).add(x11.multiply(mx12)).add(x12.multiply(mx22)).subtract(m[1][2]).multiply(0.5));
+            o[2][0] = x20.subtract(x20.multiply(mx00).add(x21.multiply(mx10)).add(x22.multiply(mx20)).subtract(m[2][0]).multiply(0.5));
+            o[2][1] = x21.subtract(x20.multiply(mx01).add(x21.multiply(mx11)).add(x22.multiply(mx21)).subtract(m[2][1]).multiply(0.5));
+            o[2][2] = x22.subtract(x20.multiply(mx02).add(x21.multiply(mx12)).add(x22.multiply(mx22)).subtract(m[2][2]).multiply(0.5));
+
+            // correction on each elements
+            final double corr00 = o[0][0].getReal() - m[0][0].getReal();
+            final double corr01 = o[0][1].getReal() - m[0][1].getReal();
+            final double corr02 = o[0][2].getReal() - m[0][2].getReal();
+            final double corr10 = o[1][0].getReal() - m[1][0].getReal();
+            final double corr11 = o[1][1].getReal() - m[1][1].getReal();
+            final double corr12 = o[1][2].getReal() - m[1][2].getReal();
+            final double corr20 = o[2][0].getReal() - m[2][0].getReal();
+            final double corr21 = o[2][1].getReal() - m[2][1].getReal();
+            final double corr22 = o[2][2].getReal() - m[2][2].getReal();
+
+            // Frobenius norm of the correction
+            fn1 = corr00 * corr00 + corr01 * corr01 + corr02 * corr02 +
+                  corr10 * corr10 + corr11 * corr11 + corr12 * corr12 +
+                  corr20 * corr20 + corr21 * corr21 + corr22 * corr22;
+
+            // convergence test
+            if (FastMath.abs(fn1 - fn) <= threshold) {
+                return o;
+            }
+
+            // prepare next iteration
+            x00 = o[0][0];
+            x01 = o[0][1];
+            x02 = o[0][2];
+            x10 = o[1][0];
+            x11 = o[1][1];
+            x12 = o[1][2];
+            x20 = o[2][0];
+            x21 = o[2][1];
+            x22 = o[2][2];
+            fn  = fn1;
+
+        }
+
+        // the algorithm did not converge after 10 iterations
+        throw new NotARotationMatrixException(LocalizedFormats.UNABLE_TO_ORTHOGONOLIZE_MATRIX,
+                                              i - 1);
+
+    }
+
+    /** Compute the <i>distance</i> between two rotations.
+     * <p>The <i>distance</i> is intended here as a way to check if two
+     * rotations are almost similar (i.e. they transform vectors the same way)
+     * or very different. It is mathematically defined as the angle of
+     * the rotation r that prepended to one of the rotations gives the other
+     * one:</p>
+     * <pre>
+     *        r<sub>1</sub>(r) = r<sub>2</sub>
+     * </pre>
+     * <p>This distance is an angle between 0 and &pi;. Its value is the smallest
+     * possible upper bound of the angle in radians between r<sub>1</sub>(v)
+     * and r<sub>2</sub>(v) for all possible vectors v. This upper bound is
+     * reached for some v. The distance is equal to 0 if and only if the two
+     * rotations are identical.</p>
+     * <p>Comparing two rotations should always be done using this value rather
+     * than for example comparing the components of the quaternions. It is much
+     * more stable, and has a geometric meaning. Also comparing quaternions
+     * components is error prone since for example quaternions (0.36, 0.48, -0.48, -0.64)
+     * and (-0.36, -0.48, 0.48, 0.64) represent exactly the same rotation despite
+     * their components are different (they are exact opposites).</p>
+     * @param r1 first rotation
+     * @param r2 second rotation
+     * @param <T> the type of the field elements
+     * @return <i>distance</i> between r1 and r2
+     */
+    public static <T extends RealFieldElement<T>> T distance(final FieldRotation<T> r1, final FieldRotation<T> r2) {
+        return r1.composeInverseInternal(r2).getAngle();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/FieldVector3D.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/FieldVector3D.java
new file mode 100644
index 0000000..0bd04e5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/FieldVector3D.java
@@ -0,0 +1,1185 @@
+/*
+ * 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.math3.geometry.euclidean.threed;
+
+import java.io.Serializable;
+import java.text.NumberFormat;
+
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * This class is a re-implementation of {@link Vector3D} using {@link RealFieldElement}.
+ * <p>Instance of this class are guaranteed to be immutable.</p>
+ * @param <T> the type of the field elements
+ * @since 3.2
+ */
+public class FieldVector3D<T extends RealFieldElement<T>> implements Serializable {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 20130224L;
+
+    /** Abscissa. */
+    private final T x;
+
+    /** Ordinate. */
+    private final T y;
+
+    /** Height. */
+    private final T z;
+
+    /** Simple constructor.
+     * Build a vector from its coordinates
+     * @param x abscissa
+     * @param y ordinate
+     * @param z height
+     * @see #getX()
+     * @see #getY()
+     * @see #getZ()
+     */
+    public FieldVector3D(final T x, final T y, final T z) {
+        this.x = x;
+        this.y = y;
+        this.z = z;
+    }
+
+    /** Simple constructor.
+     * Build a vector from its coordinates
+     * @param v coordinates array
+     * @exception DimensionMismatchException if array does not have 3 elements
+     * @see #toArray()
+     */
+    public FieldVector3D(final T[] v) throws DimensionMismatchException {
+        if (v.length != 3) {
+            throw new DimensionMismatchException(v.length, 3);
+        }
+        this.x = v[0];
+        this.y = v[1];
+        this.z = v[2];
+    }
+
+    /** Simple constructor.
+     * Build a vector from its azimuthal coordinates
+     * @param alpha azimuth (&alpha;) around Z
+     *              (0 is +X, &pi;/2 is +Y, &pi; is -X and 3&pi;/2 is -Y)
+     * @param delta elevation (&delta;) above (XY) plane, from -&pi;/2 to +&pi;/2
+     * @see #getAlpha()
+     * @see #getDelta()
+     */
+    public FieldVector3D(final T alpha, final T delta) {
+        T cosDelta = delta.cos();
+        this.x = alpha.cos().multiply(cosDelta);
+        this.y = alpha.sin().multiply(cosDelta);
+        this.z = delta.sin();
+    }
+
+    /** Multiplicative constructor
+     * Build a vector from another one and a scale factor.
+     * The vector built will be a * u
+     * @param a scale factor
+     * @param u base (unscaled) vector
+     */
+    public FieldVector3D(final T a, final FieldVector3D<T>u) {
+        this.x = a.multiply(u.x);
+        this.y = a.multiply(u.y);
+        this.z = a.multiply(u.z);
+    }
+
+    /** Multiplicative constructor
+     * Build a vector from another one and a scale factor.
+     * The vector built will be a * u
+     * @param a scale factor
+     * @param u base (unscaled) vector
+     */
+    public FieldVector3D(final T a, final Vector3D u) {
+        this.x = a.multiply(u.getX());
+        this.y = a.multiply(u.getY());
+        this.z = a.multiply(u.getZ());
+    }
+
+    /** Multiplicative constructor
+     * Build a vector from another one and a scale factor.
+     * The vector built will be a * u
+     * @param a scale factor
+     * @param u base (unscaled) vector
+     */
+    public FieldVector3D(final double a, final FieldVector3D<T> u) {
+        this.x = u.x.multiply(a);
+        this.y = u.y.multiply(a);
+        this.z = u.z.multiply(a);
+    }
+
+    /** Linear constructor
+     * Build a vector from two other ones and corresponding scale factors.
+     * The vector built will be a1 * u1 + a2 * u2
+     * @param a1 first scale factor
+     * @param u1 first base (unscaled) vector
+     * @param a2 second scale factor
+     * @param u2 second base (unscaled) vector
+     */
+    public FieldVector3D(final T a1, final FieldVector3D<T> u1,
+                         final T a2, final FieldVector3D<T> u2) {
+        final T prototype = a1;
+        this.x = prototype.linearCombination(a1, u1.getX(), a2, u2.getX());
+        this.y = prototype.linearCombination(a1, u1.getY(), a2, u2.getY());
+        this.z = prototype.linearCombination(a1, u1.getZ(), a2, u2.getZ());
+    }
+
+    /** Linear constructor
+     * Build a vector from two other ones and corresponding scale factors.
+     * The vector built will be a1 * u1 + a2 * u2
+     * @param a1 first scale factor
+     * @param u1 first base (unscaled) vector
+     * @param a2 second scale factor
+     * @param u2 second base (unscaled) vector
+     */
+    public FieldVector3D(final T a1, final Vector3D u1,
+                         final T a2, final Vector3D u2) {
+        final T prototype = a1;
+        this.x = prototype.linearCombination(u1.getX(), a1, u2.getX(), a2);
+        this.y = prototype.linearCombination(u1.getY(), a1, u2.getY(), a2);
+        this.z = prototype.linearCombination(u1.getZ(), a1, u2.getZ(), a2);
+    }
+
+    /** Linear constructor
+     * Build a vector from two other ones and corresponding scale factors.
+     * The vector built will be a1 * u1 + a2 * u2
+     * @param a1 first scale factor
+     * @param u1 first base (unscaled) vector
+     * @param a2 second scale factor
+     * @param u2 second base (unscaled) vector
+     */
+    public FieldVector3D(final double a1, final FieldVector3D<T> u1,
+                         final double a2, final FieldVector3D<T> u2) {
+        final T prototype = u1.getX();
+        this.x = prototype.linearCombination(a1, u1.getX(), a2, u2.getX());
+        this.y = prototype.linearCombination(a1, u1.getY(), a2, u2.getY());
+        this.z = prototype.linearCombination(a1, u1.getZ(), a2, u2.getZ());
+    }
+
+    /** Linear constructor
+     * Build a vector from three other ones and corresponding scale factors.
+     * The vector built will be a1 * u1 + a2 * u2 + a3 * u3
+     * @param a1 first scale factor
+     * @param u1 first base (unscaled) vector
+     * @param a2 second scale factor
+     * @param u2 second base (unscaled) vector
+     * @param a3 third scale factor
+     * @param u3 third base (unscaled) vector
+     */
+    public FieldVector3D(final T a1, final FieldVector3D<T> u1,
+                         final T a2, final FieldVector3D<T> u2,
+                         final T a3, final FieldVector3D<T> u3) {
+        final T prototype = a1;
+        this.x = prototype.linearCombination(a1, u1.getX(), a2, u2.getX(), a3, u3.getX());
+        this.y = prototype.linearCombination(a1, u1.getY(), a2, u2.getY(), a3, u3.getY());
+        this.z = prototype.linearCombination(a1, u1.getZ(), a2, u2.getZ(), a3, u3.getZ());
+    }
+
+    /** Linear constructor
+     * Build a vector from three other ones and corresponding scale factors.
+     * The vector built will be a1 * u1 + a2 * u2 + a3 * u3
+     * @param a1 first scale factor
+     * @param u1 first base (unscaled) vector
+     * @param a2 second scale factor
+     * @param u2 second base (unscaled) vector
+     * @param a3 third scale factor
+     * @param u3 third base (unscaled) vector
+     */
+    public FieldVector3D(final T a1, final Vector3D u1,
+                         final T a2, final Vector3D u2,
+                         final T a3, final Vector3D u3) {
+        final T prototype = a1;
+        this.x = prototype.linearCombination(u1.getX(), a1, u2.getX(), a2, u3.getX(), a3);
+        this.y = prototype.linearCombination(u1.getY(), a1, u2.getY(), a2, u3.getY(), a3);
+        this.z = prototype.linearCombination(u1.getZ(), a1, u2.getZ(), a2, u3.getZ(), a3);
+    }
+
+    /** Linear constructor
+     * Build a vector from three other ones and corresponding scale factors.
+     * The vector built will be a1 * u1 + a2 * u2 + a3 * u3
+     * @param a1 first scale factor
+     * @param u1 first base (unscaled) vector
+     * @param a2 second scale factor
+     * @param u2 second base (unscaled) vector
+     * @param a3 third scale factor
+     * @param u3 third base (unscaled) vector
+     */
+    public FieldVector3D(final double a1, final FieldVector3D<T> u1,
+                         final double a2, final FieldVector3D<T> u2,
+                         final double a3, final FieldVector3D<T> u3) {
+        final T prototype = u1.getX();
+        this.x = prototype.linearCombination(a1, u1.getX(), a2, u2.getX(), a3, u3.getX());
+        this.y = prototype.linearCombination(a1, u1.getY(), a2, u2.getY(), a3, u3.getY());
+        this.z = prototype.linearCombination(a1, u1.getZ(), a2, u2.getZ(), a3, u3.getZ());
+    }
+
+    /** Linear constructor
+     * Build a vector from four other ones and corresponding scale factors.
+     * The vector built will be a1 * u1 + a2 * u2 + a3 * u3 + a4 * u4
+     * @param a1 first scale factor
+     * @param u1 first base (unscaled) vector
+     * @param a2 second scale factor
+     * @param u2 second base (unscaled) vector
+     * @param a3 third scale factor
+     * @param u3 third base (unscaled) vector
+     * @param a4 fourth scale factor
+     * @param u4 fourth base (unscaled) vector
+     */
+    public FieldVector3D(final T a1, final FieldVector3D<T> u1,
+                         final T a2, final FieldVector3D<T> u2,
+                         final T a3, final FieldVector3D<T> u3,
+                         final T a4, final FieldVector3D<T> u4) {
+        final T prototype = a1;
+        this.x = prototype.linearCombination(a1, u1.getX(), a2, u2.getX(), a3, u3.getX(), a4, u4.getX());
+        this.y = prototype.linearCombination(a1, u1.getY(), a2, u2.getY(), a3, u3.getY(), a4, u4.getY());
+        this.z = prototype.linearCombination(a1, u1.getZ(), a2, u2.getZ(), a3, u3.getZ(), a4, u4.getZ());
+    }
+
+    /** Linear constructor
+     * Build a vector from four other ones and corresponding scale factors.
+     * The vector built will be a1 * u1 + a2 * u2 + a3 * u3 + a4 * u4
+     * @param a1 first scale factor
+     * @param u1 first base (unscaled) vector
+     * @param a2 second scale factor
+     * @param u2 second base (unscaled) vector
+     * @param a3 third scale factor
+     * @param u3 third base (unscaled) vector
+     * @param a4 fourth scale factor
+     * @param u4 fourth base (unscaled) vector
+     */
+    public FieldVector3D(final T a1, final Vector3D u1,
+                         final T a2, final Vector3D u2,
+                         final T a3, final Vector3D u3,
+                         final T a4, final Vector3D u4) {
+        final T prototype = a1;
+        this.x = prototype.linearCombination(u1.getX(), a1, u2.getX(), a2, u3.getX(), a3, u4.getX(), a4);
+        this.y = prototype.linearCombination(u1.getY(), a1, u2.getY(), a2, u3.getY(), a3, u4.getY(), a4);
+        this.z = prototype.linearCombination(u1.getZ(), a1, u2.getZ(), a2, u3.getZ(), a3, u4.getZ(), a4);
+    }
+
+    /** Linear constructor
+     * Build a vector from four other ones and corresponding scale factors.
+     * The vector built will be a1 * u1 + a2 * u2 + a3 * u3 + a4 * u4
+     * @param a1 first scale factor
+     * @param u1 first base (unscaled) vector
+     * @param a2 second scale factor
+     * @param u2 second base (unscaled) vector
+     * @param a3 third scale factor
+     * @param u3 third base (unscaled) vector
+     * @param a4 fourth scale factor
+     * @param u4 fourth base (unscaled) vector
+     */
+    public FieldVector3D(final double a1, final FieldVector3D<T> u1,
+                         final double a2, final FieldVector3D<T> u2,
+                         final double a3, final FieldVector3D<T> u3,
+                         final double a4, final FieldVector3D<T> u4) {
+        final T prototype = u1.getX();
+        this.x = prototype.linearCombination(a1, u1.getX(), a2, u2.getX(), a3, u3.getX(), a4, u4.getX());
+        this.y = prototype.linearCombination(a1, u1.getY(), a2, u2.getY(), a3, u3.getY(), a4, u4.getY());
+        this.z = prototype.linearCombination(a1, u1.getZ(), a2, u2.getZ(), a3, u3.getZ(), a4, u4.getZ());
+    }
+
+    /** Get the abscissa of the vector.
+     * @return abscissa of the vector
+     * @see #FieldVector3D(RealFieldElement, RealFieldElement, RealFieldElement)
+     */
+    public T getX() {
+        return x;
+    }
+
+    /** Get the ordinate of the vector.
+     * @return ordinate of the vector
+     * @see #FieldVector3D(RealFieldElement, RealFieldElement, RealFieldElement)
+     */
+    public T getY() {
+        return y;
+    }
+
+    /** Get the height of the vector.
+     * @return height of the vector
+     * @see #FieldVector3D(RealFieldElement, RealFieldElement, RealFieldElement)
+     */
+    public T getZ() {
+        return z;
+    }
+
+    /** Get the vector coordinates as a dimension 3 array.
+     * @return vector coordinates
+     * @see #FieldVector3D(RealFieldElement[])
+     */
+    public T[] toArray() {
+        final T[] array = MathArrays.buildArray(x.getField(), 3);
+        array[0] = x;
+        array[1] = y;
+        array[2] = z;
+        return array;
+    }
+
+    /** Convert to a constant vector without derivatives.
+     * @return a constant vector
+     */
+    public Vector3D toVector3D() {
+        return new Vector3D(x.getReal(), y.getReal(), z.getReal());
+    }
+
+    /** Get the L<sub>1</sub> norm for the vector.
+     * @return L<sub>1</sub> norm for the vector
+     */
+    public T getNorm1() {
+        return x.abs().add(y.abs()).add(z.abs());
+    }
+
+    /** Get the L<sub>2</sub> norm for the vector.
+     * @return Euclidean norm for the vector
+     */
+    public T getNorm() {
+        // there are no cancellation problems here, so we use the straightforward formula
+        return x.multiply(x).add(y.multiply(y)).add(z.multiply(z)).sqrt();
+    }
+
+    /** Get the square of the norm for the vector.
+     * @return square of the Euclidean norm for the vector
+     */
+    public T getNormSq() {
+        // there are no cancellation problems here, so we use the straightforward formula
+        return x.multiply(x).add(y.multiply(y)).add(z.multiply(z));
+    }
+
+    /** Get the L<sub>&infin;</sub> norm for the vector.
+     * @return L<sub>&infin;</sub> norm for the vector
+     */
+    public T getNormInf() {
+        final T xAbs = x.abs();
+        final T yAbs = y.abs();
+        final T zAbs = z.abs();
+        if (xAbs.getReal() <= yAbs.getReal()) {
+            if (yAbs.getReal() <= zAbs.getReal()) {
+                return zAbs;
+            } else {
+                return yAbs;
+            }
+        } else {
+            if (xAbs.getReal() <= zAbs.getReal()) {
+                return zAbs;
+            } else {
+                return xAbs;
+            }
+        }
+    }
+
+    /** Get the azimuth of the vector.
+     * @return azimuth (&alpha;) of the vector, between -&pi; and +&pi;
+     * @see #FieldVector3D(RealFieldElement, RealFieldElement)
+     */
+    public T getAlpha() {
+        return y.atan2(x);
+    }
+
+    /** Get the elevation of the vector.
+     * @return elevation (&delta;) of the vector, between -&pi;/2 and +&pi;/2
+     * @see #FieldVector3D(RealFieldElement, RealFieldElement)
+     */
+    public T getDelta() {
+        return z.divide(getNorm()).asin();
+    }
+
+    /** Add a vector to the instance.
+     * @param v vector to add
+     * @return a new vector
+     */
+    public FieldVector3D<T> add(final FieldVector3D<T> v) {
+        return new FieldVector3D<T>(x.add(v.x), y.add(v.y), z.add(v.z));
+    }
+
+    /** Add a vector to the instance.
+     * @param v vector to add
+     * @return a new vector
+     */
+    public FieldVector3D<T> add(final Vector3D v) {
+        return new FieldVector3D<T>(x.add(v.getX()), y.add(v.getY()), z.add(v.getZ()));
+    }
+
+    /** Add a scaled vector to the instance.
+     * @param factor scale factor to apply to v before adding it
+     * @param v vector to add
+     * @return a new vector
+     */
+    public FieldVector3D<T> add(final T factor, final FieldVector3D<T> v) {
+        return new FieldVector3D<T>(x.getField().getOne(), this, factor, v);
+    }
+
+    /** Add a scaled vector to the instance.
+     * @param factor scale factor to apply to v before adding it
+     * @param v vector to add
+     * @return a new vector
+     */
+    public FieldVector3D<T> add(final T factor, final Vector3D v) {
+        return new FieldVector3D<T>(x.add(factor.multiply(v.getX())),
+                                    y.add(factor.multiply(v.getY())),
+                                    z.add(factor.multiply(v.getZ())));
+    }
+
+    /** Add a scaled vector to the instance.
+     * @param factor scale factor to apply to v before adding it
+     * @param v vector to add
+     * @return a new vector
+     */
+    public FieldVector3D<T> add(final double factor, final FieldVector3D<T> v) {
+        return new FieldVector3D<T>(1.0, this, factor, v);
+    }
+
+    /** Add a scaled vector to the instance.
+     * @param factor scale factor to apply to v before adding it
+     * @param v vector to add
+     * @return a new vector
+     */
+    public FieldVector3D<T> add(final double factor, final Vector3D v) {
+        return new FieldVector3D<T>(x.add(factor * v.getX()),
+                                    y.add(factor * v.getY()),
+                                    z.add(factor * v.getZ()));
+    }
+
+    /** Subtract a vector from the instance.
+     * @param v vector to subtract
+     * @return a new vector
+     */
+    public FieldVector3D<T> subtract(final FieldVector3D<T> v) {
+        return new FieldVector3D<T>(x.subtract(v.x), y.subtract(v.y), z.subtract(v.z));
+    }
+
+    /** Subtract a vector from the instance.
+     * @param v vector to subtract
+     * @return a new vector
+     */
+    public FieldVector3D<T> subtract(final Vector3D v) {
+        return new FieldVector3D<T>(x.subtract(v.getX()), y.subtract(v.getY()), z.subtract(v.getZ()));
+    }
+
+    /** Subtract a scaled vector from the instance.
+     * @param factor scale factor to apply to v before subtracting it
+     * @param v vector to subtract
+     * @return a new vector
+     */
+    public FieldVector3D<T> subtract(final T factor, final FieldVector3D<T> v) {
+        return new FieldVector3D<T>(x.getField().getOne(), this, factor.negate(), v);
+    }
+
+    /** Subtract a scaled vector from the instance.
+     * @param factor scale factor to apply to v before subtracting it
+     * @param v vector to subtract
+     * @return a new vector
+     */
+    public FieldVector3D<T> subtract(final T factor, final Vector3D v) {
+        return new FieldVector3D<T>(x.subtract(factor.multiply(v.getX())),
+                                    y.subtract(factor.multiply(v.getY())),
+                                    z.subtract(factor.multiply(v.getZ())));
+    }
+
+    /** Subtract a scaled vector from the instance.
+     * @param factor scale factor to apply to v before subtracting it
+     * @param v vector to subtract
+     * @return a new vector
+     */
+    public FieldVector3D<T> subtract(final double factor, final FieldVector3D<T> v) {
+        return new FieldVector3D<T>(1.0, this, -factor, v);
+    }
+
+    /** Subtract a scaled vector from the instance.
+     * @param factor scale factor to apply to v before subtracting it
+     * @param v vector to subtract
+     * @return a new vector
+     */
+    public FieldVector3D<T> subtract(final double factor, final Vector3D v) {
+        return new FieldVector3D<T>(x.subtract(factor * v.getX()),
+                                    y.subtract(factor * v.getY()),
+                                    z.subtract(factor * v.getZ()));
+    }
+
+    /** Get a normalized vector aligned with the instance.
+     * @return a new normalized vector
+     * @exception MathArithmeticException if the norm is zero
+     */
+    public FieldVector3D<T> normalize() throws MathArithmeticException {
+        final T s = getNorm();
+        if (s.getReal() == 0) {
+            throw new MathArithmeticException(LocalizedFormats.CANNOT_NORMALIZE_A_ZERO_NORM_VECTOR);
+        }
+        return scalarMultiply(s.reciprocal());
+    }
+
+    /** Get a vector orthogonal to the instance.
+     * <p>There are an infinite number of normalized vectors orthogonal
+     * to the instance. This method picks up one of them almost
+     * arbitrarily. It is useful when one needs to compute a reference
+     * frame with one of the axes in a predefined direction. The
+     * following example shows how to build a frame having the k axis
+     * aligned with the known vector u :
+     * <pre><code>
+     *   Vector3D k = u.normalize();
+     *   Vector3D i = k.orthogonal();
+     *   Vector3D j = Vector3D.crossProduct(k, i);
+     * </code></pre></p>
+     * @return a new normalized vector orthogonal to the instance
+     * @exception MathArithmeticException if the norm of the instance is null
+     */
+    public FieldVector3D<T> orthogonal() throws MathArithmeticException {
+
+        final double threshold = 0.6 * getNorm().getReal();
+        if (threshold == 0) {
+            throw new MathArithmeticException(LocalizedFormats.ZERO_NORM);
+        }
+
+        if (FastMath.abs(x.getReal()) <= threshold) {
+            final T inverse  = y.multiply(y).add(z.multiply(z)).sqrt().reciprocal();
+            return new FieldVector3D<T>(inverse.getField().getZero(), inverse.multiply(z), inverse.multiply(y).negate());
+        } else if (FastMath.abs(y.getReal()) <= threshold) {
+            final T inverse  = x.multiply(x).add(z.multiply(z)).sqrt().reciprocal();
+            return new FieldVector3D<T>(inverse.multiply(z).negate(), inverse.getField().getZero(), inverse.multiply(x));
+        } else {
+            final T inverse  = x.multiply(x).add(y.multiply(y)).sqrt().reciprocal();
+            return new FieldVector3D<T>(inverse.multiply(y), inverse.multiply(x).negate(), inverse.getField().getZero());
+        }
+
+    }
+
+    /** Compute the angular separation between two vectors.
+     * <p>This method computes the angular separation between two
+     * vectors using the dot product for well separated vectors and the
+     * cross product for almost aligned vectors. This allows to have a
+     * good accuracy in all cases, even for vectors very close to each
+     * other.</p>
+     * @param v1 first vector
+     * @param v2 second vector
+     * @param <T> the type of the field elements
+     * @return angular separation between v1 and v2
+     * @exception MathArithmeticException if either vector has a null norm
+     */
+    public static <T extends RealFieldElement<T>> T angle(final FieldVector3D<T> v1, final FieldVector3D<T> v2)
+        throws MathArithmeticException {
+
+        final T normProduct = v1.getNorm().multiply(v2.getNorm());
+        if (normProduct.getReal() == 0) {
+            throw new MathArithmeticException(LocalizedFormats.ZERO_NORM);
+        }
+
+        final T dot = dotProduct(v1, v2);
+        final double threshold = normProduct.getReal() * 0.9999;
+        if ((dot.getReal() < -threshold) || (dot.getReal() > threshold)) {
+            // the vectors are almost aligned, compute using the sine
+            FieldVector3D<T> v3 = crossProduct(v1, v2);
+            if (dot.getReal() >= 0) {
+                return v3.getNorm().divide(normProduct).asin();
+            }
+            return v3.getNorm().divide(normProduct).asin().subtract(FastMath.PI).negate();
+        }
+
+        // the vectors are sufficiently separated to use the cosine
+        return dot.divide(normProduct).acos();
+
+    }
+
+    /** Compute the angular separation between two vectors.
+     * <p>This method computes the angular separation between two
+     * vectors using the dot product for well separated vectors and the
+     * cross product for almost aligned vectors. This allows to have a
+     * good accuracy in all cases, even for vectors very close to each
+     * other.</p>
+     * @param v1 first vector
+     * @param v2 second vector
+     * @param <T> the type of the field elements
+     * @return angular separation between v1 and v2
+     * @exception MathArithmeticException if either vector has a null norm
+     */
+    public static <T extends RealFieldElement<T>> T angle(final FieldVector3D<T> v1, final Vector3D v2)
+        throws MathArithmeticException {
+
+        final T normProduct = v1.getNorm().multiply(v2.getNorm());
+        if (normProduct.getReal() == 0) {
+            throw new MathArithmeticException(LocalizedFormats.ZERO_NORM);
+        }
+
+        final T dot = dotProduct(v1, v2);
+        final double threshold = normProduct.getReal() * 0.9999;
+        if ((dot.getReal() < -threshold) || (dot.getReal() > threshold)) {
+            // the vectors are almost aligned, compute using the sine
+            FieldVector3D<T> v3 = crossProduct(v1, v2);
+            if (dot.getReal() >= 0) {
+                return v3.getNorm().divide(normProduct).asin();
+            }
+            return v3.getNorm().divide(normProduct).asin().subtract(FastMath.PI).negate();
+        }
+
+        // the vectors are sufficiently separated to use the cosine
+        return dot.divide(normProduct).acos();
+
+    }
+
+    /** Compute the angular separation between two vectors.
+     * <p>This method computes the angular separation between two
+     * vectors using the dot product for well separated vectors and the
+     * cross product for almost aligned vectors. This allows to have a
+     * good accuracy in all cases, even for vectors very close to each
+     * other.</p>
+     * @param v1 first vector
+     * @param v2 second vector
+     * @param <T> the type of the field elements
+     * @return angular separation between v1 and v2
+     * @exception MathArithmeticException if either vector has a null norm
+     */
+    public static <T extends RealFieldElement<T>> T angle(final Vector3D v1, final FieldVector3D<T> v2)
+        throws MathArithmeticException {
+        return angle(v2, v1);
+    }
+
+    /** Get the opposite of the instance.
+     * @return a new vector which is opposite to the instance
+     */
+    public FieldVector3D<T> negate() {
+        return new FieldVector3D<T>(x.negate(), y.negate(), z.negate());
+    }
+
+    /** Multiply the instance by a scalar.
+     * @param a scalar
+     * @return a new vector
+     */
+    public FieldVector3D<T> scalarMultiply(final T a) {
+        return new FieldVector3D<T>(x.multiply(a), y.multiply(a), z.multiply(a));
+    }
+
+    /** Multiply the instance by a scalar.
+     * @param a scalar
+     * @return a new vector
+     */
+    public FieldVector3D<T> scalarMultiply(final double a) {
+        return new FieldVector3D<T>(x.multiply(a), y.multiply(a), z.multiply(a));
+    }
+
+    /**
+     * Returns true if any coordinate of this vector is NaN; false otherwise
+     * @return  true if any coordinate of this vector is NaN; false otherwise
+     */
+    public boolean isNaN() {
+        return Double.isNaN(x.getReal()) || Double.isNaN(y.getReal()) || Double.isNaN(z.getReal());
+    }
+
+    /**
+     * Returns true if any coordinate of this vector is infinite and none are NaN;
+     * false otherwise
+     * @return  true if any coordinate of this vector is infinite and none are NaN;
+     * false otherwise
+     */
+    public boolean isInfinite() {
+        return !isNaN() && (Double.isInfinite(x.getReal()) || Double.isInfinite(y.getReal()) || Double.isInfinite(z.getReal()));
+    }
+
+    /**
+     * Test for the equality of two 3D vectors.
+     * <p>
+     * If all coordinates of two 3D vectors are exactly the same, and none of their
+     * {@link RealFieldElement#getReal() real part} are <code>NaN</code>, the
+     * two 3D vectors are considered to be equal.
+     * </p>
+     * <p>
+     * <code>NaN</code> coordinates are considered to affect globally the vector
+     * and be equals to each other - i.e, if either (or all) real part of the
+     * coordinates of the 3D vector are <code>NaN</code>, the 3D vector is <code>NaN</code>.
+     * </p>
+     *
+     * @param other Object to test for equality to this
+     * @return true if two 3D vector objects are equal, false if
+     *         object is null, not an instance of Vector3D, or
+     *         not equal to this Vector3D instance
+     *
+     */
+    @Override
+    public boolean equals(Object other) {
+
+        if (this == other) {
+            return true;
+        }
+
+        if (other instanceof FieldVector3D) {
+            @SuppressWarnings("unchecked")
+            final FieldVector3D<T> rhs = (FieldVector3D<T>) other;
+            if (rhs.isNaN()) {
+                return this.isNaN();
+            }
+
+            return x.equals(rhs.x) && y.equals(rhs.y) && z.equals(rhs.z);
+
+        }
+        return false;
+    }
+
+    /**
+     * Get a hashCode for the 3D vector.
+     * <p>
+     * All NaN values have the same hash code.</p>
+     *
+     * @return a hash code value for this object
+     */
+    @Override
+    public int hashCode() {
+        if (isNaN()) {
+            return 409;
+        }
+        return 311 * (107 * x.hashCode() + 83 * y.hashCode() +  z.hashCode());
+    }
+
+    /** Compute the dot-product of the instance and another vector.
+     * <p>
+     * The implementation uses specific multiplication and addition
+     * algorithms to preserve accuracy and reduce cancellation effects.
+     * It should be very accurate even for nearly orthogonal vectors.
+     * </p>
+     * @see MathArrays#linearCombination(double, double, double, double, double, double)
+     * @param v second vector
+     * @return the dot product this.v
+     */
+    public T dotProduct(final FieldVector3D<T> v) {
+        return x.linearCombination(x, v.x, y, v.y, z, v.z);
+    }
+
+    /** Compute the dot-product of the instance and another vector.
+     * <p>
+     * The implementation uses specific multiplication and addition
+     * algorithms to preserve accuracy and reduce cancellation effects.
+     * It should be very accurate even for nearly orthogonal vectors.
+     * </p>
+     * @see MathArrays#linearCombination(double, double, double, double, double, double)
+     * @param v second vector
+     * @return the dot product this.v
+     */
+    public T dotProduct(final Vector3D v) {
+        return x.linearCombination(v.getX(), x, v.getY(), y, v.getZ(), z);
+    }
+
+    /** Compute the cross-product of the instance with another vector.
+     * @param v other vector
+     * @return the cross product this ^ v as a new Vector3D
+     */
+    public FieldVector3D<T> crossProduct(final FieldVector3D<T> v) {
+        return new FieldVector3D<T>(x.linearCombination(y, v.z, z.negate(), v.y),
+                                    y.linearCombination(z, v.x, x.negate(), v.z),
+                                    z.linearCombination(x, v.y, y.negate(), v.x));
+    }
+
+    /** Compute the cross-product of the instance with another vector.
+     * @param v other vector
+     * @return the cross product this ^ v as a new Vector3D
+     */
+    public FieldVector3D<T> crossProduct(final Vector3D v) {
+        return new FieldVector3D<T>(x.linearCombination(v.getZ(), y, -v.getY(), z),
+                                    y.linearCombination(v.getX(), z, -v.getZ(), x),
+                                    z.linearCombination(v.getY(), x, -v.getX(), y));
+    }
+
+    /** Compute the distance between the instance and another vector according to the L<sub>1</sub> norm.
+     * <p>Calling this method is equivalent to calling:
+     * <code>q.subtract(p).getNorm1()</code> except that no intermediate
+     * vector is built</p>
+     * @param v second vector
+     * @return the distance between the instance and p according to the L<sub>1</sub> norm
+     */
+    public T distance1(final FieldVector3D<T> v) {
+        final T dx = v.x.subtract(x).abs();
+        final T dy = v.y.subtract(y).abs();
+        final T dz = v.z.subtract(z).abs();
+        return dx.add(dy).add(dz);
+    }
+
+    /** Compute the distance between the instance and another vector according to the L<sub>1</sub> norm.
+     * <p>Calling this method is equivalent to calling:
+     * <code>q.subtract(p).getNorm1()</code> except that no intermediate
+     * vector is built</p>
+     * @param v second vector
+     * @return the distance between the instance and p according to the L<sub>1</sub> norm
+     */
+    public T distance1(final Vector3D v) {
+        final T dx = x.subtract(v.getX()).abs();
+        final T dy = y.subtract(v.getY()).abs();
+        final T dz = z.subtract(v.getZ()).abs();
+        return dx.add(dy).add(dz);
+    }
+
+    /** Compute the distance between the instance and another vector according to the L<sub>2</sub> norm.
+     * <p>Calling this method is equivalent to calling:
+     * <code>q.subtract(p).getNorm()</code> except that no intermediate
+     * vector is built</p>
+     * @param v second vector
+     * @return the distance between the instance and p according to the L<sub>2</sub> norm
+     */
+    public T distance(final FieldVector3D<T> v) {
+        final T dx = v.x.subtract(x);
+        final T dy = v.y.subtract(y);
+        final T dz = v.z.subtract(z);
+        return dx.multiply(dx).add(dy.multiply(dy)).add(dz.multiply(dz)).sqrt();
+    }
+
+    /** Compute the distance between the instance and another vector according to the L<sub>2</sub> norm.
+     * <p>Calling this method is equivalent to calling:
+     * <code>q.subtract(p).getNorm()</code> except that no intermediate
+     * vector is built</p>
+     * @param v second vector
+     * @return the distance between the instance and p according to the L<sub>2</sub> norm
+     */
+    public T distance(final Vector3D v) {
+        final T dx = x.subtract(v.getX());
+        final T dy = y.subtract(v.getY());
+        final T dz = z.subtract(v.getZ());
+        return dx.multiply(dx).add(dy.multiply(dy)).add(dz.multiply(dz)).sqrt();
+    }
+
+    /** Compute the distance between the instance and another vector according to the L<sub>&infin;</sub> norm.
+     * <p>Calling this method is equivalent to calling:
+     * <code>q.subtract(p).getNormInf()</code> except that no intermediate
+     * vector is built</p>
+     * @param v second vector
+     * @return the distance between the instance and p according to the L<sub>&infin;</sub> norm
+     */
+    public T distanceInf(final FieldVector3D<T> v) {
+        final T dx = v.x.subtract(x).abs();
+        final T dy = v.y.subtract(y).abs();
+        final T dz = v.z.subtract(z).abs();
+        if (dx.getReal() <= dy.getReal()) {
+            if (dy.getReal() <= dz.getReal()) {
+                return dz;
+            } else {
+                return dy;
+            }
+        } else {
+            if (dx.getReal() <= dz.getReal()) {
+                return dz;
+            } else {
+                return dx;
+            }
+        }
+    }
+
+    /** Compute the distance between the instance and another vector according to the L<sub>&infin;</sub> norm.
+     * <p>Calling this method is equivalent to calling:
+     * <code>q.subtract(p).getNormInf()</code> except that no intermediate
+     * vector is built</p>
+     * @param v second vector
+     * @return the distance between the instance and p according to the L<sub>&infin;</sub> norm
+     */
+    public T distanceInf(final Vector3D v) {
+        final T dx = x.subtract(v.getX()).abs();
+        final T dy = y.subtract(v.getY()).abs();
+        final T dz = z.subtract(v.getZ()).abs();
+        if (dx.getReal() <= dy.getReal()) {
+            if (dy.getReal() <= dz.getReal()) {
+                return dz;
+            } else {
+                return dy;
+            }
+        } else {
+            if (dx.getReal() <= dz.getReal()) {
+                return dz;
+            } else {
+                return dx;
+            }
+        }
+    }
+
+    /** Compute the square of the distance between the instance and another vector.
+     * <p>Calling this method is equivalent to calling:
+     * <code>q.subtract(p).getNormSq()</code> except that no intermediate
+     * vector is built</p>
+     * @param v second vector
+     * @return the square of the distance between the instance and p
+     */
+    public T distanceSq(final FieldVector3D<T> v) {
+        final T dx = v.x.subtract(x);
+        final T dy = v.y.subtract(y);
+        final T dz = v.z.subtract(z);
+        return dx.multiply(dx).add(dy.multiply(dy)).add(dz.multiply(dz));
+    }
+
+    /** Compute the square of the distance between the instance and another vector.
+     * <p>Calling this method is equivalent to calling:
+     * <code>q.subtract(p).getNormSq()</code> except that no intermediate
+     * vector is built</p>
+     * @param v second vector
+     * @return the square of the distance between the instance and p
+     */
+    public T distanceSq(final Vector3D v) {
+        final T dx = x.subtract(v.getX());
+        final T dy = y.subtract(v.getY());
+        final T dz = z.subtract(v.getZ());
+        return dx.multiply(dx).add(dy.multiply(dy)).add(dz.multiply(dz));
+    }
+
+    /** Compute the dot-product of two vectors.
+     * @param v1 first vector
+     * @param v2 second vector
+     * @param <T> the type of the field elements
+     * @return the dot product v1.v2
+     */
+    public static <T extends RealFieldElement<T>> T dotProduct(final FieldVector3D<T> v1,
+                                                                   final FieldVector3D<T> v2) {
+        return v1.dotProduct(v2);
+    }
+
+    /** Compute the dot-product of two vectors.
+     * @param v1 first vector
+     * @param v2 second vector
+     * @param <T> the type of the field elements
+     * @return the dot product v1.v2
+     */
+    public static <T extends RealFieldElement<T>> T dotProduct(final FieldVector3D<T> v1,
+                                                                   final Vector3D v2) {
+        return v1.dotProduct(v2);
+    }
+
+    /** Compute the dot-product of two vectors.
+     * @param v1 first vector
+     * @param v2 second vector
+     * @param <T> the type of the field elements
+     * @return the dot product v1.v2
+     */
+    public static <T extends RealFieldElement<T>> T dotProduct(final Vector3D v1,
+                                                                   final FieldVector3D<T> v2) {
+        return v2.dotProduct(v1);
+    }
+
+    /** Compute the cross-product of two vectors.
+     * @param v1 first vector
+     * @param v2 second vector
+     * @param <T> the type of the field elements
+     * @return the cross product v1 ^ v2 as a new Vector
+     */
+    public static <T extends RealFieldElement<T>> FieldVector3D<T> crossProduct(final FieldVector3D<T> v1,
+                                                                                    final FieldVector3D<T> v2) {
+        return v1.crossProduct(v2);
+    }
+
+    /** Compute the cross-product of two vectors.
+     * @param v1 first vector
+     * @param v2 second vector
+     * @param <T> the type of the field elements
+     * @return the cross product v1 ^ v2 as a new Vector
+     */
+    public static <T extends RealFieldElement<T>> FieldVector3D<T> crossProduct(final FieldVector3D<T> v1,
+                                                                                    final Vector3D v2) {
+        return v1.crossProduct(v2);
+    }
+
+    /** Compute the cross-product of two vectors.
+     * @param v1 first vector
+     * @param v2 second vector
+     * @param <T> the type of the field elements
+     * @return the cross product v1 ^ v2 as a new Vector
+     */
+    public static <T extends RealFieldElement<T>> FieldVector3D<T> crossProduct(final Vector3D v1,
+                                                                                    final FieldVector3D<T> v2) {
+        return new FieldVector3D<T>(v2.x.linearCombination(v1.getY(), v2.z, -v1.getZ(), v2.y),
+                                    v2.y.linearCombination(v1.getZ(), v2.x, -v1.getX(), v2.z),
+                                    v2.z.linearCombination(v1.getX(), v2.y, -v1.getY(), v2.x));
+    }
+
+    /** Compute the distance between two vectors according to the L<sub>1</sub> norm.
+     * <p>Calling this method is equivalent to calling:
+     * <code>v1.subtract(v2).getNorm1()</code> except that no intermediate
+     * vector is built</p>
+     * @param v1 first vector
+     * @param v2 second vector
+     * @param <T> the type of the field elements
+     * @return the distance between v1 and v2 according to the L<sub>1</sub> norm
+     */
+    public static <T extends RealFieldElement<T>> T distance1(final FieldVector3D<T> v1,
+                                                                  final FieldVector3D<T> v2) {
+        return v1.distance1(v2);
+    }
+
+    /** Compute the distance between two vectors according to the L<sub>1</sub> norm.
+     * <p>Calling this method is equivalent to calling:
+     * <code>v1.subtract(v2).getNorm1()</code> except that no intermediate
+     * vector is built</p>
+     * @param v1 first vector
+     * @param v2 second vector
+     * @param <T> the type of the field elements
+     * @return the distance between v1 and v2 according to the L<sub>1</sub> norm
+     */
+    public static <T extends RealFieldElement<T>> T distance1(final FieldVector3D<T> v1,
+                                                                  final Vector3D v2) {
+        return v1.distance1(v2);
+    }
+
+    /** Compute the distance between two vectors according to the L<sub>1</sub> norm.
+     * <p>Calling this method is equivalent to calling:
+     * <code>v1.subtract(v2).getNorm1()</code> except that no intermediate
+     * vector is built</p>
+     * @param v1 first vector
+     * @param v2 second vector
+     * @param <T> the type of the field elements
+     * @return the distance between v1 and v2 according to the L<sub>1</sub> norm
+     */
+    public static <T extends RealFieldElement<T>> T distance1(final Vector3D v1,
+                                                                  final FieldVector3D<T> v2) {
+        return v2.distance1(v1);
+    }
+
+    /** Compute the distance between two vectors according to the L<sub>2</sub> norm.
+     * <p>Calling this method is equivalent to calling:
+     * <code>v1.subtract(v2).getNorm()</code> except that no intermediate
+     * vector is built</p>
+     * @param v1 first vector
+     * @param v2 second vector
+     * @param <T> the type of the field elements
+     * @return the distance between v1 and v2 according to the L<sub>2</sub> norm
+     */
+    public static <T extends RealFieldElement<T>> T distance(final FieldVector3D<T> v1,
+                                                                 final FieldVector3D<T> v2) {
+        return v1.distance(v2);
+    }
+
+    /** Compute the distance between two vectors according to the L<sub>2</sub> norm.
+     * <p>Calling this method is equivalent to calling:
+     * <code>v1.subtract(v2).getNorm()</code> except that no intermediate
+     * vector is built</p>
+     * @param v1 first vector
+     * @param v2 second vector
+     * @param <T> the type of the field elements
+     * @return the distance between v1 and v2 according to the L<sub>2</sub> norm
+     */
+    public static <T extends RealFieldElement<T>> T distance(final FieldVector3D<T> v1,
+                                                                 final Vector3D v2) {
+        return v1.distance(v2);
+    }
+
+    /** Compute the distance between two vectors according to the L<sub>2</sub> norm.
+     * <p>Calling this method is equivalent to calling:
+     * <code>v1.subtract(v2).getNorm()</code> except that no intermediate
+     * vector is built</p>
+     * @param v1 first vector
+     * @param v2 second vector
+     * @param <T> the type of the field elements
+     * @return the distance between v1 and v2 according to the L<sub>2</sub> norm
+     */
+    public static <T extends RealFieldElement<T>> T distance(final Vector3D v1,
+                                                                 final FieldVector3D<T> v2) {
+        return v2.distance(v1);
+    }
+
+    /** Compute the distance between two vectors according to the L<sub>&infin;</sub> norm.
+     * <p>Calling this method is equivalent to calling:
+     * <code>v1.subtract(v2).getNormInf()</code> except that no intermediate
+     * vector is built</p>
+     * @param v1 first vector
+     * @param v2 second vector
+     * @param <T> the type of the field elements
+     * @return the distance between v1 and v2 according to the L<sub>&infin;</sub> norm
+     */
+    public static <T extends RealFieldElement<T>> T distanceInf(final FieldVector3D<T> v1,
+                                                                    final FieldVector3D<T> v2) {
+        return v1.distanceInf(v2);
+    }
+
+    /** Compute the distance between two vectors according to the L<sub>&infin;</sub> norm.
+     * <p>Calling this method is equivalent to calling:
+     * <code>v1.subtract(v2).getNormInf()</code> except that no intermediate
+     * vector is built</p>
+     * @param v1 first vector
+     * @param v2 second vector
+     * @param <T> the type of the field elements
+     * @return the distance between v1 and v2 according to the L<sub>&infin;</sub> norm
+     */
+    public static <T extends RealFieldElement<T>> T distanceInf(final FieldVector3D<T> v1,
+                                                                    final Vector3D v2) {
+        return v1.distanceInf(v2);
+    }
+
+    /** Compute the distance between two vectors according to the L<sub>&infin;</sub> norm.
+     * <p>Calling this method is equivalent to calling:
+     * <code>v1.subtract(v2).getNormInf()</code> except that no intermediate
+     * vector is built</p>
+     * @param v1 first vector
+     * @param v2 second vector
+     * @param <T> the type of the field elements
+     * @return the distance between v1 and v2 according to the L<sub>&infin;</sub> norm
+     */
+    public static <T extends RealFieldElement<T>> T distanceInf(final Vector3D v1,
+                                                                    final FieldVector3D<T> v2) {
+        return v2.distanceInf(v1);
+    }
+
+    /** Compute the square of the distance between two vectors.
+     * <p>Calling this method is equivalent to calling:
+     * <code>v1.subtract(v2).getNormSq()</code> except that no intermediate
+     * vector is built</p>
+     * @param v1 first vector
+     * @param v2 second vector
+     * @param <T> the type of the field elements
+     * @return the square of the distance between v1 and v2
+     */
+    public static <T extends RealFieldElement<T>> T distanceSq(final FieldVector3D<T> v1,
+                                                                   final FieldVector3D<T> v2) {
+        return v1.distanceSq(v2);
+    }
+
+    /** Compute the square of the distance between two vectors.
+     * <p>Calling this method is equivalent to calling:
+     * <code>v1.subtract(v2).getNormSq()</code> except that no intermediate
+     * vector is built</p>
+     * @param v1 first vector
+     * @param v2 second vector
+     * @param <T> the type of the field elements
+     * @return the square of the distance between v1 and v2
+     */
+    public static <T extends RealFieldElement<T>> T distanceSq(final FieldVector3D<T> v1,
+                                                                   final Vector3D v2) {
+        return v1.distanceSq(v2);
+    }
+
+    /** Compute the square of the distance between two vectors.
+     * <p>Calling this method is equivalent to calling:
+     * <code>v1.subtract(v2).getNormSq()</code> except that no intermediate
+     * vector is built</p>
+     * @param v1 first vector
+     * @param v2 second vector
+     * @param <T> the type of the field elements
+     * @return the square of the distance between v1 and v2
+     */
+    public static <T extends RealFieldElement<T>> T distanceSq(final Vector3D v1,
+                                                                   final FieldVector3D<T> v2) {
+        return v2.distanceSq(v1);
+    }
+
+    /** Get a string representation of this vector.
+     * @return a string representation of this vector
+     */
+    @Override
+    public String toString() {
+        return Vector3DFormat.getInstance().format(toVector3D());
+    }
+
+    /** Get a string representation of this vector.
+     * @param format the custom format for components
+     * @return a string representation of this vector
+     */
+    public String toString(final NumberFormat format) {
+        return new Vector3DFormat(format).format(toVector3D());
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/Line.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/Line.java
new file mode 100644
index 0000000..e234495
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/Line.java
@@ -0,0 +1,275 @@
+/*
+ * 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.math3.geometry.euclidean.threed;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.Vector;
+import org.apache.commons.math3.geometry.euclidean.oned.Euclidean1D;
+import org.apache.commons.math3.geometry.euclidean.oned.IntervalsSet;
+import org.apache.commons.math3.geometry.euclidean.oned.Vector1D;
+import org.apache.commons.math3.geometry.partitioning.Embedding;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.Precision;
+
+/** The class represent lines in a three dimensional space.
+
+ * <p>Each oriented line is intrinsically associated with an abscissa
+ * which is a coordinate on the line. The point at abscissa 0 is the
+ * orthogonal projection of the origin on the line, another equivalent
+ * way to express this is to say that it is the point of the line
+ * which is closest to the origin. Abscissa increases in the line
+ * direction.</p>
+
+ * @since 3.0
+ */
+public class Line implements Embedding<Euclidean3D, Euclidean1D> {
+
+    /** Default value for tolerance. */
+    private static final double DEFAULT_TOLERANCE = 1.0e-10;
+
+    /** Line direction. */
+    private Vector3D direction;
+
+    /** Line point closest to the origin. */
+    private Vector3D zero;
+
+    /** Tolerance below which points are considered identical. */
+    private final double tolerance;
+
+    /** Build a line from two points.
+     * @param p1 first point belonging to the line (this can be any point)
+     * @param p2 second point belonging to the line (this can be any point, different from p1)
+     * @param tolerance tolerance below which points are considered identical
+     * @exception MathIllegalArgumentException if the points are equal
+     * @since 3.3
+     */
+    public Line(final Vector3D p1, final Vector3D p2, final double tolerance)
+        throws MathIllegalArgumentException {
+        reset(p1, p2);
+        this.tolerance = tolerance;
+    }
+
+    /** Copy constructor.
+     * <p>The created instance is completely independent from the
+     * original instance, it is a deep copy.</p>
+     * @param line line to copy
+     */
+    public Line(final Line line) {
+        this.direction = line.direction;
+        this.zero      = line.zero;
+        this.tolerance = line.tolerance;
+    }
+
+    /** Build a line from two points.
+     * @param p1 first point belonging to the line (this can be any point)
+     * @param p2 second point belonging to the line (this can be any point, different from p1)
+     * @exception MathIllegalArgumentException if the points are equal
+     * @deprecated as of 3.3, replaced with {@link #Line(Vector3D, Vector3D, double)}
+     */
+    @Deprecated
+    public Line(final Vector3D p1, final Vector3D p2) throws MathIllegalArgumentException {
+        this(p1, p2, DEFAULT_TOLERANCE);
+    }
+
+    /** Reset the instance as if built from two points.
+     * @param p1 first point belonging to the line (this can be any point)
+     * @param p2 second point belonging to the line (this can be any point, different from p1)
+     * @exception MathIllegalArgumentException if the points are equal
+     */
+    public void reset(final Vector3D p1, final Vector3D p2) throws MathIllegalArgumentException {
+        final Vector3D delta = p2.subtract(p1);
+        final double norm2 = delta.getNormSq();
+        if (norm2 == 0.0) {
+            throw new MathIllegalArgumentException(LocalizedFormats.ZERO_NORM);
+        }
+        this.direction = new Vector3D(1.0 / FastMath.sqrt(norm2), delta);
+        zero = new Vector3D(1.0, p1, -p1.dotProduct(delta) / norm2, delta);
+    }
+
+    /** Get the tolerance below which points are considered identical.
+     * @return tolerance below which points are considered identical
+     * @since 3.3
+     */
+    public double getTolerance() {
+        return tolerance;
+    }
+
+    /** Get a line with reversed direction.
+     * @return a new instance, with reversed direction
+     */
+    public Line revert() {
+        final Line reverted = new Line(this);
+        reverted.direction = reverted.direction.negate();
+        return reverted;
+    }
+
+    /** Get the normalized direction vector.
+     * @return normalized direction vector
+     */
+    public Vector3D getDirection() {
+        return direction;
+    }
+
+    /** Get the line point closest to the origin.
+     * @return line point closest to the origin
+     */
+    public Vector3D getOrigin() {
+        return zero;
+    }
+
+    /** Get the abscissa of a point with respect to the line.
+     * <p>The abscissa is 0 if the projection of the point and the
+     * projection of the frame origin on the line are the same
+     * point.</p>
+     * @param point point to check
+     * @return abscissa of the point
+     */
+    public double getAbscissa(final Vector3D point) {
+        return point.subtract(zero).dotProduct(direction);
+    }
+
+    /** Get one point from the line.
+     * @param abscissa desired abscissa for the point
+     * @return one point belonging to the line, at specified abscissa
+     */
+    public Vector3D pointAt(final double abscissa) {
+        return new Vector3D(1.0, zero, abscissa, direction);
+    }
+
+    /** Transform a space point into a sub-space point.
+     * @param vector n-dimension point of the space
+     * @return (n-1)-dimension point of the sub-space corresponding to
+     * the specified space point
+     */
+    public Vector1D toSubSpace(Vector<Euclidean3D> vector) {
+        return toSubSpace((Point<Euclidean3D>) vector);
+    }
+
+    /** Transform a sub-space point into a space point.
+     * @param vector (n-1)-dimension point of the sub-space
+     * @return n-dimension point of the space corresponding to the
+     * specified sub-space point
+     */
+    public Vector3D toSpace(Vector<Euclidean1D> vector) {
+        return toSpace((Point<Euclidean1D>) vector);
+    }
+
+    /** {@inheritDoc}
+     * @see #getAbscissa(Vector3D)
+     */
+    public Vector1D toSubSpace(final Point<Euclidean3D> point) {
+        return new Vector1D(getAbscissa((Vector3D) point));
+    }
+
+    /** {@inheritDoc}
+     * @see #pointAt(double)
+     */
+    public Vector3D toSpace(final Point<Euclidean1D> point) {
+        return pointAt(((Vector1D) point).getX());
+    }
+
+    /** Check if the instance is similar to another line.
+     * <p>Lines are considered similar if they contain the same
+     * points. This does not mean they are equal since they can have
+     * opposite directions.</p>
+     * @param line line to which instance should be compared
+     * @return true if the lines are similar
+     */
+    public boolean isSimilarTo(final Line line) {
+        final double angle = Vector3D.angle(direction, line.direction);
+        return ((angle < tolerance) || (angle > (FastMath.PI - tolerance))) && contains(line.zero);
+    }
+
+    /** Check if the instance contains a point.
+     * @param p point to check
+     * @return true if p belongs to the line
+     */
+    public boolean contains(final Vector3D p) {
+        return distance(p) < tolerance;
+    }
+
+    /** Compute the distance between the instance and a point.
+     * @param p to check
+     * @return distance between the instance and the point
+     */
+    public double distance(final Vector3D p) {
+        final Vector3D d = p.subtract(zero);
+        final Vector3D n = new Vector3D(1.0, d, -d.dotProduct(direction), direction);
+        return n.getNorm();
+    }
+
+    /** Compute the shortest distance between the instance and another line.
+     * @param line line to check against the instance
+     * @return shortest distance between the instance and the line
+     */
+    public double distance(final Line line) {
+
+        final Vector3D normal = Vector3D.crossProduct(direction, line.direction);
+        final double n = normal.getNorm();
+        if (n < Precision.SAFE_MIN) {
+            // lines are parallel
+            return distance(line.zero);
+        }
+
+        // signed separation of the two parallel planes that contains the lines
+        final double offset = line.zero.subtract(zero).dotProduct(normal) / n;
+
+        return FastMath.abs(offset);
+
+    }
+
+    /** Compute the point of the instance closest to another line.
+     * @param line line to check against the instance
+     * @return point of the instance closest to another line
+     */
+    public Vector3D closestPoint(final Line line) {
+
+        final double cos = direction.dotProduct(line.direction);
+        final double n = 1 - cos * cos;
+        if (n < Precision.EPSILON) {
+            // the lines are parallel
+            return zero;
+        }
+
+        final Vector3D delta0 = line.zero.subtract(zero);
+        final double a        = delta0.dotProduct(direction);
+        final double b        = delta0.dotProduct(line.direction);
+
+        return new Vector3D(1, zero, (a - b * cos) / n, direction);
+
+    }
+
+    /** Get the intersection point of the instance and another line.
+     * @param line other line
+     * @return intersection point of the instance and the other line
+     * or null if there are no intersection points
+     */
+    public Vector3D intersection(final Line line) {
+        final Vector3D closest = closestPoint(line);
+        return line.contains(closest) ? closest : null;
+    }
+
+    /** Build a sub-line covering the whole line.
+     * @return a sub-line covering the whole line
+     */
+    public SubLine wholeLine() {
+        return new SubLine(this, new IntervalsSet(tolerance));
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/NotARotationMatrixException.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/NotARotationMatrixException.java
new file mode 100644
index 0000000..3f1f3d3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/NotARotationMatrixException.java
@@ -0,0 +1,47 @@
+/*
+ * 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.math3.geometry.euclidean.threed;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.Localizable;
+
+/**
+ * This class represents exceptions thrown while building rotations
+ * from matrices.
+ *
+ * @since 1.2
+ */
+
+public class NotARotationMatrixException
+  extends MathIllegalArgumentException {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 5647178478658937642L;
+
+    /**
+     * Simple constructor.
+     * Build an exception by translating and formating a message
+     * @param specifier format specifier (to be translated)
+     * @param parts to insert in the format (no translation)
+     * @since 2.2
+     */
+    public NotARotationMatrixException(Localizable specifier, Object ... parts) {
+        super(specifier, parts);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/OutlineExtractor.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/OutlineExtractor.java
new file mode 100644
index 0000000..0f8af88
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/OutlineExtractor.java
@@ -0,0 +1,263 @@
+/*
+ * 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.math3.geometry.euclidean.threed;
+
+import java.util.ArrayList;
+
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.euclidean.twod.Euclidean2D;
+import org.apache.commons.math3.geometry.euclidean.twod.PolygonsSet;
+import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
+import org.apache.commons.math3.geometry.partitioning.AbstractSubHyperplane;
+import org.apache.commons.math3.geometry.partitioning.BSPTree;
+import org.apache.commons.math3.geometry.partitioning.BSPTreeVisitor;
+import org.apache.commons.math3.geometry.partitioning.BoundaryAttribute;
+import org.apache.commons.math3.geometry.partitioning.RegionFactory;
+import org.apache.commons.math3.geometry.partitioning.SubHyperplane;
+import org.apache.commons.math3.util.FastMath;
+
+/** Extractor for {@link PolygonsSet polyhedrons sets} outlines.
+ * <p>This class extracts the 2D outlines from {{@link PolygonsSet
+ * polyhedrons sets} in a specified projection plane.</p>
+ * @since 3.0
+ */
+public class OutlineExtractor {
+
+    /** Abscissa axis of the projection plane. */
+    private Vector3D u;
+
+    /** Ordinate axis of the projection plane. */
+    private Vector3D v;
+
+    /** Normal of the projection plane (viewing direction). */
+    private Vector3D w;
+
+    /** Build an extractor for a specific projection plane.
+     * @param u abscissa axis of the projection point
+     * @param v ordinate axis of the projection point
+     */
+    public OutlineExtractor(final Vector3D u, final Vector3D v) {
+        this.u = u;
+        this.v = v;
+        w = Vector3D.crossProduct(u, v);
+    }
+
+    /** Extract the outline of a polyhedrons set.
+     * @param polyhedronsSet polyhedrons set whose outline must be extracted
+     * @return an outline, as an array of loops.
+     */
+    public Vector2D[][] getOutline(final PolyhedronsSet polyhedronsSet) {
+
+        // project all boundary facets into one polygons set
+        final BoundaryProjector projector = new BoundaryProjector(polyhedronsSet.getTolerance());
+        polyhedronsSet.getTree(true).visit(projector);
+        final PolygonsSet projected = projector.getProjected();
+
+        // Remove the spurious intermediate vertices from the outline
+        final Vector2D[][] outline = projected.getVertices();
+        for (int i = 0; i < outline.length; ++i) {
+            final Vector2D[] rawLoop = outline[i];
+            int end = rawLoop.length;
+            int j = 0;
+            while (j < end) {
+                if (pointIsBetween(rawLoop, end, j)) {
+                    // the point should be removed
+                    for (int k = j; k < (end - 1); ++k) {
+                        rawLoop[k] = rawLoop[k + 1];
+                    }
+                    --end;
+                } else {
+                    // the point remains in the loop
+                    ++j;
+                }
+            }
+            if (end != rawLoop.length) {
+                // resize the array
+                outline[i] = new Vector2D[end];
+                System.arraycopy(rawLoop, 0, outline[i], 0, end);
+            }
+        }
+
+        return outline;
+
+    }
+
+    /** Check if a point is geometrically between its neighbor in an array.
+     * <p>The neighbors are computed considering the array is a loop
+     * (i.e. point at index (n-1) is before point at index 0)</p>
+     * @param loop points array
+     * @param n number of points to consider in the array
+     * @param i index of the point to check (must be between 0 and n-1)
+     * @return true if the point is exactly between its neighbors
+     */
+    private boolean pointIsBetween(final Vector2D[] loop, final int n, final int i) {
+        final Vector2D previous = loop[(i + n - 1) % n];
+        final Vector2D current  = loop[i];
+        final Vector2D next     = loop[(i + 1) % n];
+        final double dx1       = current.getX() - previous.getX();
+        final double dy1       = current.getY() - previous.getY();
+        final double dx2       = next.getX()    - current.getX();
+        final double dy2       = next.getY()    - current.getY();
+        final double cross     = dx1 * dy2 - dx2 * dy1;
+        final double dot       = dx1 * dx2 + dy1 * dy2;
+        final double d1d2      = FastMath.sqrt((dx1 * dx1 + dy1 * dy1) * (dx2 * dx2 + dy2 * dy2));
+        return (FastMath.abs(cross) <= (1.0e-6 * d1d2)) && (dot >= 0.0);
+    }
+
+    /** Visitor projecting the boundary facets on a plane. */
+    private class BoundaryProjector implements BSPTreeVisitor<Euclidean3D> {
+
+        /** Projection of the polyhedrons set on the plane. */
+        private PolygonsSet projected;
+
+        /** Tolerance below which points are considered identical. */
+        private final double tolerance;
+
+        /** Simple constructor.
+         * @param tolerance tolerance below which points are considered identical
+         */
+        BoundaryProjector(final double tolerance) {
+            this.projected = new PolygonsSet(new BSPTree<Euclidean2D>(Boolean.FALSE), tolerance);
+            this.tolerance = tolerance;
+        }
+
+        /** {@inheritDoc} */
+        public Order visitOrder(final BSPTree<Euclidean3D> node) {
+            return Order.MINUS_SUB_PLUS;
+        }
+
+        /** {@inheritDoc} */
+        public void visitInternalNode(final BSPTree<Euclidean3D> node) {
+            @SuppressWarnings("unchecked")
+            final BoundaryAttribute<Euclidean3D> attribute =
+                (BoundaryAttribute<Euclidean3D>) node.getAttribute();
+            if (attribute.getPlusOutside() != null) {
+                addContribution(attribute.getPlusOutside(), false);
+            }
+            if (attribute.getPlusInside() != null) {
+                addContribution(attribute.getPlusInside(), true);
+            }
+        }
+
+        /** {@inheritDoc} */
+        public void visitLeafNode(final BSPTree<Euclidean3D> node) {
+        }
+
+        /** Add he contribution of a boundary facet.
+         * @param facet boundary facet
+         * @param reversed if true, the facet has the inside on its plus side
+         */
+        private void addContribution(final SubHyperplane<Euclidean3D> facet, final boolean reversed) {
+
+            // extract the vertices of the facet
+            @SuppressWarnings("unchecked")
+            final AbstractSubHyperplane<Euclidean3D, Euclidean2D> absFacet =
+                (AbstractSubHyperplane<Euclidean3D, Euclidean2D>) facet;
+            final Plane plane    = (Plane) facet.getHyperplane();
+
+            final double scal = plane.getNormal().dotProduct(w);
+            if (FastMath.abs(scal) > 1.0e-3) {
+                Vector2D[][] vertices =
+                    ((PolygonsSet) absFacet.getRemainingRegion()).getVertices();
+
+                if ((scal < 0) ^ reversed) {
+                    // the facet is seen from the inside,
+                    // we need to invert its boundary orientation
+                    final Vector2D[][] newVertices = new Vector2D[vertices.length][];
+                    for (int i = 0; i < vertices.length; ++i) {
+                        final Vector2D[] loop = vertices[i];
+                        final Vector2D[] newLoop = new Vector2D[loop.length];
+                        if (loop[0] == null) {
+                            newLoop[0] = null;
+                            for (int j = 1; j < loop.length; ++j) {
+                                newLoop[j] = loop[loop.length - j];
+                            }
+                        } else {
+                            for (int j = 0; j < loop.length; ++j) {
+                                newLoop[j] = loop[loop.length - (j + 1)];
+                            }
+                        }
+                        newVertices[i] = newLoop;
+                    }
+
+                    // use the reverted vertices
+                    vertices = newVertices;
+
+                }
+
+                // compute the projection of the facet in the outline plane
+                final ArrayList<SubHyperplane<Euclidean2D>> edges = new ArrayList<SubHyperplane<Euclidean2D>>();
+                for (Vector2D[] loop : vertices) {
+                    final boolean closed = loop[0] != null;
+                    int previous         = closed ? (loop.length - 1) : 1;
+                    Vector3D previous3D  = plane.toSpace((Point<Euclidean2D>) loop[previous]);
+                    int current          = (previous + 1) % loop.length;
+                    Vector2D pPoint       = new Vector2D(previous3D.dotProduct(u),
+                                                         previous3D.dotProduct(v));
+                    while (current < loop.length) {
+
+                        final Vector3D current3D = plane.toSpace((Point<Euclidean2D>) loop[current]);
+                        final Vector2D  cPoint    = new Vector2D(current3D.dotProduct(u),
+                                                                 current3D.dotProduct(v));
+                        final org.apache.commons.math3.geometry.euclidean.twod.Line line =
+                            new org.apache.commons.math3.geometry.euclidean.twod.Line(pPoint, cPoint, tolerance);
+                        SubHyperplane<Euclidean2D> edge = line.wholeHyperplane();
+
+                        if (closed || (previous != 1)) {
+                            // the previous point is a real vertex
+                            // it defines one bounding point of the edge
+                            final double angle = line.getAngle() + 0.5 * FastMath.PI;
+                            final org.apache.commons.math3.geometry.euclidean.twod.Line l =
+                                new org.apache.commons.math3.geometry.euclidean.twod.Line(pPoint, angle, tolerance);
+                            edge = edge.split(l).getPlus();
+                        }
+
+                        if (closed || (current != (loop.length - 1))) {
+                            // the current point is a real vertex
+                            // it defines one bounding point of the edge
+                            final double angle = line.getAngle() + 0.5 * FastMath.PI;
+                            final org.apache.commons.math3.geometry.euclidean.twod.Line l =
+                                new org.apache.commons.math3.geometry.euclidean.twod.Line(cPoint, angle, tolerance);
+                            edge = edge.split(l).getMinus();
+                        }
+
+                        edges.add(edge);
+
+                        previous   = current++;
+                        previous3D = current3D;
+                        pPoint     = cPoint;
+
+                    }
+                }
+                final PolygonsSet projectedFacet = new PolygonsSet(edges, tolerance);
+
+                // add the contribution of the facet to the global outline
+                projected = (PolygonsSet) new RegionFactory<Euclidean2D>().union(projected, projectedFacet);
+
+            }
+        }
+
+        /** Get the projection of the polyhedrons set on the plane.
+         * @return projection of the polyhedrons set on the plane
+         */
+        public PolygonsSet getProjected() {
+            return projected;
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/Plane.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/Plane.java
new file mode 100644
index 0000000..158818d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/Plane.java
@@ -0,0 +1,527 @@
+/*
+ * 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.math3.geometry.euclidean.threed;
+
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.Vector;
+import org.apache.commons.math3.geometry.euclidean.oned.Euclidean1D;
+import org.apache.commons.math3.geometry.euclidean.oned.Vector1D;
+import org.apache.commons.math3.geometry.euclidean.twod.Euclidean2D;
+import org.apache.commons.math3.geometry.euclidean.twod.PolygonsSet;
+import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
+import org.apache.commons.math3.geometry.partitioning.Embedding;
+import org.apache.commons.math3.geometry.partitioning.Hyperplane;
+import org.apache.commons.math3.util.FastMath;
+
+/** The class represent planes in a three dimensional space.
+ * @since 3.0
+ */
+public class Plane implements Hyperplane<Euclidean3D>, Embedding<Euclidean3D, Euclidean2D> {
+
+    /** Default value for tolerance. */
+    private static final double DEFAULT_TOLERANCE = 1.0e-10;
+
+    /** Offset of the origin with respect to the plane. */
+    private double originOffset;
+
+    /** Origin of the plane frame. */
+    private Vector3D origin;
+
+    /** First vector of the plane frame (in plane). */
+    private Vector3D u;
+
+    /** Second vector of the plane frame (in plane). */
+    private Vector3D v;
+
+    /** Third vector of the plane frame (plane normal). */
+    private Vector3D w;
+
+    /** Tolerance below which points are considered identical. */
+    private final double tolerance;
+
+    /** Build a plane normal to a given direction and containing the origin.
+     * @param normal normal direction to the plane
+     * @param tolerance tolerance below which points are considered identical
+     * @exception MathArithmeticException if the normal norm is too small
+     * @since 3.3
+     */
+    public Plane(final Vector3D normal, final double tolerance)
+        throws MathArithmeticException {
+        setNormal(normal);
+        this.tolerance = tolerance;
+        originOffset = 0;
+        setFrame();
+    }
+
+    /** Build a plane from a point and a normal.
+     * @param p point belonging to the plane
+     * @param normal normal direction to the plane
+     * @param tolerance tolerance below which points are considered identical
+     * @exception MathArithmeticException if the normal norm is too small
+     * @since 3.3
+     */
+    public Plane(final Vector3D p, final Vector3D normal, final double tolerance)
+        throws MathArithmeticException {
+        setNormal(normal);
+        this.tolerance = tolerance;
+        originOffset = -p.dotProduct(w);
+        setFrame();
+    }
+
+    /** Build a plane from three points.
+     * <p>The plane is oriented in the direction of
+     * {@code (p2-p1) ^ (p3-p1)}</p>
+     * @param p1 first point belonging to the plane
+     * @param p2 second point belonging to the plane
+     * @param p3 third point belonging to the plane
+     * @param tolerance tolerance below which points are considered identical
+     * @exception MathArithmeticException if the points do not constitute a plane
+     * @since 3.3
+     */
+    public Plane(final Vector3D p1, final Vector3D p2, final Vector3D p3, final double tolerance)
+        throws MathArithmeticException {
+        this(p1, p2.subtract(p1).crossProduct(p3.subtract(p1)), tolerance);
+    }
+
+    /** Build a plane normal to a given direction and containing the origin.
+     * @param normal normal direction to the plane
+     * @exception MathArithmeticException if the normal norm is too small
+     * @deprecated as of 3.3, replaced with {@link #Plane(Vector3D, double)}
+     */
+    @Deprecated
+    public Plane(final Vector3D normal) throws MathArithmeticException {
+        this(normal, DEFAULT_TOLERANCE);
+    }
+
+    /** Build a plane from a point and a normal.
+     * @param p point belonging to the plane
+     * @param normal normal direction to the plane
+     * @exception MathArithmeticException if the normal norm is too small
+     * @deprecated as of 3.3, replaced with {@link #Plane(Vector3D, Vector3D, double)}
+     */
+    @Deprecated
+    public Plane(final Vector3D p, final Vector3D normal) throws MathArithmeticException {
+        this(p, normal, DEFAULT_TOLERANCE);
+    }
+
+    /** Build a plane from three points.
+     * <p>The plane is oriented in the direction of
+     * {@code (p2-p1) ^ (p3-p1)}</p>
+     * @param p1 first point belonging to the plane
+     * @param p2 second point belonging to the plane
+     * @param p3 third point belonging to the plane
+     * @exception MathArithmeticException if the points do not constitute a plane
+     * @deprecated as of 3.3, replaced with {@link #Plane(Vector3D, Vector3D, Vector3D, double)}
+     */
+    @Deprecated
+    public Plane(final Vector3D p1, final Vector3D p2, final Vector3D p3)
+        throws MathArithmeticException {
+        this(p1, p2, p3, DEFAULT_TOLERANCE);
+    }
+
+    /** Copy constructor.
+     * <p>The instance created is completely independant of the original
+     * one. A deep copy is used, none of the underlying object are
+     * shared.</p>
+     * @param plane plane to copy
+     */
+    public Plane(final Plane plane) {
+        originOffset = plane.originOffset;
+        origin       = plane.origin;
+        u            = plane.u;
+        v            = plane.v;
+        w            = plane.w;
+        tolerance    = plane.tolerance;
+    }
+
+    /** Copy the instance.
+     * <p>The instance created is completely independant of the original
+     * one. A deep copy is used, none of the underlying objects are
+     * shared (except for immutable objects).</p>
+     * @return a new hyperplane, copy of the instance
+     */
+    public Plane copySelf() {
+        return new Plane(this);
+    }
+
+    /** Reset the instance as if built from a point and a normal.
+     * @param p point belonging to the plane
+     * @param normal normal direction to the plane
+     * @exception MathArithmeticException if the normal norm is too small
+     */
+    public void reset(final Vector3D p, final Vector3D normal) throws MathArithmeticException {
+        setNormal(normal);
+        originOffset = -p.dotProduct(w);
+        setFrame();
+    }
+
+    /** Reset the instance from another one.
+     * <p>The updated instance is completely independant of the original
+     * one. A deep reset is used none of the underlying object is
+     * shared.</p>
+     * @param original plane to reset from
+     */
+    public void reset(final Plane original) {
+        originOffset = original.originOffset;
+        origin       = original.origin;
+        u            = original.u;
+        v            = original.v;
+        w            = original.w;
+    }
+
+    /** Set the normal vactor.
+     * @param normal normal direction to the plane (will be copied)
+     * @exception MathArithmeticException if the normal norm is too small
+     */
+    private void setNormal(final Vector3D normal) throws MathArithmeticException {
+        final double norm = normal.getNorm();
+        if (norm < 1.0e-10) {
+            throw new MathArithmeticException(LocalizedFormats.ZERO_NORM);
+        }
+        w = new Vector3D(1.0 / norm, normal);
+    }
+
+    /** Reset the plane frame.
+     */
+    private void setFrame() {
+        origin = new Vector3D(-originOffset, w);
+        u = w.orthogonal();
+        v = Vector3D.crossProduct(w, u);
+    }
+
+    /** Get the origin point of the plane frame.
+     * <p>The point returned is the orthogonal projection of the
+     * 3D-space origin in the plane.</p>
+     * @return the origin point of the plane frame (point closest to the
+     * 3D-space origin)
+     */
+    public Vector3D getOrigin() {
+        return origin;
+    }
+
+    /** Get the normalized normal vector.
+     * <p>The frame defined by ({@link #getU getU}, {@link #getV getV},
+     * {@link #getNormal getNormal}) is a rigth-handed orthonormalized
+     * frame).</p>
+     * @return normalized normal vector
+     * @see #getU
+     * @see #getV
+     */
+    public Vector3D getNormal() {
+        return w;
+    }
+
+    /** Get the plane first canonical vector.
+     * <p>The frame defined by ({@link #getU getU}, {@link #getV getV},
+     * {@link #getNormal getNormal}) is a rigth-handed orthonormalized
+     * frame).</p>
+     * @return normalized first canonical vector
+     * @see #getV
+     * @see #getNormal
+     */
+    public Vector3D getU() {
+        return u;
+    }
+
+    /** Get the plane second canonical vector.
+     * <p>The frame defined by ({@link #getU getU}, {@link #getV getV},
+     * {@link #getNormal getNormal}) is a rigth-handed orthonormalized
+     * frame).</p>
+     * @return normalized second canonical vector
+     * @see #getU
+     * @see #getNormal
+     */
+    public Vector3D getV() {
+        return v;
+    }
+
+    /** {@inheritDoc}
+     * @since 3.3
+     */
+    public Point<Euclidean3D> project(Point<Euclidean3D> point) {
+        return toSpace(toSubSpace(point));
+    }
+
+    /** {@inheritDoc}
+     * @since 3.3
+     */
+    public double getTolerance() {
+        return tolerance;
+    }
+
+    /** Revert the plane.
+     * <p>Replace the instance by a similar plane with opposite orientation.</p>
+     * <p>The new plane frame is chosen in such a way that a 3D point that had
+     * {@code (x, y)} in-plane coordinates and {@code z} offset with
+     * respect to the plane and is unaffected by the change will have
+     * {@code (y, x)} in-plane coordinates and {@code -z} offset with
+     * respect to the new plane. This means that the {@code u} and {@code v}
+     * vectors returned by the {@link #getU} and {@link #getV} methods are exchanged,
+     * and the {@code w} vector returned by the {@link #getNormal} method is
+     * reversed.</p>
+     */
+    public void revertSelf() {
+        final Vector3D tmp = u;
+        u = v;
+        v = tmp;
+        w = w.negate();
+        originOffset = -originOffset;
+    }
+
+    /** Transform a space point into a sub-space point.
+     * @param vector n-dimension point of the space
+     * @return (n-1)-dimension point of the sub-space corresponding to
+     * the specified space point
+     */
+    public Vector2D toSubSpace(Vector<Euclidean3D> vector) {
+        return toSubSpace((Point<Euclidean3D>) vector);
+    }
+
+    /** Transform a sub-space point into a space point.
+     * @param vector (n-1)-dimension point of the sub-space
+     * @return n-dimension point of the space corresponding to the
+     * specified sub-space point
+     */
+    public Vector3D toSpace(Vector<Euclidean2D> vector) {
+        return toSpace((Point<Euclidean2D>) vector);
+    }
+
+    /** Transform a 3D space point into an in-plane point.
+     * @param point point of the space (must be a {@link Vector3D
+     * Vector3D} instance)
+     * @return in-plane point (really a {@link
+     * org.apache.commons.math3.geometry.euclidean.twod.Vector2D Vector2D} instance)
+     * @see #toSpace
+     */
+    public Vector2D toSubSpace(final Point<Euclidean3D> point) {
+        final Vector3D p3D = (Vector3D) point;
+        return new Vector2D(p3D.dotProduct(u), p3D.dotProduct(v));
+    }
+
+    /** Transform an in-plane point into a 3D space point.
+     * @param point in-plane point (must be a {@link
+     * org.apache.commons.math3.geometry.euclidean.twod.Vector2D Vector2D} instance)
+     * @return 3D space point (really a {@link Vector3D Vector3D} instance)
+     * @see #toSubSpace
+     */
+    public Vector3D toSpace(final Point<Euclidean2D> point) {
+        final Vector2D p2D = (Vector2D) point;
+        return new Vector3D(p2D.getX(), u, p2D.getY(), v, -originOffset, w);
+    }
+
+    /** Get one point from the 3D-space.
+     * @param inPlane desired in-plane coordinates for the point in the
+     * plane
+     * @param offset desired offset for the point
+     * @return one point in the 3D-space, with given coordinates and offset
+     * relative to the plane
+     */
+    public Vector3D getPointAt(final Vector2D inPlane, final double offset) {
+        return new Vector3D(inPlane.getX(), u, inPlane.getY(), v, offset - originOffset, w);
+    }
+
+    /** Check if the instance is similar to another plane.
+     * <p>Planes are considered similar if they contain the same
+     * points. This does not mean they are equal since they can have
+     * opposite normals.</p>
+     * @param plane plane to which the instance is compared
+     * @return true if the planes are similar
+     */
+    public boolean isSimilarTo(final Plane plane) {
+        final double angle = Vector3D.angle(w, plane.w);
+        return ((angle < 1.0e-10) && (FastMath.abs(originOffset - plane.originOffset) < tolerance)) ||
+               ((angle > (FastMath.PI - 1.0e-10)) && (FastMath.abs(originOffset + plane.originOffset) < tolerance));
+    }
+
+    /** Rotate the plane around the specified point.
+     * <p>The instance is not modified, a new instance is created.</p>
+     * @param center rotation center
+     * @param rotation vectorial rotation operator
+     * @return a new plane
+     */
+    public Plane rotate(final Vector3D center, final Rotation rotation) {
+
+        final Vector3D delta = origin.subtract(center);
+        final Plane plane = new Plane(center.add(rotation.applyTo(delta)),
+                                      rotation.applyTo(w), tolerance);
+
+        // make sure the frame is transformed as desired
+        plane.u = rotation.applyTo(u);
+        plane.v = rotation.applyTo(v);
+
+        return plane;
+
+    }
+
+    /** Translate the plane by the specified amount.
+     * <p>The instance is not modified, a new instance is created.</p>
+     * @param translation translation to apply
+     * @return a new plane
+     */
+    public Plane translate(final Vector3D translation) {
+
+        final Plane plane = new Plane(origin.add(translation), w, tolerance);
+
+        // make sure the frame is transformed as desired
+        plane.u = u;
+        plane.v = v;
+
+        return plane;
+
+    }
+
+    /** Get the intersection of a line with the instance.
+     * @param line line intersecting the instance
+     * @return intersection point between between the line and the
+     * instance (null if the line is parallel to the instance)
+     */
+    public Vector3D intersection(final Line line) {
+        final Vector3D direction = line.getDirection();
+        final double   dot       = w.dotProduct(direction);
+        if (FastMath.abs(dot) < 1.0e-10) {
+            return null;
+        }
+        final Vector3D point = line.toSpace((Point<Euclidean1D>) Vector1D.ZERO);
+        final double   k     = -(originOffset + w.dotProduct(point)) / dot;
+        return new Vector3D(1.0, point, k, direction);
+    }
+
+    /** Build the line shared by the instance and another plane.
+     * @param other other plane
+     * @return line at the intersection of the instance and the
+     * other plane (really a {@link Line Line} instance)
+     */
+    public Line intersection(final Plane other) {
+        final Vector3D direction = Vector3D.crossProduct(w, other.w);
+        if (direction.getNorm() < tolerance) {
+            return null;
+        }
+        final Vector3D point = intersection(this, other, new Plane(direction, tolerance));
+        return new Line(point, point.add(direction), tolerance);
+    }
+
+    /** Get the intersection point of three planes.
+     * @param plane1 first plane1
+     * @param plane2 second plane2
+     * @param plane3 third plane2
+     * @return intersection point of three planes, null if some planes are parallel
+     */
+    public static Vector3D intersection(final Plane plane1, final Plane plane2, final Plane plane3) {
+
+        // coefficients of the three planes linear equations
+        final double a1 = plane1.w.getX();
+        final double b1 = plane1.w.getY();
+        final double c1 = plane1.w.getZ();
+        final double d1 = plane1.originOffset;
+
+        final double a2 = plane2.w.getX();
+        final double b2 = plane2.w.getY();
+        final double c2 = plane2.w.getZ();
+        final double d2 = plane2.originOffset;
+
+        final double a3 = plane3.w.getX();
+        final double b3 = plane3.w.getY();
+        final double c3 = plane3.w.getZ();
+        final double d3 = plane3.originOffset;
+
+        // direct Cramer resolution of the linear system
+        // (this is still feasible for a 3x3 system)
+        final double a23         = b2 * c3 - b3 * c2;
+        final double b23         = c2 * a3 - c3 * a2;
+        final double c23         = a2 * b3 - a3 * b2;
+        final double determinant = a1 * a23 + b1 * b23 + c1 * c23;
+        if (FastMath.abs(determinant) < 1.0e-10) {
+            return null;
+        }
+
+        final double r = 1.0 / determinant;
+        return new Vector3D(
+                            (-a23 * d1 - (c1 * b3 - c3 * b1) * d2 - (c2 * b1 - c1 * b2) * d3) * r,
+                            (-b23 * d1 - (c3 * a1 - c1 * a3) * d2 - (c1 * a2 - c2 * a1) * d3) * r,
+                            (-c23 * d1 - (b1 * a3 - b3 * a1) * d2 - (b2 * a1 - b1 * a2) * d3) * r);
+
+    }
+
+    /** Build a region covering the whole hyperplane.
+     * @return a region covering the whole hyperplane
+     */
+    public SubPlane wholeHyperplane() {
+        return new SubPlane(this, new PolygonsSet(tolerance));
+    }
+
+    /** Build a region covering the whole space.
+     * @return a region containing the instance (really a {@link
+     * PolyhedronsSet PolyhedronsSet} instance)
+     */
+    public PolyhedronsSet wholeSpace() {
+        return new PolyhedronsSet(tolerance);
+    }
+
+    /** Check if the instance contains a point.
+     * @param p point to check
+     * @return true if p belongs to the plane
+     */
+    public boolean contains(final Vector3D p) {
+        return FastMath.abs(getOffset(p)) < tolerance;
+    }
+
+    /** Get the offset (oriented distance) of a parallel plane.
+     * <p>This method should be called only for parallel planes otherwise
+     * the result is not meaningful.</p>
+     * <p>The offset is 0 if both planes are the same, it is
+     * positive if the plane is on the plus side of the instance and
+     * negative if it is on the minus side, according to its natural
+     * orientation.</p>
+     * @param plane plane to check
+     * @return offset of the plane
+     */
+    public double getOffset(final Plane plane) {
+        return originOffset + (sameOrientationAs(plane) ? -plane.originOffset : plane.originOffset);
+    }
+
+    /** Get the offset (oriented distance) of a vector.
+     * @param vector vector to check
+     * @return offset of the vector
+     */
+    public double getOffset(Vector<Euclidean3D> vector) {
+        return getOffset((Point<Euclidean3D>) vector);
+    }
+
+    /** Get the offset (oriented distance) of a point.
+     * <p>The offset is 0 if the point is on the underlying hyperplane,
+     * it is positive if the point is on one particular side of the
+     * hyperplane, and it is negative if the point is on the other side,
+     * according to the hyperplane natural orientation.</p>
+     * @param point point to check
+     * @return offset of the point
+     */
+    public double getOffset(final Point<Euclidean3D> point) {
+        return ((Vector3D) point).dotProduct(w) + originOffset;
+    }
+
+    /** Check if the instance has the same orientation as another hyperplane.
+     * @param other other hyperplane to check against the instance
+     * @return true if the instance and the other hyperplane have
+     * the same orientation
+     */
+    public boolean sameOrientationAs(final Hyperplane<Euclidean3D> other) {
+        return (((Plane) other).w).dotProduct(w) > 0.0;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/PolyhedronsSet.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/PolyhedronsSet.java
new file mode 100644
index 0000000..f190e22
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/PolyhedronsSet.java
@@ -0,0 +1,739 @@
+/*
+ * 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.math3.geometry.euclidean.threed;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.euclidean.oned.Euclidean1D;
+import org.apache.commons.math3.geometry.euclidean.twod.Euclidean2D;
+import org.apache.commons.math3.geometry.euclidean.twod.PolygonsSet;
+import org.apache.commons.math3.geometry.euclidean.twod.SubLine;
+import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
+import org.apache.commons.math3.geometry.partitioning.AbstractRegion;
+import org.apache.commons.math3.geometry.partitioning.BSPTree;
+import org.apache.commons.math3.geometry.partitioning.BSPTreeVisitor;
+import org.apache.commons.math3.geometry.partitioning.BoundaryAttribute;
+import org.apache.commons.math3.geometry.partitioning.Hyperplane;
+import org.apache.commons.math3.geometry.partitioning.Region;
+import org.apache.commons.math3.geometry.partitioning.RegionFactory;
+import org.apache.commons.math3.geometry.partitioning.SubHyperplane;
+import org.apache.commons.math3.geometry.partitioning.Transform;
+import org.apache.commons.math3.util.FastMath;
+
+/** This class represents a 3D region: a set of polyhedrons.
+ * @since 3.0
+ */
+public class PolyhedronsSet extends AbstractRegion<Euclidean3D, Euclidean2D> {
+
+    /** Default value for tolerance. */
+    private static final double DEFAULT_TOLERANCE = 1.0e-10;
+
+    /** Build a polyhedrons set representing the whole real line.
+     * @param tolerance tolerance below which points are considered identical
+     * @since 3.3
+     */
+    public PolyhedronsSet(final double tolerance) {
+        super(tolerance);
+    }
+
+    /** Build a polyhedrons set from a BSP tree.
+     * <p>The leaf nodes of the BSP tree <em>must</em> have a
+     * {@code Boolean} attribute representing the inside status of
+     * the corresponding cell (true for inside cells, false for outside
+     * cells). In order to avoid building too many small objects, it is
+     * recommended to use the predefined constants
+     * {@code Boolean.TRUE} and {@code Boolean.FALSE}</p>
+     * <p>
+     * This constructor is aimed at expert use, as building the tree may
+     * be a difficult task. It is not intended for general use and for
+     * performances reasons does not check thoroughly its input, as this would
+     * require walking the full tree each time. Failing to provide a tree with
+     * the proper attributes, <em>will</em> therefore generate problems like
+     * {@link NullPointerException} or {@link ClassCastException} only later on.
+     * This limitation is known and explains why this constructor is for expert
+     * use only. The caller does have the responsibility to provided correct arguments.
+     * </p>
+     * @param tree inside/outside BSP tree representing the region
+     * @param tolerance tolerance below which points are considered identical
+     * @since 3.3
+     */
+    public PolyhedronsSet(final BSPTree<Euclidean3D> tree, final double tolerance) {
+        super(tree, tolerance);
+    }
+
+    /** Build a polyhedrons set from a Boundary REPresentation (B-rep) specified by sub-hyperplanes.
+     * <p>The boundary is provided as a collection of {@link
+     * SubHyperplane sub-hyperplanes}. Each sub-hyperplane has the
+     * interior part of the region on its minus side and the exterior on
+     * its plus side.</p>
+     * <p>The boundary elements can be in any order, and can form
+     * several non-connected sets (like for example polyhedrons with holes
+     * or a set of disjoint polyhedrons considered as a whole). In
+     * fact, the elements do not even need to be connected together
+     * (their topological connections are not used here). However, if the
+     * boundary does not really separate an inside open from an outside
+     * open (open having here its topological meaning), then subsequent
+     * calls to the {@link Region#checkPoint(Point) checkPoint} method will
+     * not be meaningful anymore.</p>
+     * <p>If the boundary is empty, the region will represent the whole
+     * space.</p>
+     * @param boundary collection of boundary elements, as a
+     * collection of {@link SubHyperplane SubHyperplane} objects
+     * @param tolerance tolerance below which points are considered identical
+     * @since 3.3
+     */
+    public PolyhedronsSet(final Collection<SubHyperplane<Euclidean3D>> boundary,
+                          final double tolerance) {
+        super(boundary, tolerance);
+    }
+
+    /** Build a polyhedrons set from a Boundary REPresentation (B-rep) specified by connected vertices.
+     * <p>
+     * The boundary is provided as a list of vertices and a list of facets.
+     * Each facet is specified as an integer array containing the arrays vertices
+     * indices in the vertices list. Each facet normal is oriented by right hand
+     * rule to the facet vertices list.
+     * </p>
+     * <p>
+     * Some basic sanity checks are performed but not everything is thoroughly
+     * assessed, so it remains under caller responsibility to ensure the vertices
+     * and facets are consistent and properly define a polyhedrons set.
+     * </p>
+     * @param vertices list of polyhedrons set vertices
+     * @param facets list of facets, as vertices indices in the vertices list
+     * @param tolerance tolerance below which points are considered identical
+     * @exception MathIllegalArgumentException if some basic sanity checks fail
+     * @since 3.5
+     */
+    public PolyhedronsSet(final List<Vector3D> vertices, final List<int[]> facets,
+                          final double tolerance) {
+        super(buildBoundary(vertices, facets, tolerance), tolerance);
+    }
+
+    /** Build a parallellepipedic box.
+     * @param xMin low bound along the x direction
+     * @param xMax high bound along the x direction
+     * @param yMin low bound along the y direction
+     * @param yMax high bound along the y direction
+     * @param zMin low bound along the z direction
+     * @param zMax high bound along the z direction
+     * @param tolerance tolerance below which points are considered identical
+     * @since 3.3
+     */
+    public PolyhedronsSet(final double xMin, final double xMax,
+                          final double yMin, final double yMax,
+                          final double zMin, final double zMax,
+                          final double tolerance) {
+        super(buildBoundary(xMin, xMax, yMin, yMax, zMin, zMax, tolerance), tolerance);
+    }
+
+    /** Build a polyhedrons set representing the whole real line.
+     * @deprecated as of 3.3, replaced with {@link #PolyhedronsSet(double)}
+     */
+    @Deprecated
+    public PolyhedronsSet() {
+        this(DEFAULT_TOLERANCE);
+    }
+
+    /** Build a polyhedrons set from a BSP tree.
+     * <p>The leaf nodes of the BSP tree <em>must</em> have a
+     * {@code Boolean} attribute representing the inside status of
+     * the corresponding cell (true for inside cells, false for outside
+     * cells). In order to avoid building too many small objects, it is
+     * recommended to use the predefined constants
+     * {@code Boolean.TRUE} and {@code Boolean.FALSE}</p>
+     * @param tree inside/outside BSP tree representing the region
+     * @deprecated as of 3.3, replaced with {@link #PolyhedronsSet(BSPTree, double)}
+     */
+    @Deprecated
+    public PolyhedronsSet(final BSPTree<Euclidean3D> tree) {
+        this(tree, DEFAULT_TOLERANCE);
+    }
+
+    /** Build a polyhedrons set from a Boundary REPresentation (B-rep).
+     * <p>The boundary is provided as a collection of {@link
+     * SubHyperplane sub-hyperplanes}. Each sub-hyperplane has the
+     * interior part of the region on its minus side and the exterior on
+     * its plus side.</p>
+     * <p>The boundary elements can be in any order, and can form
+     * several non-connected sets (like for example polyhedrons with holes
+     * or a set of disjoint polyhedrons considered as a whole). In
+     * fact, the elements do not even need to be connected together
+     * (their topological connections are not used here). However, if the
+     * boundary does not really separate an inside open from an outside
+     * open (open having here its topological meaning), then subsequent
+     * calls to the {@link Region#checkPoint(Point) checkPoint} method will
+     * not be meaningful anymore.</p>
+     * <p>If the boundary is empty, the region will represent the whole
+     * space.</p>
+     * @param boundary collection of boundary elements, as a
+     * collection of {@link SubHyperplane SubHyperplane} objects
+     * @deprecated as of 3.3, replaced with {@link #PolyhedronsSet(Collection, double)}
+     */
+    @Deprecated
+    public PolyhedronsSet(final Collection<SubHyperplane<Euclidean3D>> boundary) {
+        this(boundary, DEFAULT_TOLERANCE);
+    }
+
+    /** Build a parallellepipedic box.
+     * @param xMin low bound along the x direction
+     * @param xMax high bound along the x direction
+     * @param yMin low bound along the y direction
+     * @param yMax high bound along the y direction
+     * @param zMin low bound along the z direction
+     * @param zMax high bound along the z direction
+     * @deprecated as of 3.3, replaced with {@link #PolyhedronsSet(double, double,
+     * double, double, double, double, double)}
+     */
+    @Deprecated
+    public PolyhedronsSet(final double xMin, final double xMax,
+                          final double yMin, final double yMax,
+                          final double zMin, final double zMax) {
+        this(xMin, xMax, yMin, yMax, zMin, zMax, DEFAULT_TOLERANCE);
+    }
+
+    /** Build a parallellepipedic box boundary.
+     * @param xMin low bound along the x direction
+     * @param xMax high bound along the x direction
+     * @param yMin low bound along the y direction
+     * @param yMax high bound along the y direction
+     * @param zMin low bound along the z direction
+     * @param zMax high bound along the z direction
+     * @param tolerance tolerance below which points are considered identical
+     * @return boundary tree
+     * @since 3.3
+     */
+    private static BSPTree<Euclidean3D> buildBoundary(final double xMin, final double xMax,
+                                                      final double yMin, final double yMax,
+                                                      final double zMin, final double zMax,
+                                                      final double tolerance) {
+        if ((xMin >= xMax - tolerance) || (yMin >= yMax - tolerance) || (zMin >= zMax - tolerance)) {
+            // too thin box, build an empty polygons set
+            return new BSPTree<Euclidean3D>(Boolean.FALSE);
+        }
+        final Plane pxMin = new Plane(new Vector3D(xMin, 0,    0),   Vector3D.MINUS_I, tolerance);
+        final Plane pxMax = new Plane(new Vector3D(xMax, 0,    0),   Vector3D.PLUS_I,  tolerance);
+        final Plane pyMin = new Plane(new Vector3D(0,    yMin, 0),   Vector3D.MINUS_J, tolerance);
+        final Plane pyMax = new Plane(new Vector3D(0,    yMax, 0),   Vector3D.PLUS_J,  tolerance);
+        final Plane pzMin = new Plane(new Vector3D(0,    0,   zMin), Vector3D.MINUS_K, tolerance);
+        final Plane pzMax = new Plane(new Vector3D(0,    0,   zMax), Vector3D.PLUS_K,  tolerance);
+        @SuppressWarnings("unchecked")
+        final Region<Euclidean3D> boundary =
+        new RegionFactory<Euclidean3D>().buildConvex(pxMin, pxMax, pyMin, pyMax, pzMin, pzMax);
+        return boundary.getTree(false);
+    }
+
+    /** Build boundary from vertices and facets.
+     * @param vertices list of polyhedrons set vertices
+     * @param facets list of facets, as vertices indices in the vertices list
+     * @param tolerance tolerance below which points are considered identical
+     * @return boundary as a list of sub-hyperplanes
+     * @exception MathIllegalArgumentException if some basic sanity checks fail
+     * @since 3.5
+     */
+    private static List<SubHyperplane<Euclidean3D>> buildBoundary(final List<Vector3D> vertices,
+                                                                  final List<int[]> facets,
+                                                                  final double tolerance) {
+
+        // check vertices distances
+        for (int i = 0; i < vertices.size() - 1; ++i) {
+            final Vector3D vi = vertices.get(i);
+            for (int j = i + 1; j < vertices.size(); ++j) {
+                if (Vector3D.distance(vi, vertices.get(j)) <= tolerance) {
+                    throw new MathIllegalArgumentException(LocalizedFormats.CLOSE_VERTICES,
+                                                           vi.getX(), vi.getY(), vi.getZ());
+                }
+            }
+        }
+
+        // find how vertices are referenced by facets
+        final int[][] references = findReferences(vertices, facets);
+
+        // find how vertices are linked together by edges along the facets they belong to
+        final int[][] successors = successors(vertices, facets, references);
+
+        // check edges orientations
+        for (int vA = 0; vA < vertices.size(); ++vA) {
+            for (final int vB : successors[vA]) {
+
+                if (vB >= 0) {
+                    // when facets are properly oriented, if vB is the successor of vA on facet f1,
+                    // then there must be an adjacent facet f2 where vA is the successor of vB
+                    boolean found = false;
+                    for (final int v : successors[vB]) {
+                        found = found || (v == vA);
+                    }
+                    if (!found) {
+                        final Vector3D start = vertices.get(vA);
+                        final Vector3D end   = vertices.get(vB);
+                        throw new MathIllegalArgumentException(LocalizedFormats.EDGE_CONNECTED_TO_ONE_FACET,
+                                                               start.getX(), start.getY(), start.getZ(),
+                                                               end.getX(),   end.getY(),   end.getZ());
+                    }
+                }
+            }
+        }
+
+        final List<SubHyperplane<Euclidean3D>> boundary = new ArrayList<SubHyperplane<Euclidean3D>>();
+
+        for (final int[] facet : facets) {
+
+            // define facet plane from the first 3 points
+            Plane plane = new Plane(vertices.get(facet[0]), vertices.get(facet[1]), vertices.get(facet[2]),
+                                    tolerance);
+
+            // check all points are in the plane
+            final Vector2D[] two2Points = new Vector2D[facet.length];
+            for (int i = 0 ; i < facet.length; ++i) {
+                final Vector3D v = vertices.get(facet[i]);
+                if (!plane.contains(v)) {
+                    throw new MathIllegalArgumentException(LocalizedFormats.OUT_OF_PLANE,
+                                                           v.getX(), v.getY(), v.getZ());
+                }
+                two2Points[i] = plane.toSubSpace(v);
+            }
+
+            // create the polygonal facet
+            boundary.add(new SubPlane(plane, new PolygonsSet(tolerance, two2Points)));
+
+        }
+
+        return boundary;
+
+    }
+
+    /** Find the facets that reference each edges.
+     * @param vertices list of polyhedrons set vertices
+     * @param facets list of facets, as vertices indices in the vertices list
+     * @return references array such that r[v][k] = f for some k if facet f contains vertex v
+     * @exception MathIllegalArgumentException if some facets have fewer than 3 vertices
+     * @since 3.5
+     */
+    private static int[][] findReferences(final List<Vector3D> vertices, final List<int[]> facets) {
+
+        // find the maximum number of facets a vertex belongs to
+        final int[] nbFacets = new int[vertices.size()];
+        int maxFacets  = 0;
+        for (final int[] facet : facets) {
+            if (facet.length < 3) {
+                throw new NumberIsTooSmallException(LocalizedFormats.WRONG_NUMBER_OF_POINTS,
+                                                    3, facet.length, true);
+            }
+            for (final int index : facet) {
+                maxFacets = FastMath.max(maxFacets, ++nbFacets[index]);
+            }
+        }
+
+        // set up the references array
+        final int[][] references = new int[vertices.size()][maxFacets];
+        for (int[] r : references) {
+            Arrays.fill(r, -1);
+        }
+        for (int f = 0; f < facets.size(); ++f) {
+            for (final int v : facets.get(f)) {
+                // vertex v is referenced by facet f
+                int k = 0;
+                while (k < maxFacets && references[v][k] >= 0) {
+                    ++k;
+                }
+                references[v][k] = f;
+            }
+        }
+
+        return references;
+
+    }
+
+    /** Find the successors of all vertices among all facets they belong to.
+     * @param vertices list of polyhedrons set vertices
+     * @param facets list of facets, as vertices indices in the vertices list
+     * @param references facets references array
+     * @return indices of vertices that follow vertex v in some facet (the array
+     * may contain extra entries at the end, set to negative indices)
+     * @exception MathIllegalArgumentException if the same vertex appears more than
+     * once in the successors list (which means one facet orientation is wrong)
+     * @since 3.5
+     */
+    private static int[][] successors(final List<Vector3D> vertices, final List<int[]> facets,
+                                      final int[][] references) {
+
+        // create an array large enough
+        final int[][] successors = new int[vertices.size()][references[0].length];
+        for (final int[] s : successors) {
+            Arrays.fill(s, -1);
+        }
+
+        for (int v = 0; v < vertices.size(); ++v) {
+            for (int k = 0; k < successors[v].length && references[v][k] >= 0; ++k) {
+
+                // look for vertex v
+                final int[] facet = facets.get(references[v][k]);
+                int i = 0;
+                while (i < facet.length && facet[i] != v) {
+                    ++i;
+                }
+
+                // we have found vertex v, we deduce its successor on current facet
+                successors[v][k] = facet[(i + 1) % facet.length];
+                for (int l = 0; l < k; ++l) {
+                    if (successors[v][l] == successors[v][k]) {
+                        final Vector3D start = vertices.get(v);
+                        final Vector3D end   = vertices.get(successors[v][k]);
+                        throw new MathIllegalArgumentException(LocalizedFormats.FACET_ORIENTATION_MISMATCH,
+                                                               start.getX(), start.getY(), start.getZ(),
+                                                               end.getX(),   end.getY(),   end.getZ());
+                    }
+                }
+
+            }
+        }
+
+        return successors;
+
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public PolyhedronsSet buildNew(final BSPTree<Euclidean3D> tree) {
+        return new PolyhedronsSet(tree, getTolerance());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void computeGeometricalProperties() {
+
+        // compute the contribution of all boundary facets
+        getTree(true).visit(new FacetsContributionVisitor());
+
+        if (getSize() < 0) {
+            // the polyhedrons set as a finite outside
+            // surrounded by an infinite inside
+            setSize(Double.POSITIVE_INFINITY);
+            setBarycenter((Point<Euclidean3D>) Vector3D.NaN);
+        } else {
+            // the polyhedrons set is finite, apply the remaining scaling factors
+            setSize(getSize() / 3.0);
+            setBarycenter((Point<Euclidean3D>) new Vector3D(1.0 / (4 * getSize()), (Vector3D) getBarycenter()));
+        }
+
+    }
+
+    /** Visitor computing geometrical properties. */
+    private class FacetsContributionVisitor implements BSPTreeVisitor<Euclidean3D> {
+
+        /** Simple constructor. */
+        FacetsContributionVisitor() {
+            setSize(0);
+            setBarycenter((Point<Euclidean3D>) new Vector3D(0, 0, 0));
+        }
+
+        /** {@inheritDoc} */
+        public Order visitOrder(final BSPTree<Euclidean3D> node) {
+            return Order.MINUS_SUB_PLUS;
+        }
+
+        /** {@inheritDoc} */
+        public void visitInternalNode(final BSPTree<Euclidean3D> node) {
+            @SuppressWarnings("unchecked")
+            final BoundaryAttribute<Euclidean3D> attribute =
+                (BoundaryAttribute<Euclidean3D>) node.getAttribute();
+            if (attribute.getPlusOutside() != null) {
+                addContribution(attribute.getPlusOutside(), false);
+            }
+            if (attribute.getPlusInside() != null) {
+                addContribution(attribute.getPlusInside(), true);
+            }
+        }
+
+        /** {@inheritDoc} */
+        public void visitLeafNode(final BSPTree<Euclidean3D> node) {
+        }
+
+        /** Add he contribution of a boundary facet.
+         * @param facet boundary facet
+         * @param reversed if true, the facet has the inside on its plus side
+         */
+        private void addContribution(final SubHyperplane<Euclidean3D> facet, final boolean reversed) {
+
+            final Region<Euclidean2D> polygon = ((SubPlane) facet).getRemainingRegion();
+            final double area    = polygon.getSize();
+
+            if (Double.isInfinite(area)) {
+                setSize(Double.POSITIVE_INFINITY);
+                setBarycenter((Point<Euclidean3D>) Vector3D.NaN);
+            } else {
+
+                final Plane    plane  = (Plane) facet.getHyperplane();
+                final Vector3D facetB = plane.toSpace(polygon.getBarycenter());
+                double   scaled = area * facetB.dotProduct(plane.getNormal());
+                if (reversed) {
+                    scaled = -scaled;
+                }
+
+                setSize(getSize() + scaled);
+                setBarycenter((Point<Euclidean3D>) new Vector3D(1.0, (Vector3D) getBarycenter(), scaled, facetB));
+
+            }
+
+        }
+
+    }
+
+    /** Get the first sub-hyperplane crossed by a semi-infinite line.
+     * @param point start point of the part of the line considered
+     * @param line line to consider (contains point)
+     * @return the first sub-hyperplane crossed by the line after the
+     * given point, or null if the line does not intersect any
+     * sub-hyperplane
+     */
+    public SubHyperplane<Euclidean3D> firstIntersection(final Vector3D point, final Line line) {
+        return recurseFirstIntersection(getTree(true), point, line);
+    }
+
+    /** Get the first sub-hyperplane crossed by a semi-infinite line.
+     * @param node current node
+     * @param point start point of the part of the line considered
+     * @param line line to consider (contains point)
+     * @return the first sub-hyperplane crossed by the line after the
+     * given point, or null if the line does not intersect any
+     * sub-hyperplane
+     */
+    private SubHyperplane<Euclidean3D> recurseFirstIntersection(final BSPTree<Euclidean3D> node,
+                                                                final Vector3D point,
+                                                                final Line line) {
+
+        final SubHyperplane<Euclidean3D> cut = node.getCut();
+        if (cut == null) {
+            return null;
+        }
+        final BSPTree<Euclidean3D> minus = node.getMinus();
+        final BSPTree<Euclidean3D> plus  = node.getPlus();
+        final Plane                plane = (Plane) cut.getHyperplane();
+
+        // establish search order
+        final double offset = plane.getOffset((Point<Euclidean3D>) point);
+        final boolean in    = FastMath.abs(offset) < getTolerance();
+        final BSPTree<Euclidean3D> near;
+        final BSPTree<Euclidean3D> far;
+        if (offset < 0) {
+            near = minus;
+            far  = plus;
+        } else {
+            near = plus;
+            far  = minus;
+        }
+
+        if (in) {
+            // search in the cut hyperplane
+            final SubHyperplane<Euclidean3D> facet = boundaryFacet(point, node);
+            if (facet != null) {
+                return facet;
+            }
+        }
+
+        // search in the near branch
+        final SubHyperplane<Euclidean3D> crossed = recurseFirstIntersection(near, point, line);
+        if (crossed != null) {
+            return crossed;
+        }
+
+        if (!in) {
+            // search in the cut hyperplane
+            final Vector3D hit3D = plane.intersection(line);
+            if (hit3D != null && line.getAbscissa(hit3D) > line.getAbscissa(point)) {
+                final SubHyperplane<Euclidean3D> facet = boundaryFacet(hit3D, node);
+                if (facet != null) {
+                    return facet;
+                }
+            }
+        }
+
+        // search in the far branch
+        return recurseFirstIntersection(far, point, line);
+
+    }
+
+    /** Check if a point belongs to the boundary part of a node.
+     * @param point point to check
+     * @param node node containing the boundary facet to check
+     * @return the boundary facet this points belongs to (or null if it
+     * does not belong to any boundary facet)
+     */
+    private SubHyperplane<Euclidean3D> boundaryFacet(final Vector3D point,
+                                                     final BSPTree<Euclidean3D> node) {
+        final Vector2D point2D = ((Plane) node.getCut().getHyperplane()).toSubSpace((Point<Euclidean3D>) point);
+        @SuppressWarnings("unchecked")
+        final BoundaryAttribute<Euclidean3D> attribute =
+            (BoundaryAttribute<Euclidean3D>) node.getAttribute();
+        if ((attribute.getPlusOutside() != null) &&
+            (((SubPlane) attribute.getPlusOutside()).getRemainingRegion().checkPoint(point2D) == Location.INSIDE)) {
+            return attribute.getPlusOutside();
+        }
+        if ((attribute.getPlusInside() != null) &&
+            (((SubPlane) attribute.getPlusInside()).getRemainingRegion().checkPoint(point2D) == Location.INSIDE)) {
+            return attribute.getPlusInside();
+        }
+        return null;
+    }
+
+    /** Rotate the region around the specified point.
+     * <p>The instance is not modified, a new instance is created.</p>
+     * @param center rotation center
+     * @param rotation vectorial rotation operator
+     * @return a new instance representing the rotated region
+     */
+    public PolyhedronsSet rotate(final Vector3D center, final Rotation rotation) {
+        return (PolyhedronsSet) applyTransform(new RotationTransform(center, rotation));
+    }
+
+    /** 3D rotation as a Transform. */
+    private static class RotationTransform implements Transform<Euclidean3D, Euclidean2D> {
+
+        /** Center point of the rotation. */
+        private Vector3D   center;
+
+        /** Vectorial rotation. */
+        private Rotation   rotation;
+
+        /** Cached original hyperplane. */
+        private Plane cachedOriginal;
+
+        /** Cached 2D transform valid inside the cached original hyperplane. */
+        private Transform<Euclidean2D, Euclidean1D>  cachedTransform;
+
+        /** Build a rotation transform.
+         * @param center center point of the rotation
+         * @param rotation vectorial rotation
+         */
+        RotationTransform(final Vector3D center, final Rotation rotation) {
+            this.center   = center;
+            this.rotation = rotation;
+        }
+
+        /** {@inheritDoc} */
+        public Vector3D apply(final Point<Euclidean3D> point) {
+            final Vector3D delta = ((Vector3D) point).subtract(center);
+            return new Vector3D(1.0, center, 1.0, rotation.applyTo(delta));
+        }
+
+        /** {@inheritDoc} */
+        public Plane apply(final Hyperplane<Euclidean3D> hyperplane) {
+            return ((Plane) hyperplane).rotate(center, rotation);
+        }
+
+        /** {@inheritDoc} */
+        public SubHyperplane<Euclidean2D> apply(final SubHyperplane<Euclidean2D> sub,
+                                                final Hyperplane<Euclidean3D> original,
+                                                final Hyperplane<Euclidean3D> transformed) {
+            if (original != cachedOriginal) {
+                // we have changed hyperplane, reset the in-hyperplane transform
+
+                final Plane    oPlane = (Plane) original;
+                final Plane    tPlane = (Plane) transformed;
+                final Vector3D p00    = oPlane.getOrigin();
+                final Vector3D p10    = oPlane.toSpace((Point<Euclidean2D>) new Vector2D(1.0, 0.0));
+                final Vector3D p01    = oPlane.toSpace((Point<Euclidean2D>) new Vector2D(0.0, 1.0));
+                final Vector2D tP00   = tPlane.toSubSpace((Point<Euclidean3D>) apply(p00));
+                final Vector2D tP10   = tPlane.toSubSpace((Point<Euclidean3D>) apply(p10));
+                final Vector2D tP01   = tPlane.toSubSpace((Point<Euclidean3D>) apply(p01));
+
+                cachedOriginal  = (Plane) original;
+                cachedTransform =
+                        org.apache.commons.math3.geometry.euclidean.twod.Line.getTransform(tP10.getX() - tP00.getX(),
+                                                                                           tP10.getY() - tP00.getY(),
+                                                                                           tP01.getX() - tP00.getX(),
+                                                                                           tP01.getY() - tP00.getY(),
+                                                                                           tP00.getX(),
+                                                                                           tP00.getY());
+
+            }
+            return ((SubLine) sub).applyTransform(cachedTransform);
+        }
+
+    }
+
+    /** Translate the region by the specified amount.
+     * <p>The instance is not modified, a new instance is created.</p>
+     * @param translation translation to apply
+     * @return a new instance representing the translated region
+     */
+    public PolyhedronsSet translate(final Vector3D translation) {
+        return (PolyhedronsSet) applyTransform(new TranslationTransform(translation));
+    }
+
+    /** 3D translation as a transform. */
+    private static class TranslationTransform implements Transform<Euclidean3D, Euclidean2D> {
+
+        /** Translation vector. */
+        private Vector3D   translation;
+
+        /** Cached original hyperplane. */
+        private Plane cachedOriginal;
+
+        /** Cached 2D transform valid inside the cached original hyperplane. */
+        private Transform<Euclidean2D, Euclidean1D>  cachedTransform;
+
+        /** Build a translation transform.
+         * @param translation translation vector
+         */
+        TranslationTransform(final Vector3D translation) {
+            this.translation = translation;
+        }
+
+        /** {@inheritDoc} */
+        public Vector3D apply(final Point<Euclidean3D> point) {
+            return new Vector3D(1.0, (Vector3D) point, 1.0, translation);
+        }
+
+        /** {@inheritDoc} */
+        public Plane apply(final Hyperplane<Euclidean3D> hyperplane) {
+            return ((Plane) hyperplane).translate(translation);
+        }
+
+        /** {@inheritDoc} */
+        public SubHyperplane<Euclidean2D> apply(final SubHyperplane<Euclidean2D> sub,
+                                                final Hyperplane<Euclidean3D> original,
+                                                final Hyperplane<Euclidean3D> transformed) {
+            if (original != cachedOriginal) {
+                // we have changed hyperplane, reset the in-hyperplane transform
+
+                final Plane   oPlane = (Plane) original;
+                final Plane   tPlane = (Plane) transformed;
+                final Vector2D shift  = tPlane.toSubSpace((Point<Euclidean3D>) apply(oPlane.getOrigin()));
+
+                cachedOriginal  = (Plane) original;
+                cachedTransform =
+                        org.apache.commons.math3.geometry.euclidean.twod.Line.getTransform(1, 0, 0, 1,
+                                                                                           shift.getX(),
+                                                                                           shift.getY());
+
+            }
+
+            return ((SubLine) sub).applyTransform(cachedTransform);
+
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/Rotation.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/Rotation.java
new file mode 100644
index 0000000..f4df3b5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/Rotation.java
@@ -0,0 +1,1424 @@
+/*
+ * 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.math3.geometry.euclidean.threed;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * This class implements rotations in a three-dimensional space.
+ *
+ * <p>Rotations can be represented by several different mathematical
+ * entities (matrices, axe and angle, Cardan or Euler angles,
+ * quaternions). This class presents an higher level abstraction, more
+ * user-oriented and hiding this implementation details. Well, for the
+ * curious, we use quaternions for the internal representation. The
+ * user can build a rotation from any of these representations, and
+ * any of these representations can be retrieved from a
+ * <code>Rotation</code> instance (see the various constructors and
+ * getters). In addition, a rotation can also be built implicitly
+ * from a set of vectors and their image.</p>
+ * <p>This implies that this class can be used to convert from one
+ * representation to another one. For example, converting a rotation
+ * matrix into a set of Cardan angles from can be done using the
+ * following single line of code:</p>
+ * <pre>
+ * double[] angles = new Rotation(matrix, 1.0e-10).getAngles(RotationOrder.XYZ);
+ * </pre>
+ * <p>Focus is oriented on what a rotation <em>do</em> rather than on its
+ * underlying representation. Once it has been built, and regardless of its
+ * internal representation, a rotation is an <em>operator</em> which basically
+ * transforms three dimensional {@link Vector3D vectors} into other three
+ * dimensional {@link Vector3D vectors}. Depending on the application, the
+ * meaning of these vectors may vary and the semantics of the rotation also.</p>
+ * <p>For example in an spacecraft attitude simulation tool, users will often
+ * consider the vectors are fixed (say the Earth direction for example) and the
+ * frames change. The rotation transforms the coordinates of the vector in inertial
+ * frame into the coordinates of the same vector in satellite frame. In this
+ * case, the rotation implicitly defines the relation between the two frames.</p>
+ * <p>Another example could be a telescope control application, where the rotation
+ * would transform the sighting direction at rest into the desired observing
+ * direction when the telescope is pointed towards an object of interest. In this
+ * case the rotation transforms the direction at rest in a topocentric frame
+ * into the sighting direction in the same topocentric frame. This implies in this
+ * case the frame is fixed and the vector moves.</p>
+ * <p>In many case, both approaches will be combined. In our telescope example,
+ * we will probably also need to transform the observing direction in the topocentric
+ * frame into the observing direction in inertial frame taking into account the observatory
+ * location and the Earth rotation, which would essentially be an application of the
+ * first approach.</p>
+ *
+ * <p>These examples show that a rotation is what the user wants it to be. This
+ * class does not push the user towards one specific definition and hence does not
+ * provide methods like <code>projectVectorIntoDestinationFrame</code> or
+ * <code>computeTransformedDirection</code>. It provides simpler and more generic
+ * methods: {@link #applyTo(Vector3D) applyTo(Vector3D)} and {@link
+ * #applyInverseTo(Vector3D) applyInverseTo(Vector3D)}.</p>
+ *
+ * <p>Since a rotation is basically a vectorial operator, several rotations can be
+ * composed together and the composite operation <code>r = r<sub>1</sub> o
+ * r<sub>2</sub></code> (which means that for each vector <code>u</code>,
+ * <code>r(u) = r<sub>1</sub>(r<sub>2</sub>(u))</code>) is also a rotation. Hence
+ * we can consider that in addition to vectors, a rotation can be applied to other
+ * rotations as well (or to itself). With our previous notations, we would say we
+ * can apply <code>r<sub>1</sub></code> to <code>r<sub>2</sub></code> and the result
+ * we get is <code>r = r<sub>1</sub> o r<sub>2</sub></code>. For this purpose, the
+ * class provides the methods: {@link #applyTo(Rotation) applyTo(Rotation)} and
+ * {@link #applyInverseTo(Rotation) applyInverseTo(Rotation)}.</p>
+ *
+ * <p>Rotations are guaranteed to be immutable objects.</p>
+ *
+ * @see Vector3D
+ * @see RotationOrder
+ * @since 1.2
+ */
+
+public class Rotation implements Serializable {
+
+  /** Identity rotation. */
+  public static final Rotation IDENTITY = new Rotation(1.0, 0.0, 0.0, 0.0, false);
+
+  /** Serializable version identifier */
+  private static final long serialVersionUID = -2153622329907944313L;
+
+  /** Scalar coordinate of the quaternion. */
+  private final double q0;
+
+  /** First coordinate of the vectorial part of the quaternion. */
+  private final double q1;
+
+  /** Second coordinate of the vectorial part of the quaternion. */
+  private final double q2;
+
+  /** Third coordinate of the vectorial part of the quaternion. */
+  private final double q3;
+
+  /** Build a rotation from the quaternion coordinates.
+   * <p>A rotation can be built from a <em>normalized</em> quaternion,
+   * i.e. a quaternion for which q<sub>0</sub><sup>2</sup> +
+   * q<sub>1</sub><sup>2</sup> + q<sub>2</sub><sup>2</sup> +
+   * q<sub>3</sub><sup>2</sup> = 1. If the quaternion is not normalized,
+   * the constructor can normalize it in a preprocessing step.</p>
+   * <p>Note that some conventions put the scalar part of the quaternion
+   * as the 4<sup>th</sup> component and the vector part as the first three
+   * components. This is <em>not</em> our convention. We put the scalar part
+   * as the first component.</p>
+   * @param q0 scalar part of the quaternion
+   * @param q1 first coordinate of the vectorial part of the quaternion
+   * @param q2 second coordinate of the vectorial part of the quaternion
+   * @param q3 third coordinate of the vectorial part of the quaternion
+   * @param needsNormalization if true, the coordinates are considered
+   * not to be normalized, a normalization preprocessing step is performed
+   * before using them
+   */
+  public Rotation(double q0, double q1, double q2, double q3,
+                  boolean needsNormalization) {
+
+    if (needsNormalization) {
+      // normalization preprocessing
+      double inv = 1.0 / FastMath.sqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3);
+      q0 *= inv;
+      q1 *= inv;
+      q2 *= inv;
+      q3 *= inv;
+    }
+
+    this.q0 = q0;
+    this.q1 = q1;
+    this.q2 = q2;
+    this.q3 = q3;
+
+  }
+
+  /** Build a rotation from an axis and an angle.
+   * <p>
+   * Calling this constructor is equivalent to call
+   * {@link #Rotation(Vector3D, double, RotationConvention)
+   * new Rotation(axis, angle, RotationConvention.VECTOR_OPERATOR)}
+   * </p>
+   * @param axis axis around which to rotate
+   * @param angle rotation angle.
+   * @exception MathIllegalArgumentException if the axis norm is zero
+   * @deprecated as of 3.6, replaced with {@link #Rotation(Vector3D, double, RotationConvention)}
+   */
+  @Deprecated
+  public Rotation(Vector3D axis, double angle) throws MathIllegalArgumentException {
+      this(axis, angle, RotationConvention.VECTOR_OPERATOR);
+  }
+
+  /** Build a rotation from an axis and an angle.
+   * @param axis axis around which to rotate
+   * @param angle rotation angle
+   * @param convention convention to use for the semantics of the angle
+   * @exception MathIllegalArgumentException if the axis norm is zero
+   * @since 3.6
+   */
+  public Rotation(final Vector3D axis, final double angle, final RotationConvention convention)
+      throws MathIllegalArgumentException {
+
+    double norm = axis.getNorm();
+    if (norm == 0) {
+      throw new MathIllegalArgumentException(LocalizedFormats.ZERO_NORM_FOR_ROTATION_AXIS);
+    }
+
+    double halfAngle = convention == RotationConvention.VECTOR_OPERATOR ? -0.5 * angle : +0.5 * angle;
+    double coeff = FastMath.sin(halfAngle) / norm;
+
+    q0 = FastMath.cos (halfAngle);
+    q1 = coeff * axis.getX();
+    q2 = coeff * axis.getY();
+    q3 = coeff * axis.getZ();
+
+  }
+
+  /** Build a rotation from a 3X3 matrix.
+
+   * <p>Rotation matrices are orthogonal matrices, i.e. unit matrices
+   * (which are matrices for which m.m<sup>T</sup> = I) with real
+   * coefficients. The module of the determinant of unit matrices is
+   * 1, among the orthogonal 3X3 matrices, only the ones having a
+   * positive determinant (+1) are rotation matrices.</p>
+
+   * <p>When a rotation is defined by a matrix with truncated values
+   * (typically when it is extracted from a technical sheet where only
+   * four to five significant digits are available), the matrix is not
+   * orthogonal anymore. This constructor handles this case
+   * transparently by using a copy of the given matrix and applying a
+   * correction to the copy in order to perfect its orthogonality. If
+   * the Frobenius norm of the correction needed is above the given
+   * threshold, then the matrix is considered to be too far from a
+   * true rotation matrix and an exception is thrown.<p>
+
+   * @param m rotation matrix
+   * @param threshold convergence threshold for the iterative
+   * orthogonality correction (convergence is reached when the
+   * difference between two steps of the Frobenius norm of the
+   * correction is below this threshold)
+
+   * @exception NotARotationMatrixException if the matrix is not a 3X3
+   * matrix, or if it cannot be transformed into an orthogonal matrix
+   * with the given threshold, or if the determinant of the resulting
+   * orthogonal matrix is negative
+
+   */
+  public Rotation(double[][] m, double threshold)
+    throws NotARotationMatrixException {
+
+    // dimension check
+    if ((m.length != 3) || (m[0].length != 3) ||
+        (m[1].length != 3) || (m[2].length != 3)) {
+      throw new NotARotationMatrixException(
+              LocalizedFormats.ROTATION_MATRIX_DIMENSIONS,
+              m.length, m[0].length);
+    }
+
+    // compute a "close" orthogonal matrix
+    double[][] ort = orthogonalizeMatrix(m, threshold);
+
+    // check the sign of the determinant
+    double det = ort[0][0] * (ort[1][1] * ort[2][2] - ort[2][1] * ort[1][2]) -
+                 ort[1][0] * (ort[0][1] * ort[2][2] - ort[2][1] * ort[0][2]) +
+                 ort[2][0] * (ort[0][1] * ort[1][2] - ort[1][1] * ort[0][2]);
+    if (det < 0.0) {
+      throw new NotARotationMatrixException(
+              LocalizedFormats.CLOSEST_ORTHOGONAL_MATRIX_HAS_NEGATIVE_DETERMINANT,
+              det);
+    }
+
+    double[] quat = mat2quat(ort);
+    q0 = quat[0];
+    q1 = quat[1];
+    q2 = quat[2];
+    q3 = quat[3];
+
+  }
+
+  /** Build the rotation that transforms a pair of vectors into another pair.
+
+   * <p>Except for possible scale factors, if the instance were applied to
+   * the pair (u<sub>1</sub>, u<sub>2</sub>) it will produce the pair
+   * (v<sub>1</sub>, v<sub>2</sub>).</p>
+
+   * <p>If the angular separation between u<sub>1</sub> and u<sub>2</sub> is
+   * not the same as the angular separation between v<sub>1</sub> and
+   * v<sub>2</sub>, then a corrected v'<sub>2</sub> will be used rather than
+   * v<sub>2</sub>, the corrected vector will be in the (&pm;v<sub>1</sub>,
+   * +v<sub>2</sub>) half-plane.</p>
+
+   * @param u1 first vector of the origin pair
+   * @param u2 second vector of the origin pair
+   * @param v1 desired image of u1 by the rotation
+   * @param v2 desired image of u2 by the rotation
+   * @exception MathArithmeticException if the norm of one of the vectors is zero,
+   * or if one of the pair is degenerated (i.e. the vectors of the pair are collinear)
+   */
+  public Rotation(Vector3D u1, Vector3D u2, Vector3D v1, Vector3D v2)
+      throws MathArithmeticException {
+
+      // build orthonormalized base from u1, u2
+      // this fails when vectors are null or collinear, which is forbidden to define a rotation
+      final Vector3D u3 = u1.crossProduct(u2).normalize();
+      u2 = u3.crossProduct(u1).normalize();
+      u1 = u1.normalize();
+
+      // build an orthonormalized base from v1, v2
+      // this fails when vectors are null or collinear, which is forbidden to define a rotation
+      final Vector3D v3 = v1.crossProduct(v2).normalize();
+      v2 = v3.crossProduct(v1).normalize();
+      v1 = v1.normalize();
+
+      // buid a matrix transforming the first base into the second one
+      final double[][] m = new double[][] {
+          {
+              MathArrays.linearCombination(u1.getX(), v1.getX(), u2.getX(), v2.getX(), u3.getX(), v3.getX()),
+              MathArrays.linearCombination(u1.getY(), v1.getX(), u2.getY(), v2.getX(), u3.getY(), v3.getX()),
+              MathArrays.linearCombination(u1.getZ(), v1.getX(), u2.getZ(), v2.getX(), u3.getZ(), v3.getX())
+          },
+          {
+              MathArrays.linearCombination(u1.getX(), v1.getY(), u2.getX(), v2.getY(), u3.getX(), v3.getY()),
+              MathArrays.linearCombination(u1.getY(), v1.getY(), u2.getY(), v2.getY(), u3.getY(), v3.getY()),
+              MathArrays.linearCombination(u1.getZ(), v1.getY(), u2.getZ(), v2.getY(), u3.getZ(), v3.getY())
+          },
+          {
+              MathArrays.linearCombination(u1.getX(), v1.getZ(), u2.getX(), v2.getZ(), u3.getX(), v3.getZ()),
+              MathArrays.linearCombination(u1.getY(), v1.getZ(), u2.getY(), v2.getZ(), u3.getY(), v3.getZ()),
+              MathArrays.linearCombination(u1.getZ(), v1.getZ(), u2.getZ(), v2.getZ(), u3.getZ(), v3.getZ())
+          }
+      };
+
+      double[] quat = mat2quat(m);
+      q0 = quat[0];
+      q1 = quat[1];
+      q2 = quat[2];
+      q3 = quat[3];
+
+  }
+
+  /** Build one of the rotations that transform one vector into another one.
+
+   * <p>Except for a possible scale factor, if the instance were
+   * applied to the vector u it will produce the vector v. There is an
+   * infinite number of such rotations, this constructor choose the
+   * one with the smallest associated angle (i.e. the one whose axis
+   * is orthogonal to the (u, v) plane). If u and v are collinear, an
+   * arbitrary rotation axis is chosen.</p>
+
+   * @param u origin vector
+   * @param v desired image of u by the rotation
+   * @exception MathArithmeticException if the norm of one of the vectors is zero
+   */
+  public Rotation(Vector3D u, Vector3D v) throws MathArithmeticException {
+
+    double normProduct = u.getNorm() * v.getNorm();
+    if (normProduct == 0) {
+        throw new MathArithmeticException(LocalizedFormats.ZERO_NORM_FOR_ROTATION_DEFINING_VECTOR);
+    }
+
+    double dot = u.dotProduct(v);
+
+    if (dot < ((2.0e-15 - 1.0) * normProduct)) {
+      // special case u = -v: we select a PI angle rotation around
+      // an arbitrary vector orthogonal to u
+      Vector3D w = u.orthogonal();
+      q0 = 0.0;
+      q1 = -w.getX();
+      q2 = -w.getY();
+      q3 = -w.getZ();
+    } else {
+      // general case: (u, v) defines a plane, we select
+      // the shortest possible rotation: axis orthogonal to this plane
+      q0 = FastMath.sqrt(0.5 * (1.0 + dot / normProduct));
+      double coeff = 1.0 / (2.0 * q0 * normProduct);
+      Vector3D q = v.crossProduct(u);
+      q1 = coeff * q.getX();
+      q2 = coeff * q.getY();
+      q3 = coeff * q.getZ();
+    }
+
+  }
+
+  /** Build a rotation from three Cardan or Euler elementary rotations.
+
+   * <p>
+   * Calling this constructor is equivalent to call
+   * {@link #Rotation(RotationOrder, RotationConvention, double, double, double)
+   * new Rotation(order, RotationConvention.VECTOR_OPERATOR, alpha1, alpha2, alpha3)}
+   * </p>
+
+   * @param order order of rotations to use
+   * @param alpha1 angle of the first elementary rotation
+   * @param alpha2 angle of the second elementary rotation
+   * @param alpha3 angle of the third elementary rotation
+   * @deprecated as of 3.6, replaced with {@link
+   * #Rotation(RotationOrder, RotationConvention, double, double, double)}
+   */
+  @Deprecated
+  public Rotation(RotationOrder order,
+                  double alpha1, double alpha2, double alpha3) {
+      this(order, RotationConvention.VECTOR_OPERATOR, alpha1, alpha2, alpha3);
+  }
+
+  /** Build a rotation from three Cardan or Euler elementary rotations.
+
+   * <p>Cardan rotations are three successive rotations around the
+   * canonical axes X, Y and Z, each axis being used once. There are
+   * 6 such sets of rotations (XYZ, XZY, YXZ, YZX, ZXY and ZYX). Euler
+   * rotations are three successive rotations around the canonical
+   * axes X, Y and Z, the first and last rotations being around the
+   * same axis. There are 6 such sets of rotations (XYX, XZX, YXY,
+   * YZY, ZXZ and ZYZ), the most popular one being ZXZ.</p>
+   * <p>Beware that many people routinely use the term Euler angles even
+   * for what really are Cardan angles (this confusion is especially
+   * widespread in the aerospace business where Roll, Pitch and Yaw angles
+   * are often wrongly tagged as Euler angles).</p>
+
+   * @param order order of rotations to compose, from left to right
+   * (i.e. we will use {@code r1.compose(r2.compose(r3, convention), convention)})
+   * @param convention convention to use for the semantics of the angle
+   * @param alpha1 angle of the first elementary rotation
+   * @param alpha2 angle of the second elementary rotation
+   * @param alpha3 angle of the third elementary rotation
+   * @since 3.6
+   */
+  public Rotation(RotationOrder order, RotationConvention convention,
+                  double alpha1, double alpha2, double alpha3) {
+      Rotation r1 = new Rotation(order.getA1(), alpha1, convention);
+      Rotation r2 = new Rotation(order.getA2(), alpha2, convention);
+      Rotation r3 = new Rotation(order.getA3(), alpha3, convention);
+      Rotation composed = r1.compose(r2.compose(r3, convention), convention);
+      q0 = composed.q0;
+      q1 = composed.q1;
+      q2 = composed.q2;
+      q3 = composed.q3;
+  }
+
+  /** Convert an orthogonal rotation matrix to a quaternion.
+   * @param ort orthogonal rotation matrix
+   * @return quaternion corresponding to the matrix
+   */
+  private static double[] mat2quat(final double[][] ort) {
+
+      final double[] quat = new double[4];
+
+      // There are different ways to compute the quaternions elements
+      // from the matrix. They all involve computing one element from
+      // the diagonal of the matrix, and computing the three other ones
+      // using a formula involving a division by the first element,
+      // which unfortunately can be zero. Since the norm of the
+      // quaternion is 1, we know at least one element has an absolute
+      // value greater or equal to 0.5, so it is always possible to
+      // select the right formula and avoid division by zero and even
+      // numerical inaccuracy. Checking the elements in turn and using
+      // the first one greater than 0.45 is safe (this leads to a simple
+      // test since qi = 0.45 implies 4 qi^2 - 1 = -0.19)
+      double s = ort[0][0] + ort[1][1] + ort[2][2];
+      if (s > -0.19) {
+          // compute q0 and deduce q1, q2 and q3
+          quat[0] = 0.5 * FastMath.sqrt(s + 1.0);
+          double inv = 0.25 / quat[0];
+          quat[1] = inv * (ort[1][2] - ort[2][1]);
+          quat[2] = inv * (ort[2][0] - ort[0][2]);
+          quat[3] = inv * (ort[0][1] - ort[1][0]);
+      } else {
+          s = ort[0][0] - ort[1][1] - ort[2][2];
+          if (s > -0.19) {
+              // compute q1 and deduce q0, q2 and q3
+              quat[1] = 0.5 * FastMath.sqrt(s + 1.0);
+              double inv = 0.25 / quat[1];
+              quat[0] = inv * (ort[1][2] - ort[2][1]);
+              quat[2] = inv * (ort[0][1] + ort[1][0]);
+              quat[3] = inv * (ort[0][2] + ort[2][0]);
+          } else {
+              s = ort[1][1] - ort[0][0] - ort[2][2];
+              if (s > -0.19) {
+                  // compute q2 and deduce q0, q1 and q3
+                  quat[2] = 0.5 * FastMath.sqrt(s + 1.0);
+                  double inv = 0.25 / quat[2];
+                  quat[0] = inv * (ort[2][0] - ort[0][2]);
+                  quat[1] = inv * (ort[0][1] + ort[1][0]);
+                  quat[3] = inv * (ort[2][1] + ort[1][2]);
+              } else {
+                  // compute q3 and deduce q0, q1 and q2
+                  s = ort[2][2] - ort[0][0] - ort[1][1];
+                  quat[3] = 0.5 * FastMath.sqrt(s + 1.0);
+                  double inv = 0.25 / quat[3];
+                  quat[0] = inv * (ort[0][1] - ort[1][0]);
+                  quat[1] = inv * (ort[0][2] + ort[2][0]);
+                  quat[2] = inv * (ort[2][1] + ort[1][2]);
+              }
+          }
+      }
+
+      return quat;
+
+  }
+
+  /** Revert a rotation.
+   * Build a rotation which reverse the effect of another
+   * rotation. This means that if r(u) = v, then r.revert(v) = u. The
+   * instance is not changed.
+   * @return a new rotation whose effect is the reverse of the effect
+   * of the instance
+   */
+  public Rotation revert() {
+    return new Rotation(-q0, q1, q2, q3, false);
+  }
+
+  /** Get the scalar coordinate of the quaternion.
+   * @return scalar coordinate of the quaternion
+   */
+  public double getQ0() {
+    return q0;
+  }
+
+  /** Get the first coordinate of the vectorial part of the quaternion.
+   * @return first coordinate of the vectorial part of the quaternion
+   */
+  public double getQ1() {
+    return q1;
+  }
+
+  /** Get the second coordinate of the vectorial part of the quaternion.
+   * @return second coordinate of the vectorial part of the quaternion
+   */
+  public double getQ2() {
+    return q2;
+  }
+
+  /** Get the third coordinate of the vectorial part of the quaternion.
+   * @return third coordinate of the vectorial part of the quaternion
+   */
+  public double getQ3() {
+    return q3;
+  }
+
+  /** Get the normalized axis of the rotation.
+   * <p>
+   * Calling this method is equivalent to call
+   * {@link #getAxis(RotationConvention) getAxis(RotationConvention.VECTOR_OPERATOR)}
+   * </p>
+   * @return normalized axis of the rotation
+   * @see #Rotation(Vector3D, double, RotationConvention)
+   * @deprecated as of 3.6, replaced with {@link #getAxis(RotationConvention)}
+   */
+  @Deprecated
+  public Vector3D getAxis() {
+    return getAxis(RotationConvention.VECTOR_OPERATOR);
+  }
+
+  /** Get the normalized axis of the rotation.
+   * <p>
+   * Note that as {@link #getAngle()} always returns an angle
+   * between 0 and &pi;, changing the convention changes the
+   * direction of the axis, not the sign of the angle.
+   * </p>
+   * @param convention convention to use for the semantics of the angle
+   * @return normalized axis of the rotation
+   * @see #Rotation(Vector3D, double, RotationConvention)
+   * @since 3.6
+   */
+  public Vector3D getAxis(final RotationConvention convention) {
+    final double squaredSine = q1 * q1 + q2 * q2 + q3 * q3;
+    if (squaredSine == 0) {
+      return convention == RotationConvention.VECTOR_OPERATOR ? Vector3D.PLUS_I : Vector3D.MINUS_I;
+    } else {
+        final double sgn = convention == RotationConvention.VECTOR_OPERATOR ? +1 : -1;
+        if (q0 < 0) {
+            final double inverse = sgn / FastMath.sqrt(squaredSine);
+            return new Vector3D(q1 * inverse, q2 * inverse, q3 * inverse);
+        }
+        final double inverse = -sgn / FastMath.sqrt(squaredSine);
+        return new Vector3D(q1 * inverse, q2 * inverse, q3 * inverse);
+    }
+  }
+
+  /** Get the angle of the rotation.
+   * @return angle of the rotation (between 0 and &pi;)
+   * @see #Rotation(Vector3D, double)
+   */
+  public double getAngle() {
+    if ((q0 < -0.1) || (q0 > 0.1)) {
+      return 2 * FastMath.asin(FastMath.sqrt(q1 * q1 + q2 * q2 + q3 * q3));
+    } else if (q0 < 0) {
+      return 2 * FastMath.acos(-q0);
+    }
+    return 2 * FastMath.acos(q0);
+  }
+
+  /** Get the Cardan or Euler angles corresponding to the instance.
+
+   * <p>
+   * Calling this method is equivalent to call
+   * {@link #getAngles(RotationOrder, RotationConvention)
+   * getAngles(order, RotationConvention.VECTOR_OPERATOR)}
+   * </p>
+
+   * @param order rotation order to use
+   * @return an array of three angles, in the order specified by the set
+   * @exception CardanEulerSingularityException if the rotation is
+   * singular with respect to the angles set specified
+   * @deprecated as of 3.6, replaced with {@link #getAngles(RotationOrder, RotationConvention)}
+   */
+  @Deprecated
+  public double[] getAngles(RotationOrder order)
+      throws CardanEulerSingularityException {
+      return getAngles(order, RotationConvention.VECTOR_OPERATOR);
+  }
+
+  /** Get the Cardan or Euler angles corresponding to the instance.
+
+   * <p>The equations show that each rotation can be defined by two
+   * different values of the Cardan or Euler angles set. For example
+   * if Cardan angles are used, the rotation defined by the angles
+   * a<sub>1</sub>, a<sub>2</sub> and a<sub>3</sub> is the same as
+   * the rotation defined by the angles &pi; + a<sub>1</sub>, &pi;
+   * - a<sub>2</sub> and &pi; + a<sub>3</sub>. This method implements
+   * the following arbitrary choices:</p>
+   * <ul>
+   *   <li>for Cardan angles, the chosen set is the one for which the
+   *   second angle is between -&pi;/2 and &pi;/2 (i.e its cosine is
+   *   positive),</li>
+   *   <li>for Euler angles, the chosen set is the one for which the
+   *   second angle is between 0 and &pi; (i.e its sine is positive).</li>
+   * </ul>
+
+   * <p>Cardan and Euler angle have a very disappointing drawback: all
+   * of them have singularities. This means that if the instance is
+   * too close to the singularities corresponding to the given
+   * rotation order, it will be impossible to retrieve the angles. For
+   * Cardan angles, this is often called gimbal lock. There is
+   * <em>nothing</em> to do to prevent this, it is an intrinsic problem
+   * with Cardan and Euler representation (but not a problem with the
+   * rotation itself, which is perfectly well defined). For Cardan
+   * angles, singularities occur when the second angle is close to
+   * -&pi;/2 or +&pi;/2, for Euler angle singularities occur when the
+   * second angle is close to 0 or &pi;, this implies that the identity
+   * rotation is always singular for Euler angles!</p>
+
+   * @param order rotation order to use
+   * @param convention convention to use for the semantics of the angle
+   * @return an array of three angles, in the order specified by the set
+   * @exception CardanEulerSingularityException if the rotation is
+   * singular with respect to the angles set specified
+   * @since 3.6
+   */
+  public double[] getAngles(RotationOrder order, RotationConvention convention)
+      throws CardanEulerSingularityException {
+
+      if (convention == RotationConvention.VECTOR_OPERATOR) {
+          if (order == RotationOrder.XYZ) {
+
+              // r (Vector3D.plusK) coordinates are :
+              //  sin (theta), -cos (theta) sin (phi), cos (theta) cos (phi)
+              // (-r) (Vector3D.plusI) coordinates are :
+              // cos (psi) cos (theta), -sin (psi) cos (theta), sin (theta)
+              // and we can choose to have theta in the interval [-PI/2 ; +PI/2]
+              Vector3D v1 = applyTo(Vector3D.PLUS_K);
+              Vector3D v2 = applyInverseTo(Vector3D.PLUS_I);
+              if  ((v2.getZ() < -0.9999999999) || (v2.getZ() > 0.9999999999)) {
+                  throw new CardanEulerSingularityException(true);
+              }
+              return new double[] {
+                  FastMath.atan2(-(v1.getY()), v1.getZ()),
+                  FastMath.asin(v2.getZ()),
+                  FastMath.atan2(-(v2.getY()), v2.getX())
+              };
+
+          } else if (order == RotationOrder.XZY) {
+
+              // r (Vector3D.plusJ) coordinates are :
+              // -sin (psi), cos (psi) cos (phi), cos (psi) sin (phi)
+              // (-r) (Vector3D.plusI) coordinates are :
+              // cos (theta) cos (psi), -sin (psi), sin (theta) cos (psi)
+              // and we can choose to have psi in the interval [-PI/2 ; +PI/2]
+              Vector3D v1 = applyTo(Vector3D.PLUS_J);
+              Vector3D v2 = applyInverseTo(Vector3D.PLUS_I);
+              if ((v2.getY() < -0.9999999999) || (v2.getY() > 0.9999999999)) {
+                  throw new CardanEulerSingularityException(true);
+              }
+              return new double[] {
+                  FastMath.atan2(v1.getZ(), v1.getY()),
+                 -FastMath.asin(v2.getY()),
+                  FastMath.atan2(v2.getZ(), v2.getX())
+              };
+
+          } else if (order == RotationOrder.YXZ) {
+
+              // r (Vector3D.plusK) coordinates are :
+              //  cos (phi) sin (theta), -sin (phi), cos (phi) cos (theta)
+              // (-r) (Vector3D.plusJ) coordinates are :
+              // sin (psi) cos (phi), cos (psi) cos (phi), -sin (phi)
+              // and we can choose to have phi in the interval [-PI/2 ; +PI/2]
+              Vector3D v1 = applyTo(Vector3D.PLUS_K);
+              Vector3D v2 = applyInverseTo(Vector3D.PLUS_J);
+              if ((v2.getZ() < -0.9999999999) || (v2.getZ() > 0.9999999999)) {
+                  throw new CardanEulerSingularityException(true);
+              }
+              return new double[] {
+                  FastMath.atan2(v1.getX(), v1.getZ()),
+                 -FastMath.asin(v2.getZ()),
+                  FastMath.atan2(v2.getX(), v2.getY())
+              };
+
+          } else if (order == RotationOrder.YZX) {
+
+              // r (Vector3D.plusI) coordinates are :
+              // cos (psi) cos (theta), sin (psi), -cos (psi) sin (theta)
+              // (-r) (Vector3D.plusJ) coordinates are :
+              // sin (psi), cos (phi) cos (psi), -sin (phi) cos (psi)
+              // and we can choose to have psi in the interval [-PI/2 ; +PI/2]
+              Vector3D v1 = applyTo(Vector3D.PLUS_I);
+              Vector3D v2 = applyInverseTo(Vector3D.PLUS_J);
+              if ((v2.getX() < -0.9999999999) || (v2.getX() > 0.9999999999)) {
+                  throw new CardanEulerSingularityException(true);
+              }
+              return new double[] {
+                  FastMath.atan2(-(v1.getZ()), v1.getX()),
+                  FastMath.asin(v2.getX()),
+                  FastMath.atan2(-(v2.getZ()), v2.getY())
+              };
+
+          } else if (order == RotationOrder.ZXY) {
+
+              // r (Vector3D.plusJ) coordinates are :
+              // -cos (phi) sin (psi), cos (phi) cos (psi), sin (phi)
+              // (-r) (Vector3D.plusK) coordinates are :
+              // -sin (theta) cos (phi), sin (phi), cos (theta) cos (phi)
+              // and we can choose to have phi in the interval [-PI/2 ; +PI/2]
+              Vector3D v1 = applyTo(Vector3D.PLUS_J);
+              Vector3D v2 = applyInverseTo(Vector3D.PLUS_K);
+              if ((v2.getY() < -0.9999999999) || (v2.getY() > 0.9999999999)) {
+                  throw new CardanEulerSingularityException(true);
+              }
+              return new double[] {
+                  FastMath.atan2(-(v1.getX()), v1.getY()),
+                  FastMath.asin(v2.getY()),
+                  FastMath.atan2(-(v2.getX()), v2.getZ())
+              };
+
+          } else if (order == RotationOrder.ZYX) {
+
+              // r (Vector3D.plusI) coordinates are :
+              //  cos (theta) cos (psi), cos (theta) sin (psi), -sin (theta)
+              // (-r) (Vector3D.plusK) coordinates are :
+              // -sin (theta), sin (phi) cos (theta), cos (phi) cos (theta)
+              // and we can choose to have theta in the interval [-PI/2 ; +PI/2]
+              Vector3D v1 = applyTo(Vector3D.PLUS_I);
+              Vector3D v2 = applyInverseTo(Vector3D.PLUS_K);
+              if ((v2.getX() < -0.9999999999) || (v2.getX() > 0.9999999999)) {
+                  throw new CardanEulerSingularityException(true);
+              }
+              return new double[] {
+                  FastMath.atan2(v1.getY(), v1.getX()),
+                 -FastMath.asin(v2.getX()),
+                  FastMath.atan2(v2.getY(), v2.getZ())
+              };
+
+          } else if (order == RotationOrder.XYX) {
+
+              // r (Vector3D.plusI) coordinates are :
+              //  cos (theta), sin (phi1) sin (theta), -cos (phi1) sin (theta)
+              // (-r) (Vector3D.plusI) coordinates are :
+              // cos (theta), sin (theta) sin (phi2), sin (theta) cos (phi2)
+              // and we can choose to have theta in the interval [0 ; PI]
+              Vector3D v1 = applyTo(Vector3D.PLUS_I);
+              Vector3D v2 = applyInverseTo(Vector3D.PLUS_I);
+              if ((v2.getX() < -0.9999999999) || (v2.getX() > 0.9999999999)) {
+                  throw new CardanEulerSingularityException(false);
+              }
+              return new double[] {
+                  FastMath.atan2(v1.getY(), -v1.getZ()),
+                  FastMath.acos(v2.getX()),
+                  FastMath.atan2(v2.getY(), v2.getZ())
+              };
+
+          } else if (order == RotationOrder.XZX) {
+
+              // r (Vector3D.plusI) coordinates are :
+              //  cos (psi), cos (phi1) sin (psi), sin (phi1) sin (psi)
+              // (-r) (Vector3D.plusI) coordinates are :
+              // cos (psi), -sin (psi) cos (phi2), sin (psi) sin (phi2)
+              // and we can choose to have psi in the interval [0 ; PI]
+              Vector3D v1 = applyTo(Vector3D.PLUS_I);
+              Vector3D v2 = applyInverseTo(Vector3D.PLUS_I);
+              if ((v2.getX() < -0.9999999999) || (v2.getX() > 0.9999999999)) {
+                  throw new CardanEulerSingularityException(false);
+              }
+              return new double[] {
+                  FastMath.atan2(v1.getZ(), v1.getY()),
+                  FastMath.acos(v2.getX()),
+                  FastMath.atan2(v2.getZ(), -v2.getY())
+              };
+
+          } else if (order == RotationOrder.YXY) {
+
+              // r (Vector3D.plusJ) coordinates are :
+              //  sin (theta1) sin (phi), cos (phi), cos (theta1) sin (phi)
+              // (-r) (Vector3D.plusJ) coordinates are :
+              // sin (phi) sin (theta2), cos (phi), -sin (phi) cos (theta2)
+              // and we can choose to have phi in the interval [0 ; PI]
+              Vector3D v1 = applyTo(Vector3D.PLUS_J);
+              Vector3D v2 = applyInverseTo(Vector3D.PLUS_J);
+              if ((v2.getY() < -0.9999999999) || (v2.getY() > 0.9999999999)) {
+                  throw new CardanEulerSingularityException(false);
+              }
+              return new double[] {
+                  FastMath.atan2(v1.getX(), v1.getZ()),
+                  FastMath.acos(v2.getY()),
+                  FastMath.atan2(v2.getX(), -v2.getZ())
+              };
+
+          } else if (order == RotationOrder.YZY) {
+
+              // r (Vector3D.plusJ) coordinates are :
+              //  -cos (theta1) sin (psi), cos (psi), sin (theta1) sin (psi)
+              // (-r) (Vector3D.plusJ) coordinates are :
+              // sin (psi) cos (theta2), cos (psi), sin (psi) sin (theta2)
+              // and we can choose to have psi in the interval [0 ; PI]
+              Vector3D v1 = applyTo(Vector3D.PLUS_J);
+              Vector3D v2 = applyInverseTo(Vector3D.PLUS_J);
+              if ((v2.getY() < -0.9999999999) || (v2.getY() > 0.9999999999)) {
+                  throw new CardanEulerSingularityException(false);
+              }
+              return new double[] {
+                  FastMath.atan2(v1.getZ(), -v1.getX()),
+                  FastMath.acos(v2.getY()),
+                  FastMath.atan2(v2.getZ(), v2.getX())
+              };
+
+          } else if (order == RotationOrder.ZXZ) {
+
+              // r (Vector3D.plusK) coordinates are :
+              //  sin (psi1) sin (phi), -cos (psi1) sin (phi), cos (phi)
+              // (-r) (Vector3D.plusK) coordinates are :
+              // sin (phi) sin (psi2), sin (phi) cos (psi2), cos (phi)
+              // and we can choose to have phi in the interval [0 ; PI]
+              Vector3D v1 = applyTo(Vector3D.PLUS_K);
+              Vector3D v2 = applyInverseTo(Vector3D.PLUS_K);
+              if ((v2.getZ() < -0.9999999999) || (v2.getZ() > 0.9999999999)) {
+                  throw new CardanEulerSingularityException(false);
+              }
+              return new double[] {
+                  FastMath.atan2(v1.getX(), -v1.getY()),
+                  FastMath.acos(v2.getZ()),
+                  FastMath.atan2(v2.getX(), v2.getY())
+              };
+
+          } else { // last possibility is ZYZ
+
+              // r (Vector3D.plusK) coordinates are :
+              //  cos (psi1) sin (theta), sin (psi1) sin (theta), cos (theta)
+              // (-r) (Vector3D.plusK) coordinates are :
+              // -sin (theta) cos (psi2), sin (theta) sin (psi2), cos (theta)
+              // and we can choose to have theta in the interval [0 ; PI]
+              Vector3D v1 = applyTo(Vector3D.PLUS_K);
+              Vector3D v2 = applyInverseTo(Vector3D.PLUS_K);
+              if ((v2.getZ() < -0.9999999999) || (v2.getZ() > 0.9999999999)) {
+                  throw new CardanEulerSingularityException(false);
+              }
+              return new double[] {
+                  FastMath.atan2(v1.getY(), v1.getX()),
+                  FastMath.acos(v2.getZ()),
+                  FastMath.atan2(v2.getY(), -v2.getX())
+              };
+
+          }
+      } else {
+          if (order == RotationOrder.XYZ) {
+
+              // r (Vector3D.plusI) coordinates are :
+              //  cos (theta) cos (psi), -cos (theta) sin (psi), sin (theta)
+              // (-r) (Vector3D.plusK) coordinates are :
+              // sin (theta), -sin (phi) cos (theta), cos (phi) cos (theta)
+              // and we can choose to have theta in the interval [-PI/2 ; +PI/2]
+              Vector3D v1 = applyTo(Vector3D.PLUS_I);
+              Vector3D v2 = applyInverseTo(Vector3D.PLUS_K);
+              if ((v2.getX() < -0.9999999999) || (v2.getX() > 0.9999999999)) {
+                  throw new CardanEulerSingularityException(true);
+              }
+              return new double[] {
+                  FastMath.atan2(-v2.getY(), v2.getZ()),
+                  FastMath.asin(v2.getX()),
+                  FastMath.atan2(-v1.getY(), v1.getX())
+              };
+
+          } else if (order == RotationOrder.XZY) {
+
+              // r (Vector3D.plusI) coordinates are :
+              // cos (psi) cos (theta), -sin (psi), cos (psi) sin (theta)
+              // (-r) (Vector3D.plusJ) coordinates are :
+              // -sin (psi), cos (phi) cos (psi), sin (phi) cos (psi)
+              // and we can choose to have psi in the interval [-PI/2 ; +PI/2]
+              Vector3D v1 = applyTo(Vector3D.PLUS_I);
+              Vector3D v2 = applyInverseTo(Vector3D.PLUS_J);
+              if ((v2.getX() < -0.9999999999) || (v2.getX() > 0.9999999999)) {
+                  throw new CardanEulerSingularityException(true);
+              }
+              return new double[] {
+                  FastMath.atan2(v2.getZ(), v2.getY()),
+                 -FastMath.asin(v2.getX()),
+                  FastMath.atan2(v1.getZ(), v1.getX())
+              };
+
+          } else if (order == RotationOrder.YXZ) {
+
+              // r (Vector3D.plusJ) coordinates are :
+              // cos (phi) sin (psi), cos (phi) cos (psi), -sin (phi)
+              // (-r) (Vector3D.plusK) coordinates are :
+              // sin (theta) cos (phi), -sin (phi), cos (theta) cos (phi)
+              // and we can choose to have phi in the interval [-PI/2 ; +PI/2]
+              Vector3D v1 = applyTo(Vector3D.PLUS_J);
+              Vector3D v2 = applyInverseTo(Vector3D.PLUS_K);
+              if ((v2.getY() < -0.9999999999) || (v2.getY() > 0.9999999999)) {
+                  throw new CardanEulerSingularityException(true);
+              }
+              return new double[] {
+                  FastMath.atan2(v2.getX(), v2.getZ()),
+                 -FastMath.asin(v2.getY()),
+                  FastMath.atan2(v1.getX(), v1.getY())
+              };
+
+          } else if (order == RotationOrder.YZX) {
+
+              // r (Vector3D.plusJ) coordinates are :
+              // sin (psi), cos (psi) cos (phi), -cos (psi) sin (phi)
+              // (-r) (Vector3D.plusI) coordinates are :
+              // cos (theta) cos (psi), sin (psi), -sin (theta) cos (psi)
+              // and we can choose to have psi in the interval [-PI/2 ; +PI/2]
+              Vector3D v1 = applyTo(Vector3D.PLUS_J);
+              Vector3D v2 = applyInverseTo(Vector3D.PLUS_I);
+              if ((v2.getY() < -0.9999999999) || (v2.getY() > 0.9999999999)) {
+                  throw new CardanEulerSingularityException(true);
+              }
+              return new double[] {
+                  FastMath.atan2(-v2.getZ(), v2.getX()),
+                  FastMath.asin(v2.getY()),
+                  FastMath.atan2(-v1.getZ(), v1.getY())
+              };
+
+          } else if (order == RotationOrder.ZXY) {
+
+              // r (Vector3D.plusK) coordinates are :
+              //  -cos (phi) sin (theta), sin (phi), cos (phi) cos (theta)
+              // (-r) (Vector3D.plusJ) coordinates are :
+              // -sin (psi) cos (phi), cos (psi) cos (phi), sin (phi)
+              // and we can choose to have phi in the interval [-PI/2 ; +PI/2]
+              Vector3D v1 = applyTo(Vector3D.PLUS_K);
+              Vector3D v2 = applyInverseTo(Vector3D.PLUS_J);
+              if ((v2.getZ() < -0.9999999999) || (v2.getZ() > 0.9999999999)) {
+                  throw new CardanEulerSingularityException(true);
+              }
+              return new double[] {
+                  FastMath.atan2(-v2.getX(), v2.getY()),
+                  FastMath.asin(v2.getZ()),
+                  FastMath.atan2(-v1.getX(), v1.getZ())
+              };
+
+          } else if (order == RotationOrder.ZYX) {
+
+              // r (Vector3D.plusK) coordinates are :
+              //  -sin (theta), cos (theta) sin (phi), cos (theta) cos (phi)
+              // (-r) (Vector3D.plusI) coordinates are :
+              // cos (psi) cos (theta), sin (psi) cos (theta), -sin (theta)
+              // and we can choose to have theta in the interval [-PI/2 ; +PI/2]
+              Vector3D v1 = applyTo(Vector3D.PLUS_K);
+              Vector3D v2 = applyInverseTo(Vector3D.PLUS_I);
+              if  ((v2.getZ() < -0.9999999999) || (v2.getZ() > 0.9999999999)) {
+                  throw new CardanEulerSingularityException(true);
+              }
+              return new double[] {
+                  FastMath.atan2(v2.getY(), v2.getX()),
+                 -FastMath.asin(v2.getZ()),
+                  FastMath.atan2(v1.getY(), v1.getZ())
+              };
+
+          } else if (order == RotationOrder.XYX) {
+
+              // r (Vector3D.plusI) coordinates are :
+              //  cos (theta), sin (phi2) sin (theta), cos (phi2) sin (theta)
+              // (-r) (Vector3D.plusI) coordinates are :
+              // cos (theta), sin (theta) sin (phi1), -sin (theta) cos (phi1)
+              // and we can choose to have theta in the interval [0 ; PI]
+              Vector3D v1 = applyTo(Vector3D.PLUS_I);
+              Vector3D v2 = applyInverseTo(Vector3D.PLUS_I);
+              if ((v2.getX() < -0.9999999999) || (v2.getX() > 0.9999999999)) {
+                  throw new CardanEulerSingularityException(false);
+              }
+              return new double[] {
+                  FastMath.atan2(v2.getY(), -v2.getZ()),
+                  FastMath.acos(v2.getX()),
+                  FastMath.atan2(v1.getY(), v1.getZ())
+              };
+
+          } else if (order == RotationOrder.XZX) {
+
+              // r (Vector3D.plusI) coordinates are :
+              //  cos (psi), -cos (phi2) sin (psi), sin (phi2) sin (psi)
+              // (-r) (Vector3D.plusI) coordinates are :
+              // cos (psi), sin (psi) cos (phi1), sin (psi) sin (phi1)
+              // and we can choose to have psi in the interval [0 ; PI]
+              Vector3D v1 = applyTo(Vector3D.PLUS_I);
+              Vector3D v2 = applyInverseTo(Vector3D.PLUS_I);
+              if ((v2.getX() < -0.9999999999) || (v2.getX() > 0.9999999999)) {
+                  throw new CardanEulerSingularityException(false);
+              }
+              return new double[] {
+                  FastMath.atan2(v2.getZ(), v2.getY()),
+                  FastMath.acos(v2.getX()),
+                  FastMath.atan2(v1.getZ(), -v1.getY())
+              };
+
+          } else if (order == RotationOrder.YXY) {
+
+              // r (Vector3D.plusJ) coordinates are :
+              // sin (phi) sin (theta2), cos (phi), -sin (phi) cos (theta2)
+              // (-r) (Vector3D.plusJ) coordinates are :
+              //  sin (theta1) sin (phi), cos (phi), cos (theta1) sin (phi)
+              // and we can choose to have phi in the interval [0 ; PI]
+              Vector3D v1 = applyTo(Vector3D.PLUS_J);
+              Vector3D v2 = applyInverseTo(Vector3D.PLUS_J);
+              if ((v2.getY() < -0.9999999999) || (v2.getY() > 0.9999999999)) {
+                  throw new CardanEulerSingularityException(false);
+              }
+              return new double[] {
+                  FastMath.atan2(v2.getX(), v2.getZ()),
+                  FastMath.acos(v2.getY()),
+                  FastMath.atan2(v1.getX(), -v1.getZ())
+              };
+
+          } else if (order == RotationOrder.YZY) {
+
+              // r (Vector3D.plusJ) coordinates are :
+              // sin (psi) cos (theta2), cos (psi), sin (psi) sin (theta2)
+              // (-r) (Vector3D.plusJ) coordinates are :
+              //  -cos (theta1) sin (psi), cos (psi), sin (theta1) sin (psi)
+              // and we can choose to have psi in the interval [0 ; PI]
+              Vector3D v1 = applyTo(Vector3D.PLUS_J);
+              Vector3D v2 = applyInverseTo(Vector3D.PLUS_J);
+              if ((v2.getY() < -0.9999999999) || (v2.getY() > 0.9999999999)) {
+                  throw new CardanEulerSingularityException(false);
+              }
+              return new double[] {
+                  FastMath.atan2(v2.getZ(), -v2.getX()),
+                  FastMath.acos(v2.getY()),
+                  FastMath.atan2(v1.getZ(), v1.getX())
+              };
+
+          } else if (order == RotationOrder.ZXZ) {
+
+              // r (Vector3D.plusK) coordinates are :
+              // sin (phi) sin (psi2), sin (phi) cos (psi2), cos (phi)
+              // (-r) (Vector3D.plusK) coordinates are :
+              //  sin (psi1) sin (phi), -cos (psi1) sin (phi), cos (phi)
+              // and we can choose to have phi in the interval [0 ; PI]
+              Vector3D v1 = applyTo(Vector3D.PLUS_K);
+              Vector3D v2 = applyInverseTo(Vector3D.PLUS_K);
+              if ((v2.getZ() < -0.9999999999) || (v2.getZ() > 0.9999999999)) {
+                  throw new CardanEulerSingularityException(false);
+              }
+              return new double[] {
+                  FastMath.atan2(v2.getX(), -v2.getY()),
+                  FastMath.acos(v2.getZ()),
+                  FastMath.atan2(v1.getX(), v1.getY())
+              };
+
+          } else { // last possibility is ZYZ
+
+              // r (Vector3D.plusK) coordinates are :
+              // -sin (theta) cos (psi2), sin (theta) sin (psi2), cos (theta)
+              // (-r) (Vector3D.plusK) coordinates are :
+              //  cos (psi1) sin (theta), sin (psi1) sin (theta), cos (theta)
+              // and we can choose to have theta in the interval [0 ; PI]
+              Vector3D v1 = applyTo(Vector3D.PLUS_K);
+              Vector3D v2 = applyInverseTo(Vector3D.PLUS_K);
+              if ((v2.getZ() < -0.9999999999) || (v2.getZ() > 0.9999999999)) {
+                  throw new CardanEulerSingularityException(false);
+              }
+              return new double[] {
+                  FastMath.atan2(v2.getY(), v2.getX()),
+                  FastMath.acos(v2.getZ()),
+                  FastMath.atan2(v1.getY(), -v1.getX())
+              };
+
+          }
+      }
+
+  }
+
+  /** Get the 3X3 matrix corresponding to the instance
+   * @return the matrix corresponding to the instance
+   */
+  public double[][] getMatrix() {
+
+    // products
+    double q0q0  = q0 * q0;
+    double q0q1  = q0 * q1;
+    double q0q2  = q0 * q2;
+    double q0q3  = q0 * q3;
+    double q1q1  = q1 * q1;
+    double q1q2  = q1 * q2;
+    double q1q3  = q1 * q3;
+    double q2q2  = q2 * q2;
+    double q2q3  = q2 * q3;
+    double q3q3  = q3 * q3;
+
+    // create the matrix
+    double[][] m = new double[3][];
+    m[0] = new double[3];
+    m[1] = new double[3];
+    m[2] = new double[3];
+
+    m [0][0] = 2.0 * (q0q0 + q1q1) - 1.0;
+    m [1][0] = 2.0 * (q1q2 - q0q3);
+    m [2][0] = 2.0 * (q1q3 + q0q2);
+
+    m [0][1] = 2.0 * (q1q2 + q0q3);
+    m [1][1] = 2.0 * (q0q0 + q2q2) - 1.0;
+    m [2][1] = 2.0 * (q2q3 - q0q1);
+
+    m [0][2] = 2.0 * (q1q3 - q0q2);
+    m [1][2] = 2.0 * (q2q3 + q0q1);
+    m [2][2] = 2.0 * (q0q0 + q3q3) - 1.0;
+
+    return m;
+
+  }
+
+  /** Apply the rotation to a vector.
+   * @param u vector to apply the rotation to
+   * @return a new vector which is the image of u by the rotation
+   */
+  public Vector3D applyTo(Vector3D u) {
+
+    double x = u.getX();
+    double y = u.getY();
+    double z = u.getZ();
+
+    double s = q1 * x + q2 * y + q3 * z;
+
+    return new Vector3D(2 * (q0 * (x * q0 - (q2 * z - q3 * y)) + s * q1) - x,
+                        2 * (q0 * (y * q0 - (q3 * x - q1 * z)) + s * q2) - y,
+                        2 * (q0 * (z * q0 - (q1 * y - q2 * x)) + s * q3) - z);
+
+  }
+
+  /** Apply the rotation to a vector stored in an array.
+   * @param in an array with three items which stores vector to rotate
+   * @param out an array with three items to put result to (it can be the same
+   * array as in)
+   */
+  public void applyTo(final double[] in, final double[] out) {
+
+      final double x = in[0];
+      final double y = in[1];
+      final double z = in[2];
+
+      final double s = q1 * x + q2 * y + q3 * z;
+
+      out[0] = 2 * (q0 * (x * q0 - (q2 * z - q3 * y)) + s * q1) - x;
+      out[1] = 2 * (q0 * (y * q0 - (q3 * x - q1 * z)) + s * q2) - y;
+      out[2] = 2 * (q0 * (z * q0 - (q1 * y - q2 * x)) + s * q3) - z;
+
+  }
+
+  /** Apply the inverse of the rotation to a vector.
+   * @param u vector to apply the inverse of the rotation to
+   * @return a new vector which such that u is its image by the rotation
+   */
+  public Vector3D applyInverseTo(Vector3D u) {
+
+    double x = u.getX();
+    double y = u.getY();
+    double z = u.getZ();
+
+    double s = q1 * x + q2 * y + q3 * z;
+    double m0 = -q0;
+
+    return new Vector3D(2 * (m0 * (x * m0 - (q2 * z - q3 * y)) + s * q1) - x,
+                        2 * (m0 * (y * m0 - (q3 * x - q1 * z)) + s * q2) - y,
+                        2 * (m0 * (z * m0 - (q1 * y - q2 * x)) + s * q3) - z);
+
+  }
+
+  /** Apply the inverse of the rotation to a vector stored in an array.
+   * @param in an array with three items which stores vector to rotate
+   * @param out an array with three items to put result to (it can be the same
+   * array as in)
+   */
+  public void applyInverseTo(final double[] in, final double[] out) {
+
+      final double x = in[0];
+      final double y = in[1];
+      final double z = in[2];
+
+      final double s = q1 * x + q2 * y + q3 * z;
+      final double m0 = -q0;
+
+      out[0] = 2 * (m0 * (x * m0 - (q2 * z - q3 * y)) + s * q1) - x;
+      out[1] = 2 * (m0 * (y * m0 - (q3 * x - q1 * z)) + s * q2) - y;
+      out[2] = 2 * (m0 * (z * m0 - (q1 * y - q2 * x)) + s * q3) - z;
+
+  }
+
+  /** Apply the instance to another rotation.
+   * <p>
+   * Calling this method is equivalent to call
+   * {@link #compose(Rotation, RotationConvention)
+   * compose(r, RotationConvention.VECTOR_OPERATOR)}.
+   * </p>
+   * @param r rotation to apply the rotation to
+   * @return a new rotation which is the composition of r by the instance
+   */
+  public Rotation applyTo(Rotation r) {
+    return compose(r, RotationConvention.VECTOR_OPERATOR);
+  }
+
+  /** Compose the instance with another rotation.
+   * <p>
+   * If the semantics of the rotations composition corresponds to a
+   * {@link RotationConvention#VECTOR_OPERATOR vector operator} convention,
+   * applying the instance to a rotation is computing the composition
+   * in an order compliant with the following rule : let {@code u} be any
+   * vector and {@code v} its image by {@code r1} (i.e.
+   * {@code r1.applyTo(u) = v}). Let {@code w} be the image of {@code v} by
+   * rotation {@code r2} (i.e. {@code r2.applyTo(v) = w}). Then
+   * {@code w = comp.applyTo(u)}, where
+   * {@code comp = r2.compose(r1, RotationConvention.VECTOR_OPERATOR)}.
+   * </p>
+   * <p>
+   * If the semantics of the rotations composition corresponds to a
+   * {@link RotationConvention#FRAME_TRANSFORM frame transform} convention,
+   * the application order will be reversed. So keeping the exact same
+   * meaning of all {@code r1}, {@code r2}, {@code u}, {@code v}, {@code w}
+   * and  {@code comp} as above, {@code comp} could also be computed as
+   * {@code comp = r1.compose(r2, RotationConvention.FRAME_TRANSFORM)}.
+   * </p>
+   * @param r rotation to apply the rotation to
+   * @param convention convention to use for the semantics of the angle
+   * @return a new rotation which is the composition of r by the instance
+   */
+  public Rotation compose(final Rotation r, final RotationConvention convention) {
+    return convention == RotationConvention.VECTOR_OPERATOR ?
+           composeInternal(r) : r.composeInternal(this);
+  }
+
+  /** Compose the instance with another rotation using vector operator convention.
+   * @param r rotation to apply the rotation to
+   * @return a new rotation which is the composition of r by the instance
+   * using vector operator convention
+   */
+  private Rotation composeInternal(final Rotation r) {
+    return new Rotation(r.q0 * q0 - (r.q1 * q1 + r.q2 * q2 + r.q3 * q3),
+                        r.q1 * q0 + r.q0 * q1 + (r.q2 * q3 - r.q3 * q2),
+                        r.q2 * q0 + r.q0 * q2 + (r.q3 * q1 - r.q1 * q3),
+                        r.q3 * q0 + r.q0 * q3 + (r.q1 * q2 - r.q2 * q1),
+                        false);
+  }
+
+  /** Apply the inverse of the instance to another rotation.
+   * <p>
+   * Calling this method is equivalent to call
+   * {@link #composeInverse(Rotation, RotationConvention)
+   * composeInverse(r, RotationConvention.VECTOR_OPERATOR)}.
+   * </p>
+   * @param r rotation to apply the rotation to
+   * @return a new rotation which is the composition of r by the inverse
+   * of the instance
+   */
+  public Rotation applyInverseTo(Rotation r) {
+    return composeInverse(r, RotationConvention.VECTOR_OPERATOR);
+  }
+
+  /** Compose the inverse of the instance with another rotation.
+   * <p>
+   * If the semantics of the rotations composition corresponds to a
+   * {@link RotationConvention#VECTOR_OPERATOR vector operator} convention,
+   * applying the inverse of the instance to a rotation is computing
+   * the composition in an order compliant with the following rule :
+   * let {@code u} be any vector and {@code v} its image by {@code r1}
+   * (i.e. {@code r1.applyTo(u) = v}). Let {@code w} be the inverse image
+   * of {@code v} by {@code r2} (i.e. {@code r2.applyInverseTo(v) = w}).
+   * Then {@code w = comp.applyTo(u)}, where
+   * {@code comp = r2.composeInverse(r1)}.
+   * </p>
+   * <p>
+   * If the semantics of the rotations composition corresponds to a
+   * {@link RotationConvention#FRAME_TRANSFORM frame transform} convention,
+   * the application order will be reversed, which means it is the
+   * <em>innermost</em> rotation that will be reversed. So keeping the exact same
+   * meaning of all {@code r1}, {@code r2}, {@code u}, {@code v}, {@code w}
+   * and  {@code comp} as above, {@code comp} could also be computed as
+   * {@code comp = r1.revert().composeInverse(r2.revert(), RotationConvention.FRAME_TRANSFORM)}.
+   * </p>
+   * @param r rotation to apply the rotation to
+   * @param convention convention to use for the semantics of the angle
+   * @return a new rotation which is the composition of r by the inverse
+   * of the instance
+   */
+  public Rotation composeInverse(final Rotation r, final RotationConvention convention) {
+    return convention == RotationConvention.VECTOR_OPERATOR ?
+           composeInverseInternal(r) : r.composeInternal(revert());
+  }
+
+  /** Compose the inverse of the instance with another rotation
+   * using vector operator convention.
+   * @param r rotation to apply the rotation to
+   * @return a new rotation which is the composition of r by the inverse
+   * of the instance using vector operator convention
+   */
+  private Rotation composeInverseInternal(Rotation r) {
+    return new Rotation(-r.q0 * q0 - (r.q1 * q1 + r.q2 * q2 + r.q3 * q3),
+                        -r.q1 * q0 + r.q0 * q1 + (r.q2 * q3 - r.q3 * q2),
+                        -r.q2 * q0 + r.q0 * q2 + (r.q3 * q1 - r.q1 * q3),
+                        -r.q3 * q0 + r.q0 * q3 + (r.q1 * q2 - r.q2 * q1),
+                        false);
+  }
+
+  /** Perfect orthogonality on a 3X3 matrix.
+   * @param m initial matrix (not exactly orthogonal)
+   * @param threshold convergence threshold for the iterative
+   * orthogonality correction (convergence is reached when the
+   * difference between two steps of the Frobenius norm of the
+   * correction is below this threshold)
+   * @return an orthogonal matrix close to m
+   * @exception NotARotationMatrixException if the matrix cannot be
+   * orthogonalized with the given threshold after 10 iterations
+   */
+  private double[][] orthogonalizeMatrix(double[][] m, double threshold)
+    throws NotARotationMatrixException {
+    double[] m0 = m[0];
+    double[] m1 = m[1];
+    double[] m2 = m[2];
+    double x00 = m0[0];
+    double x01 = m0[1];
+    double x02 = m0[2];
+    double x10 = m1[0];
+    double x11 = m1[1];
+    double x12 = m1[2];
+    double x20 = m2[0];
+    double x21 = m2[1];
+    double x22 = m2[2];
+    double fn = 0;
+    double fn1;
+
+    double[][] o = new double[3][3];
+    double[] o0 = o[0];
+    double[] o1 = o[1];
+    double[] o2 = o[2];
+
+    // iterative correction: Xn+1 = Xn - 0.5 * (Xn.Mt.Xn - M)
+    int i = 0;
+    while (++i < 11) {
+
+      // Mt.Xn
+      double mx00 = m0[0] * x00 + m1[0] * x10 + m2[0] * x20;
+      double mx10 = m0[1] * x00 + m1[1] * x10 + m2[1] * x20;
+      double mx20 = m0[2] * x00 + m1[2] * x10 + m2[2] * x20;
+      double mx01 = m0[0] * x01 + m1[0] * x11 + m2[0] * x21;
+      double mx11 = m0[1] * x01 + m1[1] * x11 + m2[1] * x21;
+      double mx21 = m0[2] * x01 + m1[2] * x11 + m2[2] * x21;
+      double mx02 = m0[0] * x02 + m1[0] * x12 + m2[0] * x22;
+      double mx12 = m0[1] * x02 + m1[1] * x12 + m2[1] * x22;
+      double mx22 = m0[2] * x02 + m1[2] * x12 + m2[2] * x22;
+
+      // Xn+1
+      o0[0] = x00 - 0.5 * (x00 * mx00 + x01 * mx10 + x02 * mx20 - m0[0]);
+      o0[1] = x01 - 0.5 * (x00 * mx01 + x01 * mx11 + x02 * mx21 - m0[1]);
+      o0[2] = x02 - 0.5 * (x00 * mx02 + x01 * mx12 + x02 * mx22 - m0[2]);
+      o1[0] = x10 - 0.5 * (x10 * mx00 + x11 * mx10 + x12 * mx20 - m1[0]);
+      o1[1] = x11 - 0.5 * (x10 * mx01 + x11 * mx11 + x12 * mx21 - m1[1]);
+      o1[2] = x12 - 0.5 * (x10 * mx02 + x11 * mx12 + x12 * mx22 - m1[2]);
+      o2[0] = x20 - 0.5 * (x20 * mx00 + x21 * mx10 + x22 * mx20 - m2[0]);
+      o2[1] = x21 - 0.5 * (x20 * mx01 + x21 * mx11 + x22 * mx21 - m2[1]);
+      o2[2] = x22 - 0.5 * (x20 * mx02 + x21 * mx12 + x22 * mx22 - m2[2]);
+
+      // correction on each elements
+      double corr00 = o0[0] - m0[0];
+      double corr01 = o0[1] - m0[1];
+      double corr02 = o0[2] - m0[2];
+      double corr10 = o1[0] - m1[0];
+      double corr11 = o1[1] - m1[1];
+      double corr12 = o1[2] - m1[2];
+      double corr20 = o2[0] - m2[0];
+      double corr21 = o2[1] - m2[1];
+      double corr22 = o2[2] - m2[2];
+
+      // Frobenius norm of the correction
+      fn1 = corr00 * corr00 + corr01 * corr01 + corr02 * corr02 +
+            corr10 * corr10 + corr11 * corr11 + corr12 * corr12 +
+            corr20 * corr20 + corr21 * corr21 + corr22 * corr22;
+
+      // convergence test
+      if (FastMath.abs(fn1 - fn) <= threshold) {
+          return o;
+      }
+
+      // prepare next iteration
+      x00 = o0[0];
+      x01 = o0[1];
+      x02 = o0[2];
+      x10 = o1[0];
+      x11 = o1[1];
+      x12 = o1[2];
+      x20 = o2[0];
+      x21 = o2[1];
+      x22 = o2[2];
+      fn  = fn1;
+
+    }
+
+    // the algorithm did not converge after 10 iterations
+    throw new NotARotationMatrixException(
+            LocalizedFormats.UNABLE_TO_ORTHOGONOLIZE_MATRIX,
+            i - 1);
+  }
+
+  /** Compute the <i>distance</i> between two rotations.
+   * <p>The <i>distance</i> is intended here as a way to check if two
+   * rotations are almost similar (i.e. they transform vectors the same way)
+   * or very different. It is mathematically defined as the angle of
+   * the rotation r that prepended to one of the rotations gives the other
+   * one:</p>
+   * <pre>
+   *        r<sub>1</sub>(r) = r<sub>2</sub>
+   * </pre>
+   * <p>This distance is an angle between 0 and &pi;. Its value is the smallest
+   * possible upper bound of the angle in radians between r<sub>1</sub>(v)
+   * and r<sub>2</sub>(v) for all possible vectors v. This upper bound is
+   * reached for some v. The distance is equal to 0 if and only if the two
+   * rotations are identical.</p>
+   * <p>Comparing two rotations should always be done using this value rather
+   * than for example comparing the components of the quaternions. It is much
+   * more stable, and has a geometric meaning. Also comparing quaternions
+   * components is error prone since for example quaternions (0.36, 0.48, -0.48, -0.64)
+   * and (-0.36, -0.48, 0.48, 0.64) represent exactly the same rotation despite
+   * their components are different (they are exact opposites).</p>
+   * @param r1 first rotation
+   * @param r2 second rotation
+   * @return <i>distance</i> between r1 and r2
+   */
+  public static double distance(Rotation r1, Rotation r2) {
+      return r1.composeInverseInternal(r2).getAngle();
+  }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/RotationConvention.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/RotationConvention.java
new file mode 100644
index 0000000..6111ac3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/RotationConvention.java
@@ -0,0 +1,79 @@
+/*
+ * 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.math3.geometry.euclidean.threed;
+
+/**
+ * This enumerates is used to differentiate the semantics of a rotation.
+ * @see Rotation
+ * @since 3.6
+ */
+public enum RotationConvention {
+
+    /** Constant for rotation that have the semantics of a vector operator.
+     * <p>
+     * According to this convention, the rotation moves vectors with respect
+     * to a fixed reference frame.
+     * </p>
+     * <p>
+     * This means that if we define rotation r is a 90 degrees rotation around
+     * the Z axis, the image of vector {@link Vector3D#PLUS_I} would be
+     * {@link Vector3D#PLUS_J}, the image of vector {@link Vector3D#PLUS_J}
+     * would be {@link Vector3D#MINUS_I}, the image of vector {@link Vector3D#PLUS_K}
+     * would be {@link Vector3D#PLUS_K}, and the image of vector with coordinates (1, 2, 3)
+     * would be vector (-2, 1, 3). This means that the vector rotates counterclockwise.
+     * </p>
+     * <p>
+     * This convention was the only one supported by Apache Commons Math up to version 3.5.
+     * </p>
+     * <p>
+     * The difference with {@link #FRAME_TRANSFORM} is only the semantics of the sign
+     * of the angle. It is always possible to create or use a rotation using either
+     * convention to really represent a rotation that would have been best created or
+     * used with the other convention, by changing accordingly the sign of the
+     * rotation angle. This is how things were done up to version 3.5.
+     * </p>
+     */
+    VECTOR_OPERATOR,
+
+    /** Constant for rotation that have the semantics of a frame conversion.
+     * <p>
+     * According to this convention, the rotation considered vectors to be fixed,
+     * but their coordinates change as they are converted from an initial frame to
+     * a destination frame rotated with respect to the initial frame.
+     * </p>
+     * <p>
+     * This means that if we define rotation r is a 90 degrees rotation around
+     * the Z axis, the image of vector {@link Vector3D#PLUS_I} would be
+     * {@link Vector3D#MINUS_J}, the image of vector {@link Vector3D#PLUS_J}
+     * would be {@link Vector3D#PLUS_I}, the image of vector {@link Vector3D#PLUS_K}
+     * would be {@link Vector3D#PLUS_K}, and the image of vector with coordinates (1, 2, 3)
+     * would be vector (2, -1, 3). This means that the coordinates of the vector rotates
+     * clockwise, because they are expressed with respect to a destination frame that is rotated
+     * counterclockwise.
+     * </p>
+     * <p>
+     * The difference with {@link #VECTOR_OPERATOR} is only the semantics of the sign
+     * of the angle. It is always possible to create or use a rotation using either
+     * convention to really represent a rotation that would have been best created or
+     * used with the other convention, by changing accordingly the sign of the
+     * rotation angle. This is how things were done up to version 3.5.
+     * </p>
+     */
+    FRAME_TRANSFORM;
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/RotationOrder.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/RotationOrder.java
new file mode 100644
index 0000000..03bc1c2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/RotationOrder.java
@@ -0,0 +1,174 @@
+/*
+ * 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.math3.geometry.euclidean.threed;
+
+/**
+ * This class is a utility representing a rotation order specification
+ * for Cardan or Euler angles specification.
+ *
+ * This class cannot be instanciated by the user. He can only use one
+ * of the twelve predefined supported orders as an argument to either
+ * the {@link Rotation#Rotation(RotationOrder,double,double,double)}
+ * constructor or the {@link Rotation#getAngles} method.
+ *
+ * @since 1.2
+ */
+public final class RotationOrder {
+
+    /** Set of Cardan angles.
+     * this ordered set of rotations is around X, then around Y, then
+     * around Z
+     */
+    public static final RotationOrder XYZ =
+      new RotationOrder("XYZ", Vector3D.PLUS_I, Vector3D.PLUS_J, Vector3D.PLUS_K);
+
+    /** Set of Cardan angles.
+     * this ordered set of rotations is around X, then around Z, then
+     * around Y
+     */
+    public static final RotationOrder XZY =
+      new RotationOrder("XZY", Vector3D.PLUS_I, Vector3D.PLUS_K, Vector3D.PLUS_J);
+
+    /** Set of Cardan angles.
+     * this ordered set of rotations is around Y, then around X, then
+     * around Z
+     */
+    public static final RotationOrder YXZ =
+      new RotationOrder("YXZ", Vector3D.PLUS_J, Vector3D.PLUS_I, Vector3D.PLUS_K);
+
+    /** Set of Cardan angles.
+     * this ordered set of rotations is around Y, then around Z, then
+     * around X
+     */
+    public static final RotationOrder YZX =
+      new RotationOrder("YZX", Vector3D.PLUS_J, Vector3D.PLUS_K, Vector3D.PLUS_I);
+
+    /** Set of Cardan angles.
+     * this ordered set of rotations is around Z, then around X, then
+     * around Y
+     */
+    public static final RotationOrder ZXY =
+      new RotationOrder("ZXY", Vector3D.PLUS_K, Vector3D.PLUS_I, Vector3D.PLUS_J);
+
+    /** Set of Cardan angles.
+     * this ordered set of rotations is around Z, then around Y, then
+     * around X
+     */
+    public static final RotationOrder ZYX =
+      new RotationOrder("ZYX", Vector3D.PLUS_K, Vector3D.PLUS_J, Vector3D.PLUS_I);
+
+    /** Set of Euler angles.
+     * this ordered set of rotations is around X, then around Y, then
+     * around X
+     */
+    public static final RotationOrder XYX =
+      new RotationOrder("XYX", Vector3D.PLUS_I, Vector3D.PLUS_J, Vector3D.PLUS_I);
+
+    /** Set of Euler angles.
+     * this ordered set of rotations is around X, then around Z, then
+     * around X
+     */
+    public static final RotationOrder XZX =
+      new RotationOrder("XZX", Vector3D.PLUS_I, Vector3D.PLUS_K, Vector3D.PLUS_I);
+
+    /** Set of Euler angles.
+     * this ordered set of rotations is around Y, then around X, then
+     * around Y
+     */
+    public static final RotationOrder YXY =
+      new RotationOrder("YXY", Vector3D.PLUS_J, Vector3D.PLUS_I, Vector3D.PLUS_J);
+
+    /** Set of Euler angles.
+     * this ordered set of rotations is around Y, then around Z, then
+     * around Y
+     */
+    public static final RotationOrder YZY =
+      new RotationOrder("YZY", Vector3D.PLUS_J, Vector3D.PLUS_K, Vector3D.PLUS_J);
+
+    /** Set of Euler angles.
+     * this ordered set of rotations is around Z, then around X, then
+     * around Z
+     */
+    public static final RotationOrder ZXZ =
+      new RotationOrder("ZXZ", Vector3D.PLUS_K, Vector3D.PLUS_I, Vector3D.PLUS_K);
+
+    /** Set of Euler angles.
+     * this ordered set of rotations is around Z, then around Y, then
+     * around Z
+     */
+    public static final RotationOrder ZYZ =
+      new RotationOrder("ZYZ", Vector3D.PLUS_K, Vector3D.PLUS_J, Vector3D.PLUS_K);
+
+    /** Name of the rotations order. */
+    private final String name;
+
+    /** Axis of the first rotation. */
+    private final Vector3D a1;
+
+    /** Axis of the second rotation. */
+    private final Vector3D a2;
+
+    /** Axis of the third rotation. */
+    private final Vector3D a3;
+
+    /** Private constructor.
+     * This is a utility class that cannot be instantiated by the user,
+     * so its only constructor is private.
+     * @param name name of the rotation order
+     * @param a1 axis of the first rotation
+     * @param a2 axis of the second rotation
+     * @param a3 axis of the third rotation
+     */
+    private RotationOrder(final String name,
+                          final Vector3D a1, final Vector3D a2, final Vector3D a3) {
+        this.name = name;
+        this.a1   = a1;
+        this.a2   = a2;
+        this.a3   = a3;
+    }
+
+    /** Get a string representation of the instance.
+     * @return a string representation of the instance (in fact, its name)
+     */
+    @Override
+    public String toString() {
+        return name;
+    }
+
+    /** Get the axis of the first rotation.
+     * @return axis of the first rotation
+     */
+    public Vector3D getA1() {
+        return a1;
+    }
+
+    /** Get the axis of the second rotation.
+     * @return axis of the second rotation
+     */
+    public Vector3D getA2() {
+        return a2;
+    }
+
+    /** Get the axis of the second rotation.
+     * @return axis of the second rotation
+     */
+    public Vector3D getA3() {
+        return a3;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/Segment.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/Segment.java
new file mode 100644
index 0000000..200b462
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/Segment.java
@@ -0,0 +1,66 @@
+/*
+ * 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.math3.geometry.euclidean.threed;
+
+
+/** Simple container for a two-points segment.
+ * @since 3.0
+ */
+public class Segment {
+
+    /** Start point of the segment. */
+    private final Vector3D start;
+
+    /** End point of the segments. */
+    private final Vector3D end;
+
+    /** Line containing the segment. */
+    private final Line     line;
+
+    /** Build a segment.
+     * @param start start point of the segment
+     * @param end end point of the segment
+     * @param line line containing the segment
+     */
+    public Segment(final Vector3D start, final Vector3D end, final Line line) {
+        this.start  = start;
+        this.end    = end;
+        this.line   = line;
+    }
+
+    /** Get the start point of the segment.
+     * @return start point of the segment
+     */
+    public Vector3D getStart() {
+        return start;
+    }
+
+    /** Get the end point of the segment.
+     * @return end point of the segment
+     */
+    public Vector3D getEnd() {
+        return end;
+    }
+
+    /** Get the line containing the segment.
+     * @return line containing the segment
+     */
+    public Line getLine() {
+        return line;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/SphereGenerator.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/SphereGenerator.java
new file mode 100644
index 0000000..b553510
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/SphereGenerator.java
@@ -0,0 +1,152 @@
+/*
+ * 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.math3.geometry.euclidean.threed;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.math3.fraction.BigFraction;
+import org.apache.commons.math3.geometry.enclosing.EnclosingBall;
+import org.apache.commons.math3.geometry.enclosing.SupportBallGenerator;
+import org.apache.commons.math3.geometry.euclidean.twod.DiskGenerator;
+import org.apache.commons.math3.geometry.euclidean.twod.Euclidean2D;
+import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
+import org.apache.commons.math3.util.FastMath;
+
+/** Class generating an enclosing ball from its support points.
+ * @since 3.3
+ */
+public class SphereGenerator implements SupportBallGenerator<Euclidean3D, Vector3D> {
+
+    /** {@inheritDoc} */
+    public EnclosingBall<Euclidean3D, Vector3D> ballOnSupport(final List<Vector3D> support) {
+
+        if (support.size() < 1) {
+            return new EnclosingBall<Euclidean3D, Vector3D>(Vector3D.ZERO, Double.NEGATIVE_INFINITY);
+        } else {
+            final Vector3D vA = support.get(0);
+            if (support.size() < 2) {
+                return new EnclosingBall<Euclidean3D, Vector3D>(vA, 0, vA);
+            } else {
+                final Vector3D vB = support.get(1);
+                if (support.size() < 3) {
+                    return new EnclosingBall<Euclidean3D, Vector3D>(new Vector3D(0.5, vA, 0.5, vB),
+                                                                    0.5 * vA.distance(vB),
+                                                                    vA, vB);
+                } else {
+                    final Vector3D vC = support.get(2);
+                    if (support.size() < 4) {
+
+                        // delegate to 2D disk generator
+                        final Plane p = new Plane(vA, vB, vC,
+                                                  1.0e-10 * (vA.getNorm1() + vB.getNorm1() + vC.getNorm1()));
+                        final EnclosingBall<Euclidean2D, Vector2D> disk =
+                                new DiskGenerator().ballOnSupport(Arrays.asList(p.toSubSpace(vA),
+                                                                                p.toSubSpace(vB),
+                                                                                p.toSubSpace(vC)));
+
+                        // convert back to 3D
+                        return new EnclosingBall<Euclidean3D, Vector3D>(p.toSpace(disk.getCenter()),
+                                                                        disk.getRadius(), vA, vB, vC);
+
+                    } else {
+                        final Vector3D vD = support.get(3);
+                        // a sphere is 3D can be defined as:
+                        // (1)   (x - x_0)^2 + (y - y_0)^2 + (z - z_0)^2 = r^2
+                        // which can be written:
+                        // (2)   (x^2 + y^2 + z^2) - 2 x_0 x - 2 y_0 y - 2 z_0 z + (x_0^2 + y_0^2 + z_0^2 - r^2) = 0
+                        // or simply:
+                        // (3)   (x^2 + y^2 + z^2) + a x + b y + c z + d = 0
+                        // with sphere center coordinates -a/2, -b/2, -c/2
+                        // If the sphere exists, a b, c and d are a non zero solution to
+                        // [ (x^2  + y^2  + z^2)    x    y   z    1 ]   [ 1 ]   [ 0 ]
+                        // [ (xA^2 + yA^2 + zA^2)   xA   yA  zA   1 ]   [ a ]   [ 0 ]
+                        // [ (xB^2 + yB^2 + zB^2)   xB   yB  zB   1 ] * [ b ] = [ 0 ]
+                        // [ (xC^2 + yC^2 + zC^2)   xC   yC  zC   1 ]   [ c ]   [ 0 ]
+                        // [ (xD^2 + yD^2 + zD^2)   xD   yD  zD   1 ]   [ d ]   [ 0 ]
+                        // So the determinant of the matrix is zero. Computing this determinant
+                        // by expanding it using the minors m_ij of first row leads to
+                        // (4)   m_11 (x^2 + y^2 + z^2) - m_12 x + m_13 y - m_14 z + m_15 = 0
+                        // So by identifying equations (2) and (4) we get the coordinates
+                        // of center as:
+                        //      x_0 = +m_12 / (2 m_11)
+                        //      y_0 = -m_13 / (2 m_11)
+                        //      z_0 = +m_14 / (2 m_11)
+                        // Note that the minors m_11, m_12, m_13 and m_14 all have the last column
+                        // filled with 1.0, hence simplifying the computation
+                        final BigFraction[] c2 = new BigFraction[] {
+                            new BigFraction(vA.getX()), new BigFraction(vB.getX()),
+                            new BigFraction(vC.getX()), new BigFraction(vD.getX())
+                        };
+                        final BigFraction[] c3 = new BigFraction[] {
+                            new BigFraction(vA.getY()), new BigFraction(vB.getY()),
+                            new BigFraction(vC.getY()), new BigFraction(vD.getY())
+                        };
+                        final BigFraction[] c4 = new BigFraction[] {
+                            new BigFraction(vA.getZ()), new BigFraction(vB.getZ()),
+                            new BigFraction(vC.getZ()), new BigFraction(vD.getZ())
+                        };
+                        final BigFraction[] c1 = new BigFraction[] {
+                            c2[0].multiply(c2[0]).add(c3[0].multiply(c3[0])).add(c4[0].multiply(c4[0])),
+                            c2[1].multiply(c2[1]).add(c3[1].multiply(c3[1])).add(c4[1].multiply(c4[1])),
+                            c2[2].multiply(c2[2]).add(c3[2].multiply(c3[2])).add(c4[2].multiply(c4[2])),
+                            c2[3].multiply(c2[3]).add(c3[3].multiply(c3[3])).add(c4[3].multiply(c4[3]))
+                        };
+                        final BigFraction twoM11  = minor(c2, c3, c4).multiply(2);
+                        final BigFraction m12     = minor(c1, c3, c4);
+                        final BigFraction m13     = minor(c1, c2, c4);
+                        final BigFraction m14     = minor(c1, c2, c3);
+                        final BigFraction centerX = m12.divide(twoM11);
+                        final BigFraction centerY = m13.divide(twoM11).negate();
+                        final BigFraction centerZ = m14.divide(twoM11);
+                        final BigFraction dx      = c2[0].subtract(centerX);
+                        final BigFraction dy      = c3[0].subtract(centerY);
+                        final BigFraction dz      = c4[0].subtract(centerZ);
+                        final BigFraction r2      = dx.multiply(dx).add(dy.multiply(dy)).add(dz.multiply(dz));
+                        return new EnclosingBall<Euclidean3D, Vector3D>(new Vector3D(centerX.doubleValue(),
+                                                                                     centerY.doubleValue(),
+                                                                                     centerZ.doubleValue()),
+                                                                        FastMath.sqrt(r2.doubleValue()),
+                                                                        vA, vB, vC, vD);
+                    }
+                }
+            }
+        }
+    }
+
+    /** Compute a dimension 4 minor, when 4<sup>th</sup> column is known to be filled with 1.0.
+     * @param c1 first column
+     * @param c2 second column
+     * @param c3 third column
+     * @return value of the minor computed has an exact fraction
+     */
+    private BigFraction minor(final BigFraction[] c1, final BigFraction[] c2, final BigFraction[] c3) {
+        return      c2[0].multiply(c3[1]).multiply(c1[2].subtract(c1[3])).
+                add(c2[0].multiply(c3[2]).multiply(c1[3].subtract(c1[1]))).
+                add(c2[0].multiply(c3[3]).multiply(c1[1].subtract(c1[2]))).
+                add(c2[1].multiply(c3[0]).multiply(c1[3].subtract(c1[2]))).
+                add(c2[1].multiply(c3[2]).multiply(c1[0].subtract(c1[3]))).
+                add(c2[1].multiply(c3[3]).multiply(c1[2].subtract(c1[0]))).
+                add(c2[2].multiply(c3[0]).multiply(c1[1].subtract(c1[3]))).
+                add(c2[2].multiply(c3[1]).multiply(c1[3].subtract(c1[0]))).
+                add(c2[2].multiply(c3[3]).multiply(c1[0].subtract(c1[1]))).
+                add(c2[3].multiply(c3[0]).multiply(c1[2].subtract(c1[1]))).
+                add(c2[3].multiply(c3[1]).multiply(c1[0].subtract(c1[2]))).
+                add(c2[3].multiply(c3[2]).multiply(c1[1].subtract(c1[0])));
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/SphericalCoordinates.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/SphericalCoordinates.java
new file mode 100644
index 0000000..016e0a0
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/SphericalCoordinates.java
@@ -0,0 +1,395 @@
+/*
+ * 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.math3.geometry.euclidean.threed;
+
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.util.FastMath;
+
+/** This class provides conversions related to <a
+ * href="http://mathworld.wolfram.com/SphericalCoordinates.html">spherical coordinates</a>.
+ * <p>
+ * The conventions used here are the mathematical ones, i.e. spherical coordinates are
+ * related to Cartesian coordinates as follows:
+ * </p>
+ * <ul>
+ *   <li>x = r cos(&theta;) sin(&Phi;)</li>
+ *   <li>y = r sin(&theta;) sin(&Phi;)</li>
+ *   <li>z = r cos(&Phi;)</li>
+ * </ul>
+ * <ul>
+ *   <li>r       = &radic;(x<sup>2</sup>+y<sup>2</sup>+z<sup>2</sup>)</li>
+ *   <li>&theta; = atan2(y, x)</li>
+ *   <li>&Phi;   = acos(z/r)</li>
+ * </ul>
+ * <p>
+ * r is the radius, &theta; is the azimuthal angle in the x-y plane and &Phi; is the polar
+ * (co-latitude) angle. These conventions are <em>different</em> from the conventions used
+ * in physics (and in particular in spherical harmonics) where the meanings of &theta; and
+ * &Phi; are reversed.
+ * </p>
+ * <p>
+ * This class provides conversion of coordinates and also of gradient and Hessian
+ * between spherical and Cartesian coordinates.
+ * </p>
+ * @since 3.2
+ */
+public class SphericalCoordinates implements Serializable {
+
+    /** Serializable UID. */
+    private static final long serialVersionUID = 20130206L;
+
+    /** Cartesian coordinates. */
+    private final Vector3D v;
+
+    /** Radius. */
+    private final double r;
+
+    /** Azimuthal angle in the x-y plane &theta;. */
+    private final double theta;
+
+    /** Polar angle (co-latitude) &Phi;. */
+    private final double phi;
+
+    /** Jacobian of (r, &theta; &Phi). */
+    private double[][] jacobian;
+
+    /** Hessian of radius. */
+    private double[][] rHessian;
+
+    /** Hessian of azimuthal angle in the x-y plane &theta;. */
+    private double[][] thetaHessian;
+
+    /** Hessian of polar (co-latitude) angle &Phi;. */
+    private double[][] phiHessian;
+
+    /** Build a spherical coordinates transformer from Cartesian coordinates.
+     * @param v Cartesian coordinates
+     */
+    public SphericalCoordinates(final Vector3D v) {
+
+        // Cartesian coordinates
+        this.v = v;
+
+        // remaining spherical coordinates
+        this.r     = v.getNorm();
+        this.theta = v.getAlpha();
+        this.phi   = FastMath.acos(v.getZ() / r);
+
+    }
+
+    /** Build a spherical coordinates transformer from spherical coordinates.
+     * @param r radius
+     * @param theta azimuthal angle in x-y plane
+     * @param phi polar (co-latitude) angle
+     */
+    public SphericalCoordinates(final double r, final double theta, final double phi) {
+
+        final double cosTheta = FastMath.cos(theta);
+        final double sinTheta = FastMath.sin(theta);
+        final double cosPhi   = FastMath.cos(phi);
+        final double sinPhi   = FastMath.sin(phi);
+
+        // spherical coordinates
+        this.r     = r;
+        this.theta = theta;
+        this.phi   = phi;
+
+        // Cartesian coordinates
+        this.v  = new Vector3D(r * cosTheta * sinPhi,
+                               r * sinTheta * sinPhi,
+                               r * cosPhi);
+
+    }
+
+    /** Get the Cartesian coordinates.
+     * @return Cartesian coordinates
+     */
+    public Vector3D getCartesian() {
+        return v;
+    }
+
+    /** Get the radius.
+     * @return radius r
+     * @see #getTheta()
+     * @see #getPhi()
+     */
+    public double getR() {
+        return r;
+    }
+
+    /** Get the azimuthal angle in x-y plane.
+     * @return azimuthal angle in x-y plane &theta;
+     * @see #getR()
+     * @see #getPhi()
+     */
+    public double getTheta() {
+        return theta;
+    }
+
+    /** Get the polar (co-latitude) angle.
+     * @return polar (co-latitude) angle &Phi;
+     * @see #getR()
+     * @see #getTheta()
+     */
+    public double getPhi() {
+        return phi;
+    }
+
+    /** Convert a gradient with respect to spherical coordinates into a gradient
+     * with respect to Cartesian coordinates.
+     * @param sGradient gradient with respect to spherical coordinates
+     * {df/dr, df/d&theta;, df/d&Phi;}
+     * @return gradient with respect to Cartesian coordinates
+     * {df/dx, df/dy, df/dz}
+     */
+    public double[] toCartesianGradient(final double[] sGradient) {
+
+        // lazy evaluation of Jacobian
+        computeJacobian();
+
+        // compose derivatives as gradient^T . J
+        // the expressions have been simplified since we know jacobian[1][2] = dTheta/dZ = 0
+        return new double[] {
+            sGradient[0] * jacobian[0][0] + sGradient[1] * jacobian[1][0] + sGradient[2] * jacobian[2][0],
+            sGradient[0] * jacobian[0][1] + sGradient[1] * jacobian[1][1] + sGradient[2] * jacobian[2][1],
+            sGradient[0] * jacobian[0][2]                                 + sGradient[2] * jacobian[2][2]
+        };
+
+    }
+
+    /** Convert a Hessian with respect to spherical coordinates into a Hessian
+     * with respect to Cartesian coordinates.
+     * <p>
+     * As Hessian are always symmetric, we use only the lower left part of the provided
+     * spherical Hessian, so the upper part may not be initialized. However, we still
+     * do fill up the complete array we create, with guaranteed symmetry.
+     * </p>
+     * @param sHessian Hessian with respect to spherical coordinates
+     * {{d<sup>2</sup>f/dr<sup>2</sup>, d<sup>2</sup>f/drd&theta;, d<sup>2</sup>f/drd&Phi;},
+     *  {d<sup>2</sup>f/drd&theta;, d<sup>2</sup>f/d&theta;<sup>2</sup>, d<sup>2</sup>f/d&theta;d&Phi;},
+     *  {d<sup>2</sup>f/drd&Phi;, d<sup>2</sup>f/d&theta;d&Phi;, d<sup>2</sup>f/d&Phi;<sup>2</sup>}
+     * @param sGradient gradient with respect to spherical coordinates
+     * {df/dr, df/d&theta;, df/d&Phi;}
+     * @return Hessian with respect to Cartesian coordinates
+     * {{d<sup>2</sup>f/dx<sup>2</sup>, d<sup>2</sup>f/dxdy, d<sup>2</sup>f/dxdz},
+     *  {d<sup>2</sup>f/dxdy, d<sup>2</sup>f/dy<sup>2</sup>, d<sup>2</sup>f/dydz},
+     *  {d<sup>2</sup>f/dxdz, d<sup>2</sup>f/dydz, d<sup>2</sup>f/dz<sup>2</sup>}}
+     */
+    public double[][] toCartesianHessian(final double[][] sHessian, final double[] sGradient) {
+
+        computeJacobian();
+        computeHessians();
+
+        // compose derivative as J^T . H_f . J + df/dr H_r + df/dtheta H_theta + df/dphi H_phi
+        // the expressions have been simplified since we know jacobian[1][2] = dTheta/dZ = 0
+        // and H_theta is only a 2x2 matrix as it does not depend on z
+        final double[][] hj = new double[3][3];
+        final double[][] cHessian = new double[3][3];
+
+        // compute H_f . J
+        // beware we use ONLY the lower-left part of sHessian
+        hj[0][0] = sHessian[0][0] * jacobian[0][0] + sHessian[1][0] * jacobian[1][0] + sHessian[2][0] * jacobian[2][0];
+        hj[0][1] = sHessian[0][0] * jacobian[0][1] + sHessian[1][0] * jacobian[1][1] + sHessian[2][0] * jacobian[2][1];
+        hj[0][2] = sHessian[0][0] * jacobian[0][2]                                   + sHessian[2][0] * jacobian[2][2];
+        hj[1][0] = sHessian[1][0] * jacobian[0][0] + sHessian[1][1] * jacobian[1][0] + sHessian[2][1] * jacobian[2][0];
+        hj[1][1] = sHessian[1][0] * jacobian[0][1] + sHessian[1][1] * jacobian[1][1] + sHessian[2][1] * jacobian[2][1];
+        // don't compute hj[1][2] as it is not used below
+        hj[2][0] = sHessian[2][0] * jacobian[0][0] + sHessian[2][1] * jacobian[1][0] + sHessian[2][2] * jacobian[2][0];
+        hj[2][1] = sHessian[2][0] * jacobian[0][1] + sHessian[2][1] * jacobian[1][1] + sHessian[2][2] * jacobian[2][1];
+        hj[2][2] = sHessian[2][0] * jacobian[0][2]                                   + sHessian[2][2] * jacobian[2][2];
+
+        // compute lower-left part of J^T . H_f . J
+        cHessian[0][0] = jacobian[0][0] * hj[0][0] + jacobian[1][0] * hj[1][0] + jacobian[2][0] * hj[2][0];
+        cHessian[1][0] = jacobian[0][1] * hj[0][0] + jacobian[1][1] * hj[1][0] + jacobian[2][1] * hj[2][0];
+        cHessian[2][0] = jacobian[0][2] * hj[0][0]                             + jacobian[2][2] * hj[2][0];
+        cHessian[1][1] = jacobian[0][1] * hj[0][1] + jacobian[1][1] * hj[1][1] + jacobian[2][1] * hj[2][1];
+        cHessian[2][1] = jacobian[0][2] * hj[0][1]                             + jacobian[2][2] * hj[2][1];
+        cHessian[2][2] = jacobian[0][2] * hj[0][2]                             + jacobian[2][2] * hj[2][2];
+
+        // add gradient contribution
+        cHessian[0][0] += sGradient[0] * rHessian[0][0] + sGradient[1] * thetaHessian[0][0] + sGradient[2] * phiHessian[0][0];
+        cHessian[1][0] += sGradient[0] * rHessian[1][0] + sGradient[1] * thetaHessian[1][0] + sGradient[2] * phiHessian[1][0];
+        cHessian[2][0] += sGradient[0] * rHessian[2][0]                                     + sGradient[2] * phiHessian[2][0];
+        cHessian[1][1] += sGradient[0] * rHessian[1][1] + sGradient[1] * thetaHessian[1][1] + sGradient[2] * phiHessian[1][1];
+        cHessian[2][1] += sGradient[0] * rHessian[2][1]                                     + sGradient[2] * phiHessian[2][1];
+        cHessian[2][2] += sGradient[0] * rHessian[2][2]                                     + sGradient[2] * phiHessian[2][2];
+
+        // ensure symmetry
+        cHessian[0][1] = cHessian[1][0];
+        cHessian[0][2] = cHessian[2][0];
+        cHessian[1][2] = cHessian[2][1];
+
+        return cHessian;
+
+    }
+
+    /** Lazy evaluation of (r, &theta;, &phi;) Jacobian.
+     */
+    private void computeJacobian() {
+        if (jacobian == null) {
+
+            // intermediate variables
+            final double x    = v.getX();
+            final double y    = v.getY();
+            final double z    = v.getZ();
+            final double rho2 = x * x + y * y;
+            final double rho  = FastMath.sqrt(rho2);
+            final double r2   = rho2 + z * z;
+
+            jacobian = new double[3][3];
+
+            // row representing the gradient of r
+            jacobian[0][0] = x / r;
+            jacobian[0][1] = y / r;
+            jacobian[0][2] = z / r;
+
+            // row representing the gradient of theta
+            jacobian[1][0] = -y / rho2;
+            jacobian[1][1] =  x / rho2;
+            // jacobian[1][2] is already set to 0 at allocation time
+
+            // row representing the gradient of phi
+            jacobian[2][0] = x * z / (rho * r2);
+            jacobian[2][1] = y * z / (rho * r2);
+            jacobian[2][2] = -rho / r2;
+
+        }
+    }
+
+    /** Lazy evaluation of Hessians.
+     */
+    private void computeHessians() {
+
+        if (rHessian == null) {
+
+            // intermediate variables
+            final double x      = v.getX();
+            final double y      = v.getY();
+            final double z      = v.getZ();
+            final double x2     = x * x;
+            final double y2     = y * y;
+            final double z2     = z * z;
+            final double rho2   = x2 + y2;
+            final double rho    = FastMath.sqrt(rho2);
+            final double r2     = rho2 + z2;
+            final double xOr    = x / r;
+            final double yOr    = y / r;
+            final double zOr    = z / r;
+            final double xOrho2 = x / rho2;
+            final double yOrho2 = y / rho2;
+            final double xOr3   = xOr / r2;
+            final double yOr3   = yOr / r2;
+            final double zOr3   = zOr / r2;
+
+            // lower-left part of Hessian of r
+            rHessian = new double[3][3];
+            rHessian[0][0] = y * yOr3 + z * zOr3;
+            rHessian[1][0] = -x * yOr3;
+            rHessian[2][0] = -z * xOr3;
+            rHessian[1][1] = x * xOr3 + z * zOr3;
+            rHessian[2][1] = -y * zOr3;
+            rHessian[2][2] = x * xOr3 + y * yOr3;
+
+            // upper-right part is symmetric
+            rHessian[0][1] = rHessian[1][0];
+            rHessian[0][2] = rHessian[2][0];
+            rHessian[1][2] = rHessian[2][1];
+
+            // lower-left part of Hessian of azimuthal angle theta
+            thetaHessian = new double[2][2];
+            thetaHessian[0][0] = 2 * xOrho2 * yOrho2;
+            thetaHessian[1][0] = yOrho2 * yOrho2 - xOrho2 * xOrho2;
+            thetaHessian[1][1] = -2 * xOrho2 * yOrho2;
+
+            // upper-right part is symmetric
+            thetaHessian[0][1] = thetaHessian[1][0];
+
+            // lower-left part of Hessian of polar (co-latitude) angle phi
+            final double rhor2       = rho * r2;
+            final double rho2r2      = rho * rhor2;
+            final double rhor4       = rhor2 * r2;
+            final double rho3r4      = rhor4 * rho2;
+            final double r2P2rho2    = 3 * rho2 + z2;
+            phiHessian = new double[3][3];
+            phiHessian[0][0] = z * (rho2r2 - x2 * r2P2rho2) / rho3r4;
+            phiHessian[1][0] = -x * y * z * r2P2rho2 / rho3r4;
+            phiHessian[2][0] = x * (rho2 - z2) / rhor4;
+            phiHessian[1][1] = z * (rho2r2 - y2 * r2P2rho2) / rho3r4;
+            phiHessian[2][1] = y * (rho2 - z2) / rhor4;
+            phiHessian[2][2] = 2 * rho * zOr3 / r;
+
+            // upper-right part is symmetric
+            phiHessian[0][1] = phiHessian[1][0];
+            phiHessian[0][2] = phiHessian[2][0];
+            phiHessian[1][2] = phiHessian[2][1];
+
+        }
+
+    }
+
+    /**
+     * Replace the instance with a data transfer object for serialization.
+     * @return data transfer object that will be serialized
+     */
+    private Object writeReplace() {
+        return new DataTransferObject(v.getX(), v.getY(), v.getZ());
+    }
+
+    /** Internal class used only for serialization. */
+    private static class DataTransferObject implements Serializable {
+
+        /** Serializable UID. */
+        private static final long serialVersionUID = 20130206L;
+
+        /** Abscissa.
+         * @serial
+         */
+        private final double x;
+
+        /** Ordinate.
+         * @serial
+         */
+        private final double y;
+
+        /** Height.
+         * @serial
+         */
+        private final double z;
+
+        /** Simple constructor.
+         * @param x abscissa
+         * @param y ordinate
+         * @param z height
+         */
+        DataTransferObject(final double x, final double y, final double z) {
+            this.x = x;
+            this.y = y;
+            this.z = z;
+        }
+
+        /** Replace the deserialized data transfer object with a {@link SphericalCoordinates}.
+         * @return replacement {@link SphericalCoordinates}
+         */
+        private Object readResolve() {
+            return new SphericalCoordinates(new Vector3D(x, y, z));
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/SubLine.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/SubLine.java
new file mode 100644
index 0000000..2ac917f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/SubLine.java
@@ -0,0 +1,165 @@
+/*
+ * 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.math3.geometry.euclidean.threed;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.euclidean.oned.Euclidean1D;
+import org.apache.commons.math3.geometry.euclidean.oned.Interval;
+import org.apache.commons.math3.geometry.euclidean.oned.IntervalsSet;
+import org.apache.commons.math3.geometry.euclidean.oned.Vector1D;
+import org.apache.commons.math3.geometry.partitioning.Region.Location;
+
+/** This class represents a subset of a {@link Line}.
+ * @since 3.0
+ */
+public class SubLine {
+
+    /** Default value for tolerance. */
+    private static final double DEFAULT_TOLERANCE = 1.0e-10;
+
+    /** Underlying line. */
+    private final Line line;
+
+    /** Remaining region of the hyperplane. */
+    private final IntervalsSet remainingRegion;
+
+    /** Simple constructor.
+     * @param line underlying line
+     * @param remainingRegion remaining region of the line
+     */
+    public SubLine(final Line line, final IntervalsSet remainingRegion) {
+        this.line            = line;
+        this.remainingRegion = remainingRegion;
+    }
+
+    /** Create a sub-line from two endpoints.
+     * @param start start point
+     * @param end end point
+     * @param tolerance tolerance below which points are considered identical
+     * @exception MathIllegalArgumentException if the points are equal
+     * @since 3.3
+     */
+    public SubLine(final Vector3D start, final Vector3D end, final double tolerance)
+        throws MathIllegalArgumentException {
+        this(new Line(start, end, tolerance), buildIntervalSet(start, end, tolerance));
+    }
+
+    /** Create a sub-line from two endpoints.
+     * @param start start point
+     * @param end end point
+     * @exception MathIllegalArgumentException if the points are equal
+     * @deprecated as of 3.3, replaced with {@link #SubLine(Vector3D, Vector3D, double)}
+     */
+    public SubLine(final Vector3D start, final Vector3D end)
+        throws MathIllegalArgumentException {
+        this(start, end, DEFAULT_TOLERANCE);
+    }
+
+    /** Create a sub-line from a segment.
+     * @param segment single segment forming the sub-line
+     * @exception MathIllegalArgumentException if the segment endpoints are equal
+     */
+    public SubLine(final Segment segment) throws MathIllegalArgumentException {
+        this(segment.getLine(),
+             buildIntervalSet(segment.getStart(), segment.getEnd(), segment.getLine().getTolerance()));
+    }
+
+    /** Get the endpoints of the sub-line.
+     * <p>
+     * A subline may be any arbitrary number of disjoints segments, so the endpoints
+     * are provided as a list of endpoint pairs. Each element of the list represents
+     * one segment, and each segment contains a start point at index 0 and an end point
+     * at index 1. If the sub-line is unbounded in the negative infinity direction,
+     * the start point of the first segment will have infinite coordinates. If the
+     * sub-line is unbounded in the positive infinity direction, the end point of the
+     * last segment will have infinite coordinates. So a sub-line covering the whole
+     * line will contain just one row and both elements of this row will have infinite
+     * coordinates. If the sub-line is empty, the returned list will contain 0 segments.
+     * </p>
+     * @return list of segments endpoints
+     */
+    public List<Segment> getSegments() {
+
+        final List<Interval> list = remainingRegion.asList();
+        final List<Segment> segments = new ArrayList<Segment>(list.size());
+
+        for (final Interval interval : list) {
+            final Vector3D start = line.toSpace((Point<Euclidean1D>) new Vector1D(interval.getInf()));
+            final Vector3D end   = line.toSpace((Point<Euclidean1D>) new Vector1D(interval.getSup()));
+            segments.add(new Segment(start, end, line));
+        }
+
+        return segments;
+
+    }
+
+    /** Get the intersection of the instance and another sub-line.
+     * <p>
+     * This method is related to the {@link Line#intersection(Line)
+     * intersection} method in the {@link Line Line} class, but in addition
+     * to compute the point along infinite lines, it also checks the point
+     * lies on both sub-line ranges.
+     * </p>
+     * @param subLine other sub-line which may intersect instance
+     * @param includeEndPoints if true, endpoints are considered to belong to
+     * instance (i.e. they are closed sets) and may be returned, otherwise endpoints
+     * are considered to not belong to instance (i.e. they are open sets) and intersection
+     * occurring on endpoints lead to null being returned
+     * @return the intersection point if there is one, null if the sub-lines don't intersect
+     */
+    public Vector3D intersection(final SubLine subLine, final boolean includeEndPoints) {
+
+        // compute the intersection on infinite line
+        Vector3D v1D = line.intersection(subLine.line);
+        if (v1D == null) {
+            return null;
+        }
+
+        // check location of point with respect to first sub-line
+        Location loc1 = remainingRegion.checkPoint((Point<Euclidean1D>) line.toSubSpace((Point<Euclidean3D>) v1D));
+
+        // check location of point with respect to second sub-line
+        Location loc2 = subLine.remainingRegion.checkPoint((Point<Euclidean1D>) subLine.line.toSubSpace((Point<Euclidean3D>) v1D));
+
+        if (includeEndPoints) {
+            return ((loc1 != Location.OUTSIDE) && (loc2 != Location.OUTSIDE)) ? v1D : null;
+        } else {
+            return ((loc1 == Location.INSIDE) && (loc2 == Location.INSIDE)) ? v1D : null;
+        }
+
+    }
+
+    /** Build an interval set from two points.
+     * @param start start point
+     * @param end end point
+     * @return an interval set
+     * @param tolerance tolerance below which points are considered identical
+     * @exception MathIllegalArgumentException if the points are equal
+     */
+    private static IntervalsSet buildIntervalSet(final Vector3D start, final Vector3D end, final double tolerance)
+        throws MathIllegalArgumentException {
+        final Line line = new Line(start, end, tolerance);
+        return new IntervalsSet(line.toSubSpace((Point<Euclidean3D>) start).getX(),
+                                line.toSubSpace((Point<Euclidean3D>) end).getX(),
+                                tolerance);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/SubPlane.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/SubPlane.java
new file mode 100644
index 0000000..ce02a38
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/SubPlane.java
@@ -0,0 +1,108 @@
+/*
+ * 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.math3.geometry.euclidean.threed;
+
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.euclidean.oned.Euclidean1D;
+import org.apache.commons.math3.geometry.euclidean.oned.Vector1D;
+import org.apache.commons.math3.geometry.euclidean.twod.Euclidean2D;
+import org.apache.commons.math3.geometry.euclidean.twod.PolygonsSet;
+import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
+import org.apache.commons.math3.geometry.partitioning.AbstractSubHyperplane;
+import org.apache.commons.math3.geometry.partitioning.BSPTree;
+import org.apache.commons.math3.geometry.partitioning.Hyperplane;
+import org.apache.commons.math3.geometry.partitioning.Region;
+import org.apache.commons.math3.geometry.partitioning.SubHyperplane;
+
+/** This class represents a sub-hyperplane for {@link Plane}.
+ * @since 3.0
+ */
+public class SubPlane extends AbstractSubHyperplane<Euclidean3D, Euclidean2D> {
+
+    /** Simple constructor.
+     * @param hyperplane underlying hyperplane
+     * @param remainingRegion remaining region of the hyperplane
+     */
+    public SubPlane(final Hyperplane<Euclidean3D> hyperplane,
+                    final Region<Euclidean2D> remainingRegion) {
+        super(hyperplane, remainingRegion);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected AbstractSubHyperplane<Euclidean3D, Euclidean2D> buildNew(final Hyperplane<Euclidean3D> hyperplane,
+                                                                       final Region<Euclidean2D> remainingRegion) {
+        return new SubPlane(hyperplane, remainingRegion);
+    }
+
+    /** Split the instance in two parts by an hyperplane.
+     * @param hyperplane splitting hyperplane
+     * @return an object containing both the part of the instance
+     * on the plus side of the instance and the part of the
+     * instance on the minus side of the instance
+     */
+    @Override
+    public SplitSubHyperplane<Euclidean3D> split(Hyperplane<Euclidean3D> hyperplane) {
+
+        final Plane otherPlane = (Plane) hyperplane;
+        final Plane thisPlane  = (Plane) getHyperplane();
+        final Line  inter      = otherPlane.intersection(thisPlane);
+        final double tolerance = thisPlane.getTolerance();
+
+        if (inter == null) {
+            // the hyperplanes are parallel
+            final double global = otherPlane.getOffset(thisPlane);
+            if (global < -tolerance) {
+                return new SplitSubHyperplane<Euclidean3D>(null, this);
+            } else if (global > tolerance) {
+                return new SplitSubHyperplane<Euclidean3D>(this, null);
+            } else {
+                return new SplitSubHyperplane<Euclidean3D>(null, null);
+            }
+        }
+
+        // the hyperplanes do intersect
+        Vector2D p = thisPlane.toSubSpace((Point<Euclidean3D>) inter.toSpace((Point<Euclidean1D>) Vector1D.ZERO));
+        Vector2D q = thisPlane.toSubSpace((Point<Euclidean3D>) inter.toSpace((Point<Euclidean1D>) Vector1D.ONE));
+        Vector3D crossP = Vector3D.crossProduct(inter.getDirection(), thisPlane.getNormal());
+        if (crossP.dotProduct(otherPlane.getNormal()) < 0) {
+            final Vector2D tmp = p;
+            p           = q;
+            q           = tmp;
+        }
+        final SubHyperplane<Euclidean2D> l2DMinus =
+            new org.apache.commons.math3.geometry.euclidean.twod.Line(p, q, tolerance).wholeHyperplane();
+        final SubHyperplane<Euclidean2D> l2DPlus =
+            new org.apache.commons.math3.geometry.euclidean.twod.Line(q, p, tolerance).wholeHyperplane();
+
+        final BSPTree<Euclidean2D> splitTree = getRemainingRegion().getTree(false).split(l2DMinus);
+        final BSPTree<Euclidean2D> plusTree  = getRemainingRegion().isEmpty(splitTree.getPlus()) ?
+                                               new BSPTree<Euclidean2D>(Boolean.FALSE) :
+                                               new BSPTree<Euclidean2D>(l2DPlus, new BSPTree<Euclidean2D>(Boolean.FALSE),
+                                                                        splitTree.getPlus(), null);
+
+        final BSPTree<Euclidean2D> minusTree = getRemainingRegion().isEmpty(splitTree.getMinus()) ?
+                                               new BSPTree<Euclidean2D>(Boolean.FALSE) :
+                                                   new BSPTree<Euclidean2D>(l2DMinus, new BSPTree<Euclidean2D>(Boolean.FALSE),
+                                                                            splitTree.getMinus(), null);
+
+        return new SplitSubHyperplane<Euclidean3D>(new SubPlane(thisPlane.copySelf(), new PolygonsSet(plusTree, tolerance)),
+                                                   new SubPlane(thisPlane.copySelf(), new PolygonsSet(minusTree, tolerance)));
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/Vector3D.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/Vector3D.java
new file mode 100644
index 0000000..3eaea3a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/Vector3D.java
@@ -0,0 +1,588 @@
+/*
+ * 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.math3.geometry.euclidean.threed;
+
+import java.io.Serializable;
+import java.text.NumberFormat;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.Space;
+import org.apache.commons.math3.geometry.Vector;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * This class implements vectors in a three-dimensional space.
+ * <p>Instance of this class are guaranteed to be immutable.</p>
+ * @since 1.2
+ */
+public class Vector3D implements Serializable, Vector<Euclidean3D> {
+
+    /** Null vector (coordinates: 0, 0, 0). */
+    public static final Vector3D ZERO   = new Vector3D(0, 0, 0);
+
+    /** First canonical vector (coordinates: 1, 0, 0). */
+    public static final Vector3D PLUS_I = new Vector3D(1, 0, 0);
+
+    /** Opposite of the first canonical vector (coordinates: -1, 0, 0). */
+    public static final Vector3D MINUS_I = new Vector3D(-1, 0, 0);
+
+    /** Second canonical vector (coordinates: 0, 1, 0). */
+    public static final Vector3D PLUS_J = new Vector3D(0, 1, 0);
+
+    /** Opposite of the second canonical vector (coordinates: 0, -1, 0). */
+    public static final Vector3D MINUS_J = new Vector3D(0, -1, 0);
+
+    /** Third canonical vector (coordinates: 0, 0, 1). */
+    public static final Vector3D PLUS_K = new Vector3D(0, 0, 1);
+
+    /** Opposite of the third canonical vector (coordinates: 0, 0, -1).  */
+    public static final Vector3D MINUS_K = new Vector3D(0, 0, -1);
+
+    // CHECKSTYLE: stop ConstantName
+    /** A vector with all coordinates set to NaN. */
+    public static final Vector3D NaN = new Vector3D(Double.NaN, Double.NaN, Double.NaN);
+    // CHECKSTYLE: resume ConstantName
+
+    /** A vector with all coordinates set to positive infinity. */
+    public static final Vector3D POSITIVE_INFINITY =
+        new Vector3D(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
+
+    /** A vector with all coordinates set to negative infinity. */
+    public static final Vector3D NEGATIVE_INFINITY =
+        new Vector3D(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 1313493323784566947L;
+
+    /** Abscissa. */
+    private final double x;
+
+    /** Ordinate. */
+    private final double y;
+
+    /** Height. */
+    private final double z;
+
+    /** Simple constructor.
+     * Build a vector from its coordinates
+     * @param x abscissa
+     * @param y ordinate
+     * @param z height
+     * @see #getX()
+     * @see #getY()
+     * @see #getZ()
+     */
+    public Vector3D(double x, double y, double z) {
+        this.x = x;
+        this.y = y;
+        this.z = z;
+    }
+
+    /** Simple constructor.
+     * Build a vector from its coordinates
+     * @param v coordinates array
+     * @exception DimensionMismatchException if array does not have 3 elements
+     * @see #toArray()
+     */
+    public Vector3D(double[] v) throws DimensionMismatchException {
+        if (v.length != 3) {
+            throw new DimensionMismatchException(v.length, 3);
+        }
+        this.x = v[0];
+        this.y = v[1];
+        this.z = v[2];
+    }
+
+    /** Simple constructor.
+     * Build a vector from its azimuthal coordinates
+     * @param alpha azimuth (&alpha;) around Z
+     *              (0 is +X, &pi;/2 is +Y, &pi; is -X and 3&pi;/2 is -Y)
+     * @param delta elevation (&delta;) above (XY) plane, from -&pi;/2 to +&pi;/2
+     * @see #getAlpha()
+     * @see #getDelta()
+     */
+    public Vector3D(double alpha, double delta) {
+        double cosDelta = FastMath.cos(delta);
+        this.x = FastMath.cos(alpha) * cosDelta;
+        this.y = FastMath.sin(alpha) * cosDelta;
+        this.z = FastMath.sin(delta);
+    }
+
+    /** Multiplicative constructor
+     * Build a vector from another one and a scale factor.
+     * The vector built will be a * u
+     * @param a scale factor
+     * @param u base (unscaled) vector
+     */
+    public Vector3D(double a, Vector3D u) {
+        this.x = a * u.x;
+        this.y = a * u.y;
+        this.z = a * u.z;
+    }
+
+    /** Linear constructor
+     * Build a vector from two other ones and corresponding scale factors.
+     * The vector built will be a1 * u1 + a2 * u2
+     * @param a1 first scale factor
+     * @param u1 first base (unscaled) vector
+     * @param a2 second scale factor
+     * @param u2 second base (unscaled) vector
+     */
+    public Vector3D(double a1, Vector3D u1, double a2, Vector3D u2) {
+        this.x = MathArrays.linearCombination(a1, u1.x, a2, u2.x);
+        this.y = MathArrays.linearCombination(a1, u1.y, a2, u2.y);
+        this.z = MathArrays.linearCombination(a1, u1.z, a2, u2.z);
+    }
+
+    /** Linear constructor
+     * Build a vector from three other ones and corresponding scale factors.
+     * The vector built will be a1 * u1 + a2 * u2 + a3 * u3
+     * @param a1 first scale factor
+     * @param u1 first base (unscaled) vector
+     * @param a2 second scale factor
+     * @param u2 second base (unscaled) vector
+     * @param a3 third scale factor
+     * @param u3 third base (unscaled) vector
+     */
+    public Vector3D(double a1, Vector3D u1, double a2, Vector3D u2,
+                    double a3, Vector3D u3) {
+        this.x = MathArrays.linearCombination(a1, u1.x, a2, u2.x, a3, u3.x);
+        this.y = MathArrays.linearCombination(a1, u1.y, a2, u2.y, a3, u3.y);
+        this.z = MathArrays.linearCombination(a1, u1.z, a2, u2.z, a3, u3.z);
+    }
+
+    /** Linear constructor
+     * Build a vector from four other ones and corresponding scale factors.
+     * The vector built will be a1 * u1 + a2 * u2 + a3 * u3 + a4 * u4
+     * @param a1 first scale factor
+     * @param u1 first base (unscaled) vector
+     * @param a2 second scale factor
+     * @param u2 second base (unscaled) vector
+     * @param a3 third scale factor
+     * @param u3 third base (unscaled) vector
+     * @param a4 fourth scale factor
+     * @param u4 fourth base (unscaled) vector
+     */
+    public Vector3D(double a1, Vector3D u1, double a2, Vector3D u2,
+                    double a3, Vector3D u3, double a4, Vector3D u4) {
+        this.x = MathArrays.linearCombination(a1, u1.x, a2, u2.x, a3, u3.x, a4, u4.x);
+        this.y = MathArrays.linearCombination(a1, u1.y, a2, u2.y, a3, u3.y, a4, u4.y);
+        this.z = MathArrays.linearCombination(a1, u1.z, a2, u2.z, a3, u3.z, a4, u4.z);
+    }
+
+    /** Get the abscissa of the vector.
+     * @return abscissa of the vector
+     * @see #Vector3D(double, double, double)
+     */
+    public double getX() {
+        return x;
+    }
+
+    /** Get the ordinate of the vector.
+     * @return ordinate of the vector
+     * @see #Vector3D(double, double, double)
+     */
+    public double getY() {
+        return y;
+    }
+
+    /** Get the height of the vector.
+     * @return height of the vector
+     * @see #Vector3D(double, double, double)
+     */
+    public double getZ() {
+        return z;
+    }
+
+    /** Get the vector coordinates as a dimension 3 array.
+     * @return vector coordinates
+     * @see #Vector3D(double[])
+     */
+    public double[] toArray() {
+        return new double[] { x, y, z };
+    }
+
+    /** {@inheritDoc} */
+    public Space getSpace() {
+        return Euclidean3D.getInstance();
+    }
+
+    /** {@inheritDoc} */
+    public Vector3D getZero() {
+        return ZERO;
+    }
+
+    /** {@inheritDoc} */
+    public double getNorm1() {
+        return FastMath.abs(x) + FastMath.abs(y) + FastMath.abs(z);
+    }
+
+    /** {@inheritDoc} */
+    public double getNorm() {
+        // there are no cancellation problems here, so we use the straightforward formula
+        return FastMath.sqrt (x * x + y * y + z * z);
+    }
+
+    /** {@inheritDoc} */
+    public double getNormSq() {
+        // there are no cancellation problems here, so we use the straightforward formula
+        return x * x + y * y + z * z;
+    }
+
+    /** {@inheritDoc} */
+    public double getNormInf() {
+        return FastMath.max(FastMath.max(FastMath.abs(x), FastMath.abs(y)), FastMath.abs(z));
+    }
+
+    /** Get the azimuth of the vector.
+     * @return azimuth (&alpha;) of the vector, between -&pi; and +&pi;
+     * @see #Vector3D(double, double)
+     */
+    public double getAlpha() {
+        return FastMath.atan2(y, x);
+    }
+
+    /** Get the elevation of the vector.
+     * @return elevation (&delta;) of the vector, between -&pi;/2 and +&pi;/2
+     * @see #Vector3D(double, double)
+     */
+    public double getDelta() {
+        return FastMath.asin(z / getNorm());
+    }
+
+    /** {@inheritDoc} */
+    public Vector3D add(final Vector<Euclidean3D> v) {
+        final Vector3D v3 = (Vector3D) v;
+        return new Vector3D(x + v3.x, y + v3.y, z + v3.z);
+    }
+
+    /** {@inheritDoc} */
+    public Vector3D add(double factor, final Vector<Euclidean3D> v) {
+        return new Vector3D(1, this, factor, (Vector3D) v);
+    }
+
+    /** {@inheritDoc} */
+    public Vector3D subtract(final Vector<Euclidean3D> v) {
+        final Vector3D v3 = (Vector3D) v;
+        return new Vector3D(x - v3.x, y - v3.y, z - v3.z);
+    }
+
+    /** {@inheritDoc} */
+    public Vector3D subtract(final double factor, final Vector<Euclidean3D> v) {
+        return new Vector3D(1, this, -factor, (Vector3D) v);
+    }
+
+    /** {@inheritDoc} */
+    public Vector3D normalize() throws MathArithmeticException {
+        double s = getNorm();
+        if (s == 0) {
+            throw new MathArithmeticException(LocalizedFormats.CANNOT_NORMALIZE_A_ZERO_NORM_VECTOR);
+        }
+        return scalarMultiply(1 / s);
+    }
+
+    /** Get a vector orthogonal to the instance.
+     * <p>There are an infinite number of normalized vectors orthogonal
+     * to the instance. This method picks up one of them almost
+     * arbitrarily. It is useful when one needs to compute a reference
+     * frame with one of the axes in a predefined direction. The
+     * following example shows how to build a frame having the k axis
+     * aligned with the known vector u :
+     * <pre><code>
+     *   Vector3D k = u.normalize();
+     *   Vector3D i = k.orthogonal();
+     *   Vector3D j = Vector3D.crossProduct(k, i);
+     * </code></pre></p>
+     * @return a new normalized vector orthogonal to the instance
+     * @exception MathArithmeticException if the norm of the instance is null
+     */
+    public Vector3D orthogonal() throws MathArithmeticException {
+
+        double threshold = 0.6 * getNorm();
+        if (threshold == 0) {
+            throw new MathArithmeticException(LocalizedFormats.ZERO_NORM);
+        }
+
+        if (FastMath.abs(x) <= threshold) {
+            double inverse  = 1 / FastMath.sqrt(y * y + z * z);
+            return new Vector3D(0, inverse * z, -inverse * y);
+        } else if (FastMath.abs(y) <= threshold) {
+            double inverse  = 1 / FastMath.sqrt(x * x + z * z);
+            return new Vector3D(-inverse * z, 0, inverse * x);
+        }
+        double inverse  = 1 / FastMath.sqrt(x * x + y * y);
+        return new Vector3D(inverse * y, -inverse * x, 0);
+
+    }
+
+    /** Compute the angular separation between two vectors.
+     * <p>This method computes the angular separation between two
+     * vectors using the dot product for well separated vectors and the
+     * cross product for almost aligned vectors. This allows to have a
+     * good accuracy in all cases, even for vectors very close to each
+     * other.</p>
+     * @param v1 first vector
+     * @param v2 second vector
+     * @return angular separation between v1 and v2
+     * @exception MathArithmeticException if either vector has a null norm
+     */
+    public static double angle(Vector3D v1, Vector3D v2) throws MathArithmeticException {
+
+        double normProduct = v1.getNorm() * v2.getNorm();
+        if (normProduct == 0) {
+            throw new MathArithmeticException(LocalizedFormats.ZERO_NORM);
+        }
+
+        double dot = v1.dotProduct(v2);
+        double threshold = normProduct * 0.9999;
+        if ((dot < -threshold) || (dot > threshold)) {
+            // the vectors are almost aligned, compute using the sine
+            Vector3D v3 = crossProduct(v1, v2);
+            if (dot >= 0) {
+                return FastMath.asin(v3.getNorm() / normProduct);
+            }
+            return FastMath.PI - FastMath.asin(v3.getNorm() / normProduct);
+        }
+
+        // the vectors are sufficiently separated to use the cosine
+        return FastMath.acos(dot / normProduct);
+
+    }
+
+    /** {@inheritDoc} */
+    public Vector3D negate() {
+        return new Vector3D(-x, -y, -z);
+    }
+
+    /** {@inheritDoc} */
+    public Vector3D scalarMultiply(double a) {
+        return new Vector3D(a * x, a * y, a * z);
+    }
+
+    /** {@inheritDoc} */
+    public boolean isNaN() {
+        return Double.isNaN(x) || Double.isNaN(y) || Double.isNaN(z);
+    }
+
+    /** {@inheritDoc} */
+    public boolean isInfinite() {
+        return !isNaN() && (Double.isInfinite(x) || Double.isInfinite(y) || Double.isInfinite(z));
+    }
+
+    /**
+     * Test for the equality of two 3D vectors.
+     * <p>
+     * If all coordinates of two 3D vectors are exactly the same, and none are
+     * <code>Double.NaN</code>, the two 3D vectors are considered to be equal.
+     * </p>
+     * <p>
+     * <code>NaN</code> coordinates are considered to affect globally the vector
+     * and be equals to each other - i.e, if either (or all) coordinates of the
+     * 3D vector are equal to <code>Double.NaN</code>, the 3D vector is equal to
+     * {@link #NaN}.
+     * </p>
+     *
+     * @param other Object to test for equality to this
+     * @return true if two 3D vector objects are equal, false if
+     *         object is null, not an instance of Vector3D, or
+     *         not equal to this Vector3D instance
+     *
+     */
+    @Override
+    public boolean equals(Object other) {
+
+        if (this == other) {
+            return true;
+        }
+
+        if (other instanceof Vector3D) {
+            final Vector3D rhs = (Vector3D)other;
+            if (rhs.isNaN()) {
+                return this.isNaN();
+            }
+
+            return (x == rhs.x) && (y == rhs.y) && (z == rhs.z);
+        }
+        return false;
+    }
+
+    /**
+     * Get a hashCode for the 3D vector.
+     * <p>
+     * All NaN values have the same hash code.</p>
+     *
+     * @return a hash code value for this object
+     */
+    @Override
+    public int hashCode() {
+        if (isNaN()) {
+            return 642;
+        }
+        return 643 * (164 * MathUtils.hash(x) +  3 * MathUtils.hash(y) +  MathUtils.hash(z));
+    }
+
+    /** {@inheritDoc}
+     * <p>
+     * The implementation uses specific multiplication and addition
+     * algorithms to preserve accuracy and reduce cancellation effects.
+     * It should be very accurate even for nearly orthogonal vectors.
+     * </p>
+     * @see MathArrays#linearCombination(double, double, double, double, double, double)
+     */
+    public double dotProduct(final Vector<Euclidean3D> v) {
+        final Vector3D v3 = (Vector3D) v;
+        return MathArrays.linearCombination(x, v3.x, y, v3.y, z, v3.z);
+    }
+
+    /** Compute the cross-product of the instance with another vector.
+     * @param v other vector
+     * @return the cross product this ^ v as a new Vector3D
+     */
+    public Vector3D crossProduct(final Vector<Euclidean3D> v) {
+        final Vector3D v3 = (Vector3D) v;
+        return new Vector3D(MathArrays.linearCombination(y, v3.z, -z, v3.y),
+                            MathArrays.linearCombination(z, v3.x, -x, v3.z),
+                            MathArrays.linearCombination(x, v3.y, -y, v3.x));
+    }
+
+    /** {@inheritDoc} */
+    public double distance1(Vector<Euclidean3D> v) {
+        final Vector3D v3 = (Vector3D) v;
+        final double dx = FastMath.abs(v3.x - x);
+        final double dy = FastMath.abs(v3.y - y);
+        final double dz = FastMath.abs(v3.z - z);
+        return dx + dy + dz;
+    }
+
+    /** {@inheritDoc} */
+    public double distance(Vector<Euclidean3D> v) {
+        return distance((Point<Euclidean3D>) v);
+    }
+
+    /** {@inheritDoc} */
+    public double distance(Point<Euclidean3D> v) {
+        final Vector3D v3 = (Vector3D) v;
+        final double dx = v3.x - x;
+        final double dy = v3.y - y;
+        final double dz = v3.z - z;
+        return FastMath.sqrt(dx * dx + dy * dy + dz * dz);
+    }
+
+    /** {@inheritDoc} */
+    public double distanceInf(Vector<Euclidean3D> v) {
+        final Vector3D v3 = (Vector3D) v;
+        final double dx = FastMath.abs(v3.x - x);
+        final double dy = FastMath.abs(v3.y - y);
+        final double dz = FastMath.abs(v3.z - z);
+        return FastMath.max(FastMath.max(dx, dy), dz);
+    }
+
+    /** {@inheritDoc} */
+    public double distanceSq(Vector<Euclidean3D> v) {
+        final Vector3D v3 = (Vector3D) v;
+        final double dx = v3.x - x;
+        final double dy = v3.y - y;
+        final double dz = v3.z - z;
+        return dx * dx + dy * dy + dz * dz;
+    }
+
+    /** Compute the dot-product of two vectors.
+     * @param v1 first vector
+     * @param v2 second vector
+     * @return the dot product v1.v2
+     */
+    public static double dotProduct(Vector3D v1, Vector3D v2) {
+        return v1.dotProduct(v2);
+    }
+
+    /** Compute the cross-product of two vectors.
+     * @param v1 first vector
+     * @param v2 second vector
+     * @return the cross product v1 ^ v2 as a new Vector
+     */
+    public static Vector3D crossProduct(final Vector3D v1, final Vector3D v2) {
+        return v1.crossProduct(v2);
+    }
+
+    /** Compute the distance between two vectors according to the L<sub>1</sub> norm.
+     * <p>Calling this method is equivalent to calling:
+     * <code>v1.subtract(v2).getNorm1()</code> except that no intermediate
+     * vector is built</p>
+     * @param v1 first vector
+     * @param v2 second vector
+     * @return the distance between v1 and v2 according to the L<sub>1</sub> norm
+     */
+    public static double distance1(Vector3D v1, Vector3D v2) {
+        return v1.distance1(v2);
+    }
+
+    /** Compute the distance between two vectors according to the L<sub>2</sub> norm.
+     * <p>Calling this method is equivalent to calling:
+     * <code>v1.subtract(v2).getNorm()</code> except that no intermediate
+     * vector is built</p>
+     * @param v1 first vector
+     * @param v2 second vector
+     * @return the distance between v1 and v2 according to the L<sub>2</sub> norm
+     */
+    public static double distance(Vector3D v1, Vector3D v2) {
+        return v1.distance(v2);
+    }
+
+    /** Compute the distance between two vectors according to the L<sub>&infin;</sub> norm.
+     * <p>Calling this method is equivalent to calling:
+     * <code>v1.subtract(v2).getNormInf()</code> except that no intermediate
+     * vector is built</p>
+     * @param v1 first vector
+     * @param v2 second vector
+     * @return the distance between v1 and v2 according to the L<sub>&infin;</sub> norm
+     */
+    public static double distanceInf(Vector3D v1, Vector3D v2) {
+        return v1.distanceInf(v2);
+    }
+
+    /** Compute the square of the distance between two vectors.
+     * <p>Calling this method is equivalent to calling:
+     * <code>v1.subtract(v2).getNormSq()</code> except that no intermediate
+     * vector is built</p>
+     * @param v1 first vector
+     * @param v2 second vector
+     * @return the square of the distance between v1 and v2
+     */
+    public static double distanceSq(Vector3D v1, Vector3D v2) {
+        return v1.distanceSq(v2);
+    }
+
+    /** Get a string representation of this vector.
+     * @return a string representation of this vector
+     */
+    @Override
+    public String toString() {
+        return Vector3DFormat.getInstance().format(this);
+    }
+
+    /** {@inheritDoc} */
+    public String toString(final NumberFormat format) {
+        return new Vector3DFormat(format).format(this);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/Vector3DFormat.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/Vector3DFormat.java
new file mode 100644
index 0000000..da3f71e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/Vector3DFormat.java
@@ -0,0 +1,155 @@
+/*
+ * 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.math3.geometry.euclidean.threed;
+
+import java.text.FieldPosition;
+import java.text.NumberFormat;
+import java.text.ParsePosition;
+import java.util.Locale;
+
+import org.apache.commons.math3.exception.MathParseException;
+import org.apache.commons.math3.geometry.Vector;
+import org.apache.commons.math3.geometry.VectorFormat;
+import org.apache.commons.math3.util.CompositeFormat;
+
+/**
+ * Formats a 3D vector in components list format "{x; y; z}".
+ * <p>The prefix and suffix "{" and "}" and the separator "; " can be replaced by
+ * any user-defined strings. The number format for components can be configured.</p>
+ * <p>White space is ignored at parse time, even if it is in the prefix, suffix
+ * or separator specifications. So even if the default separator does include a space
+ * character that is used at format time, both input string "{1;1;1}" and
+ * " { 1 ; 1 ; 1 } " will be parsed without error and the same vector will be
+ * returned. In the second case, however, the parse position after parsing will be
+ * just after the closing curly brace, i.e. just before the trailing space.</p>
+ * <p><b>Note:</b> using "," as a separator may interfere with the grouping separator
+ * of the default {@link NumberFormat} for the current locale. Thus it is advised
+ * to use a {@link NumberFormat} instance with disabled grouping in such a case.</p>
+ *
+ */
+public class Vector3DFormat extends VectorFormat<Euclidean3D> {
+
+    /**
+     * Create an instance with default settings.
+     * <p>The instance uses the default prefix, suffix and separator:
+     * "{", "}", and "; " and the default number format for components.</p>
+     */
+    public Vector3DFormat() {
+        super(DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_SEPARATOR,
+              CompositeFormat.getDefaultNumberFormat());
+    }
+
+    /**
+     * Create an instance with a custom number format for components.
+     * @param format the custom format for components.
+     */
+    public Vector3DFormat(final NumberFormat format) {
+        super(DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_SEPARATOR, format);
+    }
+
+    /**
+     * Create an instance with custom prefix, suffix and separator.
+     * @param prefix prefix to use instead of the default "{"
+     * @param suffix suffix to use instead of the default "}"
+     * @param separator separator to use instead of the default "; "
+     */
+    public Vector3DFormat(final String prefix, final String suffix,
+                         final String separator) {
+        super(prefix, suffix, separator, CompositeFormat.getDefaultNumberFormat());
+    }
+
+    /**
+     * Create an instance with custom prefix, suffix, separator and format
+     * for components.
+     * @param prefix prefix to use instead of the default "{"
+     * @param suffix suffix to use instead of the default "}"
+     * @param separator separator to use instead of the default "; "
+     * @param format the custom format for components.
+     */
+    public Vector3DFormat(final String prefix, final String suffix,
+                         final String separator, final NumberFormat format) {
+        super(prefix, suffix, separator, format);
+    }
+
+    /**
+     * Returns the default 3D vector format for the current locale.
+     * @return the default 3D vector format.
+     */
+    public static Vector3DFormat getInstance() {
+        return getInstance(Locale.getDefault());
+    }
+
+    /**
+     * Returns the default 3D vector format for the given locale.
+     * @param locale the specific locale used by the format.
+     * @return the 3D vector format specific to the given locale.
+     */
+    public static Vector3DFormat getInstance(final Locale locale) {
+        return new Vector3DFormat(CompositeFormat.getDefaultNumberFormat(locale));
+    }
+
+    /**
+     * Formats a {@link Vector3D} object to produce a string.
+     * @param vector the object to format.
+     * @param toAppendTo where the text is to be appended
+     * @param pos On input: an alignment field, if desired. On output: the
+     *            offsets of the alignment field
+     * @return the value passed in as toAppendTo.
+     */
+    @Override
+    public StringBuffer format(final Vector<Euclidean3D> vector, final StringBuffer toAppendTo,
+                               final FieldPosition pos) {
+        final Vector3D v3 = (Vector3D) vector;
+        return format(toAppendTo, pos, v3.getX(), v3.getY(), v3.getZ());
+    }
+
+    /**
+     * Parses a string to produce a {@link Vector3D} object.
+     * @param source the string to parse
+     * @return the parsed {@link Vector3D} object.
+     * @throws MathParseException if the beginning of the specified string
+     * cannot be parsed.
+     */
+    @Override
+    public Vector3D parse(final String source) throws MathParseException {
+        ParsePosition parsePosition = new ParsePosition(0);
+        Vector3D result = parse(source, parsePosition);
+        if (parsePosition.getIndex() == 0) {
+            throw new MathParseException(source,
+                                         parsePosition.getErrorIndex(),
+                                         Vector3D.class);
+        }
+        return result;
+    }
+
+    /**
+     * Parses a string to produce a {@link Vector3D} object.
+     * @param source the string to parse
+     * @param pos input/ouput parsing parameter.
+     * @return the parsed {@link Vector3D} object.
+     */
+    @Override
+    public Vector3D parse(final String source, final ParsePosition pos) {
+        final double[] coordinates = parseCoordinates(3, source, pos);
+        if (coordinates == null) {
+            return null;
+        }
+        return new Vector3D(coordinates[0], coordinates[1], coordinates[2]);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/package-info.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/package-info.java
new file mode 100644
index 0000000..eaa3c6a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * <p>
+ * This package provides basic 3D geometry components.
+ * </p>
+ *
+ */
+package org.apache.commons.math3.geometry.euclidean.threed;
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/DiskGenerator.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/DiskGenerator.java
new file mode 100644
index 0000000..332b1b7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/DiskGenerator.java
@@ -0,0 +1,108 @@
+/*
+ * 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.math3.geometry.euclidean.twod;
+
+import java.util.List;
+
+import org.apache.commons.math3.fraction.BigFraction;
+import org.apache.commons.math3.geometry.enclosing.EnclosingBall;
+import org.apache.commons.math3.geometry.enclosing.SupportBallGenerator;
+import org.apache.commons.math3.util.FastMath;
+
+/** Class generating an enclosing ball from its support points.
+ * @since 3.3
+ */
+public class DiskGenerator implements SupportBallGenerator<Euclidean2D, Vector2D> {
+
+    /** {@inheritDoc} */
+    public EnclosingBall<Euclidean2D, Vector2D> ballOnSupport(final List<Vector2D> support) {
+
+        if (support.size() < 1) {
+            return new EnclosingBall<Euclidean2D, Vector2D>(Vector2D.ZERO, Double.NEGATIVE_INFINITY);
+        } else {
+            final Vector2D vA = support.get(0);
+            if (support.size() < 2) {
+                return new EnclosingBall<Euclidean2D, Vector2D>(vA, 0, vA);
+            } else {
+                final Vector2D vB = support.get(1);
+                if (support.size() < 3) {
+                    return new EnclosingBall<Euclidean2D, Vector2D>(new Vector2D(0.5, vA, 0.5, vB),
+                                                                    0.5 * vA.distance(vB),
+                                                                    vA, vB);
+                } else {
+                    final Vector2D vC = support.get(2);
+                    // a disk is 2D can be defined as:
+                    // (1)   (x - x_0)^2 + (y - y_0)^2 = r^2
+                    // which can be written:
+                    // (2)   (x^2 + y^2) - 2 x_0 x - 2 y_0 y + (x_0^2 + y_0^2 - r^2) = 0
+                    // or simply:
+                    // (3)   (x^2 + y^2) + a x + b y + c = 0
+                    // with disk center coordinates -a/2, -b/2
+                    // If the disk exists, a, b and c are a non-zero solution to
+                    // [ (x^2  + y^2 )   x    y   1 ]   [ 1 ]   [ 0 ]
+                    // [ (xA^2 + yA^2)   xA   yA  1 ]   [ a ]   [ 0 ]
+                    // [ (xB^2 + yB^2)   xB   yB  1 ] * [ b ] = [ 0 ]
+                    // [ (xC^2 + yC^2)   xC   yC  1 ]   [ c ]   [ 0 ]
+                    // So the determinant of the matrix is zero. Computing this determinant
+                    // by expanding it using the minors m_ij of first row leads to
+                    // (4)   m_11 (x^2 + y^2) - m_12 x + m_13 y - m_14 = 0
+                    // So by identifying equations (2) and (4) we get the coordinates
+                    // of center as:
+                    //      x_0 = +m_12 / (2 m_11)
+                    //      y_0 = -m_13 / (2 m_11)
+                    // Note that the minors m_11, m_12 and m_13 all have the last column
+                    // filled with 1.0, hence simplifying the computation
+                    final BigFraction[] c2 = new BigFraction[] {
+                        new BigFraction(vA.getX()), new BigFraction(vB.getX()), new BigFraction(vC.getX())
+                    };
+                    final BigFraction[] c3 = new BigFraction[] {
+                        new BigFraction(vA.getY()), new BigFraction(vB.getY()), new BigFraction(vC.getY())
+                    };
+                    final BigFraction[] c1 = new BigFraction[] {
+                        c2[0].multiply(c2[0]).add(c3[0].multiply(c3[0])),
+                        c2[1].multiply(c2[1]).add(c3[1].multiply(c3[1])),
+                        c2[2].multiply(c2[2]).add(c3[2].multiply(c3[2]))
+                    };
+                    final BigFraction twoM11  = minor(c2, c3).multiply(2);
+                    final BigFraction m12     = minor(c1, c3);
+                    final BigFraction m13     = minor(c1, c2);
+                    final BigFraction centerX = m12.divide(twoM11);
+                    final BigFraction centerY = m13.divide(twoM11).negate();
+                    final BigFraction dx      = c2[0].subtract(centerX);
+                    final BigFraction dy      = c3[0].subtract(centerY);
+                    final BigFraction r2      = dx.multiply(dx).add(dy.multiply(dy));
+                    return new EnclosingBall<Euclidean2D, Vector2D>(new Vector2D(centerX.doubleValue(),
+                                                                                 centerY.doubleValue()),
+                                                                    FastMath.sqrt(r2.doubleValue()),
+                                                                    vA, vB, vC);
+                }
+            }
+        }
+    }
+
+    /** Compute a dimension 3 minor, when 3<sup>d</sup> column is known to be filled with 1.0.
+     * @param c1 first column
+     * @param c2 second column
+     * @return value of the minor computed has an exact fraction
+     */
+    private BigFraction minor(final BigFraction[] c1, final BigFraction[] c2) {
+        return      c2[0].multiply(c1[2].subtract(c1[1])).
+                add(c2[1].multiply(c1[0].subtract(c1[2]))).
+                add(c2[2].multiply(c1[1].subtract(c1[0])));
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/Euclidean2D.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/Euclidean2D.java
new file mode 100644
index 0000000..af7630d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/Euclidean2D.java
@@ -0,0 +1,74 @@
+/*
+ * 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.math3.geometry.euclidean.twod;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.geometry.Space;
+import org.apache.commons.math3.geometry.euclidean.oned.Euclidean1D;
+
+/**
+ * This class implements a two-dimensional space.
+ * @since 3.0
+ */
+public class Euclidean2D implements Serializable, Space {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 4793432849757649566L;
+
+    /** Private constructor for the singleton.
+     */
+    private Euclidean2D() {
+    }
+
+    /** Get the unique instance.
+     * @return the unique instance
+     */
+    public static Euclidean2D getInstance() {
+        return LazyHolder.INSTANCE;
+    }
+
+    /** {@inheritDoc} */
+    public int getDimension() {
+        return 2;
+    }
+
+    /** {@inheritDoc} */
+    public Euclidean1D getSubSpace() {
+        return Euclidean1D.getInstance();
+    }
+
+    // CHECKSTYLE: stop HideUtilityClassConstructor
+    /** Holder for the instance.
+     * <p>We use here the Initialization On Demand Holder Idiom.</p>
+     */
+    private static class LazyHolder {
+        /** Cached field instance. */
+        private static final Euclidean2D INSTANCE = new Euclidean2D();
+    }
+    // CHECKSTYLE: resume HideUtilityClassConstructor
+
+    /** Handle deserialization of the singleton.
+     * @return the singleton instance
+     */
+    private Object readResolve() {
+        // return the singleton instance
+        return LazyHolder.INSTANCE;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/Line.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/Line.java
new file mode 100644
index 0000000..c300fa1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/Line.java
@@ -0,0 +1,587 @@
+/*
+ * 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.math3.geometry.euclidean.twod;
+
+import java.awt.geom.AffineTransform;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.Vector;
+import org.apache.commons.math3.geometry.euclidean.oned.Euclidean1D;
+import org.apache.commons.math3.geometry.euclidean.oned.IntervalsSet;
+import org.apache.commons.math3.geometry.euclidean.oned.OrientedPoint;
+import org.apache.commons.math3.geometry.euclidean.oned.Vector1D;
+import org.apache.commons.math3.geometry.partitioning.Embedding;
+import org.apache.commons.math3.geometry.partitioning.Hyperplane;
+import org.apache.commons.math3.geometry.partitioning.SubHyperplane;
+import org.apache.commons.math3.geometry.partitioning.Transform;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.util.MathUtils;
+
+/** This class represents an oriented line in the 2D plane.
+
+ * <p>An oriented line can be defined either by prolongating a line
+ * segment between two points past these points, or by one point and
+ * an angular direction (in trigonometric orientation).</p>
+
+ * <p>Since it is oriented the two half planes at its two sides are
+ * unambiguously identified as a left half plane and a right half
+ * plane. This can be used to identify the interior and the exterior
+ * in a simple way by local properties only when part of a line is
+ * used to define part of a polygon boundary.</p>
+
+ * <p>A line can also be used to completely define a reference frame
+ * in the plane. It is sufficient to select one specific point in the
+ * line (the orthogonal projection of the original reference frame on
+ * the line) and to use the unit vector in the line direction and the
+ * orthogonal vector oriented from left half plane to right half
+ * plane. We define two coordinates by the process, the
+ * <em>abscissa</em> along the line, and the <em>offset</em> across
+ * the line. All points of the plane are uniquely identified by these
+ * two coordinates. The line is the set of points at zero offset, the
+ * left half plane is the set of points with negative offsets and the
+ * right half plane is the set of points with positive offsets.</p>
+
+ * @since 3.0
+ */
+public class Line implements Hyperplane<Euclidean2D>, Embedding<Euclidean2D, Euclidean1D> {
+
+    /** Default value for tolerance. */
+    private static final double DEFAULT_TOLERANCE = 1.0e-10;
+
+    /** Angle with respect to the abscissa axis. */
+    private double angle;
+
+    /** Cosine of the line angle. */
+    private double cos;
+
+    /** Sine of the line angle. */
+    private double sin;
+
+    /** Offset of the frame origin. */
+    private double originOffset;
+
+    /** Tolerance below which points are considered identical. */
+    private final double tolerance;
+
+    /** Reverse line. */
+    private Line reverse;
+
+    /** Build a line from two points.
+     * <p>The line is oriented from p1 to p2</p>
+     * @param p1 first point
+     * @param p2 second point
+     * @param tolerance tolerance below which points are considered identical
+     * @since 3.3
+     */
+    public Line(final Vector2D p1, final Vector2D p2, final double tolerance) {
+        reset(p1, p2);
+        this.tolerance = tolerance;
+    }
+
+    /** Build a line from a point and an angle.
+     * @param p point belonging to the line
+     * @param angle angle of the line with respect to abscissa axis
+     * @param tolerance tolerance below which points are considered identical
+     * @since 3.3
+     */
+    public Line(final Vector2D p, final double angle, final double tolerance) {
+        reset(p, angle);
+        this.tolerance = tolerance;
+    }
+
+    /** Build a line from its internal characteristics.
+     * @param angle angle of the line with respect to abscissa axis
+     * @param cos cosine of the angle
+     * @param sin sine of the angle
+     * @param originOffset offset of the origin
+     * @param tolerance tolerance below which points are considered identical
+     * @since 3.3
+     */
+    private Line(final double angle, final double cos, final double sin,
+                 final double originOffset, final double tolerance) {
+        this.angle        = angle;
+        this.cos          = cos;
+        this.sin          = sin;
+        this.originOffset = originOffset;
+        this.tolerance    = tolerance;
+        this.reverse      = null;
+    }
+
+    /** Build a line from two points.
+     * <p>The line is oriented from p1 to p2</p>
+     * @param p1 first point
+     * @param p2 second point
+     * @deprecated as of 3.3, replaced with {@link #Line(Vector2D, Vector2D, double)}
+     */
+    @Deprecated
+    public Line(final Vector2D p1, final Vector2D p2) {
+        this(p1, p2, DEFAULT_TOLERANCE);
+    }
+
+    /** Build a line from a point and an angle.
+     * @param p point belonging to the line
+     * @param angle angle of the line with respect to abscissa axis
+     * @deprecated as of 3.3, replaced with {@link #Line(Vector2D, double, double)}
+     */
+    @Deprecated
+    public Line(final Vector2D p, final double angle) {
+        this(p, angle, DEFAULT_TOLERANCE);
+    }
+
+    /** Copy constructor.
+     * <p>The created instance is completely independent from the
+     * original instance, it is a deep copy.</p>
+     * @param line line to copy
+     */
+    public Line(final Line line) {
+        angle        = MathUtils.normalizeAngle(line.angle, FastMath.PI);
+        cos          = line.cos;
+        sin          = line.sin;
+        originOffset = line.originOffset;
+        tolerance    = line.tolerance;
+        reverse      = null;
+    }
+
+    /** {@inheritDoc} */
+    public Line copySelf() {
+        return new Line(this);
+    }
+
+    /** Reset the instance as if built from two points.
+     * <p>The line is oriented from p1 to p2</p>
+     * @param p1 first point
+     * @param p2 second point
+     */
+    public void reset(final Vector2D p1, final Vector2D p2) {
+        unlinkReverse();
+        final double dx = p2.getX() - p1.getX();
+        final double dy = p2.getY() - p1.getY();
+        final double d = FastMath.hypot(dx, dy);
+        if (d == 0.0) {
+            angle        = 0.0;
+            cos          = 1.0;
+            sin          = 0.0;
+            originOffset = p1.getY();
+        } else {
+            angle        = FastMath.PI + FastMath.atan2(-dy, -dx);
+            cos          = dx / d;
+            sin          = dy / d;
+            originOffset = MathArrays.linearCombination(p2.getX(), p1.getY(), -p1.getX(), p2.getY()) / d;
+        }
+    }
+
+    /** Reset the instance as if built from a line and an angle.
+     * @param p point belonging to the line
+     * @param alpha angle of the line with respect to abscissa axis
+     */
+    public void reset(final Vector2D p, final double alpha) {
+        unlinkReverse();
+        this.angle   = MathUtils.normalizeAngle(alpha, FastMath.PI);
+        cos          = FastMath.cos(this.angle);
+        sin          = FastMath.sin(this.angle);
+        originOffset = MathArrays.linearCombination(cos, p.getY(), -sin, p.getX());
+    }
+
+    /** Revert the instance.
+     */
+    public void revertSelf() {
+        unlinkReverse();
+        if (angle < FastMath.PI) {
+            angle += FastMath.PI;
+        } else {
+            angle -= FastMath.PI;
+        }
+        cos          = -cos;
+        sin          = -sin;
+        originOffset = -originOffset;
+    }
+
+    /** Unset the link between an instance and its reverse.
+     */
+    private void unlinkReverse() {
+        if (reverse != null) {
+            reverse.reverse = null;
+        }
+        reverse = null;
+    }
+
+    /** Get the reverse of the instance.
+     * <p>Get a line with reversed orientation with respect to the
+     * instance.</p>
+     * <p>
+     * As long as neither the instance nor its reverse are modified
+     * (i.e. as long as none of the {@link #reset(Vector2D, Vector2D)},
+     * {@link #reset(Vector2D, double)}, {@link #revertSelf()},
+     * {@link #setAngle(double)} or {@link #setOriginOffset(double)}
+     * methods are called), then the line and its reverse remain linked
+     * together so that {@code line.getReverse().getReverse() == line}.
+     * When one of the line is modified, the link is deleted as both
+     * instance becomes independent.
+     * </p>
+     * @return a new line, with orientation opposite to the instance orientation
+     */
+    public Line getReverse() {
+        if (reverse == null) {
+            reverse = new Line((angle < FastMath.PI) ? (angle + FastMath.PI) : (angle - FastMath.PI),
+                               -cos, -sin, -originOffset, tolerance);
+            reverse.reverse = this;
+        }
+        return reverse;
+    }
+
+    /** Transform a space point into a sub-space point.
+     * @param vector n-dimension point of the space
+     * @return (n-1)-dimension point of the sub-space corresponding to
+     * the specified space point
+     */
+    public Vector1D toSubSpace(Vector<Euclidean2D> vector) {
+        return toSubSpace((Point<Euclidean2D>) vector);
+    }
+
+    /** Transform a sub-space point into a space point.
+     * @param vector (n-1)-dimension point of the sub-space
+     * @return n-dimension point of the space corresponding to the
+     * specified sub-space point
+     */
+    public Vector2D toSpace(Vector<Euclidean1D> vector) {
+        return toSpace((Point<Euclidean1D>) vector);
+    }
+
+    /** {@inheritDoc} */
+    public Vector1D toSubSpace(final Point<Euclidean2D> point) {
+        Vector2D p2 = (Vector2D) point;
+        return new Vector1D(MathArrays.linearCombination(cos, p2.getX(), sin, p2.getY()));
+    }
+
+    /** {@inheritDoc} */
+    public Vector2D toSpace(final Point<Euclidean1D> point) {
+        final double abscissa = ((Vector1D) point).getX();
+        return new Vector2D(MathArrays.linearCombination(abscissa, cos, -originOffset, sin),
+                            MathArrays.linearCombination(abscissa, sin,  originOffset, cos));
+    }
+
+    /** Get the intersection point of the instance and another line.
+     * @param other other line
+     * @return intersection point of the instance and the other line
+     * or null if there are no intersection points
+     */
+    public Vector2D intersection(final Line other) {
+        final double d = MathArrays.linearCombination(sin, other.cos, -other.sin, cos);
+        if (FastMath.abs(d) < tolerance) {
+            return null;
+        }
+        return new Vector2D(MathArrays.linearCombination(cos, other.originOffset, -other.cos, originOffset) / d,
+                            MathArrays.linearCombination(sin, other.originOffset, -other.sin, originOffset) / d);
+    }
+
+    /** {@inheritDoc}
+     * @since 3.3
+     */
+    public Point<Euclidean2D> project(Point<Euclidean2D> point) {
+        return toSpace(toSubSpace(point));
+    }
+
+    /** {@inheritDoc}
+     * @since 3.3
+     */
+    public double getTolerance() {
+        return tolerance;
+    }
+
+    /** {@inheritDoc} */
+    public SubLine wholeHyperplane() {
+        return new SubLine(this, new IntervalsSet(tolerance));
+    }
+
+    /** Build a region covering the whole space.
+     * @return a region containing the instance (really a {@link
+     * PolygonsSet PolygonsSet} instance)
+     */
+    public PolygonsSet wholeSpace() {
+        return new PolygonsSet(tolerance);
+    }
+
+    /** Get the offset (oriented distance) of a parallel line.
+     * <p>This method should be called only for parallel lines otherwise
+     * the result is not meaningful.</p>
+     * <p>The offset is 0 if both lines are the same, it is
+     * positive if the line is on the right side of the instance and
+     * negative if it is on the left side, according to its natural
+     * orientation.</p>
+     * @param line line to check
+     * @return offset of the line
+     */
+    public double getOffset(final Line line) {
+        return originOffset +
+               (MathArrays.linearCombination(cos, line.cos, sin, line.sin) > 0 ? -line.originOffset : line.originOffset);
+    }
+
+    /** Get the offset (oriented distance) of a vector.
+     * @param vector vector to check
+     * @return offset of the vector
+     */
+    public double getOffset(Vector<Euclidean2D> vector) {
+        return getOffset((Point<Euclidean2D>) vector);
+    }
+
+    /** {@inheritDoc} */
+    public double getOffset(final Point<Euclidean2D> point) {
+        Vector2D p2 = (Vector2D) point;
+        return MathArrays.linearCombination(sin, p2.getX(), -cos, p2.getY(), 1.0, originOffset);
+    }
+
+    /** {@inheritDoc} */
+    public boolean sameOrientationAs(final Hyperplane<Euclidean2D> other) {
+        final Line otherL = (Line) other;
+        return MathArrays.linearCombination(sin, otherL.sin, cos, otherL.cos) >= 0.0;
+    }
+
+    /** Get one point from the plane.
+     * @param abscissa desired abscissa for the point
+     * @param offset desired offset for the point
+     * @return one point in the plane, with given abscissa and offset
+     * relative to the line
+     */
+    public Vector2D getPointAt(final Vector1D abscissa, final double offset) {
+        final double x       = abscissa.getX();
+        final double dOffset = offset - originOffset;
+        return new Vector2D(MathArrays.linearCombination(x, cos,  dOffset, sin),
+                            MathArrays.linearCombination(x, sin, -dOffset, cos));
+    }
+
+    /** Check if the line contains a point.
+     * @param p point to check
+     * @return true if p belongs to the line
+     */
+    public boolean contains(final Vector2D p) {
+        return FastMath.abs(getOffset(p)) < tolerance;
+    }
+
+    /** Compute the distance between the instance and a point.
+     * <p>This is a shortcut for invoking FastMath.abs(getOffset(p)),
+     * and provides consistency with what is in the
+     * org.apache.commons.math3.geometry.euclidean.threed.Line class.</p>
+     *
+     * @param p to check
+     * @return distance between the instance and the point
+     * @since 3.1
+     */
+    public double distance(final Vector2D p) {
+        return FastMath.abs(getOffset(p));
+    }
+
+    /** Check the instance is parallel to another line.
+     * @param line other line to check
+     * @return true if the instance is parallel to the other line
+     * (they can have either the same or opposite orientations)
+     */
+    public boolean isParallelTo(final Line line) {
+        return FastMath.abs(MathArrays.linearCombination(sin, line.cos, -cos, line.sin)) < tolerance;
+    }
+
+    /** Translate the line to force it passing by a point.
+     * @param p point by which the line should pass
+     */
+    public void translateToPoint(final Vector2D p) {
+        originOffset = MathArrays.linearCombination(cos, p.getY(), -sin, p.getX());
+    }
+
+    /** Get the angle of the line.
+     * @return the angle of the line with respect to the abscissa axis
+     */
+    public double getAngle() {
+        return MathUtils.normalizeAngle(angle, FastMath.PI);
+    }
+
+    /** Set the angle of the line.
+     * @param angle new angle of the line with respect to the abscissa axis
+     */
+    public void setAngle(final double angle) {
+        unlinkReverse();
+        this.angle = MathUtils.normalizeAngle(angle, FastMath.PI);
+        cos        = FastMath.cos(this.angle);
+        sin        = FastMath.sin(this.angle);
+    }
+
+    /** Get the offset of the origin.
+     * @return the offset of the origin
+     */
+    public double getOriginOffset() {
+        return originOffset;
+    }
+
+    /** Set the offset of the origin.
+     * @param offset offset of the origin
+     */
+    public void setOriginOffset(final double offset) {
+        unlinkReverse();
+        originOffset = offset;
+    }
+
+    /** Get a {@link org.apache.commons.math3.geometry.partitioning.Transform
+     * Transform} embedding an affine transform.
+     * @param transform affine transform to embed (must be inversible
+     * otherwise the {@link
+     * org.apache.commons.math3.geometry.partitioning.Transform#apply(Hyperplane)
+     * apply(Hyperplane)} method would work only for some lines, and
+     * fail for other ones)
+     * @return a new transform that can be applied to either {@link
+     * Vector2D Vector2D}, {@link Line Line} or {@link
+     * org.apache.commons.math3.geometry.partitioning.SubHyperplane
+     * SubHyperplane} instances
+     * @exception MathIllegalArgumentException if the transform is non invertible
+     * @deprecated as of 3.6, replaced with {@link #getTransform(double, double, double, double, double, double)}
+     */
+    @Deprecated
+    public static Transform<Euclidean2D, Euclidean1D> getTransform(final AffineTransform transform)
+        throws MathIllegalArgumentException {
+        final double[] m = new double[6];
+        transform.getMatrix(m);
+        return new LineTransform(m[0], m[1], m[2], m[3], m[4], m[5]);
+    }
+
+    /** Get a {@link org.apache.commons.math3.geometry.partitioning.Transform
+     * Transform} embedding an affine transform.
+     * @param cXX transform factor between input abscissa and output abscissa
+     * @param cYX transform factor between input abscissa and output ordinate
+     * @param cXY transform factor between input ordinate and output abscissa
+     * @param cYY transform factor between input ordinate and output ordinate
+     * @param cX1 transform addendum for output abscissa
+     * @param cY1 transform addendum for output ordinate
+     * @return a new transform that can be applied to either {@link
+     * Vector2D Vector2D}, {@link Line Line} or {@link
+     * org.apache.commons.math3.geometry.partitioning.SubHyperplane
+     * SubHyperplane} instances
+     * @exception MathIllegalArgumentException if the transform is non invertible
+     * @since 3.6
+     */
+    public static Transform<Euclidean2D, Euclidean1D> getTransform(final double cXX,
+                                                                   final double cYX,
+                                                                   final double cXY,
+                                                                   final double cYY,
+                                                                   final double cX1,
+                                                                   final double cY1)
+        throws MathIllegalArgumentException {
+        return new LineTransform(cXX, cYX, cXY, cYY, cX1, cY1);
+    }
+
+    /** Class embedding an affine transform.
+     * <p>This class is used in order to apply an affine transform to a
+     * line. Using a specific object allow to perform some computations
+     * on the transform only once even if the same transform is to be
+     * applied to a large number of lines (for example to a large
+     * polygon)./<p>
+     */
+    private static class LineTransform implements Transform<Euclidean2D, Euclidean1D> {
+
+        /** Transform factor between input abscissa and output abscissa. */
+        private double cXX;
+
+        /** Transform factor between input abscissa and output ordinate. */
+        private double cYX;
+
+        /** Transform factor between input ordinate and output abscissa. */
+        private double cXY;
+
+        /** Transform factor between input ordinate and output ordinate. */
+        private double cYY;
+
+        /** Transform addendum for output abscissa. */
+        private double cX1;
+
+        /** Transform addendum for output ordinate. */
+        private double cY1;
+
+        /** cXY * cY1 - cYY * cX1. */
+        private double c1Y;
+
+        /** cXX * cY1 - cYX * cX1. */
+        private double c1X;
+
+        /** cXX * cYY - cYX * cXY. */
+        private double c11;
+
+        /** Build an affine line transform from a n {@code AffineTransform}.
+         * @param cXX transform factor between input abscissa and output abscissa
+         * @param cYX transform factor between input abscissa and output ordinate
+         * @param cXY transform factor between input ordinate and output abscissa
+         * @param cYY transform factor between input ordinate and output ordinate
+         * @param cX1 transform addendum for output abscissa
+         * @param cY1 transform addendum for output ordinate
+         * @exception MathIllegalArgumentException if the transform is non invertible
+         * @since 3.6
+         */
+        LineTransform(final double cXX, final double cYX, final double cXY,
+                      final double cYY, final double cX1, final double cY1)
+            throws MathIllegalArgumentException {
+
+            this.cXX = cXX;
+            this.cYX = cYX;
+            this.cXY = cXY;
+            this.cYY = cYY;
+            this.cX1 = cX1;
+            this.cY1 = cY1;
+
+            c1Y = MathArrays.linearCombination(cXY, cY1, -cYY, cX1);
+            c1X = MathArrays.linearCombination(cXX, cY1, -cYX, cX1);
+            c11 = MathArrays.linearCombination(cXX, cYY, -cYX, cXY);
+
+            if (FastMath.abs(c11) < 1.0e-20) {
+                throw new MathIllegalArgumentException(LocalizedFormats.NON_INVERTIBLE_TRANSFORM);
+            }
+
+        }
+
+        /** {@inheritDoc} */
+        public Vector2D apply(final Point<Euclidean2D> point) {
+            final Vector2D p2D = (Vector2D) point;
+            final double  x   = p2D.getX();
+            final double  y   = p2D.getY();
+            return new Vector2D(MathArrays.linearCombination(cXX, x, cXY, y, cX1, 1),
+                                MathArrays.linearCombination(cYX, x, cYY, y, cY1, 1));
+        }
+
+        /** {@inheritDoc} */
+        public Line apply(final Hyperplane<Euclidean2D> hyperplane) {
+            final Line   line    = (Line) hyperplane;
+            final double rOffset = MathArrays.linearCombination(c1X, line.cos, c1Y, line.sin, c11, line.originOffset);
+            final double rCos    = MathArrays.linearCombination(cXX, line.cos, cXY, line.sin);
+            final double rSin    = MathArrays.linearCombination(cYX, line.cos, cYY, line.sin);
+            final double inv     = 1.0 / FastMath.sqrt(rSin * rSin + rCos * rCos);
+            return new Line(FastMath.PI + FastMath.atan2(-rSin, -rCos),
+                            inv * rCos, inv * rSin,
+                            inv * rOffset, line.tolerance);
+        }
+
+        /** {@inheritDoc} */
+        public SubHyperplane<Euclidean1D> apply(final SubHyperplane<Euclidean1D> sub,
+                                                final Hyperplane<Euclidean2D> original,
+                                                final Hyperplane<Euclidean2D> transformed) {
+            final OrientedPoint op     = (OrientedPoint) sub.getHyperplane();
+            final Line originalLine    = (Line) original;
+            final Line transformedLine = (Line) transformed;
+            final Vector1D newLoc =
+                transformedLine.toSubSpace(apply(originalLine.toSpace(op.getLocation())));
+            return new OrientedPoint(newLoc, op.isDirect(), originalLine.tolerance).wholeHyperplane();
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/NestedLoops.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/NestedLoops.java
new file mode 100644
index 0000000..83928fa
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/NestedLoops.java
@@ -0,0 +1,201 @@
+/*
+ * 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.math3.geometry.euclidean.twod;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.euclidean.oned.IntervalsSet;
+import org.apache.commons.math3.geometry.partitioning.Region;
+import org.apache.commons.math3.geometry.partitioning.RegionFactory;
+import org.apache.commons.math3.geometry.partitioning.SubHyperplane;
+
+/** This class represent a tree of nested 2D boundary loops.
+
+ * <p>This class is used for piecewise polygons construction.
+ * Polygons are built using the outline edges as
+ * representative of boundaries, the orientation of these lines are
+ * meaningful. However, we want to allow the user to specify its
+ * outline loops without having to take care of this orientation. This
+ * class is devoted to correct mis-oriented loops.<p>
+
+ * <p>Orientation is computed assuming the piecewise polygon is finite,
+ * i.e. the outermost loops have their exterior side facing points at
+ * infinity, and hence are oriented counter-clockwise. The orientation of
+ * internal loops is computed as the reverse of the orientation of
+ * their immediate surrounding loop.</p>
+
+ * @since 3.0
+ */
+class NestedLoops {
+
+    /** Boundary loop. */
+    private Vector2D[] loop;
+
+    /** Surrounded loops. */
+    private List<NestedLoops> surrounded;
+
+    /** Polygon enclosing a finite region. */
+    private Region<Euclidean2D> polygon;
+
+    /** Indicator for original loop orientation. */
+    private boolean originalIsClockwise;
+
+    /** Tolerance below which points are considered identical. */
+    private final double tolerance;
+
+    /** Simple Constructor.
+     * <p>Build an empty tree of nested loops. This instance will become
+     * the root node of a complete tree, it is not associated with any
+     * loop by itself, the outermost loops are in the root tree child
+     * nodes.</p>
+     * @param tolerance tolerance below which points are considered identical
+     * @since 3.3
+     */
+    NestedLoops(final double tolerance) {
+        this.surrounded = new ArrayList<NestedLoops>();
+        this.tolerance  = tolerance;
+    }
+
+    /** Constructor.
+     * <p>Build a tree node with neither parent nor children</p>
+     * @param loop boundary loop (will be reversed in place if needed)
+     * @param tolerance tolerance below which points are considered identical
+     * @exception MathIllegalArgumentException if an outline has an open boundary loop
+     * @since 3.3
+     */
+    private NestedLoops(final Vector2D[] loop, final double tolerance)
+        throws MathIllegalArgumentException {
+
+        if (loop[0] == null) {
+            throw new MathIllegalArgumentException(LocalizedFormats.OUTLINE_BOUNDARY_LOOP_OPEN);
+        }
+
+        this.loop       = loop;
+        this.surrounded = new ArrayList<NestedLoops>();
+        this.tolerance  = tolerance;
+
+        // build the polygon defined by the loop
+        final ArrayList<SubHyperplane<Euclidean2D>> edges = new ArrayList<SubHyperplane<Euclidean2D>>();
+        Vector2D current = loop[loop.length - 1];
+        for (int i = 0; i < loop.length; ++i) {
+            final Vector2D previous = current;
+            current = loop[i];
+            final Line   line   = new Line(previous, current, tolerance);
+            final IntervalsSet region =
+                new IntervalsSet(line.toSubSpace((Point<Euclidean2D>) previous).getX(),
+                                 line.toSubSpace((Point<Euclidean2D>) current).getX(),
+                                 tolerance);
+            edges.add(new SubLine(line, region));
+        }
+        polygon = new PolygonsSet(edges, tolerance);
+
+        // ensure the polygon encloses a finite region of the plane
+        if (Double.isInfinite(polygon.getSize())) {
+            polygon = new RegionFactory<Euclidean2D>().getComplement(polygon);
+            originalIsClockwise = false;
+        } else {
+            originalIsClockwise = true;
+        }
+
+    }
+
+    /** Add a loop in a tree.
+     * @param bLoop boundary loop (will be reversed in place if needed)
+     * @exception MathIllegalArgumentException if an outline has crossing
+     * boundary loops or open boundary loops
+     */
+    public void add(final Vector2D[] bLoop) throws MathIllegalArgumentException {
+        add(new NestedLoops(bLoop, tolerance));
+    }
+
+    /** Add a loop in a tree.
+     * @param node boundary loop (will be reversed in place if needed)
+     * @exception MathIllegalArgumentException if an outline has boundary
+     * loops that cross each other
+     */
+    private void add(final NestedLoops node) throws MathIllegalArgumentException {
+
+        // check if we can go deeper in the tree
+        for (final NestedLoops child : surrounded) {
+            if (child.polygon.contains(node.polygon)) {
+                child.add(node);
+                return;
+            }
+        }
+
+        // check if we can absorb some of the instance children
+        for (final Iterator<NestedLoops> iterator = surrounded.iterator(); iterator.hasNext();) {
+            final NestedLoops child = iterator.next();
+            if (node.polygon.contains(child.polygon)) {
+                node.surrounded.add(child);
+                iterator.remove();
+            }
+        }
+
+        // we should be separate from the remaining children
+        RegionFactory<Euclidean2D> factory = new RegionFactory<Euclidean2D>();
+        for (final NestedLoops child : surrounded) {
+            if (!factory.intersection(node.polygon, child.polygon).isEmpty()) {
+                throw new MathIllegalArgumentException(LocalizedFormats.CROSSING_BOUNDARY_LOOPS);
+            }
+        }
+
+        surrounded.add(node);
+
+    }
+
+    /** Correct the orientation of the loops contained in the tree.
+     * <p>This is this method that really inverts the loops that where
+     * provided through the {@link #add(Vector2D[]) add} method if
+     * they are mis-oriented</p>
+     */
+    public void correctOrientation() {
+        for (NestedLoops child : surrounded) {
+            child.setClockWise(true);
+        }
+    }
+
+    /** Set the loop orientation.
+     * @param clockwise if true, the loop should be set to clockwise
+     * orientation
+     */
+    private void setClockWise(final boolean clockwise) {
+
+        if (originalIsClockwise ^ clockwise) {
+            // we need to inverse the original loop
+            int min = -1;
+            int max = loop.length;
+            while (++min < --max) {
+                final Vector2D tmp = loop[min];
+                loop[min] = loop[max];
+                loop[max] = tmp;
+            }
+        }
+
+        // go deeper in the tree
+        for (final NestedLoops child : surrounded) {
+            child.setClockWise(!clockwise);
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/PolygonsSet.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/PolygonsSet.java
new file mode 100644
index 0000000..61fae9f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/PolygonsSet.java
@@ -0,0 +1,1160 @@
+/*
+ * 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.math3.geometry.euclidean.twod;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.euclidean.oned.Euclidean1D;
+import org.apache.commons.math3.geometry.euclidean.oned.Interval;
+import org.apache.commons.math3.geometry.euclidean.oned.IntervalsSet;
+import org.apache.commons.math3.geometry.euclidean.oned.Vector1D;
+import org.apache.commons.math3.geometry.partitioning.AbstractRegion;
+import org.apache.commons.math3.geometry.partitioning.AbstractSubHyperplane;
+import org.apache.commons.math3.geometry.partitioning.BSPTree;
+import org.apache.commons.math3.geometry.partitioning.BSPTreeVisitor;
+import org.apache.commons.math3.geometry.partitioning.BoundaryAttribute;
+import org.apache.commons.math3.geometry.partitioning.Hyperplane;
+import org.apache.commons.math3.geometry.partitioning.Side;
+import org.apache.commons.math3.geometry.partitioning.SubHyperplane;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.Precision;
+
+/** This class represents a 2D region: a set of polygons.
+ * @since 3.0
+ */
+public class PolygonsSet extends AbstractRegion<Euclidean2D, Euclidean1D> {
+
+    /** Default value for tolerance. */
+    private static final double DEFAULT_TOLERANCE = 1.0e-10;
+
+    /** Vertices organized as boundary loops. */
+    private Vector2D[][] vertices;
+
+    /** Build a polygons set representing the whole plane.
+     * @param tolerance tolerance below which points are considered identical
+     * @since 3.3
+     */
+    public PolygonsSet(final double tolerance) {
+        super(tolerance);
+    }
+
+    /** Build a polygons set from a BSP tree.
+     * <p>The leaf nodes of the BSP tree <em>must</em> have a
+     * {@code Boolean} attribute representing the inside status of
+     * the corresponding cell (true for inside cells, false for outside
+     * cells). In order to avoid building too many small objects, it is
+     * recommended to use the predefined constants
+     * {@code Boolean.TRUE} and {@code Boolean.FALSE}</p>
+     * <p>
+     * This constructor is aimed at expert use, as building the tree may
+     * be a difficult task. It is not intended for general use and for
+     * performances reasons does not check thoroughly its input, as this would
+     * require walking the full tree each time. Failing to provide a tree with
+     * the proper attributes, <em>will</em> therefore generate problems like
+     * {@link NullPointerException} or {@link ClassCastException} only later on.
+     * This limitation is known and explains why this constructor is for expert
+     * use only. The caller does have the responsibility to provided correct arguments.
+     * </p>
+     * @param tree inside/outside BSP tree representing the region
+     * @param tolerance tolerance below which points are considered identical
+     * @since 3.3
+     */
+    public PolygonsSet(final BSPTree<Euclidean2D> tree, final double tolerance) {
+        super(tree, tolerance);
+    }
+
+    /** Build a polygons set from a Boundary REPresentation (B-rep).
+     * <p>The boundary is provided as a collection of {@link
+     * SubHyperplane sub-hyperplanes}. Each sub-hyperplane has the
+     * interior part of the region on its minus side and the exterior on
+     * its plus side.</p>
+     * <p>The boundary elements can be in any order, and can form
+     * several non-connected sets (like for example polygons with holes
+     * or a set of disjoint polygons considered as a whole). In
+     * fact, the elements do not even need to be connected together
+     * (their topological connections are not used here). However, if the
+     * boundary does not really separate an inside open from an outside
+     * open (open having here its topological meaning), then subsequent
+     * calls to the {@link
+     * org.apache.commons.math3.geometry.partitioning.Region#checkPoint(org.apache.commons.math3.geometry.Point)
+     * checkPoint} method will not be meaningful anymore.</p>
+     * <p>If the boundary is empty, the region will represent the whole
+     * space.</p>
+     * @param boundary collection of boundary elements, as a
+     * collection of {@link SubHyperplane SubHyperplane} objects
+     * @param tolerance tolerance below which points are considered identical
+     * @since 3.3
+     */
+    public PolygonsSet(final Collection<SubHyperplane<Euclidean2D>> boundary, final double tolerance) {
+        super(boundary, tolerance);
+    }
+
+    /** Build a parallellepipedic box.
+     * @param xMin low bound along the x direction
+     * @param xMax high bound along the x direction
+     * @param yMin low bound along the y direction
+     * @param yMax high bound along the y direction
+     * @param tolerance tolerance below which points are considered identical
+     * @since 3.3
+     */
+    public PolygonsSet(final double xMin, final double xMax,
+                       final double yMin, final double yMax,
+                       final double tolerance) {
+        super(boxBoundary(xMin, xMax, yMin, yMax, tolerance), tolerance);
+    }
+
+    /** Build a polygon from a simple list of vertices.
+     * <p>The boundary is provided as a list of points considering to
+     * represent the vertices of a simple loop. The interior part of the
+     * region is on the left side of this path and the exterior is on its
+     * right side.</p>
+     * <p>This constructor does not handle polygons with a boundary
+     * forming several disconnected paths (such as polygons with holes).</p>
+     * <p>For cases where this simple constructor applies, it is expected to
+     * be numerically more robust than the {@link #PolygonsSet(Collection) general
+     * constructor} using {@link SubHyperplane subhyperplanes}.</p>
+     * <p>If the list is empty, the region will represent the whole
+     * space.</p>
+     * <p>
+     * Polygons with thin pikes or dents are inherently difficult to handle because
+     * they involve lines with almost opposite directions at some vertices. Polygons
+     * whose vertices come from some physical measurement with noise are also
+     * difficult because an edge that should be straight may be broken in lots of
+     * different pieces with almost equal directions. In both cases, computing the
+     * lines intersections is not numerically robust due to the almost 0 or almost
+     * &pi; angle. Such cases need to carefully adjust the {@code hyperplaneThickness}
+     * parameter. A too small value would often lead to completely wrong polygons
+     * with large area wrongly identified as inside or outside. Large values are
+     * often much safer. As a rule of thumb, a value slightly below the size of the
+     * most accurate detail needed is a good value for the {@code hyperplaneThickness}
+     * parameter.
+     * </p>
+     * @param hyperplaneThickness tolerance below which points are considered to
+     * belong to the hyperplane (which is therefore more a slab)
+     * @param vertices vertices of the simple loop boundary
+     */
+    public PolygonsSet(final double hyperplaneThickness, final Vector2D ... vertices) {
+        super(verticesToTree(hyperplaneThickness, vertices), hyperplaneThickness);
+    }
+
+    /** Build a polygons set representing the whole real line.
+     * @deprecated as of 3.3, replaced with {@link #PolygonsSet(double)}
+     */
+    @Deprecated
+    public PolygonsSet() {
+        this(DEFAULT_TOLERANCE);
+    }
+
+    /** Build a polygons set from a BSP tree.
+     * <p>The leaf nodes of the BSP tree <em>must</em> have a
+     * {@code Boolean} attribute representing the inside status of
+     * the corresponding cell (true for inside cells, false for outside
+     * cells). In order to avoid building too many small objects, it is
+     * recommended to use the predefined constants
+     * {@code Boolean.TRUE} and {@code Boolean.FALSE}</p>
+     * @param tree inside/outside BSP tree representing the region
+     * @deprecated as of 3.3, replaced with {@link #PolygonsSet(BSPTree, double)}
+     */
+    @Deprecated
+    public PolygonsSet(final BSPTree<Euclidean2D> tree) {
+        this(tree, DEFAULT_TOLERANCE);
+    }
+
+    /** Build a polygons set from a Boundary REPresentation (B-rep).
+     * <p>The boundary is provided as a collection of {@link
+     * SubHyperplane sub-hyperplanes}. Each sub-hyperplane has the
+     * interior part of the region on its minus side and the exterior on
+     * its plus side.</p>
+     * <p>The boundary elements can be in any order, and can form
+     * several non-connected sets (like for example polygons with holes
+     * or a set of disjoint polygons considered as a whole). In
+     * fact, the elements do not even need to be connected together
+     * (their topological connections are not used here). However, if the
+     * boundary does not really separate an inside open from an outside
+     * open (open having here its topological meaning), then subsequent
+     * calls to the {@link
+     * org.apache.commons.math3.geometry.partitioning.Region#checkPoint(org.apache.commons.math3.geometry.Point)
+     * checkPoint} method will not be meaningful anymore.</p>
+     * <p>If the boundary is empty, the region will represent the whole
+     * space.</p>
+     * @param boundary collection of boundary elements, as a
+     * collection of {@link SubHyperplane SubHyperplane} objects
+     * @deprecated as of 3.3, replaced with {@link #PolygonsSet(Collection, double)}
+     */
+    @Deprecated
+    public PolygonsSet(final Collection<SubHyperplane<Euclidean2D>> boundary) {
+        this(boundary, DEFAULT_TOLERANCE);
+    }
+
+    /** Build a parallellepipedic box.
+     * @param xMin low bound along the x direction
+     * @param xMax high bound along the x direction
+     * @param yMin low bound along the y direction
+     * @param yMax high bound along the y direction
+     * @deprecated as of 3.3, replaced with {@link #PolygonsSet(double, double, double, double, double)}
+     */
+    @Deprecated
+    public PolygonsSet(final double xMin, final double xMax,
+                       final double yMin, final double yMax) {
+        this(xMin, xMax, yMin, yMax, DEFAULT_TOLERANCE);
+    }
+
+    /** Create a list of hyperplanes representing the boundary of a box.
+     * @param xMin low bound along the x direction
+     * @param xMax high bound along the x direction
+     * @param yMin low bound along the y direction
+     * @param yMax high bound along the y direction
+     * @param tolerance tolerance below which points are considered identical
+     * @return boundary of the box
+     */
+    private static Line[] boxBoundary(final double xMin, final double xMax,
+                                      final double yMin, final double yMax,
+                                      final double tolerance) {
+        if ((xMin >= xMax - tolerance) || (yMin >= yMax - tolerance)) {
+            // too thin box, build an empty polygons set
+            return null;
+        }
+        final Vector2D minMin = new Vector2D(xMin, yMin);
+        final Vector2D minMax = new Vector2D(xMin, yMax);
+        final Vector2D maxMin = new Vector2D(xMax, yMin);
+        final Vector2D maxMax = new Vector2D(xMax, yMax);
+        return new Line[] {
+            new Line(minMin, maxMin, tolerance),
+            new Line(maxMin, maxMax, tolerance),
+            new Line(maxMax, minMax, tolerance),
+            new Line(minMax, minMin, tolerance)
+        };
+    }
+
+    /** Build the BSP tree of a polygons set from a simple list of vertices.
+     * <p>The boundary is provided as a list of points considering to
+     * represent the vertices of a simple loop. The interior part of the
+     * region is on the left side of this path and the exterior is on its
+     * right side.</p>
+     * <p>This constructor does not handle polygons with a boundary
+     * forming several disconnected paths (such as polygons with holes).</p>
+     * <p>For cases where this simple constructor applies, it is expected to
+     * be numerically more robust than the {@link #PolygonsSet(Collection) general
+     * constructor} using {@link SubHyperplane subhyperplanes}.</p>
+     * @param hyperplaneThickness tolerance below which points are consider to
+     * belong to the hyperplane (which is therefore more a slab)
+     * @param vertices vertices of the simple loop boundary
+     * @return the BSP tree of the input vertices
+     */
+    private static BSPTree<Euclidean2D> verticesToTree(final double hyperplaneThickness,
+                                                       final Vector2D ... vertices) {
+
+        final int n = vertices.length;
+        if (n == 0) {
+            // the tree represents the whole space
+            return new BSPTree<Euclidean2D>(Boolean.TRUE);
+        }
+
+        // build the vertices
+        final Vertex[] vArray = new Vertex[n];
+        for (int i = 0; i < n; ++i) {
+            vArray[i] = new Vertex(vertices[i]);
+        }
+
+        // build the edges
+        List<Edge> edges = new ArrayList<Edge>(n);
+        for (int i = 0; i < n; ++i) {
+
+            // get the endpoints of the edge
+            final Vertex start = vArray[i];
+            final Vertex end   = vArray[(i + 1) % n];
+
+            // get the line supporting the edge, taking care not to recreate it
+            // if it was already created earlier due to another edge being aligned
+            // with the current one
+            Line line = start.sharedLineWith(end);
+            if (line == null) {
+                line = new Line(start.getLocation(), end.getLocation(), hyperplaneThickness);
+            }
+
+            // create the edge and store it
+            edges.add(new Edge(start, end, line));
+
+            // check if another vertex also happens to be on this line
+            for (final Vertex vertex : vArray) {
+                if (vertex != start && vertex != end &&
+                    FastMath.abs(line.getOffset((Point<Euclidean2D>) vertex.getLocation())) <= hyperplaneThickness) {
+                    vertex.bindWith(line);
+                }
+            }
+
+        }
+
+        // build the tree top-down
+        final BSPTree<Euclidean2D> tree = new BSPTree<Euclidean2D>();
+        insertEdges(hyperplaneThickness, tree, edges);
+
+        return tree;
+
+    }
+
+    /** Recursively build a tree by inserting cut sub-hyperplanes.
+     * @param hyperplaneThickness tolerance below which points are consider to
+     * belong to the hyperplane (which is therefore more a slab)
+     * @param node current tree node (it is a leaf node at the beginning
+     * of the call)
+     * @param edges list of edges to insert in the cell defined by this node
+     * (excluding edges not belonging to the cell defined by this node)
+     */
+    private static void insertEdges(final double hyperplaneThickness,
+                                    final BSPTree<Euclidean2D> node,
+                                    final List<Edge> edges) {
+
+        // find an edge with an hyperplane that can be inserted in the node
+        int index = 0;
+        Edge inserted =null;
+        while (inserted == null && index < edges.size()) {
+            inserted = edges.get(index++);
+            if (inserted.getNode() == null) {
+                if (node.insertCut(inserted.getLine())) {
+                    inserted.setNode(node);
+                } else {
+                    inserted = null;
+                }
+            } else {
+                inserted = null;
+            }
+        }
+
+        if (inserted == null) {
+            // no suitable edge was found, the node remains a leaf node
+            // we need to set its inside/outside boolean indicator
+            final BSPTree<Euclidean2D> parent = node.getParent();
+            if (parent == null || node == parent.getMinus()) {
+                node.setAttribute(Boolean.TRUE);
+            } else {
+                node.setAttribute(Boolean.FALSE);
+            }
+            return;
+        }
+
+        // we have split the node by inserting an edge as a cut sub-hyperplane
+        // distribute the remaining edges in the two sub-trees
+        final List<Edge> plusList  = new ArrayList<Edge>();
+        final List<Edge> minusList = new ArrayList<Edge>();
+        for (final Edge edge : edges) {
+            if (edge != inserted) {
+                final double startOffset = inserted.getLine().getOffset((Point<Euclidean2D>) edge.getStart().getLocation());
+                final double endOffset   = inserted.getLine().getOffset((Point<Euclidean2D>) edge.getEnd().getLocation());
+                Side startSide = (FastMath.abs(startOffset) <= hyperplaneThickness) ?
+                                 Side.HYPER : ((startOffset < 0) ? Side.MINUS : Side.PLUS);
+                Side endSide   = (FastMath.abs(endOffset) <= hyperplaneThickness) ?
+                                 Side.HYPER : ((endOffset < 0) ? Side.MINUS : Side.PLUS);
+                switch (startSide) {
+                    case PLUS:
+                        if (endSide == Side.MINUS) {
+                            // we need to insert a split point on the hyperplane
+                            final Vertex splitPoint = edge.split(inserted.getLine());
+                            minusList.add(splitPoint.getOutgoing());
+                            plusList.add(splitPoint.getIncoming());
+                        } else {
+                            plusList.add(edge);
+                        }
+                        break;
+                    case MINUS:
+                        if (endSide == Side.PLUS) {
+                            // we need to insert a split point on the hyperplane
+                            final Vertex splitPoint = edge.split(inserted.getLine());
+                            minusList.add(splitPoint.getIncoming());
+                            plusList.add(splitPoint.getOutgoing());
+                        } else {
+                            minusList.add(edge);
+                        }
+                        break;
+                    default:
+                        if (endSide == Side.PLUS) {
+                            plusList.add(edge);
+                        } else if (endSide == Side.MINUS) {
+                            minusList.add(edge);
+                        }
+                        break;
+                }
+            }
+        }
+
+        // recurse through lower levels
+        if (!plusList.isEmpty()) {
+            insertEdges(hyperplaneThickness, node.getPlus(),  plusList);
+        } else {
+            node.getPlus().setAttribute(Boolean.FALSE);
+        }
+        if (!minusList.isEmpty()) {
+            insertEdges(hyperplaneThickness, node.getMinus(), minusList);
+        } else {
+            node.getMinus().setAttribute(Boolean.TRUE);
+        }
+
+    }
+
+    /** Internal class for holding vertices while they are processed to build a BSP tree. */
+    private static class Vertex {
+
+        /** Vertex location. */
+        private final Vector2D location;
+
+        /** Incoming edge. */
+        private Edge incoming;
+
+        /** Outgoing edge. */
+        private Edge outgoing;
+
+        /** Lines bound with this vertex. */
+        private final List<Line> lines;
+
+        /** Build a non-processed vertex not owned by any node yet.
+         * @param location vertex location
+         */
+        Vertex(final Vector2D location) {
+            this.location = location;
+            this.incoming = null;
+            this.outgoing = null;
+            this.lines    = new ArrayList<Line>();
+        }
+
+        /** Get Vertex location.
+         * @return vertex location
+         */
+        public Vector2D getLocation() {
+            return location;
+        }
+
+        /** Bind a line considered to contain this vertex.
+         * @param line line to bind with this vertex
+         */
+        public void bindWith(final Line line) {
+            lines.add(line);
+        }
+
+        /** Get the common line bound with both the instance and another vertex, if any.
+         * <p>
+         * When two vertices are both bound to the same line, this means they are
+         * already handled by node associated with this line, so there is no need
+         * to create a cut hyperplane for them.
+         * </p>
+         * @param vertex other vertex to check instance against
+         * @return line bound with both the instance and another vertex, or null if the
+         * two vertices do not share a line yet
+         */
+        public Line sharedLineWith(final Vertex vertex) {
+            for (final Line line1 : lines) {
+                for (final Line line2 : vertex.lines) {
+                    if (line1 == line2) {
+                        return line1;
+                    }
+                }
+            }
+            return null;
+        }
+
+        /** Set incoming edge.
+         * <p>
+         * The line supporting the incoming edge is automatically bound
+         * with the instance.
+         * </p>
+         * @param incoming incoming edge
+         */
+        public void setIncoming(final Edge incoming) {
+            this.incoming = incoming;
+            bindWith(incoming.getLine());
+        }
+
+        /** Get incoming edge.
+         * @return incoming edge
+         */
+        public Edge getIncoming() {
+            return incoming;
+        }
+
+        /** Set outgoing edge.
+         * <p>
+         * The line supporting the outgoing edge is automatically bound
+         * with the instance.
+         * </p>
+         * @param outgoing outgoing edge
+         */
+        public void setOutgoing(final Edge outgoing) {
+            this.outgoing = outgoing;
+            bindWith(outgoing.getLine());
+        }
+
+        /** Get outgoing edge.
+         * @return outgoing edge
+         */
+        public Edge getOutgoing() {
+            return outgoing;
+        }
+
+    }
+
+    /** Internal class for holding edges while they are processed to build a BSP tree. */
+    private static class Edge {
+
+        /** Start vertex. */
+        private final Vertex start;
+
+        /** End vertex. */
+        private final Vertex end;
+
+        /** Line supporting the edge. */
+        private final Line line;
+
+        /** Node whose cut hyperplane contains this edge. */
+        private BSPTree<Euclidean2D> node;
+
+        /** Build an edge not contained in any node yet.
+         * @param start start vertex
+         * @param end end vertex
+         * @param line line supporting the edge
+         */
+        Edge(final Vertex start, final Vertex end, final Line line) {
+
+            this.start = start;
+            this.end   = end;
+            this.line  = line;
+            this.node  = null;
+
+            // connect the vertices back to the edge
+            start.setOutgoing(this);
+            end.setIncoming(this);
+
+        }
+
+        /** Get start vertex.
+         * @return start vertex
+         */
+        public Vertex getStart() {
+            return start;
+        }
+
+        /** Get end vertex.
+         * @return end vertex
+         */
+        public Vertex getEnd() {
+            return end;
+        }
+
+        /** Get the line supporting this edge.
+         * @return line supporting this edge
+         */
+        public Line getLine() {
+            return line;
+        }
+
+        /** Set the node whose cut hyperplane contains this edge.
+         * @param node node whose cut hyperplane contains this edge
+         */
+        public void setNode(final BSPTree<Euclidean2D> node) {
+            this.node = node;
+        }
+
+        /** Get the node whose cut hyperplane contains this edge.
+         * @return node whose cut hyperplane contains this edge
+         * (null if edge has not yet been inserted into the BSP tree)
+         */
+        public BSPTree<Euclidean2D> getNode() {
+            return node;
+        }
+
+        /** Split the edge.
+         * <p>
+         * Once split, this edge is not referenced anymore by the vertices,
+         * it is replaced by the two half-edges and an intermediate splitting
+         * vertex is introduced to connect these two halves.
+         * </p>
+         * @param splitLine line splitting the edge in two halves
+         * @return split vertex (its incoming and outgoing edges are the two halves)
+         */
+        public Vertex split(final Line splitLine) {
+            final Vertex splitVertex = new Vertex(line.intersection(splitLine));
+            splitVertex.bindWith(splitLine);
+            final Edge startHalf = new Edge(start, splitVertex, line);
+            final Edge endHalf   = new Edge(splitVertex, end, line);
+            startHalf.node = node;
+            endHalf.node   = node;
+            return splitVertex;
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public PolygonsSet buildNew(final BSPTree<Euclidean2D> tree) {
+        return new PolygonsSet(tree, getTolerance());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void computeGeometricalProperties() {
+
+        final Vector2D[][] v = getVertices();
+
+        if (v.length == 0) {
+            final BSPTree<Euclidean2D> tree = getTree(false);
+            if (tree.getCut() == null && (Boolean) tree.getAttribute()) {
+                // the instance covers the whole space
+                setSize(Double.POSITIVE_INFINITY);
+                setBarycenter((Point<Euclidean2D>) Vector2D.NaN);
+            } else {
+                setSize(0);
+                setBarycenter((Point<Euclidean2D>) new Vector2D(0, 0));
+            }
+        } else if (v[0][0] == null) {
+            // there is at least one open-loop: the polygon is infinite
+            setSize(Double.POSITIVE_INFINITY);
+            setBarycenter((Point<Euclidean2D>) Vector2D.NaN);
+        } else {
+            // all loops are closed, we compute some integrals around the shape
+
+            double sum  = 0;
+            double sumX = 0;
+            double sumY = 0;
+
+            for (Vector2D[] loop : v) {
+                double x1 = loop[loop.length - 1].getX();
+                double y1 = loop[loop.length - 1].getY();
+                for (final Vector2D point : loop) {
+                    final double x0 = x1;
+                    final double y0 = y1;
+                    x1 = point.getX();
+                    y1 = point.getY();
+                    final double factor = x0 * y1 - y0 * x1;
+                    sum  += factor;
+                    sumX += factor * (x0 + x1);
+                    sumY += factor * (y0 + y1);
+                }
+            }
+
+            if (sum < 0) {
+                // the polygon as a finite outside surrounded by an infinite inside
+                setSize(Double.POSITIVE_INFINITY);
+                setBarycenter((Point<Euclidean2D>) Vector2D.NaN);
+            } else {
+                setSize(sum / 2);
+                setBarycenter((Point<Euclidean2D>) new Vector2D(sumX / (3 * sum), sumY / (3 * sum)));
+            }
+
+        }
+
+    }
+
+    /** Get the vertices of the polygon.
+     * <p>The polygon boundary can be represented as an array of loops,
+     * each loop being itself an array of vertices.</p>
+     * <p>In order to identify open loops which start and end by
+     * infinite edges, the open loops arrays start with a null point. In
+     * this case, the first non null point and the last point of the
+     * array do not represent real vertices, they are dummy points
+     * intended only to get the direction of the first and last edge. An
+     * open loop consisting of a single infinite line will therefore be
+     * represented by a three elements array with one null point
+     * followed by two dummy points. The open loops are always the first
+     * ones in the loops array.</p>
+     * <p>If the polygon has no boundary at all, a zero length loop
+     * array will be returned.</p>
+     * <p>All line segments in the various loops have the inside of the
+     * region on their left side and the outside on their right side
+     * when moving in the underlying line direction. This means that
+     * closed loops surrounding finite areas obey the direct
+     * trigonometric orientation.</p>
+     * @return vertices of the polygon, organized as oriented boundary
+     * loops with the open loops first (the returned value is guaranteed
+     * to be non-null)
+     */
+    public Vector2D[][] getVertices() {
+        if (vertices == null) {
+            if (getTree(false).getCut() == null) {
+                vertices = new Vector2D[0][];
+            } else {
+
+                // build the unconnected segments
+                final SegmentsBuilder visitor = new SegmentsBuilder(getTolerance());
+                getTree(true).visit(visitor);
+                final List<ConnectableSegment> segments = visitor.getSegments();
+
+                // connect all segments, using topological criteria first
+                // and using Euclidean distance only as a last resort
+                int pending = segments.size();
+                pending -= naturalFollowerConnections(segments);
+                if (pending > 0) {
+                    pending -= splitEdgeConnections(segments);
+                }
+                if (pending > 0) {
+                    pending -= closeVerticesConnections(segments);
+                }
+
+                // create the segment loops
+                final ArrayList<List<Segment>> loops = new ArrayList<List<Segment>>();
+                for (ConnectableSegment s = getUnprocessed(segments); s != null; s = getUnprocessed(segments)) {
+                    final List<Segment> loop = followLoop(s);
+                    if (loop != null) {
+                        if (loop.get(0).getStart() == null) {
+                            // this is an open loop, we put it on the front
+                            loops.add(0, loop);
+                        } else {
+                            // this is a closed loop, we put it on the back
+                            loops.add(loop);
+                        }
+                    }
+                }
+
+                // transform the loops in an array of arrays of points
+                vertices = new Vector2D[loops.size()][];
+                int i = 0;
+
+                for (final List<Segment> loop : loops) {
+                    if (loop.size() < 2 ||
+                        (loop.size() == 2 && loop.get(0).getStart() == null && loop.get(1).getEnd() == null)) {
+                        // single infinite line
+                        final Line line = loop.get(0).getLine();
+                        vertices[i++] = new Vector2D[] {
+                            null,
+                            line.toSpace((Point<Euclidean1D>) new Vector1D(-Float.MAX_VALUE)),
+                            line.toSpace((Point<Euclidean1D>) new Vector1D(+Float.MAX_VALUE))
+                        };
+                    } else if (loop.get(0).getStart() == null) {
+                        // open loop with at least one real point
+                        final Vector2D[] array = new Vector2D[loop.size() + 2];
+                        int j = 0;
+                        for (Segment segment : loop) {
+
+                            if (j == 0) {
+                                // null point and first dummy point
+                                double x = segment.getLine().toSubSpace((Point<Euclidean2D>) segment.getEnd()).getX();
+                                x -= FastMath.max(1.0, FastMath.abs(x / 2));
+                                array[j++] = null;
+                                array[j++] = segment.getLine().toSpace((Point<Euclidean1D>) new Vector1D(x));
+                            }
+
+                            if (j < (array.length - 1)) {
+                                // current point
+                                array[j++] = segment.getEnd();
+                            }
+
+                            if (j == (array.length - 1)) {
+                                // last dummy point
+                                double x = segment.getLine().toSubSpace((Point<Euclidean2D>) segment.getStart()).getX();
+                                x += FastMath.max(1.0, FastMath.abs(x / 2));
+                                array[j++] = segment.getLine().toSpace((Point<Euclidean1D>) new Vector1D(x));
+                            }
+
+                        }
+                        vertices[i++] = array;
+                    } else {
+                        final Vector2D[] array = new Vector2D[loop.size()];
+                        int j = 0;
+                        for (Segment segment : loop) {
+                            array[j++] = segment.getStart();
+                        }
+                        vertices[i++] = array;
+                    }
+                }
+
+            }
+        }
+
+        return vertices.clone();
+
+    }
+
+    /** Connect the segments using only natural follower information.
+     * @param segments segments complete segments list
+     * @return number of connections performed
+     */
+    private int naturalFollowerConnections(final List<ConnectableSegment> segments) {
+        int connected = 0;
+        for (final ConnectableSegment segment : segments) {
+            if (segment.getNext() == null) {
+                final BSPTree<Euclidean2D> node = segment.getNode();
+                final BSPTree<Euclidean2D> end  = segment.getEndNode();
+                for (final ConnectableSegment candidateNext : segments) {
+                    if (candidateNext.getPrevious()  == null &&
+                        candidateNext.getNode()      == end &&
+                        candidateNext.getStartNode() == node) {
+                        // connect the two segments
+                        segment.setNext(candidateNext);
+                        candidateNext.setPrevious(segment);
+                        ++connected;
+                        break;
+                    }
+                }
+            }
+        }
+        return connected;
+    }
+
+    /** Connect the segments resulting from a line splitting a straight edge.
+     * @param segments segments complete segments list
+     * @return number of connections performed
+     */
+    private int splitEdgeConnections(final List<ConnectableSegment> segments) {
+        int connected = 0;
+        for (final ConnectableSegment segment : segments) {
+            if (segment.getNext() == null) {
+                final Hyperplane<Euclidean2D> hyperplane = segment.getNode().getCut().getHyperplane();
+                final BSPTree<Euclidean2D> end  = segment.getEndNode();
+                for (final ConnectableSegment candidateNext : segments) {
+                    if (candidateNext.getPrevious()                      == null &&
+                        candidateNext.getNode().getCut().getHyperplane() == hyperplane &&
+                        candidateNext.getStartNode()                     == end) {
+                        // connect the two segments
+                        segment.setNext(candidateNext);
+                        candidateNext.setPrevious(segment);
+                        ++connected;
+                        break;
+                    }
+                }
+            }
+        }
+        return connected;
+    }
+
+    /** Connect the segments using Euclidean distance.
+     * <p>
+     * This connection heuristic should be used last, as it relies
+     * only on a fuzzy distance criterion.
+     * </p>
+     * @param segments segments complete segments list
+     * @return number of connections performed
+     */
+    private int closeVerticesConnections(final List<ConnectableSegment> segments) {
+        int connected = 0;
+        for (final ConnectableSegment segment : segments) {
+            if (segment.getNext() == null && segment.getEnd() != null) {
+                final Vector2D end = segment.getEnd();
+                ConnectableSegment selectedNext = null;
+                double min = Double.POSITIVE_INFINITY;
+                for (final ConnectableSegment candidateNext : segments) {
+                    if (candidateNext.getPrevious() == null && candidateNext.getStart() != null) {
+                        final double distance = Vector2D.distance(end, candidateNext.getStart());
+                        if (distance < min) {
+                            selectedNext = candidateNext;
+                            min          = distance;
+                        }
+                    }
+                }
+                if (min <= getTolerance()) {
+                    // connect the two segments
+                    segment.setNext(selectedNext);
+                    selectedNext.setPrevious(segment);
+                    ++connected;
+                }
+            }
+        }
+        return connected;
+    }
+
+    /** Get first unprocessed segment from a list.
+     * @param segments segments list
+     * @return first segment that has not been processed yet
+     * or null if all segments have been processed
+     */
+    private ConnectableSegment getUnprocessed(final List<ConnectableSegment> segments) {
+        for (final ConnectableSegment segment : segments) {
+            if (!segment.isProcessed()) {
+                return segment;
+            }
+        }
+        return null;
+    }
+
+    /** Build the loop containing a segment.
+     * <p>
+     * The segment put in the loop will be marked as processed.
+     * </p>
+     * @param defining segment used to define the loop
+     * @return loop containing the segment (may be null if the loop is a
+     * degenerated infinitely thin 2 points loop
+     */
+    private List<Segment> followLoop(final ConnectableSegment defining) {
+
+        final List<Segment> loop = new ArrayList<Segment>();
+        loop.add(defining);
+        defining.setProcessed(true);
+
+        // add segments in connection order
+        ConnectableSegment next = defining.getNext();
+        while (next != defining && next != null) {
+            loop.add(next);
+            next.setProcessed(true);
+            next = next.getNext();
+        }
+
+        if (next == null) {
+            // the loop is open and we have found its end,
+            // we need to find its start too
+            ConnectableSegment previous = defining.getPrevious();
+            while (previous != null) {
+                loop.add(0, previous);
+                previous.setProcessed(true);
+                previous = previous.getPrevious();
+            }
+        }
+
+        // filter out spurious vertices
+        filterSpuriousVertices(loop);
+
+        if (loop.size() == 2 && loop.get(0).getStart() != null) {
+            // this is a degenerated infinitely thin closed loop, we simply ignore it
+            return null;
+        } else {
+            return loop;
+        }
+
+    }
+
+    /** Filter out spurious vertices on straight lines (at machine precision).
+     * @param loop segments loop to filter (will be modified in-place)
+     */
+    private void filterSpuriousVertices(final List<Segment> loop) {
+        for (int i = 0; i < loop.size(); ++i) {
+            final Segment previous = loop.get(i);
+            int j = (i + 1) % loop.size();
+            final Segment next = loop.get(j);
+            if (next != null &&
+                Precision.equals(previous.getLine().getAngle(), next.getLine().getAngle(), Precision.EPSILON)) {
+                // the vertex between the two edges is a spurious one
+                // replace the two segments by a single one
+                loop.set(j, new Segment(previous.getStart(), next.getEnd(), previous.getLine()));
+                loop.remove(i--);
+            }
+        }
+    }
+
+    /** Private extension of Segment allowing connection. */
+    private static class ConnectableSegment extends Segment {
+
+        /** Node containing segment. */
+        private final BSPTree<Euclidean2D> node;
+
+        /** Node whose intersection with current node defines start point. */
+        private final BSPTree<Euclidean2D> startNode;
+
+        /** Node whose intersection with current node defines end point. */
+        private final BSPTree<Euclidean2D> endNode;
+
+        /** Previous segment. */
+        private ConnectableSegment previous;
+
+        /** Next segment. */
+        private ConnectableSegment next;
+
+        /** Indicator for completely processed segments. */
+        private boolean processed;
+
+        /** Build a segment.
+         * @param start start point of the segment
+         * @param end end point of the segment
+         * @param line line containing the segment
+         * @param node node containing the segment
+         * @param startNode node whose intersection with current node defines start point
+         * @param endNode node whose intersection with current node defines end point
+         */
+        ConnectableSegment(final Vector2D start, final Vector2D end, final Line line,
+                           final BSPTree<Euclidean2D> node,
+                           final BSPTree<Euclidean2D> startNode,
+                           final BSPTree<Euclidean2D> endNode) {
+            super(start, end, line);
+            this.node      = node;
+            this.startNode = startNode;
+            this.endNode   = endNode;
+            this.previous  = null;
+            this.next      = null;
+            this.processed = false;
+        }
+
+        /** Get the node containing segment.
+         * @return node containing segment
+         */
+        public BSPTree<Euclidean2D> getNode() {
+            return node;
+        }
+
+        /** Get the node whose intersection with current node defines start point.
+         * @return node whose intersection with current node defines start point
+         */
+        public BSPTree<Euclidean2D> getStartNode() {
+            return startNode;
+        }
+
+        /** Get the node whose intersection with current node defines end point.
+         * @return node whose intersection with current node defines end point
+         */
+        public BSPTree<Euclidean2D> getEndNode() {
+            return endNode;
+        }
+
+        /** Get the previous segment.
+         * @return previous segment
+         */
+        public ConnectableSegment getPrevious() {
+            return previous;
+        }
+
+        /** Set the previous segment.
+         * @param previous previous segment
+         */
+        public void setPrevious(final ConnectableSegment previous) {
+            this.previous = previous;
+        }
+
+        /** Get the next segment.
+         * @return next segment
+         */
+        public ConnectableSegment getNext() {
+            return next;
+        }
+
+        /** Set the next segment.
+         * @param next previous segment
+         */
+        public void setNext(final ConnectableSegment next) {
+            this.next = next;
+        }
+
+        /** Set the processed flag.
+         * @param processed processed flag to set
+         */
+        public void setProcessed(final boolean processed) {
+            this.processed = processed;
+        }
+
+        /** Check if the segment has been processed.
+         * @return true if the segment has been processed
+         */
+        public boolean isProcessed() {
+            return processed;
+        }
+
+    }
+
+    /** Visitor building segments. */
+    private static class SegmentsBuilder implements BSPTreeVisitor<Euclidean2D> {
+
+        /** Tolerance for close nodes connection. */
+        private final double tolerance;
+
+        /** Built segments. */
+        private final List<ConnectableSegment> segments;
+
+        /** Simple constructor.
+         * @param tolerance tolerance for close nodes connection
+         */
+        SegmentsBuilder(final double tolerance) {
+            this.tolerance = tolerance;
+            this.segments  = new ArrayList<ConnectableSegment>();
+        }
+
+        /** {@inheritDoc} */
+        public Order visitOrder(final BSPTree<Euclidean2D> node) {
+            return Order.MINUS_SUB_PLUS;
+        }
+
+        /** {@inheritDoc} */
+        public void visitInternalNode(final BSPTree<Euclidean2D> node) {
+            @SuppressWarnings("unchecked")
+            final BoundaryAttribute<Euclidean2D> attribute = (BoundaryAttribute<Euclidean2D>) node.getAttribute();
+            final Iterable<BSPTree<Euclidean2D>> splitters = attribute.getSplitters();
+            if (attribute.getPlusOutside() != null) {
+                addContribution(attribute.getPlusOutside(), node, splitters, false);
+            }
+            if (attribute.getPlusInside() != null) {
+                addContribution(attribute.getPlusInside(), node, splitters, true);
+            }
+        }
+
+        /** {@inheritDoc} */
+        public void visitLeafNode(final BSPTree<Euclidean2D> node) {
+        }
+
+        /** Add the contribution of a boundary facet.
+         * @param sub boundary facet
+         * @param node node containing segment
+         * @param splitters splitters for the boundary facet
+         * @param reversed if true, the facet has the inside on its plus side
+         */
+        private void addContribution(final SubHyperplane<Euclidean2D> sub,
+                                     final BSPTree<Euclidean2D> node,
+                                     final Iterable<BSPTree<Euclidean2D>> splitters,
+                                     final boolean reversed) {
+            @SuppressWarnings("unchecked")
+            final AbstractSubHyperplane<Euclidean2D, Euclidean1D> absSub =
+                (AbstractSubHyperplane<Euclidean2D, Euclidean1D>) sub;
+            final Line line      = (Line) sub.getHyperplane();
+            final List<Interval> intervals = ((IntervalsSet) absSub.getRemainingRegion()).asList();
+            for (final Interval i : intervals) {
+
+                // find the 2D points
+                final Vector2D startV = Double.isInfinite(i.getInf()) ?
+                                        null : (Vector2D) line.toSpace((Point<Euclidean1D>) new Vector1D(i.getInf()));
+                final Vector2D endV   = Double.isInfinite(i.getSup()) ?
+                                        null : (Vector2D) line.toSpace((Point<Euclidean1D>) new Vector1D(i.getSup()));
+
+                // recover the connectivity information
+                final BSPTree<Euclidean2D> startN = selectClosest(startV, splitters);
+                final BSPTree<Euclidean2D> endN   = selectClosest(endV, splitters);
+
+                if (reversed) {
+                    segments.add(new ConnectableSegment(endV, startV, line.getReverse(),
+                                                        node, endN, startN));
+                } else {
+                    segments.add(new ConnectableSegment(startV, endV, line,
+                                                        node, startN, endN));
+                }
+
+            }
+        }
+
+        /** Select the node whose cut sub-hyperplane is closest to specified point.
+         * @param point reference point
+         * @param candidates candidate nodes
+         * @return node closest to point, or null if no node is closer than tolerance
+         */
+        private BSPTree<Euclidean2D> selectClosest(final Vector2D point, final Iterable<BSPTree<Euclidean2D>> candidates) {
+
+            BSPTree<Euclidean2D> selected = null;
+            double min = Double.POSITIVE_INFINITY;
+
+            for (final BSPTree<Euclidean2D> node : candidates) {
+                final double distance = FastMath.abs(node.getCut().getHyperplane().getOffset(point));
+                if (distance < min) {
+                    selected = node;
+                    min      = distance;
+                }
+            }
+
+            return min <= tolerance ? selected : null;
+
+        }
+
+        /** Get the segments.
+         * @return built segments
+         */
+        public List<ConnectableSegment> getSegments() {
+            return segments;
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/Segment.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/Segment.java
new file mode 100644
index 0000000..2ef7f4e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/Segment.java
@@ -0,0 +1,112 @@
+/*
+ * 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.math3.geometry.euclidean.twod;
+
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.util.FastMath;
+
+/** Simple container for a two-points segment.
+ * @since 3.0
+ */
+public class Segment {
+
+    /** Start point of the segment. */
+    private final Vector2D start;
+
+    /** End point of the segment. */
+    private final Vector2D end;
+
+    /** Line containing the segment. */
+    private final Line     line;
+
+    /** Build a segment.
+     * @param start start point of the segment
+     * @param end end point of the segment
+     * @param line line containing the segment
+     */
+    public Segment(final Vector2D start, final Vector2D end, final Line line) {
+        this.start  = start;
+        this.end    = end;
+        this.line   = line;
+    }
+
+    /** Get the start point of the segment.
+     * @return start point of the segment
+     */
+    public Vector2D getStart() {
+        return start;
+    }
+
+    /** Get the end point of the segment.
+     * @return end point of the segment
+     */
+    public Vector2D getEnd() {
+        return end;
+    }
+
+    /** Get the line containing the segment.
+     * @return line containing the segment
+     */
+    public Line getLine() {
+        return line;
+    }
+
+    /** Calculates the shortest distance from a point to this line segment.
+     * <p>
+     * If the perpendicular extension from the point to the line does not
+     * cross in the bounds of the line segment, the shortest distance to
+     * the two end points will be returned.
+     * </p>
+     *
+     * Algorithm adapted from:
+     * <a href="http://www.codeguru.com/forum/printthread.php?s=cc8cf0596231f9a7dba4da6e77c29db3&t=194400&pp=15&page=1">
+     * Thread @ Codeguru</a>
+     *
+     * @param p to check
+     * @return distance between the instance and the point
+     * @since 3.1
+     */
+    public double distance(final Vector2D p) {
+        final double deltaX = end.getX() - start.getX();
+        final double deltaY = end.getY() - start.getY();
+
+        final double r = ((p.getX() - start.getX()) * deltaX + (p.getY() - start.getY()) * deltaY) /
+                         (deltaX * deltaX + deltaY * deltaY);
+
+        // r == 0 => P = startPt
+        // r == 1 => P = endPt
+        // r < 0 => P is on the backward extension of the segment
+        // r > 1 => P is on the forward extension of the segment
+        // 0 < r < 1 => P is on the segment
+
+        // if point isn't on the line segment, just return the shortest distance to the end points
+        if (r < 0 || r > 1) {
+            final double dist1 = getStart().distance((Point<Euclidean2D>) p);
+            final double dist2 = getEnd().distance((Point<Euclidean2D>) p);
+
+            return FastMath.min(dist1, dist2);
+        }
+        else {
+            // find point on line and see if it is in the line segment
+            final double px = start.getX() + r * deltaX;
+            final double py = start.getY() + r * deltaY;
+
+            final Vector2D interPt = new Vector2D(px, py);
+            return interPt.distance((Point<Euclidean2D>) p);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/SubLine.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/SubLine.java
new file mode 100644
index 0000000..d930b76
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/SubLine.java
@@ -0,0 +1,214 @@
+/*
+ * 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.math3.geometry.euclidean.twod;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.euclidean.oned.Euclidean1D;
+import org.apache.commons.math3.geometry.euclidean.oned.Interval;
+import org.apache.commons.math3.geometry.euclidean.oned.IntervalsSet;
+import org.apache.commons.math3.geometry.euclidean.oned.OrientedPoint;
+import org.apache.commons.math3.geometry.euclidean.oned.Vector1D;
+import org.apache.commons.math3.geometry.partitioning.AbstractSubHyperplane;
+import org.apache.commons.math3.geometry.partitioning.BSPTree;
+import org.apache.commons.math3.geometry.partitioning.Hyperplane;
+import org.apache.commons.math3.geometry.partitioning.Region;
+import org.apache.commons.math3.geometry.partitioning.Region.Location;
+import org.apache.commons.math3.geometry.partitioning.SubHyperplane;
+import org.apache.commons.math3.util.FastMath;
+
+/** This class represents a sub-hyperplane for {@link Line}.
+ * @since 3.0
+ */
+public class SubLine extends AbstractSubHyperplane<Euclidean2D, Euclidean1D> {
+
+    /** Default value for tolerance. */
+    private static final double DEFAULT_TOLERANCE = 1.0e-10;
+
+    /** Simple constructor.
+     * @param hyperplane underlying hyperplane
+     * @param remainingRegion remaining region of the hyperplane
+     */
+    public SubLine(final Hyperplane<Euclidean2D> hyperplane,
+                   final Region<Euclidean1D> remainingRegion) {
+        super(hyperplane, remainingRegion);
+    }
+
+    /** Create a sub-line from two endpoints.
+     * @param start start point
+     * @param end end point
+     * @param tolerance tolerance below which points are considered identical
+     * @since 3.3
+     */
+    public SubLine(final Vector2D start, final Vector2D end, final double tolerance) {
+        super(new Line(start, end, tolerance), buildIntervalSet(start, end, tolerance));
+    }
+
+    /** Create a sub-line from two endpoints.
+     * @param start start point
+     * @param end end point
+     * @deprecated as of 3.3, replaced with {@link #SubLine(Vector2D, Vector2D, double)}
+     */
+    @Deprecated
+    public SubLine(final Vector2D start, final Vector2D end) {
+        this(start, end, DEFAULT_TOLERANCE);
+    }
+
+    /** Create a sub-line from a segment.
+     * @param segment single segment forming the sub-line
+     */
+    public SubLine(final Segment segment) {
+        super(segment.getLine(),
+              buildIntervalSet(segment.getStart(), segment.getEnd(), segment.getLine().getTolerance()));
+    }
+
+    /** Get the endpoints of the sub-line.
+     * <p>
+     * A subline may be any arbitrary number of disjoints segments, so the endpoints
+     * are provided as a list of endpoint pairs. Each element of the list represents
+     * one segment, and each segment contains a start point at index 0 and an end point
+     * at index 1. If the sub-line is unbounded in the negative infinity direction,
+     * the start point of the first segment will have infinite coordinates. If the
+     * sub-line is unbounded in the positive infinity direction, the end point of the
+     * last segment will have infinite coordinates. So a sub-line covering the whole
+     * line will contain just one row and both elements of this row will have infinite
+     * coordinates. If the sub-line is empty, the returned list will contain 0 segments.
+     * </p>
+     * @return list of segments endpoints
+     */
+    public List<Segment> getSegments() {
+
+        final Line line = (Line) getHyperplane();
+        final List<Interval> list = ((IntervalsSet) getRemainingRegion()).asList();
+        final List<Segment> segments = new ArrayList<Segment>(list.size());
+
+        for (final Interval interval : list) {
+            final Vector2D start = line.toSpace((Point<Euclidean1D>) new Vector1D(interval.getInf()));
+            final Vector2D end   = line.toSpace((Point<Euclidean1D>) new Vector1D(interval.getSup()));
+            segments.add(new Segment(start, end, line));
+        }
+
+        return segments;
+
+    }
+
+    /** Get the intersection of the instance and another sub-line.
+     * <p>
+     * This method is related to the {@link Line#intersection(Line)
+     * intersection} method in the {@link Line Line} class, but in addition
+     * to compute the point along infinite lines, it also checks the point
+     * lies on both sub-line ranges.
+     * </p>
+     * @param subLine other sub-line which may intersect instance
+     * @param includeEndPoints if true, endpoints are considered to belong to
+     * instance (i.e. they are closed sets) and may be returned, otherwise endpoints
+     * are considered to not belong to instance (i.e. they are open sets) and intersection
+     * occurring on endpoints lead to null being returned
+     * @return the intersection point if there is one, null if the sub-lines don't intersect
+     */
+    public Vector2D intersection(final SubLine subLine, final boolean includeEndPoints) {
+
+        // retrieve the underlying lines
+        Line line1 = (Line) getHyperplane();
+        Line line2 = (Line) subLine.getHyperplane();
+
+        // compute the intersection on infinite line
+        Vector2D v2D = line1.intersection(line2);
+        if (v2D == null) {
+            return null;
+        }
+
+        // check location of point with respect to first sub-line
+        Location loc1 = getRemainingRegion().checkPoint(line1.toSubSpace((Point<Euclidean2D>) v2D));
+
+        // check location of point with respect to second sub-line
+        Location loc2 = subLine.getRemainingRegion().checkPoint(line2.toSubSpace((Point<Euclidean2D>) v2D));
+
+        if (includeEndPoints) {
+            return ((loc1 != Location.OUTSIDE) && (loc2 != Location.OUTSIDE)) ? v2D : null;
+        } else {
+            return ((loc1 == Location.INSIDE) && (loc2 == Location.INSIDE)) ? v2D : null;
+        }
+
+    }
+
+    /** Build an interval set from two points.
+     * @param start start point
+     * @param end end point
+     * @param tolerance tolerance below which points are considered identical
+     * @return an interval set
+     */
+    private static IntervalsSet buildIntervalSet(final Vector2D start, final Vector2D end, final double tolerance) {
+        final Line line = new Line(start, end, tolerance);
+        return new IntervalsSet(line.toSubSpace((Point<Euclidean2D>) start).getX(),
+                                line.toSubSpace((Point<Euclidean2D>) end).getX(),
+                                tolerance);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected AbstractSubHyperplane<Euclidean2D, Euclidean1D> buildNew(final Hyperplane<Euclidean2D> hyperplane,
+                                                                       final Region<Euclidean1D> remainingRegion) {
+        return new SubLine(hyperplane, remainingRegion);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public SplitSubHyperplane<Euclidean2D> split(final Hyperplane<Euclidean2D> hyperplane) {
+
+        final Line    thisLine  = (Line) getHyperplane();
+        final Line    otherLine = (Line) hyperplane;
+        final Vector2D crossing = thisLine.intersection(otherLine);
+        final double tolerance  = thisLine.getTolerance();
+
+        if (crossing == null) {
+            // the lines are parallel
+            final double global = otherLine.getOffset(thisLine);
+            if (global < -tolerance) {
+                return new SplitSubHyperplane<Euclidean2D>(null, this);
+            } else if (global > tolerance) {
+                return new SplitSubHyperplane<Euclidean2D>(this, null);
+            } else {
+                return new SplitSubHyperplane<Euclidean2D>(null, null);
+            }
+        }
+
+        // the lines do intersect
+        final boolean direct = FastMath.sin(thisLine.getAngle() - otherLine.getAngle()) < 0;
+        final Vector1D x      = thisLine.toSubSpace((Point<Euclidean2D>) crossing);
+        final SubHyperplane<Euclidean1D> subPlus  =
+                new OrientedPoint(x, !direct, tolerance).wholeHyperplane();
+        final SubHyperplane<Euclidean1D> subMinus =
+                new OrientedPoint(x,  direct, tolerance).wholeHyperplane();
+
+        final BSPTree<Euclidean1D> splitTree = getRemainingRegion().getTree(false).split(subMinus);
+        final BSPTree<Euclidean1D> plusTree  = getRemainingRegion().isEmpty(splitTree.getPlus()) ?
+                                               new BSPTree<Euclidean1D>(Boolean.FALSE) :
+                                               new BSPTree<Euclidean1D>(subPlus, new BSPTree<Euclidean1D>(Boolean.FALSE),
+                                                                        splitTree.getPlus(), null);
+        final BSPTree<Euclidean1D> minusTree = getRemainingRegion().isEmpty(splitTree.getMinus()) ?
+                                               new BSPTree<Euclidean1D>(Boolean.FALSE) :
+                                               new BSPTree<Euclidean1D>(subMinus, new BSPTree<Euclidean1D>(Boolean.FALSE),
+                                                                        splitTree.getMinus(), null);
+        return new SplitSubHyperplane<Euclidean2D>(new SubLine(thisLine.copySelf(), new IntervalsSet(plusTree, tolerance)),
+                                                   new SubLine(thisLine.copySelf(), new IntervalsSet(minusTree, tolerance)));
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/Vector2D.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/Vector2D.java
new file mode 100644
index 0000000..191d225
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/Vector2D.java
@@ -0,0 +1,460 @@
+/*
+ * 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.math3.geometry.euclidean.twod;
+
+import java.text.NumberFormat;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.Space;
+import org.apache.commons.math3.geometry.Vector;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.util.MathUtils;
+
+/** This class represents a 2D vector.
+ * <p>Instances of this class are guaranteed to be immutable.</p>
+ * @since 3.0
+ */
+public class Vector2D implements Vector<Euclidean2D> {
+
+    /** Origin (coordinates: 0, 0). */
+    public static final Vector2D ZERO   = new Vector2D(0, 0);
+
+    // CHECKSTYLE: stop ConstantName
+    /** A vector with all coordinates set to NaN. */
+    public static final Vector2D NaN = new Vector2D(Double.NaN, Double.NaN);
+    // CHECKSTYLE: resume ConstantName
+
+    /** A vector with all coordinates set to positive infinity. */
+    public static final Vector2D POSITIVE_INFINITY =
+        new Vector2D(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
+
+    /** A vector with all coordinates set to negative infinity. */
+    public static final Vector2D NEGATIVE_INFINITY =
+        new Vector2D(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
+
+    /** Serializable UID. */
+    private static final long serialVersionUID = 266938651998679754L;
+
+    /** Abscissa. */
+    private final double x;
+
+    /** Ordinate. */
+    private final double y;
+
+    /** Simple constructor.
+     * Build a vector from its coordinates
+     * @param x abscissa
+     * @param y ordinate
+     * @see #getX()
+     * @see #getY()
+     */
+    public Vector2D(double x, double y) {
+        this.x = x;
+        this.y = y;
+    }
+
+    /** Simple constructor.
+     * Build a vector from its coordinates
+     * @param v coordinates array
+     * @exception DimensionMismatchException if array does not have 2 elements
+     * @see #toArray()
+     */
+    public Vector2D(double[] v) throws DimensionMismatchException {
+        if (v.length != 2) {
+            throw new DimensionMismatchException(v.length, 2);
+        }
+        this.x = v[0];
+        this.y = v[1];
+    }
+
+    /** Multiplicative constructor
+     * Build a vector from another one and a scale factor.
+     * The vector built will be a * u
+     * @param a scale factor
+     * @param u base (unscaled) vector
+     */
+    public Vector2D(double a, Vector2D u) {
+        this.x = a * u.x;
+        this.y = a * u.y;
+    }
+
+    /** Linear constructor
+     * Build a vector from two other ones and corresponding scale factors.
+     * The vector built will be a1 * u1 + a2 * u2
+     * @param a1 first scale factor
+     * @param u1 first base (unscaled) vector
+     * @param a2 second scale factor
+     * @param u2 second base (unscaled) vector
+     */
+    public Vector2D(double a1, Vector2D u1, double a2, Vector2D u2) {
+        this.x = a1 * u1.x + a2 * u2.x;
+        this.y = a1 * u1.y + a2 * u2.y;
+    }
+
+    /** Linear constructor
+     * Build a vector from three other ones and corresponding scale factors.
+     * The vector built will be a1 * u1 + a2 * u2 + a3 * u3
+     * @param a1 first scale factor
+     * @param u1 first base (unscaled) vector
+     * @param a2 second scale factor
+     * @param u2 second base (unscaled) vector
+     * @param a3 third scale factor
+     * @param u3 third base (unscaled) vector
+     */
+    public Vector2D(double a1, Vector2D u1, double a2, Vector2D u2,
+                   double a3, Vector2D u3) {
+        this.x = a1 * u1.x + a2 * u2.x + a3 * u3.x;
+        this.y = a1 * u1.y + a2 * u2.y + a3 * u3.y;
+    }
+
+    /** Linear constructor
+     * Build a vector from four other ones and corresponding scale factors.
+     * The vector built will be a1 * u1 + a2 * u2 + a3 * u3 + a4 * u4
+     * @param a1 first scale factor
+     * @param u1 first base (unscaled) vector
+     * @param a2 second scale factor
+     * @param u2 second base (unscaled) vector
+     * @param a3 third scale factor
+     * @param u3 third base (unscaled) vector
+     * @param a4 fourth scale factor
+     * @param u4 fourth base (unscaled) vector
+     */
+    public Vector2D(double a1, Vector2D u1, double a2, Vector2D u2,
+                   double a3, Vector2D u3, double a4, Vector2D u4) {
+        this.x = a1 * u1.x + a2 * u2.x + a3 * u3.x + a4 * u4.x;
+        this.y = a1 * u1.y + a2 * u2.y + a3 * u3.y + a4 * u4.y;
+    }
+
+    /** Get the abscissa of the vector.
+     * @return abscissa of the vector
+     * @see #Vector2D(double, double)
+     */
+    public double getX() {
+        return x;
+    }
+
+    /** Get the ordinate of the vector.
+     * @return ordinate of the vector
+     * @see #Vector2D(double, double)
+     */
+    public double getY() {
+        return y;
+    }
+
+    /** Get the vector coordinates as a dimension 2 array.
+     * @return vector coordinates
+     * @see #Vector2D(double[])
+     */
+    public double[] toArray() {
+        return new double[] { x, y };
+    }
+
+    /** {@inheritDoc} */
+    public Space getSpace() {
+        return Euclidean2D.getInstance();
+    }
+
+    /** {@inheritDoc} */
+    public Vector2D getZero() {
+        return ZERO;
+    }
+
+    /** {@inheritDoc} */
+    public double getNorm1() {
+        return FastMath.abs(x) + FastMath.abs(y);
+    }
+
+    /** {@inheritDoc} */
+    public double getNorm() {
+        return FastMath.sqrt (x * x + y * y);
+    }
+
+    /** {@inheritDoc} */
+    public double getNormSq() {
+        return x * x + y * y;
+    }
+
+    /** {@inheritDoc} */
+    public double getNormInf() {
+        return FastMath.max(FastMath.abs(x), FastMath.abs(y));
+    }
+
+    /** {@inheritDoc} */
+    public Vector2D add(Vector<Euclidean2D> v) {
+        Vector2D v2 = (Vector2D) v;
+        return new Vector2D(x + v2.getX(), y + v2.getY());
+    }
+
+    /** {@inheritDoc} */
+    public Vector2D add(double factor, Vector<Euclidean2D> v) {
+        Vector2D v2 = (Vector2D) v;
+        return new Vector2D(x + factor * v2.getX(), y + factor * v2.getY());
+    }
+
+    /** {@inheritDoc} */
+    public Vector2D subtract(Vector<Euclidean2D> p) {
+        Vector2D p3 = (Vector2D) p;
+        return new Vector2D(x - p3.x, y - p3.y);
+    }
+
+    /** {@inheritDoc} */
+    public Vector2D subtract(double factor, Vector<Euclidean2D> v) {
+        Vector2D v2 = (Vector2D) v;
+        return new Vector2D(x - factor * v2.getX(), y - factor * v2.getY());
+    }
+
+    /** {@inheritDoc} */
+    public Vector2D normalize() throws MathArithmeticException {
+        double s = getNorm();
+        if (s == 0) {
+            throw new MathArithmeticException(LocalizedFormats.CANNOT_NORMALIZE_A_ZERO_NORM_VECTOR);
+        }
+        return scalarMultiply(1 / s);
+    }
+
+    /** Compute the angular separation between two vectors.
+     * <p>This method computes the angular separation between two
+     * vectors using the dot product for well separated vectors and the
+     * cross product for almost aligned vectors. This allows to have a
+     * good accuracy in all cases, even for vectors very close to each
+     * other.</p>
+     * @param v1 first vector
+     * @param v2 second vector
+     * @return angular separation between v1 and v2
+     * @exception MathArithmeticException if either vector has a null norm
+     */
+    public static double angle(Vector2D v1, Vector2D v2) throws MathArithmeticException {
+
+        double normProduct = v1.getNorm() * v2.getNorm();
+        if (normProduct == 0) {
+            throw new MathArithmeticException(LocalizedFormats.ZERO_NORM);
+        }
+
+        double dot = v1.dotProduct(v2);
+        double threshold = normProduct * 0.9999;
+        if ((dot < -threshold) || (dot > threshold)) {
+            // the vectors are almost aligned, compute using the sine
+            final double n = FastMath.abs(MathArrays.linearCombination(v1.x, v2.y, -v1.y, v2.x));
+            if (dot >= 0) {
+                return FastMath.asin(n / normProduct);
+            }
+            return FastMath.PI - FastMath.asin(n / normProduct);
+        }
+
+        // the vectors are sufficiently separated to use the cosine
+        return FastMath.acos(dot / normProduct);
+
+    }
+
+    /** {@inheritDoc} */
+    public Vector2D negate() {
+        return new Vector2D(-x, -y);
+    }
+
+    /** {@inheritDoc} */
+    public Vector2D scalarMultiply(double a) {
+        return new Vector2D(a * x, a * y);
+    }
+
+    /** {@inheritDoc} */
+    public boolean isNaN() {
+        return Double.isNaN(x) || Double.isNaN(y);
+    }
+
+    /** {@inheritDoc} */
+    public boolean isInfinite() {
+        return !isNaN() && (Double.isInfinite(x) || Double.isInfinite(y));
+    }
+
+    /** {@inheritDoc} */
+    public double distance1(Vector<Euclidean2D> p) {
+        Vector2D p3 = (Vector2D) p;
+        final double dx = FastMath.abs(p3.x - x);
+        final double dy = FastMath.abs(p3.y - y);
+        return dx + dy;
+    }
+
+    /** {@inheritDoc}
+     */
+    public double distance(Vector<Euclidean2D> p) {
+        return distance((Point<Euclidean2D>) p);
+    }
+
+    /** {@inheritDoc} */
+    public double distance(Point<Euclidean2D> p) {
+        Vector2D p3 = (Vector2D) p;
+        final double dx = p3.x - x;
+        final double dy = p3.y - y;
+        return FastMath.sqrt(dx * dx + dy * dy);
+    }
+
+    /** {@inheritDoc} */
+    public double distanceInf(Vector<Euclidean2D> p) {
+        Vector2D p3 = (Vector2D) p;
+        final double dx = FastMath.abs(p3.x - x);
+        final double dy = FastMath.abs(p3.y - y);
+        return FastMath.max(dx, dy);
+    }
+
+    /** {@inheritDoc} */
+    public double distanceSq(Vector<Euclidean2D> p) {
+        Vector2D p3 = (Vector2D) p;
+        final double dx = p3.x - x;
+        final double dy = p3.y - y;
+        return dx * dx + dy * dy;
+    }
+
+    /** {@inheritDoc} */
+    public double dotProduct(final Vector<Euclidean2D> v) {
+        final Vector2D v2 = (Vector2D) v;
+        return MathArrays.linearCombination(x, v2.x, y, v2.y);
+    }
+
+    /**
+     * Compute the cross-product of the instance and the given points.
+     * <p>
+     * The cross product can be used to determine the location of a point
+     * with regard to the line formed by (p1, p2) and is calculated as:
+     * \[
+     *    P = (x_2 - x_1)(y_3 - y_1) - (y_2 - y_1)(x_3 - x_1)
+     * \]
+     * with \(p3 = (x_3, y_3)\) being this instance.
+     * <p>
+     * If the result is 0, the points are collinear, i.e. lie on a single straight line L;
+     * if it is positive, this point lies to the left, otherwise to the right of the line
+     * formed by (p1, p2).
+     *
+     * @param p1 first point of the line
+     * @param p2 second point of the line
+     * @return the cross-product
+     *
+     * @see <a href="http://en.wikipedia.org/wiki/Cross_product">Cross product (Wikipedia)</a>
+     */
+    public double crossProduct(final Vector2D p1, final Vector2D p2) {
+        final double x1 = p2.getX() - p1.getX();
+        final double y1 = getY() - p1.getY();
+        final double x2 = getX() - p1.getX();
+        final double y2 = p2.getY() - p1.getY();
+        return MathArrays.linearCombination(x1, y1, -x2, y2);
+    }
+
+    /** Compute the distance between two vectors according to the L<sub>2</sub> norm.
+     * <p>Calling this method is equivalent to calling:
+     * <code>p1.subtract(p2).getNorm()</code> except that no intermediate
+     * vector is built</p>
+     * @param p1 first vector
+     * @param p2 second vector
+     * @return the distance between p1 and p2 according to the L<sub>2</sub> norm
+     */
+    public static double distance(Vector2D p1, Vector2D p2) {
+        return p1.distance(p2);
+    }
+
+    /** Compute the distance between two vectors according to the L<sub>&infin;</sub> norm.
+     * <p>Calling this method is equivalent to calling:
+     * <code>p1.subtract(p2).getNormInf()</code> except that no intermediate
+     * vector is built</p>
+     * @param p1 first vector
+     * @param p2 second vector
+     * @return the distance between p1 and p2 according to the L<sub>&infin;</sub> norm
+     */
+    public static double distanceInf(Vector2D p1, Vector2D p2) {
+        return p1.distanceInf(p2);
+    }
+
+    /** Compute the square of the distance between two vectors.
+     * <p>Calling this method is equivalent to calling:
+     * <code>p1.subtract(p2).getNormSq()</code> except that no intermediate
+     * vector is built</p>
+     * @param p1 first vector
+     * @param p2 second vector
+     * @return the square of the distance between p1 and p2
+     */
+    public static double distanceSq(Vector2D p1, Vector2D p2) {
+        return p1.distanceSq(p2);
+    }
+
+    /**
+     * Test for the equality of two 2D vectors.
+     * <p>
+     * If all coordinates of two 2D vectors are exactly the same, and none are
+     * <code>Double.NaN</code>, the two 2D vectors are considered to be equal.
+     * </p>
+     * <p>
+     * <code>NaN</code> coordinates are considered to affect globally the vector
+     * and be equals to each other - i.e, if either (or all) coordinates of the
+     * 2D vector are equal to <code>Double.NaN</code>, the 2D vector is equal to
+     * {@link #NaN}.
+     * </p>
+     *
+     * @param other Object to test for equality to this
+     * @return true if two 2D vector objects are equal, false if
+     *         object is null, not an instance of Vector2D, or
+     *         not equal to this Vector2D instance
+     *
+     */
+    @Override
+    public boolean equals(Object other) {
+
+        if (this == other) {
+            return true;
+        }
+
+        if (other instanceof Vector2D) {
+            final Vector2D rhs = (Vector2D)other;
+            if (rhs.isNaN()) {
+                return this.isNaN();
+            }
+
+            return (x == rhs.x) && (y == rhs.y);
+        }
+        return false;
+    }
+
+    /**
+     * Get a hashCode for the 2D vector.
+     * <p>
+     * All NaN values have the same hash code.</p>
+     *
+     * @return a hash code value for this object
+     */
+    @Override
+    public int hashCode() {
+        if (isNaN()) {
+            return 542;
+        }
+        return 122 * (76 * MathUtils.hash(x) +  MathUtils.hash(y));
+    }
+
+    /** Get a string representation of this vector.
+     * @return a string representation of this vector
+     */
+    @Override
+    public String toString() {
+        return Vector2DFormat.getInstance().format(this);
+    }
+
+    /** {@inheritDoc} */
+    public String toString(final NumberFormat format) {
+        return new Vector2DFormat(format).format(this);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/Vector2DFormat.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/Vector2DFormat.java
new file mode 100644
index 0000000..21261c5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/Vector2DFormat.java
@@ -0,0 +1,138 @@
+/*
+ * 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.math3.geometry.euclidean.twod;
+
+import java.text.FieldPosition;
+import java.text.NumberFormat;
+import java.text.ParsePosition;
+import java.util.Locale;
+
+import org.apache.commons.math3.exception.MathParseException;
+import org.apache.commons.math3.geometry.Vector;
+import org.apache.commons.math3.geometry.VectorFormat;
+import org.apache.commons.math3.util.CompositeFormat;
+
+/**
+ * Formats a 2D vector in components list format "{x; y}".
+ * <p>The prefix and suffix "{" and "}" and the separator "; " can be replaced by
+ * any user-defined strings. The number format for components can be configured.</p>
+ * <p>White space is ignored at parse time, even if it is in the prefix, suffix
+ * or separator specifications. So even if the default separator does include a space
+ * character that is used at format time, both input string "{1;1}" and
+ * " { 1 ; 1 } " will be parsed without error and the same vector will be
+ * returned. In the second case, however, the parse position after parsing will be
+ * just after the closing curly brace, i.e. just before the trailing space.</p>
+ * <p><b>Note:</b> using "," as a separator may interfere with the grouping separator
+ * of the default {@link NumberFormat} for the current locale. Thus it is advised
+ * to use a {@link NumberFormat} instance with disabled grouping in such a case.</p>
+ *
+ * @since 3.0
+ */
+public class Vector2DFormat extends VectorFormat<Euclidean2D> {
+
+    /**
+     * Create an instance with default settings.
+     * <p>The instance uses the default prefix, suffix and separator:
+     * "{", "}", and "; " and the default number format for components.</p>
+     */
+    public Vector2DFormat() {
+        super(DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_SEPARATOR,
+              CompositeFormat.getDefaultNumberFormat());
+    }
+
+    /**
+     * Create an instance with a custom number format for components.
+     * @param format the custom format for components.
+     */
+    public Vector2DFormat(final NumberFormat format) {
+        super(DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_SEPARATOR, format);
+    }
+
+    /**
+     * Create an instance with custom prefix, suffix and separator.
+     * @param prefix prefix to use instead of the default "{"
+     * @param suffix suffix to use instead of the default "}"
+     * @param separator separator to use instead of the default "; "
+     */
+    public Vector2DFormat(final String prefix, final String suffix,
+                         final String separator) {
+        super(prefix, suffix, separator, CompositeFormat.getDefaultNumberFormat());
+    }
+
+    /**
+     * Create an instance with custom prefix, suffix, separator and format
+     * for components.
+     * @param prefix prefix to use instead of the default "{"
+     * @param suffix suffix to use instead of the default "}"
+     * @param separator separator to use instead of the default "; "
+     * @param format the custom format for components.
+     */
+    public Vector2DFormat(final String prefix, final String suffix,
+                         final String separator, final NumberFormat format) {
+        super(prefix, suffix, separator, format);
+    }
+
+    /**
+     * Returns the default 2D vector format for the current locale.
+     * @return the default 2D vector format.
+     */
+    public static Vector2DFormat getInstance() {
+        return getInstance(Locale.getDefault());
+    }
+
+    /**
+     * Returns the default 2D vector format for the given locale.
+     * @param locale the specific locale used by the format.
+     * @return the 2D vector format specific to the given locale.
+     */
+    public static Vector2DFormat getInstance(final Locale locale) {
+        return new Vector2DFormat(CompositeFormat.getDefaultNumberFormat(locale));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public StringBuffer format(final Vector<Euclidean2D> vector, final StringBuffer toAppendTo,
+                               final FieldPosition pos) {
+        final Vector2D p2 = (Vector2D) vector;
+        return format(toAppendTo, pos, p2.getX(), p2.getY());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Vector2D parse(final String source) throws MathParseException {
+        ParsePosition parsePosition = new ParsePosition(0);
+        Vector2D result = parse(source, parsePosition);
+        if (parsePosition.getIndex() == 0) {
+            throw new MathParseException(source,
+                                         parsePosition.getErrorIndex(),
+                                         Vector2D.class);
+        }
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Vector2D parse(final String source, final ParsePosition pos) {
+        final double[] coordinates = parseCoordinates(2, source, pos);
+        if (coordinates == null) {
+            return null;
+        }
+        return new Vector2D(coordinates[0], coordinates[1]);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/hull/AbstractConvexHullGenerator2D.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/hull/AbstractConvexHullGenerator2D.java
new file mode 100644
index 0000000..b234ad5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/hull/AbstractConvexHullGenerator2D.java
@@ -0,0 +1,116 @@
+/*
+ * 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.math3.geometry.euclidean.twod.hull;
+
+import java.util.Collection;
+
+import org.apache.commons.math3.exception.ConvergenceException;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * Abstract base class for convex hull generators in the two-dimensional euclidean space.
+ *
+ * @since 3.3
+ */
+abstract class AbstractConvexHullGenerator2D implements ConvexHullGenerator2D {
+
+    /** Default value for tolerance. */
+    private static final double DEFAULT_TOLERANCE = 1e-10;
+
+    /** Tolerance below which points are considered identical. */
+    private final double tolerance;
+
+    /**
+     * Indicates if collinear points on the hull shall be present in the output.
+     * If {@code false}, only the extreme points are added to the hull.
+     */
+    private final boolean includeCollinearPoints;
+
+    /**
+     * Simple constructor.
+     * <p>
+     * The default tolerance (1e-10) will be used to determine identical points.
+     *
+     * @param includeCollinearPoints indicates if collinear points on the hull shall be
+     * added as hull vertices
+     */
+    protected AbstractConvexHullGenerator2D(final boolean includeCollinearPoints) {
+        this(includeCollinearPoints, DEFAULT_TOLERANCE);
+    }
+
+    /**
+     * Simple constructor.
+     *
+     * @param includeCollinearPoints indicates if collinear points on the hull shall be
+     * added as hull vertices
+     * @param tolerance tolerance below which points are considered identical
+     */
+    protected AbstractConvexHullGenerator2D(final boolean includeCollinearPoints, final double tolerance) {
+        this.includeCollinearPoints = includeCollinearPoints;
+        this.tolerance = tolerance;
+    }
+
+    /**
+     * Get the tolerance below which points are considered identical.
+     * @return the tolerance below which points are considered identical
+     */
+    public double getTolerance() {
+        return tolerance;
+    }
+
+    /**
+     * Returns if collinear points on the hull will be added as hull vertices.
+     * @return {@code true} if collinear points are added as hull vertices, or {@code false}
+     * if only extreme points are present.
+     */
+    public boolean isIncludeCollinearPoints() {
+        return includeCollinearPoints;
+    }
+
+    /** {@inheritDoc} */
+    public ConvexHull2D generate(final Collection<Vector2D> points)
+            throws NullArgumentException, ConvergenceException {
+        // check for null points
+        MathUtils.checkNotNull(points);
+
+        Collection<Vector2D> hullVertices = null;
+        if (points.size() < 2) {
+            hullVertices = points;
+        } else {
+            hullVertices = findHullVertices(points);
+        }
+
+        try {
+            return new ConvexHull2D(hullVertices.toArray(new Vector2D[hullVertices.size()]),
+                                    tolerance);
+        } catch (MathIllegalArgumentException e) {
+            // the hull vertices may not form a convex hull if the tolerance value is to large
+            throw new ConvergenceException();
+        }
+    }
+
+    /**
+     * Find the convex hull vertices from the set of input points.
+     * @param points the set of input points
+     * @return the convex hull vertices in CCW winding
+     */
+    protected abstract Collection<Vector2D> findHullVertices(Collection<Vector2D> points);
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/hull/AklToussaintHeuristic.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/hull/AklToussaintHeuristic.java
new file mode 100644
index 0000000..f5d1b84
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/hull/AklToussaintHeuristic.java
@@ -0,0 +1,153 @@
+/*
+ * 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.math3.geometry.euclidean.twod.hull;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
+
+/**
+ * A simple heuristic to improve the performance of convex hull algorithms.
+ * <p>
+ * The heuristic is based on the idea of a convex quadrilateral, which is formed by
+ * four points with the lowest and highest x / y coordinates. Any point that lies inside
+ * this quadrilateral can not be part of the convex hull and can thus be safely discarded
+ * before generating the convex hull itself.
+ * <p>
+ * The complexity of the operation is O(n), and may greatly improve the time it takes to
+ * construct the convex hull afterwards, depending on the point distribution.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Convex_hull_algorithms#Akl-Toussaint_heuristic">
+ * Akl-Toussaint heuristic (Wikipedia)</a>
+ * @since 3.3
+ */
+public final class AklToussaintHeuristic {
+
+    /** Hide utility constructor. */
+    private AklToussaintHeuristic() {
+    }
+
+    /**
+     * Returns a point set that is reduced by all points for which it is safe to assume
+     * that they are not part of the convex hull.
+     *
+     * @param points the original point set
+     * @return a reduced point set, useful as input for convex hull algorithms
+     */
+    public static Collection<Vector2D> reducePoints(final Collection<Vector2D> points) {
+
+        // find the leftmost point
+        int size = 0;
+        Vector2D minX = null;
+        Vector2D maxX = null;
+        Vector2D minY = null;
+        Vector2D maxY = null;
+        for (Vector2D p : points) {
+            if (minX == null || p.getX() < minX.getX()) {
+                minX = p;
+            }
+            if (maxX == null || p.getX() > maxX.getX()) {
+                maxX = p;
+            }
+            if (minY == null || p.getY() < minY.getY()) {
+                minY = p;
+            }
+            if (maxY == null || p.getY() > maxY.getY()) {
+                maxY = p;
+            }
+            size++;
+        }
+
+        if (size < 4) {
+            return points;
+        }
+
+        final List<Vector2D> quadrilateral = buildQuadrilateral(minY, maxX, maxY, minX);
+        // if the quadrilateral is not well formed, e.g. only 2 points, do not attempt to reduce
+        if (quadrilateral.size() < 3) {
+            return points;
+        }
+
+        final List<Vector2D> reducedPoints = new ArrayList<Vector2D>(quadrilateral);
+        for (final Vector2D p : points) {
+            // check all points if they are within the quadrilateral
+            // in which case they can not be part of the convex hull
+            if (!insideQuadrilateral(p, quadrilateral)) {
+                reducedPoints.add(p);
+            }
+        }
+
+        return reducedPoints;
+    }
+
+    /**
+     * Build the convex quadrilateral with the found corner points (with min/max x/y coordinates).
+     *
+     * @param points the respective points with min/max x/y coordinate
+     * @return the quadrilateral
+     */
+    private static List<Vector2D> buildQuadrilateral(final Vector2D... points) {
+        List<Vector2D> quadrilateral = new ArrayList<Vector2D>();
+        for (Vector2D p : points) {
+            if (!quadrilateral.contains(p)) {
+                quadrilateral.add(p);
+            }
+        }
+        return quadrilateral;
+    }
+
+    /**
+     * Checks if the given point is located within the convex quadrilateral.
+     * @param point the point to check
+     * @param quadrilateralPoints the convex quadrilateral, represented by 4 points
+     * @return {@code true} if the point is inside the quadrilateral, {@code false} otherwise
+     */
+    private static boolean insideQuadrilateral(final Vector2D point,
+                                               final List<Vector2D> quadrilateralPoints) {
+
+        Vector2D p1 = quadrilateralPoints.get(0);
+        Vector2D p2 = quadrilateralPoints.get(1);
+
+        if (point.equals(p1) || point.equals(p2)) {
+            return true;
+        }
+
+        // get the location of the point relative to the first two vertices
+        final double last = point.crossProduct(p1, p2);
+        final int size = quadrilateralPoints.size();
+        // loop through the rest of the vertices
+        for (int i = 1; i < size; i++) {
+            p1 = p2;
+            p2 = quadrilateralPoints.get((i + 1) == size ? 0 : i + 1);
+
+            if (point.equals(p1) || point.equals(p2)) {
+                return true;
+            }
+
+            // do side of line test: multiply the last location with this location
+            // if they are the same sign then the operation will yield a positive result
+            // -x * -y = +xy, x * y = +xy, -x * y = -xy, x * -y = -xy
+            if (last * point.crossProduct(p1, p2) < 0) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/hull/ConvexHull2D.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/hull/ConvexHull2D.java
new file mode 100644
index 0000000..5d9734b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/hull/ConvexHull2D.java
@@ -0,0 +1,172 @@
+/*
+ * 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.math3.geometry.euclidean.twod.hull;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.exception.InsufficientDataException;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.geometry.euclidean.twod.Euclidean2D;
+import org.apache.commons.math3.geometry.euclidean.twod.Line;
+import org.apache.commons.math3.geometry.euclidean.twod.Segment;
+import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
+import org.apache.commons.math3.geometry.hull.ConvexHull;
+import org.apache.commons.math3.geometry.partitioning.Region;
+import org.apache.commons.math3.geometry.partitioning.RegionFactory;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.util.Precision;
+
+/**
+ * This class represents a convex hull in an two-dimensional euclidean space.
+ *
+ * @since 3.3
+ */
+public class ConvexHull2D implements ConvexHull<Euclidean2D, Vector2D>, Serializable {
+
+    /** Serializable UID. */
+    private static final long serialVersionUID = 20140129L;
+
+    /** Vertices of the hull. */
+    private final Vector2D[] vertices;
+
+    /** Tolerance threshold used during creation of the hull vertices. */
+    private final double tolerance;
+
+    /**
+     * Line segments of the hull.
+     * The array is not serialized and will be created from the vertices on first access.
+     */
+    private transient Segment[] lineSegments;
+
+    /**
+     * Simple constructor.
+     * @param vertices the vertices of the convex hull, must be ordered
+     * @param tolerance tolerance below which points are considered identical
+     * @throws MathIllegalArgumentException if the vertices do not form a convex hull
+     */
+    public ConvexHull2D(final Vector2D[] vertices, final double tolerance)
+        throws MathIllegalArgumentException {
+
+        // assign tolerance as it will be used by the isConvex method
+        this.tolerance = tolerance;
+
+        if (!isConvex(vertices)) {
+            throw new MathIllegalArgumentException(LocalizedFormats.NOT_CONVEX);
+        }
+
+        this.vertices = vertices.clone();
+    }
+
+    /**
+     * Checks whether the given hull vertices form a convex hull.
+     * @param hullVertices the hull vertices
+     * @return {@code true} if the vertices form a convex hull, {@code false} otherwise
+     */
+    private boolean isConvex(final Vector2D[] hullVertices) {
+        if (hullVertices.length < 3) {
+            return true;
+        }
+
+        int sign = 0;
+        for (int i = 0; i < hullVertices.length; i++) {
+            final Vector2D p1 = hullVertices[i == 0 ? hullVertices.length - 1 : i - 1];
+            final Vector2D p2 = hullVertices[i];
+            final Vector2D p3 = hullVertices[i == hullVertices.length - 1 ? 0 : i + 1];
+
+            final Vector2D d1 = p2.subtract(p1);
+            final Vector2D d2 = p3.subtract(p2);
+
+            final double crossProduct = MathArrays.linearCombination(d1.getX(), d2.getY(), -d1.getY(), d2.getX());
+            final int cmp = Precision.compareTo(crossProduct, 0.0, tolerance);
+            // in case of collinear points the cross product will be zero
+            if (cmp != 0.0) {
+                if (sign != 0.0 && cmp != sign) {
+                    return false;
+                }
+                sign = cmp;
+            }
+        }
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    public Vector2D[] getVertices() {
+        return vertices.clone();
+    }
+
+    /**
+     * Get the line segments of the convex hull, ordered.
+     * @return the line segments of the convex hull
+     */
+    public Segment[] getLineSegments() {
+        return retrieveLineSegments().clone();
+    }
+
+    /**
+     * Retrieve the line segments from the cached array or create them if needed.
+     *
+     * @return the array of line segments
+     */
+    private Segment[] retrieveLineSegments() {
+        if (lineSegments == null) {
+            // construct the line segments - handle special cases of 1 or 2 points
+            final int size = vertices.length;
+            if (size <= 1) {
+                this.lineSegments = new Segment[0];
+            } else if (size == 2) {
+                this.lineSegments = new Segment[1];
+                final Vector2D p1 = vertices[0];
+                final Vector2D p2 = vertices[1];
+                this.lineSegments[0] = new Segment(p1, p2, new Line(p1, p2, tolerance));
+            } else {
+                this.lineSegments = new Segment[size];
+                Vector2D firstPoint = null;
+                Vector2D lastPoint = null;
+                int index = 0;
+                for (Vector2D point : vertices) {
+                    if (lastPoint == null) {
+                        firstPoint = point;
+                        lastPoint = point;
+                    } else {
+                        this.lineSegments[index++] =
+                                new Segment(lastPoint, point, new Line(lastPoint, point, tolerance));
+                        lastPoint = point;
+                    }
+                }
+                this.lineSegments[index] =
+                        new Segment(lastPoint, firstPoint, new Line(lastPoint, firstPoint, tolerance));
+            }
+        }
+        return lineSegments;
+    }
+
+    /** {@inheritDoc} */
+    public Region<Euclidean2D> createRegion() throws InsufficientDataException {
+        if (vertices.length < 3) {
+            throw new InsufficientDataException();
+        }
+        final RegionFactory<Euclidean2D> factory = new RegionFactory<Euclidean2D>();
+        final Segment[] segments = retrieveLineSegments();
+        final Line[] lineArray = new Line[segments.length];
+        for (int i = 0; i < segments.length; i++) {
+            lineArray[i] = segments[i].getLine();
+        }
+        return factory.buildConvex(lineArray);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/hull/ConvexHullGenerator2D.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/hull/ConvexHullGenerator2D.java
new file mode 100644
index 0000000..3e13e1a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/hull/ConvexHullGenerator2D.java
@@ -0,0 +1,37 @@
+/*
+ * 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.math3.geometry.euclidean.twod.hull;
+
+import java.util.Collection;
+
+import org.apache.commons.math3.exception.ConvergenceException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.geometry.euclidean.twod.Euclidean2D;
+import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
+import org.apache.commons.math3.geometry.hull.ConvexHullGenerator;
+
+/**
+ * Interface for convex hull generators in the two-dimensional euclidean space.
+ *
+ * @since 3.3
+ */
+public interface ConvexHullGenerator2D extends ConvexHullGenerator<Euclidean2D, Vector2D> {
+
+    /** {@inheritDoc} */
+    ConvexHull2D generate(Collection<Vector2D> points) throws NullArgumentException, ConvergenceException;
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/hull/MonotoneChain.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/hull/MonotoneChain.java
new file mode 100644
index 0000000..4421344
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/hull/MonotoneChain.java
@@ -0,0 +1,181 @@
+/*
+ * 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.math3.geometry.euclidean.twod.hull;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import org.apache.commons.math3.geometry.euclidean.twod.Line;
+import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.Precision;
+
+/**
+ * Implements Andrew's monotone chain method to generate the convex hull of a finite set of
+ * points in the two-dimensional euclidean space.
+ * <p>
+ * The runtime complexity is O(n log n), with n being the number of input points. If the
+ * point set is already sorted (by x-coordinate), the runtime complexity is O(n).
+ * <p>
+ * The implementation is not sensitive to collinear points on the hull. The parameter
+ * {@code includeCollinearPoints} allows to control the behavior with regard to collinear points.
+ * If {@code true}, all points on the boundary of the hull will be added to the hull vertices,
+ * otherwise only the extreme points will be present. By default, collinear points are not added
+ * as hull vertices.
+ * <p>
+ * The {@code tolerance} parameter (default: 1e-10) is used as epsilon criteria to determine
+ * identical and collinear points.
+ *
+ * @see <a href="http://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain">
+ * Andrew's monotone chain algorithm (Wikibooks)</a>
+ * @since 3.3
+ */
+public class MonotoneChain extends AbstractConvexHullGenerator2D {
+
+    /**
+     * Create a new MonotoneChain instance.
+     */
+    public MonotoneChain() {
+        this(false);
+    }
+
+    /**
+     * Create a new MonotoneChain instance.
+     * @param includeCollinearPoints whether collinear points shall be added as hull vertices
+     */
+    public MonotoneChain(final boolean includeCollinearPoints) {
+        super(includeCollinearPoints);
+    }
+
+    /**
+     * Create a new MonotoneChain instance.
+     * @param includeCollinearPoints whether collinear points shall be added as hull vertices
+     * @param tolerance tolerance below which points are considered identical
+     */
+    public MonotoneChain(final boolean includeCollinearPoints, final double tolerance) {
+        super(includeCollinearPoints, tolerance);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Collection<Vector2D> findHullVertices(final Collection<Vector2D> points) {
+
+        final List<Vector2D> pointsSortedByXAxis = new ArrayList<Vector2D>(points);
+
+        // sort the points in increasing order on the x-axis
+        Collections.sort(pointsSortedByXAxis, new Comparator<Vector2D>() {
+            /** {@inheritDoc} */
+            public int compare(final Vector2D o1, final Vector2D o2) {
+                final double tolerance = getTolerance();
+                // need to take the tolerance value into account, otherwise collinear points
+                // will not be handled correctly when building the upper/lower hull
+                final int diff = Precision.compareTo(o1.getX(), o2.getX(), tolerance);
+                if (diff == 0) {
+                    return Precision.compareTo(o1.getY(), o2.getY(), tolerance);
+                } else {
+                    return diff;
+                }
+            }
+        });
+
+        // build lower hull
+        final List<Vector2D> lowerHull = new ArrayList<Vector2D>();
+        for (Vector2D p : pointsSortedByXAxis) {
+            updateHull(p, lowerHull);
+        }
+
+        // build upper hull
+        final List<Vector2D> upperHull = new ArrayList<Vector2D>();
+        for (int idx = pointsSortedByXAxis.size() - 1; idx >= 0; idx--) {
+            final Vector2D p = pointsSortedByXAxis.get(idx);
+            updateHull(p, upperHull);
+        }
+
+        // concatenate the lower and upper hulls
+        // the last point of each list is omitted as it is repeated at the beginning of the other list
+        final List<Vector2D> hullVertices = new ArrayList<Vector2D>(lowerHull.size() + upperHull.size() - 2);
+        for (int idx = 0; idx < lowerHull.size() - 1; idx++) {
+            hullVertices.add(lowerHull.get(idx));
+        }
+        for (int idx = 0; idx < upperHull.size() - 1; idx++) {
+            hullVertices.add(upperHull.get(idx));
+        }
+
+        // special case: if the lower and upper hull may contain only 1 point if all are identical
+        if (hullVertices.isEmpty() && ! lowerHull.isEmpty()) {
+            hullVertices.add(lowerHull.get(0));
+        }
+
+        return hullVertices;
+    }
+
+    /**
+     * Update the partial hull with the current point.
+     *
+     * @param point the current point
+     * @param hull the partial hull
+     */
+    private void updateHull(final Vector2D point, final List<Vector2D> hull) {
+        final double tolerance = getTolerance();
+
+        if (hull.size() == 1) {
+            // ensure that we do not add an identical point
+            final Vector2D p1 = hull.get(0);
+            if (p1.distance(point) < tolerance) {
+                return;
+            }
+        }
+
+        while (hull.size() >= 2) {
+            final int size = hull.size();
+            final Vector2D p1 = hull.get(size - 2);
+            final Vector2D p2 = hull.get(size - 1);
+
+            final double offset = new Line(p1, p2, tolerance).getOffset(point);
+            if (FastMath.abs(offset) < tolerance) {
+                // the point is collinear to the line (p1, p2)
+
+                final double distanceToCurrent = p1.distance(point);
+                if (distanceToCurrent < tolerance || p2.distance(point) < tolerance) {
+                    // the point is assumed to be identical to either p1 or p2
+                    return;
+                }
+
+                final double distanceToLast = p1.distance(p2);
+                if (isIncludeCollinearPoints()) {
+                    final int index = distanceToCurrent < distanceToLast ? size - 1 : size;
+                    hull.add(index, point);
+                } else {
+                    if (distanceToCurrent > distanceToLast) {
+                        hull.remove(size - 1);
+                        hull.add(point);
+                    }
+                }
+                return;
+            } else if (offset > 0) {
+                hull.remove(size - 1);
+            } else {
+                break;
+            }
+        }
+        hull.add(point);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/hull/package-info.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/hull/package-info.java
new file mode 100644
index 0000000..d0469a4
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/hull/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * <p>
+ * This package provides algorithms to generate the convex hull
+ * for a set of points in an two-dimensional euclidean space.
+ * </p>
+ *
+ */
+package org.apache.commons.math3.geometry.euclidean.twod.hull;
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/package-info.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/package-info.java
new file mode 100644
index 0000000..feb43b1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * <p>
+ * This package provides basic 2D geometry components.
+ * </p>
+ *
+ */
+package org.apache.commons.math3.geometry.euclidean.twod;
diff --git a/src/main/java/org/apache/commons/math3/geometry/hull/ConvexHull.java b/src/main/java/org/apache/commons/math3/geometry/hull/ConvexHull.java
new file mode 100644
index 0000000..8dfa3f3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/hull/ConvexHull.java
@@ -0,0 +1,48 @@
+/*
+ * 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.math3.geometry.hull;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.exception.InsufficientDataException;
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.Space;
+import org.apache.commons.math3.geometry.partitioning.Region;
+
+/**
+ * This class represents a convex hull.
+ *
+ * @param <S> Space type.
+ * @param <P> Point type.
+ * @since 3.3
+ */
+public interface ConvexHull<S extends Space, P extends Point<S>> extends Serializable {
+
+    /**
+     * Get the vertices of the convex hull.
+     * @return vertices of the convex hull
+     */
+    P[] getVertices();
+
+    /**
+     * Returns a new region that is enclosed by the convex hull.
+     * @return the region enclosed by the convex hull
+     * @throws InsufficientDataException if the number of vertices is not enough to
+     * build a region in the respective space
+     */
+    Region<S> createRegion() throws InsufficientDataException;
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/hull/ConvexHullGenerator.java b/src/main/java/org/apache/commons/math3/geometry/hull/ConvexHullGenerator.java
new file mode 100644
index 0000000..8f601d2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/hull/ConvexHullGenerator.java
@@ -0,0 +1,49 @@
+/*
+ * 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.math3.geometry.hull;
+
+import java.util.Collection;
+
+import org.apache.commons.math3.exception.ConvergenceException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.Space;
+
+/**
+ * Interface for convex hull generators.
+ *
+ * @param <S> Type of the {@link Space}
+ * @param <P> Type of the {@link Point}
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Convex_hull">Convex Hull (Wikipedia)</a>
+ * @see <a href="http://mathworld.wolfram.com/ConvexHull.html">Convex Hull (MathWorld)</a>
+ *
+ * @since 3.3
+ */
+public interface ConvexHullGenerator<S extends Space, P extends Point<S>> {
+
+    /**
+     * Builds the convex hull from the set of input points.
+     *
+     * @param points the set of input points
+     * @return the convex hull
+     * @throws NullArgumentException if the input collection is {@code null}
+     * @throws ConvergenceException if generator fails to generate a convex hull for
+     * the given set of input points
+     */
+    ConvexHull<S, P> generate(Collection<P> points) throws NullArgumentException, ConvergenceException;
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/hull/package-info.java b/src/main/java/org/apache/commons/math3/geometry/hull/package-info.java
new file mode 100644
index 0000000..2246682
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/hull/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * <p>
+ * This package provides interfaces and classes related to the convex hull problem.
+ * </p>
+ *
+ */
+package org.apache.commons.math3.geometry.hull;
diff --git a/src/main/java/org/apache/commons/math3/geometry/package-info.java b/src/main/java/org/apache/commons/math3/geometry/package-info.java
new file mode 100644
index 0000000..6c39cff
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+/**
+ * This package is the top level package for geometry. It provides only a few interfaces related to
+ * vectorial/affine spaces that are implemented in sub-packages.
+ */
+package org.apache.commons.math3.geometry;
diff --git a/src/main/java/org/apache/commons/math3/geometry/partitioning/AbstractRegion.java b/src/main/java/org/apache/commons/math3/geometry/partitioning/AbstractRegion.java
new file mode 100644
index 0000000..d901ab4
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/partitioning/AbstractRegion.java
@@ -0,0 +1,540 @@
+/*
+ * 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.math3.geometry.partitioning;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.TreeSet;
+
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.Space;
+import org.apache.commons.math3.geometry.Vector;
+
+/** Abstract class for all regions, independently of geometry type or dimension.
+
+ * @param <S> Type of the space.
+ * @param <T> Type of the sub-space.
+
+ * @since 3.0
+ */
+public abstract class AbstractRegion<S extends Space, T extends Space> implements Region<S> {
+
+    /** Inside/Outside BSP tree. */
+    private BSPTree<S> tree;
+
+    /** Tolerance below which points are considered to belong to hyperplanes. */
+    private final double tolerance;
+
+    /** Size of the instance. */
+    private double size;
+
+    /** Barycenter. */
+    private Point<S> barycenter;
+
+    /** Build a region representing the whole space.
+     * @param tolerance tolerance below which points are considered identical.
+     */
+    protected AbstractRegion(final double tolerance) {
+        this.tree      = new BSPTree<S>(Boolean.TRUE);
+        this.tolerance = tolerance;
+    }
+
+    /** Build a region from an inside/outside BSP tree.
+     * <p>The leaf nodes of the BSP tree <em>must</em> have a
+     * {@code Boolean} attribute representing the inside status of
+     * the corresponding cell (true for inside cells, false for outside
+     * cells). In order to avoid building too many small objects, it is
+     * recommended to use the predefined constants
+     * {@code Boolean.TRUE} and {@code Boolean.FALSE}. The
+     * tree also <em>must</em> have either null internal nodes or
+     * internal nodes representing the boundary as specified in the
+     * {@link #getTree getTree} method).</p>
+     * @param tree inside/outside BSP tree representing the region
+     * @param tolerance tolerance below which points are considered identical.
+     */
+    protected AbstractRegion(final BSPTree<S> tree, final double tolerance) {
+        this.tree      = tree;
+        this.tolerance = tolerance;
+    }
+
+    /** Build a Region from a Boundary REPresentation (B-rep).
+     * <p>The boundary is provided as a collection of {@link
+     * SubHyperplane sub-hyperplanes}. Each sub-hyperplane has the
+     * interior part of the region on its minus side and the exterior on
+     * its plus side.</p>
+     * <p>The boundary elements can be in any order, and can form
+     * several non-connected sets (like for example polygons with holes
+     * or a set of disjoints polyhedrons considered as a whole). In
+     * fact, the elements do not even need to be connected together
+     * (their topological connections are not used here). However, if the
+     * boundary does not really separate an inside open from an outside
+     * open (open having here its topological meaning), then subsequent
+     * calls to the {@link #checkPoint(Point) checkPoint} method will not be
+     * meaningful anymore.</p>
+     * <p>If the boundary is empty, the region will represent the whole
+     * space.</p>
+     * @param boundary collection of boundary elements, as a
+     * collection of {@link SubHyperplane SubHyperplane} objects
+     * @param tolerance tolerance below which points are considered identical.
+     */
+    protected AbstractRegion(final Collection<SubHyperplane<S>> boundary, final double tolerance) {
+
+        this.tolerance = tolerance;
+
+        if (boundary.size() == 0) {
+
+            // the tree represents the whole space
+            tree = new BSPTree<S>(Boolean.TRUE);
+
+        } else {
+
+            // sort the boundary elements in decreasing size order
+            // (we don't want equal size elements to be removed, so
+            // we use a trick to fool the TreeSet)
+            final TreeSet<SubHyperplane<S>> ordered = new TreeSet<SubHyperplane<S>>(new Comparator<SubHyperplane<S>>() {
+                /** {@inheritDoc} */
+                public int compare(final SubHyperplane<S> o1, final SubHyperplane<S> o2) {
+                    final double size1 = o1.getSize();
+                    final double size2 = o2.getSize();
+                    return (size2 < size1) ? -1 : ((o1 == o2) ? 0 : +1);
+                }
+            });
+            ordered.addAll(boundary);
+
+            // build the tree top-down
+            tree = new BSPTree<S>();
+            insertCuts(tree, ordered);
+
+            // set up the inside/outside flags
+            tree.visit(new BSPTreeVisitor<S>() {
+
+                /** {@inheritDoc} */
+                public Order visitOrder(final BSPTree<S> node) {
+                    return Order.PLUS_SUB_MINUS;
+                }
+
+                /** {@inheritDoc} */
+                public void visitInternalNode(final BSPTree<S> node) {
+                }
+
+                /** {@inheritDoc} */
+                public void visitLeafNode(final BSPTree<S> node) {
+                    if (node.getParent() == null || node == node.getParent().getMinus()) {
+                        node.setAttribute(Boolean.TRUE);
+                    } else {
+                        node.setAttribute(Boolean.FALSE);
+                    }
+                }
+            });
+
+        }
+
+    }
+
+    /** Build a convex region from an array of bounding hyperplanes.
+     * @param hyperplanes array of bounding hyperplanes (if null, an
+     * empty region will be built)
+     * @param tolerance tolerance below which points are considered identical.
+     */
+    public AbstractRegion(final Hyperplane<S>[] hyperplanes, final double tolerance) {
+        this.tolerance = tolerance;
+        if ((hyperplanes == null) || (hyperplanes.length == 0)) {
+            tree = new BSPTree<S>(Boolean.FALSE);
+        } else {
+
+            // use the first hyperplane to build the right class
+            tree = hyperplanes[0].wholeSpace().getTree(false);
+
+            // chop off parts of the space
+            BSPTree<S> node = tree;
+            node.setAttribute(Boolean.TRUE);
+            for (final Hyperplane<S> hyperplane : hyperplanes) {
+                if (node.insertCut(hyperplane)) {
+                    node.setAttribute(null);
+                    node.getPlus().setAttribute(Boolean.FALSE);
+                    node = node.getMinus();
+                    node.setAttribute(Boolean.TRUE);
+                }
+            }
+
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    public abstract AbstractRegion<S, T> buildNew(BSPTree<S> newTree);
+
+    /** Get the tolerance below which points are considered to belong to hyperplanes.
+     * @return tolerance below which points are considered to belong to hyperplanes
+     */
+    public double getTolerance() {
+        return tolerance;
+    }
+
+    /** Recursively build a tree by inserting cut sub-hyperplanes.
+     * @param node current tree node (it is a leaf node at the beginning
+     * of the call)
+     * @param boundary collection of edges belonging to the cell defined
+     * by the node
+     */
+    private void insertCuts(final BSPTree<S> node, final Collection<SubHyperplane<S>> boundary) {
+
+        final Iterator<SubHyperplane<S>> iterator = boundary.iterator();
+
+        // build the current level
+        Hyperplane<S> inserted = null;
+        while ((inserted == null) && iterator.hasNext()) {
+            inserted = iterator.next().getHyperplane();
+            if (!node.insertCut(inserted.copySelf())) {
+                inserted = null;
+            }
+        }
+
+        if (!iterator.hasNext()) {
+            return;
+        }
+
+        // distribute the remaining edges in the two sub-trees
+        final ArrayList<SubHyperplane<S>> plusList  = new ArrayList<SubHyperplane<S>>();
+        final ArrayList<SubHyperplane<S>> minusList = new ArrayList<SubHyperplane<S>>();
+        while (iterator.hasNext()) {
+            final SubHyperplane<S> other = iterator.next();
+            final SubHyperplane.SplitSubHyperplane<S> split = other.split(inserted);
+            switch (split.getSide()) {
+            case PLUS:
+                plusList.add(other);
+                break;
+            case MINUS:
+                minusList.add(other);
+                break;
+            case BOTH:
+                plusList.add(split.getPlus());
+                minusList.add(split.getMinus());
+                break;
+            default:
+                // ignore the sub-hyperplanes belonging to the cut hyperplane
+            }
+        }
+
+        // recurse through lower levels
+        insertCuts(node.getPlus(),  plusList);
+        insertCuts(node.getMinus(), minusList);
+
+    }
+
+    /** {@inheritDoc} */
+    public AbstractRegion<S, T> copySelf() {
+        return buildNew(tree.copySelf());
+    }
+
+    /** {@inheritDoc} */
+    public boolean isEmpty() {
+        return isEmpty(tree);
+    }
+
+    /** {@inheritDoc} */
+    public boolean isEmpty(final BSPTree<S> node) {
+
+        // we use a recursive function rather than the BSPTreeVisitor
+        // interface because we can stop visiting the tree as soon as we
+        // have found an inside cell
+
+        if (node.getCut() == null) {
+            // if we find an inside node, the region is not empty
+            return !((Boolean) node.getAttribute());
+        }
+
+        // check both sides of the sub-tree
+        return isEmpty(node.getMinus()) && isEmpty(node.getPlus());
+
+    }
+
+    /** {@inheritDoc} */
+    public boolean isFull() {
+        return isFull(tree);
+    }
+
+    /** {@inheritDoc} */
+    public boolean isFull(final BSPTree<S> node) {
+
+        // we use a recursive function rather than the BSPTreeVisitor
+        // interface because we can stop visiting the tree as soon as we
+        // have found an outside cell
+
+        if (node.getCut() == null) {
+            // if we find an outside node, the region does not cover full space
+            return (Boolean) node.getAttribute();
+        }
+
+        // check both sides of the sub-tree
+        return isFull(node.getMinus()) && isFull(node.getPlus());
+
+    }
+
+    /** {@inheritDoc} */
+    public boolean contains(final Region<S> region) {
+        return new RegionFactory<S>().difference(region, this).isEmpty();
+    }
+
+    /** {@inheritDoc}
+     * @since 3.3
+     */
+    public BoundaryProjection<S> projectToBoundary(final Point<S> point) {
+        final BoundaryProjector<S, T> projector = new BoundaryProjector<S, T>(point);
+        getTree(true).visit(projector);
+        return projector.getProjection();
+    }
+
+    /** Check a point with respect to the region.
+     * @param point point to check
+     * @return a code representing the point status: either {@link
+     * Region.Location#INSIDE}, {@link Region.Location#OUTSIDE} or
+     * {@link Region.Location#BOUNDARY}
+     */
+    public Location checkPoint(final Vector<S> point) {
+        return checkPoint((Point<S>) point);
+    }
+
+    /** {@inheritDoc} */
+    public Location checkPoint(final Point<S> point) {
+        return checkPoint(tree, point);
+    }
+
+    /** Check a point with respect to the region starting at a given node.
+     * @param node root node of the region
+     * @param point point to check
+     * @return a code representing the point status: either {@link
+     * Region.Location#INSIDE INSIDE}, {@link Region.Location#OUTSIDE
+     * OUTSIDE} or {@link Region.Location#BOUNDARY BOUNDARY}
+     */
+    protected Location checkPoint(final BSPTree<S> node, final Vector<S> point) {
+        return checkPoint(node, (Point<S>) point);
+    }
+
+    /** Check a point with respect to the region starting at a given node.
+     * @param node root node of the region
+     * @param point point to check
+     * @return a code representing the point status: either {@link
+     * Region.Location#INSIDE INSIDE}, {@link Region.Location#OUTSIDE
+     * OUTSIDE} or {@link Region.Location#BOUNDARY BOUNDARY}
+     */
+    protected Location checkPoint(final BSPTree<S> node, final Point<S> point) {
+        final BSPTree<S> cell = node.getCell(point, tolerance);
+        if (cell.getCut() == null) {
+            // the point is in the interior of a cell, just check the attribute
+            return ((Boolean) cell.getAttribute()) ? Location.INSIDE : Location.OUTSIDE;
+        }
+
+        // the point is on a cut-sub-hyperplane, is it on a boundary ?
+        final Location minusCode = checkPoint(cell.getMinus(), point);
+        final Location plusCode  = checkPoint(cell.getPlus(),  point);
+        return (minusCode == plusCode) ? minusCode : Location.BOUNDARY;
+
+    }
+
+    /** {@inheritDoc} */
+    public BSPTree<S> getTree(final boolean includeBoundaryAttributes) {
+        if (includeBoundaryAttributes && (tree.getCut() != null) && (tree.getAttribute() == null)) {
+            // compute the boundary attributes
+            tree.visit(new BoundaryBuilder<S>());
+        }
+        return tree;
+    }
+
+    /** {@inheritDoc} */
+    public double getBoundarySize() {
+        final BoundarySizeVisitor<S> visitor = new BoundarySizeVisitor<S>();
+        getTree(true).visit(visitor);
+        return visitor.getSize();
+    }
+
+    /** {@inheritDoc} */
+    public double getSize() {
+        if (barycenter == null) {
+            computeGeometricalProperties();
+        }
+        return size;
+    }
+
+    /** Set the size of the instance.
+     * @param size size of the instance
+     */
+    protected void setSize(final double size) {
+        this.size = size;
+    }
+
+    /** {@inheritDoc} */
+    public Point<S> getBarycenter() {
+        if (barycenter == null) {
+            computeGeometricalProperties();
+        }
+        return barycenter;
+    }
+
+    /** Set the barycenter of the instance.
+     * @param barycenter barycenter of the instance
+     */
+    protected void setBarycenter(final Vector<S> barycenter) {
+        setBarycenter((Point<S>) barycenter);
+    }
+
+    /** Set the barycenter of the instance.
+     * @param barycenter barycenter of the instance
+     */
+    protected void setBarycenter(final Point<S> barycenter) {
+        this.barycenter = barycenter;
+    }
+
+    /** Compute some geometrical properties.
+     * <p>The properties to compute are the barycenter and the size.</p>
+     */
+    protected abstract void computeGeometricalProperties();
+
+    /** {@inheritDoc} */
+    @Deprecated
+    public Side side(final Hyperplane<S> hyperplane) {
+        final InsideFinder<S> finder = new InsideFinder<S>(this);
+        finder.recurseSides(tree, hyperplane.wholeHyperplane());
+        return finder.plusFound() ?
+              (finder.minusFound() ? Side.BOTH  : Side.PLUS) :
+              (finder.minusFound() ? Side.MINUS : Side.HYPER);
+    }
+
+    /** {@inheritDoc} */
+    public SubHyperplane<S> intersection(final SubHyperplane<S> sub) {
+        return recurseIntersection(tree, sub);
+    }
+
+    /** Recursively compute the parts of a sub-hyperplane that are
+     * contained in the region.
+     * @param node current BSP tree node
+     * @param sub sub-hyperplane traversing the region
+     * @return filtered sub-hyperplane
+     */
+    private SubHyperplane<S> recurseIntersection(final BSPTree<S> node, final SubHyperplane<S> sub) {
+
+        if (node.getCut() == null) {
+            return (Boolean) node.getAttribute() ? sub.copySelf() : null;
+        }
+
+        final Hyperplane<S> hyperplane = node.getCut().getHyperplane();
+        final SubHyperplane.SplitSubHyperplane<S> split = sub.split(hyperplane);
+        if (split.getPlus() != null) {
+            if (split.getMinus() != null) {
+                // both sides
+                final SubHyperplane<S> plus  = recurseIntersection(node.getPlus(),  split.getPlus());
+                final SubHyperplane<S> minus = recurseIntersection(node.getMinus(), split.getMinus());
+                if (plus == null) {
+                    return minus;
+                } else if (minus == null) {
+                    return plus;
+                } else {
+                    return plus.reunite(minus);
+                }
+            } else {
+                // only on plus side
+                return recurseIntersection(node.getPlus(), sub);
+            }
+        } else if (split.getMinus() != null) {
+            // only on minus side
+            return recurseIntersection(node.getMinus(), sub);
+        } else {
+            // on hyperplane
+            return recurseIntersection(node.getPlus(),
+                                       recurseIntersection(node.getMinus(), sub));
+        }
+
+    }
+
+    /** Transform a region.
+     * <p>Applying a transform to a region consist in applying the
+     * transform to all the hyperplanes of the underlying BSP tree and
+     * of the boundary (and also to the sub-hyperplanes embedded in
+     * these hyperplanes) and to the barycenter. The instance is not
+     * modified, a new instance is built.</p>
+     * @param transform transform to apply
+     * @return a new region, resulting from the application of the
+     * transform to the instance
+     */
+    public AbstractRegion<S, T> applyTransform(final Transform<S, T> transform) {
+
+        // transform the tree, except for boundary attribute splitters
+        final Map<BSPTree<S>, BSPTree<S>> map = new HashMap<BSPTree<S>, BSPTree<S>>();
+        final BSPTree<S> transformedTree = recurseTransform(getTree(false), transform, map);
+
+        // set up the boundary attributes splitters
+        for (final Map.Entry<BSPTree<S>, BSPTree<S>> entry : map.entrySet()) {
+            if (entry.getKey().getCut() != null) {
+                @SuppressWarnings("unchecked")
+                BoundaryAttribute<S> original = (BoundaryAttribute<S>) entry.getKey().getAttribute();
+                if (original != null) {
+                    @SuppressWarnings("unchecked")
+                    BoundaryAttribute<S> transformed = (BoundaryAttribute<S>) entry.getValue().getAttribute();
+                    for (final BSPTree<S> splitter : original.getSplitters()) {
+                        transformed.getSplitters().add(map.get(splitter));
+                    }
+                }
+            }
+        }
+
+        return buildNew(transformedTree);
+
+    }
+
+    /** Recursively transform an inside/outside BSP-tree.
+     * @param node current BSP tree node
+     * @param transform transform to apply
+     * @param map transformed nodes map
+     * @return a new tree
+     */
+    @SuppressWarnings("unchecked")
+    private BSPTree<S> recurseTransform(final BSPTree<S> node, final Transform<S, T> transform,
+                                        final Map<BSPTree<S>, BSPTree<S>> map) {
+
+        final BSPTree<S> transformedNode;
+        if (node.getCut() == null) {
+            transformedNode = new BSPTree<S>(node.getAttribute());
+        } else {
+
+            final SubHyperplane<S>  sub = node.getCut();
+            final SubHyperplane<S> tSub = ((AbstractSubHyperplane<S, T>) sub).applyTransform(transform);
+            BoundaryAttribute<S> attribute = (BoundaryAttribute<S>) node.getAttribute();
+            if (attribute != null) {
+                final SubHyperplane<S> tPO = (attribute.getPlusOutside() == null) ?
+                    null : ((AbstractSubHyperplane<S, T>) attribute.getPlusOutside()).applyTransform(transform);
+                final SubHyperplane<S> tPI = (attribute.getPlusInside()  == null) ?
+                    null  : ((AbstractSubHyperplane<S, T>) attribute.getPlusInside()).applyTransform(transform);
+                // we start with an empty list of splitters, it will be filled in out of recursion
+                attribute = new BoundaryAttribute<S>(tPO, tPI, new NodesSet<S>());
+            }
+
+            transformedNode = new BSPTree<S>(tSub,
+                                             recurseTransform(node.getPlus(),  transform, map),
+                                             recurseTransform(node.getMinus(), transform, map),
+                                             attribute);
+        }
+
+        map.put(node, transformedNode);
+        return transformedNode;
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/partitioning/AbstractSubHyperplane.java b/src/main/java/org/apache/commons/math3/geometry/partitioning/AbstractSubHyperplane.java
new file mode 100644
index 0000000..396b795
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/partitioning/AbstractSubHyperplane.java
@@ -0,0 +1,191 @@
+/*
+ * 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.math3.geometry.partitioning;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.math3.geometry.Space;
+
+/** This class implements the dimension-independent parts of {@link SubHyperplane}.
+
+ * <p>sub-hyperplanes are obtained when parts of an {@link
+ * Hyperplane hyperplane} are chopped off by other hyperplanes that
+ * intersect it. The remaining part is a convex region. Such objects
+ * appear in {@link BSPTree BSP trees} as the intersection of a cut
+ * hyperplane with the convex region which it splits, the chopping
+ * hyperplanes are the cut hyperplanes closer to the tree root.</p>
+
+ * @param <S> Type of the embedding space.
+ * @param <T> Type of the embedded sub-space.
+
+ * @since 3.0
+ */
+public abstract class AbstractSubHyperplane<S extends Space, T extends Space>
+    implements SubHyperplane<S> {
+
+    /** Underlying hyperplane. */
+    private final Hyperplane<S> hyperplane;
+
+    /** Remaining region of the hyperplane. */
+    private final Region<T> remainingRegion;
+
+    /** Build a sub-hyperplane from an hyperplane and a region.
+     * @param hyperplane underlying hyperplane
+     * @param remainingRegion remaining region of the hyperplane
+     */
+    protected AbstractSubHyperplane(final Hyperplane<S> hyperplane,
+                                    final Region<T> remainingRegion) {
+        this.hyperplane      = hyperplane;
+        this.remainingRegion = remainingRegion;
+    }
+
+    /** Build a sub-hyperplane from an hyperplane and a region.
+     * @param hyper underlying hyperplane
+     * @param remaining remaining region of the hyperplane
+     * @return a new sub-hyperplane
+     */
+    protected abstract AbstractSubHyperplane<S, T> buildNew(final Hyperplane<S> hyper,
+                                                            final Region<T> remaining);
+
+    /** {@inheritDoc} */
+    public AbstractSubHyperplane<S, T> copySelf() {
+        return buildNew(hyperplane.copySelf(), remainingRegion);
+    }
+
+    /** Get the underlying hyperplane.
+     * @return underlying hyperplane
+     */
+    public Hyperplane<S> getHyperplane() {
+        return hyperplane;
+    }
+
+    /** Get the remaining region of the hyperplane.
+     * <p>The returned region is expressed in the canonical hyperplane
+     * frame and has the hyperplane dimension. For example a chopped
+     * hyperplane in the 3D euclidean is a 2D plane and the
+     * corresponding region is a convex 2D polygon.</p>
+     * @return remaining region of the hyperplane
+     */
+    public Region<T> getRemainingRegion() {
+        return remainingRegion;
+    }
+
+    /** {@inheritDoc} */
+    public double getSize() {
+        return remainingRegion.getSize();
+    }
+
+    /** {@inheritDoc} */
+    public AbstractSubHyperplane<S, T> reunite(final SubHyperplane<S> other) {
+        @SuppressWarnings("unchecked")
+        AbstractSubHyperplane<S, T> o = (AbstractSubHyperplane<S, T>) other;
+        return buildNew(hyperplane,
+                        new RegionFactory<T>().union(remainingRegion, o.remainingRegion));
+    }
+
+    /** Apply a transform to the instance.
+     * <p>The instance must be a (D-1)-dimension sub-hyperplane with
+     * respect to the transform <em>not</em> a (D-2)-dimension
+     * sub-hyperplane the transform knows how to transform by
+     * itself. The transform will consist in transforming first the
+     * hyperplane and then the all region using the various methods
+     * provided by the transform.</p>
+     * @param transform D-dimension transform to apply
+     * @return the transformed instance
+     */
+    public AbstractSubHyperplane<S, T> applyTransform(final Transform<S, T> transform) {
+        final Hyperplane<S> tHyperplane = transform.apply(hyperplane);
+
+        // transform the tree, except for boundary attribute splitters
+        final Map<BSPTree<T>, BSPTree<T>> map = new HashMap<BSPTree<T>, BSPTree<T>>();
+        final BSPTree<T> tTree =
+            recurseTransform(remainingRegion.getTree(false), tHyperplane, transform, map);
+
+        // set up the boundary attributes splitters
+        for (final Map.Entry<BSPTree<T>, BSPTree<T>> entry : map.entrySet()) {
+            if (entry.getKey().getCut() != null) {
+                @SuppressWarnings("unchecked")
+                BoundaryAttribute<T> original = (BoundaryAttribute<T>) entry.getKey().getAttribute();
+                if (original != null) {
+                    @SuppressWarnings("unchecked")
+                    BoundaryAttribute<T> transformed = (BoundaryAttribute<T>) entry.getValue().getAttribute();
+                    for (final BSPTree<T> splitter : original.getSplitters()) {
+                        transformed.getSplitters().add(map.get(splitter));
+                    }
+                }
+            }
+        }
+
+        return buildNew(tHyperplane, remainingRegion.buildNew(tTree));
+
+    }
+
+    /** Recursively transform a BSP-tree from a sub-hyperplane.
+     * @param node current BSP tree node
+     * @param transformed image of the instance hyperplane by the transform
+     * @param transform transform to apply
+     * @param map transformed nodes map
+     * @return a new tree
+     */
+    private BSPTree<T> recurseTransform(final BSPTree<T> node,
+                                        final Hyperplane<S> transformed,
+                                        final Transform<S, T> transform,
+                                        final Map<BSPTree<T>, BSPTree<T>> map) {
+
+        final BSPTree<T> transformedNode;
+        if (node.getCut() == null) {
+            transformedNode = new BSPTree<T>(node.getAttribute());
+        } else {
+
+            @SuppressWarnings("unchecked")
+            BoundaryAttribute<T> attribute = (BoundaryAttribute<T>) node.getAttribute();
+            if (attribute != null) {
+                final SubHyperplane<T> tPO = (attribute.getPlusOutside() == null) ?
+                    null : transform.apply(attribute.getPlusOutside(), hyperplane, transformed);
+                final SubHyperplane<T> tPI = (attribute.getPlusInside() == null) ?
+                    null : transform.apply(attribute.getPlusInside(), hyperplane, transformed);
+                // we start with an empty list of splitters, it will be filled in out of recursion
+                attribute = new BoundaryAttribute<T>(tPO, tPI, new NodesSet<T>());
+            }
+
+            transformedNode = new BSPTree<T>(transform.apply(node.getCut(), hyperplane, transformed),
+                    recurseTransform(node.getPlus(),  transformed, transform, map),
+                    recurseTransform(node.getMinus(), transformed, transform, map),
+                    attribute);
+        }
+
+        map.put(node, transformedNode);
+        return transformedNode;
+
+    }
+
+    /** {@inheritDoc} */
+    @Deprecated
+    public Side side(Hyperplane<S> hyper) {
+        return split(hyper).getSide();
+    }
+
+    /** {@inheritDoc} */
+    public abstract SplitSubHyperplane<S> split(Hyperplane<S> hyper);
+
+    /** {@inheritDoc} */
+    public boolean isEmpty() {
+        return remainingRegion.isEmpty();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/partitioning/BSPTree.java b/src/main/java/org/apache/commons/math3/geometry/partitioning/BSPTree.java
new file mode 100644
index 0000000..1f1a6ea
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/partitioning/BSPTree.java
@@ -0,0 +1,821 @@
+/*
+ * 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.math3.geometry.partitioning;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.exception.MathInternalError;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.Space;
+import org.apache.commons.math3.geometry.Vector;
+import org.apache.commons.math3.util.FastMath;
+
+/** This class represent a Binary Space Partition tree.
+
+ * <p>BSP trees are an efficient way to represent space partitions and
+ * to associate attributes with each cell. Each node in a BSP tree
+ * represents a convex region which is partitioned in two convex
+ * sub-regions at each side of a cut hyperplane. The root tree
+ * contains the complete space.</p>
+
+ * <p>The main use of such partitions is to use a boolean attribute to
+ * define an inside/outside property, hence representing arbitrary
+ * polytopes (line segments in 1D, polygons in 2D and polyhedrons in
+ * 3D) and to operate on them.</p>
+
+ * <p>Another example would be to represent Voronoi tesselations, the
+ * attribute of each cell holding the defining point of the cell.</p>
+
+ * <p>The application-defined attributes are shared among copied
+ * instances and propagated to split parts. These attributes are not
+ * used by the BSP-tree algorithms themselves, so the application can
+ * use them for any purpose. Since the tree visiting method holds
+ * internal and leaf nodes differently, it is possible to use
+ * different classes for internal nodes attributes and leaf nodes
+ * attributes. This should be used with care, though, because if the
+ * tree is modified in any way after attributes have been set, some
+ * internal nodes may become leaf nodes and some leaf nodes may become
+ * internal nodes.</p>
+
+ * <p>One of the main sources for the development of this package was
+ * Bruce Naylor, John Amanatides and William Thibault paper <a
+ * href="http://www.cs.yorku.ca/~amana/research/bsptSetOp.pdf">Merging
+ * BSP Trees Yields Polyhedral Set Operations</a> Proc. Siggraph '90,
+ * Computer Graphics 24(4), August 1990, pp 115-124, published by the
+ * Association for Computing Machinery (ACM).</p>
+
+ * @param <S> Type of the space.
+
+ * @since 3.0
+ */
+public class BSPTree<S extends Space> {
+
+    /** Cut sub-hyperplane. */
+    private SubHyperplane<S> cut;
+
+    /** Tree at the plus side of the cut hyperplane. */
+    private BSPTree<S> plus;
+
+    /** Tree at the minus side of the cut hyperplane. */
+    private BSPTree<S> minus;
+
+    /** Parent tree. */
+    private BSPTree<S> parent;
+
+    /** Application-defined attribute. */
+    private Object attribute;
+
+    /** Build a tree having only one root cell representing the whole space.
+     */
+    public BSPTree() {
+        cut       = null;
+        plus      = null;
+        minus     = null;
+        parent    = null;
+        attribute = null;
+    }
+
+    /** Build a tree having only one root cell representing the whole space.
+     * @param attribute attribute of the tree (may be null)
+     */
+    public BSPTree(final Object attribute) {
+        cut    = null;
+        plus   = null;
+        minus  = null;
+        parent = null;
+        this.attribute = attribute;
+    }
+
+    /** Build a BSPTree from its underlying elements.
+     * <p>This method does <em>not</em> perform any verification on
+     * consistency of its arguments, it should therefore be used only
+     * when then caller knows what it is doing.</p>
+     * <p>This method is mainly useful to build trees
+     * bottom-up. Building trees top-down is realized with the help of
+     * method {@link #insertCut insertCut}.</p>
+     * @param cut cut sub-hyperplane for the tree
+     * @param plus plus side sub-tree
+     * @param minus minus side sub-tree
+     * @param attribute attribute associated with the node (may be null)
+     * @see #insertCut
+     */
+    public BSPTree(final SubHyperplane<S> cut, final BSPTree<S> plus, final BSPTree<S> minus,
+                   final Object attribute) {
+        this.cut       = cut;
+        this.plus      = plus;
+        this.minus     = minus;
+        this.parent    = null;
+        this.attribute = attribute;
+        plus.parent    = this;
+        minus.parent   = this;
+    }
+
+    /** Insert a cut sub-hyperplane in a node.
+     * <p>The sub-tree starting at this node will be completely
+     * overwritten. The new cut sub-hyperplane will be built from the
+     * intersection of the provided hyperplane with the cell. If the
+     * hyperplane does intersect the cell, the cell will have two
+     * children cells with {@code null} attributes on each side of
+     * the inserted cut sub-hyperplane. If the hyperplane does not
+     * intersect the cell then <em>no</em> cut hyperplane will be
+     * inserted and the cell will be changed to a leaf cell. The
+     * attribute of the node is never changed.</p>
+     * <p>This method is mainly useful when called on leaf nodes
+     * (i.e. nodes for which {@link #getCut getCut} returns
+     * {@code null}), in this case it provides a way to build a
+     * tree top-down (whereas the {@link #BSPTree(SubHyperplane,
+     * BSPTree, BSPTree, Object) 4 arguments constructor} is devoted to
+     * build trees bottom-up).</p>
+     * @param hyperplane hyperplane to insert, it will be chopped in
+     * order to fit in the cell defined by the parent nodes of the
+     * instance
+     * @return true if a cut sub-hyperplane has been inserted (i.e. if
+     * the cell now has two leaf child nodes)
+     * @see #BSPTree(SubHyperplane, BSPTree, BSPTree, Object)
+     */
+    public boolean insertCut(final Hyperplane<S> hyperplane) {
+
+        if (cut != null) {
+            plus.parent  = null;
+            minus.parent = null;
+        }
+
+        final SubHyperplane<S> chopped = fitToCell(hyperplane.wholeHyperplane());
+        if (chopped == null || chopped.isEmpty()) {
+            cut          = null;
+            plus         = null;
+            minus        = null;
+            return false;
+        }
+
+        cut          = chopped;
+        plus         = new BSPTree<S>();
+        plus.parent  = this;
+        minus        = new BSPTree<S>();
+        minus.parent = this;
+        return true;
+
+    }
+
+    /** Copy the instance.
+     * <p>The instance created is completely independent of the original
+     * one. A deep copy is used, none of the underlying objects are
+     * shared (except for the nodes attributes and immutable
+     * objects).</p>
+     * @return a new tree, copy of the instance
+     */
+    public BSPTree<S> copySelf() {
+
+        if (cut == null) {
+            return new BSPTree<S>(attribute);
+        }
+
+        return new BSPTree<S>(cut.copySelf(), plus.copySelf(), minus.copySelf(),
+                           attribute);
+
+    }
+
+    /** Get the cut sub-hyperplane.
+     * @return cut sub-hyperplane, null if this is a leaf tree
+     */
+    public SubHyperplane<S> getCut() {
+        return cut;
+    }
+
+    /** Get the tree on the plus side of the cut hyperplane.
+     * @return tree on the plus side of the cut hyperplane, null if this
+     * is a leaf tree
+     */
+    public BSPTree<S> getPlus() {
+        return plus;
+    }
+
+    /** Get the tree on the minus side of the cut hyperplane.
+     * @return tree on the minus side of the cut hyperplane, null if this
+     * is a leaf tree
+     */
+    public BSPTree<S> getMinus() {
+        return minus;
+    }
+
+    /** Get the parent node.
+     * @return parent node, null if the node has no parents
+     */
+    public BSPTree<S> getParent() {
+        return parent;
+    }
+
+    /** Associate an attribute with the instance.
+     * @param attribute attribute to associate with the node
+     * @see #getAttribute
+     */
+    public void setAttribute(final Object attribute) {
+        this.attribute = attribute;
+    }
+
+    /** Get the attribute associated with the instance.
+     * @return attribute associated with the node or null if no
+     * attribute has been explicitly set using the {@link #setAttribute
+     * setAttribute} method
+     * @see #setAttribute
+     */
+    public Object getAttribute() {
+        return attribute;
+    }
+
+    /** Visit the BSP tree nodes.
+     * @param visitor object visiting the tree nodes
+     */
+    public void visit(final BSPTreeVisitor<S> visitor) {
+        if (cut == null) {
+            visitor.visitLeafNode(this);
+        } else {
+            switch (visitor.visitOrder(this)) {
+            case PLUS_MINUS_SUB:
+                plus.visit(visitor);
+                minus.visit(visitor);
+                visitor.visitInternalNode(this);
+                break;
+            case PLUS_SUB_MINUS:
+                plus.visit(visitor);
+                visitor.visitInternalNode(this);
+                minus.visit(visitor);
+                break;
+            case MINUS_PLUS_SUB:
+                minus.visit(visitor);
+                plus.visit(visitor);
+                visitor.visitInternalNode(this);
+                break;
+            case MINUS_SUB_PLUS:
+                minus.visit(visitor);
+                visitor.visitInternalNode(this);
+                plus.visit(visitor);
+                break;
+            case SUB_PLUS_MINUS:
+                visitor.visitInternalNode(this);
+                plus.visit(visitor);
+                minus.visit(visitor);
+                break;
+            case SUB_MINUS_PLUS:
+                visitor.visitInternalNode(this);
+                minus.visit(visitor);
+                plus.visit(visitor);
+                break;
+            default:
+                throw new MathInternalError();
+            }
+
+        }
+    }
+
+    /** Fit a sub-hyperplane inside the cell defined by the instance.
+     * <p>Fitting is done by chopping off the parts of the
+     * sub-hyperplane that lie outside of the cell using the
+     * cut-hyperplanes of the parent nodes of the instance.</p>
+     * @param sub sub-hyperplane to fit
+     * @return a new sub-hyperplane, guaranteed to have no part outside
+     * of the instance cell
+     */
+    private SubHyperplane<S> fitToCell(final SubHyperplane<S> sub) {
+        SubHyperplane<S> s = sub;
+        for (BSPTree<S> tree = this; tree.parent != null && s != null; tree = tree.parent) {
+            if (tree == tree.parent.plus) {
+                s = s.split(tree.parent.cut.getHyperplane()).getPlus();
+            } else {
+                s = s.split(tree.parent.cut.getHyperplane()).getMinus();
+            }
+        }
+        return s;
+    }
+
+    /** Get the cell to which a point belongs.
+     * <p>If the returned cell is a leaf node the points belongs to the
+     * interior of the node, if the cell is an internal node the points
+     * belongs to the node cut sub-hyperplane.</p>
+     * @param point point to check
+     * @return the tree cell to which the point belongs
+     * @deprecated as of 3.3, replaced with {@link #getCell(Point, double)}
+     */
+    @Deprecated
+    public BSPTree<S> getCell(final Vector<S> point) {
+        return getCell((Point<S>) point, 1.0e-10);
+    }
+
+    /** Get the cell to which a point belongs.
+     * <p>If the returned cell is a leaf node the points belongs to the
+     * interior of the node, if the cell is an internal node the points
+     * belongs to the node cut sub-hyperplane.</p>
+     * @param point point to check
+     * @param tolerance tolerance below which points close to a cut hyperplane
+     * are considered to belong to the hyperplane itself
+     * @return the tree cell to which the point belongs
+     */
+    public BSPTree<S> getCell(final Point<S> point, final double tolerance) {
+
+        if (cut == null) {
+            return this;
+        }
+
+        // position of the point with respect to the cut hyperplane
+        final double offset = cut.getHyperplane().getOffset(point);
+
+        if (FastMath.abs(offset) < tolerance) {
+            return this;
+        } else if (offset <= 0) {
+            // point is on the minus side of the cut hyperplane
+            return minus.getCell(point, tolerance);
+        } else {
+            // point is on the plus side of the cut hyperplane
+            return plus.getCell(point, tolerance);
+        }
+
+    }
+
+    /** Get the cells whose cut sub-hyperplanes are close to the point.
+     * @param point point to check
+     * @param maxOffset offset below which a cut sub-hyperplane is considered
+     * close to the point (in absolute value)
+     * @return close cells (may be empty if all cut sub-hyperplanes are farther
+     * than maxOffset from the point)
+     */
+    public List<BSPTree<S>> getCloseCuts(final Point<S> point, final double maxOffset) {
+        final List<BSPTree<S>> close = new ArrayList<BSPTree<S>>();
+        recurseCloseCuts(point, maxOffset, close);
+        return close;
+    }
+
+    /** Get the cells whose cut sub-hyperplanes are close to the point.
+     * @param point point to check
+     * @param maxOffset offset below which a cut sub-hyperplane is considered
+     * close to the point (in absolute value)
+     * @param close list to fill
+     */
+    private void recurseCloseCuts(final Point<S> point, final double maxOffset,
+                                  final List<BSPTree<S>> close) {
+        if (cut != null) {
+
+            // position of the point with respect to the cut hyperplane
+            final double offset = cut.getHyperplane().getOffset(point);
+
+            if (offset < -maxOffset) {
+                // point is on the minus side of the cut hyperplane
+                minus.recurseCloseCuts(point, maxOffset, close);
+            } else if (offset > maxOffset) {
+                // point is on the plus side of the cut hyperplane
+                plus.recurseCloseCuts(point, maxOffset, close);
+            } else {
+                // point is close to the cut hyperplane
+                close.add(this);
+                minus.recurseCloseCuts(point, maxOffset, close);
+                plus.recurseCloseCuts(point, maxOffset, close);
+            }
+
+        }
+    }
+
+    /** Perform condensation on a tree.
+     * <p>The condensation operation is not recursive, it must be called
+     * explicitly from leaves to root.</p>
+     */
+    private void condense() {
+        if ((cut != null) && (plus.cut == null) && (minus.cut == null) &&
+            (((plus.attribute == null) && (minus.attribute == null)) ||
+             ((plus.attribute != null) && plus.attribute.equals(minus.attribute)))) {
+            attribute = (plus.attribute == null) ? minus.attribute : plus.attribute;
+            cut       = null;
+            plus      = null;
+            minus     = null;
+        }
+    }
+
+    /** Merge a BSP tree with the instance.
+     * <p>All trees are modified (parts of them are reused in the new
+     * tree), it is the responsibility of the caller to ensure a copy
+     * has been done before if any of the former tree should be
+     * preserved, <em>no</em> such copy is done here!</p>
+     * <p>The algorithm used here is directly derived from the one
+     * described in the Naylor, Amanatides and Thibault paper (section
+     * III, Binary Partitioning of a BSP Tree).</p>
+     * @param tree other tree to merge with the instance (will be
+     * <em>unusable</em> after the operation, as well as the
+     * instance itself)
+     * @param leafMerger object implementing the final merging phase
+     * (this is where the semantic of the operation occurs, generally
+     * depending on the attribute of the leaf node)
+     * @return a new tree, result of <code>instance &lt;op&gt;
+     * tree</code>, this value can be ignored if parentTree is not null
+     * since all connections have already been established
+     */
+    public BSPTree<S> merge(final BSPTree<S> tree, final LeafMerger<S> leafMerger) {
+        return merge(tree, leafMerger, null, false);
+    }
+
+    /** Merge a BSP tree with the instance.
+     * @param tree other tree to merge with the instance (will be
+     * <em>unusable</em> after the operation, as well as the
+     * instance itself)
+     * @param leafMerger object implementing the final merging phase
+     * (this is where the semantic of the operation occurs, generally
+     * depending on the attribute of the leaf node)
+     * @param parentTree parent tree to connect to (may be null)
+     * @param isPlusChild if true and if parentTree is not null, the
+     * resulting tree should be the plus child of its parent, ignored if
+     * parentTree is null
+     * @return a new tree, result of <code>instance &lt;op&gt;
+     * tree</code>, this value can be ignored if parentTree is not null
+     * since all connections have already been established
+     */
+    private BSPTree<S> merge(final BSPTree<S> tree, final LeafMerger<S> leafMerger,
+                             final BSPTree<S> parentTree, final boolean isPlusChild) {
+        if (cut == null) {
+            // cell/tree operation
+            return leafMerger.merge(this, tree, parentTree, isPlusChild, true);
+        } else if (tree.cut == null) {
+            // tree/cell operation
+            return leafMerger.merge(tree, this, parentTree, isPlusChild, false);
+        } else {
+            // tree/tree operation
+            final BSPTree<S> merged = tree.split(cut);
+            if (parentTree != null) {
+                merged.parent = parentTree;
+                if (isPlusChild) {
+                    parentTree.plus = merged;
+                } else {
+                    parentTree.minus = merged;
+                }
+            }
+
+            // merging phase
+            plus.merge(merged.plus, leafMerger, merged, true);
+            minus.merge(merged.minus, leafMerger, merged, false);
+            merged.condense();
+            if (merged.cut != null) {
+                merged.cut = merged.fitToCell(merged.cut.getHyperplane().wholeHyperplane());
+            }
+
+            return merged;
+
+        }
+    }
+
+    /** This interface gather the merging operations between a BSP tree
+     * leaf and another BSP tree.
+     * <p>As explained in Bruce Naylor, John Amanatides and William
+     * Thibault paper <a
+     * href="http://www.cs.yorku.ca/~amana/research/bsptSetOp.pdf">Merging
+     * BSP Trees Yields Polyhedral Set Operations</a>,
+     * the operations on {@link BSPTree BSP trees} can be expressed as a
+     * generic recursive merging operation where only the final part,
+     * when one of the operand is a leaf, is specific to the real
+     * operation semantics. For example, a tree representing a region
+     * using a boolean attribute to identify inside cells and outside
+     * cells would use four different objects to implement the final
+     * merging phase of the four set operations union, intersection,
+     * difference and symmetric difference (exclusive or).</p>
+     * @param <S> Type of the space.
+     */
+    public interface LeafMerger<S extends Space> {
+
+        /** Merge a leaf node and a tree node.
+         * <p>This method is called at the end of a recursive merging
+         * resulting from a {@code tree1.merge(tree2, leafMerger)}
+         * call, when one of the sub-trees involved is a leaf (i.e. when
+         * its cut-hyperplane is null). This is the only place where the
+         * precise semantics of the operation are required. For all upper
+         * level nodes in the tree, the merging operation is only a
+         * generic partitioning algorithm.</p>
+         * <p>Since the final operation may be non-commutative, it is
+         * important to know if the leaf node comes from the instance tree
+         * ({@code tree1}) or the argument tree
+         * ({@code tree2}). The third argument of the method is
+         * devoted to this. It can be ignored for commutative
+         * operations.</p>
+         * <p>The {@link BSPTree#insertInTree BSPTree.insertInTree} method
+         * may be useful to implement this method.</p>
+         * @param leaf leaf node (its cut hyperplane is guaranteed to be
+         * null)
+         * @param tree tree node (its cut hyperplane may be null or not)
+         * @param parentTree parent tree to connect to (may be null)
+         * @param isPlusChild if true and if parentTree is not null, the
+         * resulting tree should be the plus child of its parent, ignored if
+         * parentTree is null
+         * @param leafFromInstance if true, the leaf node comes from the
+         * instance tree ({@code tree1}) and the tree node comes from
+         * the argument tree ({@code tree2})
+         * @return the BSP tree resulting from the merging (may be one of
+         * the arguments)
+         */
+        BSPTree<S> merge(BSPTree<S> leaf, BSPTree<S> tree, BSPTree<S> parentTree,
+                         boolean isPlusChild, boolean leafFromInstance);
+
+    }
+
+    /** This interface handles the corner cases when an internal node cut sub-hyperplane vanishes.
+     * <p>
+     * Such cases happens for example when a cut sub-hyperplane is inserted into
+     * another tree (during a merge operation), and is split in several parts,
+     * some of which becomes smaller than the tolerance. The corresponding node
+     * as then no cut sub-hyperplane anymore, but does have children. This interface
+     * specifies how to handle this situation.
+     * setting
+     * </p>
+     * @param <S> Type of the space.
+     * @since 3.4
+     */
+    public interface VanishingCutHandler<S extends Space> {
+
+        /** Fix a node with both vanished cut and children.
+         * @param node node to fix
+         * @return fixed node
+         */
+        BSPTree<S> fixNode(BSPTree<S> node);
+
+    }
+
+    /** Split a BSP tree by an external sub-hyperplane.
+     * <p>Split a tree in two halves, on each side of the
+     * sub-hyperplane. The instance is not modified.</p>
+     * <p>The tree returned is not upward-consistent: despite all of its
+     * sub-trees cut sub-hyperplanes (including its own cut
+     * sub-hyperplane) are bounded to the current cell, it is <em>not</em>
+     * attached to any parent tree yet. This tree is intended to be
+     * later inserted into an higher level tree.</p>
+     * <p>The algorithm used here is the one given in Naylor, Amanatides
+     * and Thibault paper (section III, Binary Partitioning of a BSP
+     * Tree).</p>
+     * @param sub partitioning sub-hyperplane, must be already clipped
+     * to the convex region represented by the instance, will be used as
+     * the cut sub-hyperplane of the returned tree
+     * @return a tree having the specified sub-hyperplane as its cut
+     * sub-hyperplane, the two parts of the split instance as its two
+     * sub-trees and a null parent
+     */
+    public BSPTree<S> split(final SubHyperplane<S> sub) {
+
+        if (cut == null) {
+            return new BSPTree<S>(sub, copySelf(), new BSPTree<S>(attribute), null);
+        }
+
+        final Hyperplane<S> cHyperplane = cut.getHyperplane();
+        final Hyperplane<S> sHyperplane = sub.getHyperplane();
+        final SubHyperplane.SplitSubHyperplane<S> subParts = sub.split(cHyperplane);
+        switch (subParts.getSide()) {
+        case PLUS :
+        { // the partitioning sub-hyperplane is entirely in the plus sub-tree
+            final BSPTree<S> split = plus.split(sub);
+            if (cut.split(sHyperplane).getSide() == Side.PLUS) {
+                split.plus =
+                    new BSPTree<S>(cut.copySelf(), split.plus, minus.copySelf(), attribute);
+                split.plus.condense();
+                split.plus.parent = split;
+            } else {
+                split.minus =
+                    new BSPTree<S>(cut.copySelf(), split.minus, minus.copySelf(), attribute);
+                split.minus.condense();
+                split.minus.parent = split;
+            }
+            return split;
+        }
+        case MINUS :
+        { // the partitioning sub-hyperplane is entirely in the minus sub-tree
+            final BSPTree<S> split = minus.split(sub);
+            if (cut.split(sHyperplane).getSide() == Side.PLUS) {
+                split.plus =
+                    new BSPTree<S>(cut.copySelf(), plus.copySelf(), split.plus, attribute);
+                split.plus.condense();
+                split.plus.parent = split;
+            } else {
+                split.minus =
+                    new BSPTree<S>(cut.copySelf(), plus.copySelf(), split.minus, attribute);
+                split.minus.condense();
+                split.minus.parent = split;
+            }
+            return split;
+        }
+        case BOTH :
+        {
+            final SubHyperplane.SplitSubHyperplane<S> cutParts = cut.split(sHyperplane);
+            final BSPTree<S> split =
+                new BSPTree<S>(sub, plus.split(subParts.getPlus()), minus.split(subParts.getMinus()),
+                               null);
+            split.plus.cut          = cutParts.getPlus();
+            split.minus.cut         = cutParts.getMinus();
+            final BSPTree<S> tmp    = split.plus.minus;
+            split.plus.minus        = split.minus.plus;
+            split.plus.minus.parent = split.plus;
+            split.minus.plus        = tmp;
+            split.minus.plus.parent = split.minus;
+            split.plus.condense();
+            split.minus.condense();
+            return split;
+        }
+        default :
+            return cHyperplane.sameOrientationAs(sHyperplane) ?
+                   new BSPTree<S>(sub, plus.copySelf(),  minus.copySelf(), attribute) :
+                   new BSPTree<S>(sub, minus.copySelf(), plus.copySelf(),  attribute);
+        }
+
+    }
+
+    /** Insert the instance into another tree.
+     * <p>The instance itself is modified so its former parent should
+     * not be used anymore.</p>
+     * @param parentTree parent tree to connect to (may be null)
+     * @param isPlusChild if true and if parentTree is not null, the
+     * resulting tree should be the plus child of its parent, ignored if
+     * parentTree is null
+     * @see LeafMerger
+     * @deprecated as of 3.4, replaced with {@link #insertInTree(BSPTree, boolean, VanishingCutHandler)}
+     */
+    @Deprecated
+    public void insertInTree(final BSPTree<S> parentTree, final boolean isPlusChild) {
+        insertInTree(parentTree, isPlusChild, new VanishingCutHandler<S>() {
+            /** {@inheritDoc} */
+            public BSPTree<S> fixNode(BSPTree<S> node) {
+                // the cut should not be null
+                throw new MathIllegalStateException(LocalizedFormats.NULL_NOT_ALLOWED);
+            }
+        });
+    }
+
+    /** Insert the instance into another tree.
+     * <p>The instance itself is modified so its former parent should
+     * not be used anymore.</p>
+     * @param parentTree parent tree to connect to (may be null)
+     * @param isPlusChild if true and if parentTree is not null, the
+     * resulting tree should be the plus child of its parent, ignored if
+     * parentTree is null
+     * @param vanishingHandler handler to use for handling very rare corner
+     * cases of vanishing cut sub-hyperplanes in internal nodes during merging
+     * @see LeafMerger
+     * @since 3.4
+     */
+    public void insertInTree(final BSPTree<S> parentTree, final boolean isPlusChild,
+                             final VanishingCutHandler<S> vanishingHandler) {
+
+        // set up parent/child links
+        parent = parentTree;
+        if (parentTree != null) {
+            if (isPlusChild) {
+                parentTree.plus = this;
+            } else {
+                parentTree.minus = this;
+            }
+        }
+
+        // make sure the inserted tree lies in the cell defined by its parent nodes
+        if (cut != null) {
+
+            // explore the parent nodes from here towards tree root
+            for (BSPTree<S> tree = this; tree.parent != null; tree = tree.parent) {
+
+                // this is an hyperplane of some parent node
+                final Hyperplane<S> hyperplane = tree.parent.cut.getHyperplane();
+
+                // chop off the parts of the inserted tree that extend
+                // on the wrong side of this parent hyperplane
+                if (tree == tree.parent.plus) {
+                    cut = cut.split(hyperplane).getPlus();
+                    plus.chopOffMinus(hyperplane, vanishingHandler);
+                    minus.chopOffMinus(hyperplane, vanishingHandler);
+                } else {
+                    cut = cut.split(hyperplane).getMinus();
+                    plus.chopOffPlus(hyperplane, vanishingHandler);
+                    minus.chopOffPlus(hyperplane, vanishingHandler);
+                }
+
+                if (cut == null) {
+                    // the cut sub-hyperplane has vanished
+                    final BSPTree<S> fixed = vanishingHandler.fixNode(this);
+                    cut       = fixed.cut;
+                    plus      = fixed.plus;
+                    minus     = fixed.minus;
+                    attribute = fixed.attribute;
+                    if (cut == null) {
+                        break;
+                    }
+                }
+
+            }
+
+            // since we may have drop some parts of the inserted tree,
+            // perform a condensation pass to keep the tree structure simple
+            condense();
+
+        }
+
+    }
+
+    /** Prune a tree around a cell.
+     * <p>
+     * This method can be used to extract a convex cell from a tree.
+     * The original cell may either be a leaf node or an internal node.
+     * If it is an internal node, it's subtree will be ignored (i.e. the
+     * extracted cell will be a leaf node in all cases). The original
+     * tree to which the original cell belongs is not touched at all,
+     * a new independent tree will be built.
+     * </p>
+     * @param cellAttribute attribute to set for the leaf node
+     * corresponding to the initial instance cell
+     * @param otherLeafsAttributes attribute to set for the other leaf
+     * nodes
+     * @param internalAttributes attribute to set for the internal nodes
+     * @return a new tree (the original tree is left untouched) containing
+     * a single branch with the cell as a leaf node, and other leaf nodes
+     * as the remnants of the pruned branches
+     * @since 3.3
+     */
+    public BSPTree<S> pruneAroundConvexCell(final Object cellAttribute,
+                                            final Object otherLeafsAttributes,
+                                            final Object internalAttributes) {
+
+        // build the current cell leaf
+        BSPTree<S> tree = new BSPTree<S>(cellAttribute);
+
+        // build the pruned tree bottom-up
+        for (BSPTree<S> current = this; current.parent != null; current = current.parent) {
+            final SubHyperplane<S> parentCut = current.parent.cut.copySelf();
+            final BSPTree<S>       sibling   = new BSPTree<S>(otherLeafsAttributes);
+            if (current == current.parent.plus) {
+                tree = new BSPTree<S>(parentCut, tree, sibling, internalAttributes);
+            } else {
+                tree = new BSPTree<S>(parentCut, sibling, tree, internalAttributes);
+            }
+        }
+
+        return tree;
+
+    }
+
+    /** Chop off parts of the tree.
+     * <p>The instance is modified in place, all the parts that are on
+     * the minus side of the chopping hyperplane are discarded, only the
+     * parts on the plus side remain.</p>
+     * @param hyperplane chopping hyperplane
+     * @param vanishingHandler handler to use for handling very rare corner
+     * cases of vanishing cut sub-hyperplanes in internal nodes during merging
+     */
+    private void chopOffMinus(final Hyperplane<S> hyperplane, final VanishingCutHandler<S> vanishingHandler) {
+        if (cut != null) {
+
+            cut = cut.split(hyperplane).getPlus();
+            plus.chopOffMinus(hyperplane, vanishingHandler);
+            minus.chopOffMinus(hyperplane, vanishingHandler);
+
+            if (cut == null) {
+                // the cut sub-hyperplane has vanished
+                final BSPTree<S> fixed = vanishingHandler.fixNode(this);
+                cut       = fixed.cut;
+                plus      = fixed.plus;
+                minus     = fixed.minus;
+                attribute = fixed.attribute;
+            }
+
+        }
+    }
+
+    /** Chop off parts of the tree.
+     * <p>The instance is modified in place, all the parts that are on
+     * the plus side of the chopping hyperplane are discarded, only the
+     * parts on the minus side remain.</p>
+     * @param hyperplane chopping hyperplane
+     * @param vanishingHandler handler to use for handling very rare corner
+     * cases of vanishing cut sub-hyperplanes in internal nodes during merging
+     */
+    private void chopOffPlus(final Hyperplane<S> hyperplane, final VanishingCutHandler<S> vanishingHandler) {
+        if (cut != null) {
+
+            cut = cut.split(hyperplane).getMinus();
+            plus.chopOffPlus(hyperplane, vanishingHandler);
+            minus.chopOffPlus(hyperplane, vanishingHandler);
+
+            if (cut == null) {
+                // the cut sub-hyperplane has vanished
+                final BSPTree<S> fixed = vanishingHandler.fixNode(this);
+                cut       = fixed.cut;
+                plus      = fixed.plus;
+                minus     = fixed.minus;
+                attribute = fixed.attribute;
+            }
+
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/partitioning/BSPTreeVisitor.java b/src/main/java/org/apache/commons/math3/geometry/partitioning/BSPTreeVisitor.java
new file mode 100644
index 0000000..3d09939
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/partitioning/BSPTreeVisitor.java
@@ -0,0 +1,114 @@
+/*
+ * 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.math3.geometry.partitioning;
+
+import org.apache.commons.math3.geometry.Space;
+
+/** This interface is used to visit {@link BSPTree BSP tree} nodes.
+
+ * <p>Navigation through {@link BSPTree BSP trees} can be done using
+ * two different point of views:</p>
+ * <ul>
+ *   <li>
+ *     the first one is in a node-oriented way using the {@link
+ *     BSPTree#getPlus}, {@link BSPTree#getMinus} and {@link
+ *     BSPTree#getParent} methods. Terminal nodes without associated
+ *     {@link SubHyperplane sub-hyperplanes} can be visited this way,
+ *     there is no constraint in the visit order, and it is possible
+ *     to visit either all nodes or only a subset of the nodes
+ *   </li>
+ *   <li>
+ *     the second one is in a sub-hyperplane-oriented way using
+ *     classes implementing this interface which obeys the visitor
+ *     design pattern. The visit order is provided by the visitor as
+ *     each node is first encountered. Each node is visited exactly
+ *     once.
+ *   </li>
+ * </ul>
+
+ * @param <S> Type of the space.
+
+ * @see BSPTree
+ * @see SubHyperplane
+
+ * @since 3.0
+ */
+public interface BSPTreeVisitor<S extends Space> {
+
+    /** Enumerate for visit order with respect to plus sub-tree, minus sub-tree and cut sub-hyperplane. */
+    enum Order {
+        /** Indicator for visit order plus sub-tree, then minus sub-tree,
+         * and last cut sub-hyperplane.
+         */
+        PLUS_MINUS_SUB,
+
+        /** Indicator for visit order plus sub-tree, then cut sub-hyperplane,
+         * and last minus sub-tree.
+         */
+        PLUS_SUB_MINUS,
+
+        /** Indicator for visit order minus sub-tree, then plus sub-tree,
+         * and last cut sub-hyperplane.
+         */
+        MINUS_PLUS_SUB,
+
+        /** Indicator for visit order minus sub-tree, then cut sub-hyperplane,
+         * and last plus sub-tree.
+         */
+        MINUS_SUB_PLUS,
+
+        /** Indicator for visit order cut sub-hyperplane, then plus sub-tree,
+         * and last minus sub-tree.
+         */
+        SUB_PLUS_MINUS,
+
+        /** Indicator for visit order cut sub-hyperplane, then minus sub-tree,
+         * and last plus sub-tree.
+         */
+        SUB_MINUS_PLUS;
+    }
+
+    /** Determine the visit order for this node.
+     * <p>Before attempting to visit an internal node, this method is
+     * called to determine the desired ordering of the visit. It is
+     * guaranteed that this method will be called before {@link
+     * #visitInternalNode visitInternalNode} for a given node, it will be
+     * called exactly once for each internal node.</p>
+     * @param node BSP node guaranteed to have a non null cut sub-hyperplane
+     * @return desired visit order, must be one of
+     * {@link Order#PLUS_MINUS_SUB}, {@link Order#PLUS_SUB_MINUS},
+     * {@link Order#MINUS_PLUS_SUB}, {@link Order#MINUS_SUB_PLUS},
+     * {@link Order#SUB_PLUS_MINUS}, {@link Order#SUB_MINUS_PLUS}
+     */
+    Order visitOrder(BSPTree<S> node);
+
+    /** Visit a BSP tree node node having a non-null sub-hyperplane.
+     * <p>It is guaranteed that this method will be called after {@link
+     * #visitOrder visitOrder} has been called for a given node,
+     * it wil be called exactly once for each internal node.</p>
+     * @param node BSP node guaranteed to have a non null cut sub-hyperplane
+     * @see #visitLeafNode
+     */
+    void visitInternalNode(BSPTree<S> node);
+
+    /** Visit a leaf BSP tree node node having a null sub-hyperplane.
+     * @param node leaf BSP node having a null sub-hyperplane
+     * @see #visitInternalNode
+     */
+    void visitLeafNode(BSPTree<S> node);
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/partitioning/BoundaryAttribute.java b/src/main/java/org/apache/commons/math3/geometry/partitioning/BoundaryAttribute.java
new file mode 100644
index 0000000..dad884c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/partitioning/BoundaryAttribute.java
@@ -0,0 +1,116 @@
+/*
+ * 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.math3.geometry.partitioning;
+
+import org.apache.commons.math3.geometry.Space;
+
+/** Class holding boundary attributes.
+ * <p>This class is used for the attributes associated with the
+ * nodes of region boundary shell trees returned by the {@link
+ * Region#getTree(boolean) Region.getTree(includeBoundaryAttributes)}
+ * when the boolean {@code includeBoundaryAttributes} parameter is
+ * set to {@code true}. It contains the parts of the node cut
+ * sub-hyperplane that belong to the boundary.</p>
+ * <p>This class is a simple placeholder, it does not provide any
+ * processing methods.</p>
+ * @param <S> Type of the space.
+ * @see Region#getTree
+ * @since 3.0
+ */
+public class BoundaryAttribute<S extends Space> {
+
+    /** Part of the node cut sub-hyperplane that belongs to the
+     * boundary and has the outside of the region on the plus side of
+     * its underlying hyperplane (may be null).
+     */
+    private final SubHyperplane<S> plusOutside;
+
+    /** Part of the node cut sub-hyperplane that belongs to the
+     * boundary and has the inside of the region on the plus side of
+     * its underlying hyperplane (may be null).
+     */
+    private final SubHyperplane<S> plusInside;
+
+    /** Sub-hyperplanes that were used to split the boundary part. */
+    private final NodesSet<S> splitters;
+
+    /** Simple constructor.
+     * @param plusOutside part of the node cut sub-hyperplane that
+     * belongs to the boundary and has the outside of the region on
+     * the plus side of its underlying hyperplane (may be null)
+     * @param plusInside part of the node cut sub-hyperplane that
+     * belongs to the boundary and has the inside of the region on the
+     * plus side of its underlying hyperplane (may be null)
+     * @deprecated as of 3.4, the constructor has been replaced by a new one
+     * which is not public anymore, as it is intended to be used only by
+     * {@link BoundaryBuilder}
+     */
+    @Deprecated
+    public BoundaryAttribute(final SubHyperplane<S> plusOutside,
+                             final SubHyperplane<S> plusInside) {
+        this(plusOutside, plusInside, null);
+    }
+
+    /** Simple constructor.
+     * @param plusOutside part of the node cut sub-hyperplane that
+     * belongs to the boundary and has the outside of the region on
+     * the plus side of its underlying hyperplane (may be null)
+     * @param plusInside part of the node cut sub-hyperplane that
+     * belongs to the boundary and has the inside of the region on the
+     * plus side of its underlying hyperplane (may be null)
+     * @param splitters sub-hyperplanes that were used to
+     * split the boundary part (may be null)
+     * @since 3.4
+     */
+    BoundaryAttribute(final SubHyperplane<S> plusOutside,
+                      final SubHyperplane<S> plusInside,
+                      final NodesSet<S> splitters) {
+        this.plusOutside = plusOutside;
+        this.plusInside  = plusInside;
+        this.splitters   = splitters;
+    }
+
+    /** Get the part of the node cut sub-hyperplane that belongs to the
+     * boundary and has the outside of the region on the plus side of
+     * its underlying hyperplane.
+     * @return part of the node cut sub-hyperplane that belongs to the
+     * boundary and has the outside of the region on the plus side of
+     * its underlying hyperplane
+     */
+    public SubHyperplane<S> getPlusOutside() {
+        return plusOutside;
+    }
+
+    /** Get the part of the node cut sub-hyperplane that belongs to the
+     * boundary and has the inside of the region on the plus side of
+     * its underlying hyperplane.
+     * @return part of the node cut sub-hyperplane that belongs to the
+     * boundary and has the inside of the region on the plus side of
+     * its underlying hyperplane
+     */
+    public SubHyperplane<S> getPlusInside() {
+        return plusInside;
+    }
+
+    /** Get the sub-hyperplanes that were used to split the boundary part.
+     * @return sub-hyperplanes that were used to split the boundary part
+     */
+    public NodesSet<S> getSplitters() {
+        return splitters;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/partitioning/BoundaryBuilder.java b/src/main/java/org/apache/commons/math3/geometry/partitioning/BoundaryBuilder.java
new file mode 100644
index 0000000..cea4de3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/partitioning/BoundaryBuilder.java
@@ -0,0 +1,95 @@
+/*
+ * 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.math3.geometry.partitioning;
+
+import org.apache.commons.math3.geometry.Space;
+
+/** Visitor building boundary shell tree.
+ * <p>
+ * The boundary shell is represented as {@link BoundaryAttribute boundary attributes}
+ * at each internal node.
+ * </p>
+ * @param <S> Type of the space.
+ * @since 3.4
+ */
+class BoundaryBuilder<S extends Space> implements BSPTreeVisitor<S> {
+
+    /** {@inheritDoc} */
+    public Order visitOrder(BSPTree<S> node) {
+        return Order.PLUS_MINUS_SUB;
+    }
+
+    /** {@inheritDoc} */
+    public void visitInternalNode(BSPTree<S> node) {
+
+        SubHyperplane<S> plusOutside = null;
+        SubHyperplane<S> plusInside  = null;
+        NodesSet<S>      splitters   = null;
+
+        // characterize the cut sub-hyperplane,
+        // first with respect to the plus sub-tree
+        final Characterization<S> plusChar = new Characterization<S>(node.getPlus(), node.getCut().copySelf());
+
+        if (plusChar.touchOutside()) {
+            // plusChar.outsideTouching() corresponds to a subset of the cut sub-hyperplane
+            // known to have outside cells on its plus side, we want to check if parts
+            // of this subset do have inside cells on their minus side
+            final Characterization<S> minusChar = new Characterization<S>(node.getMinus(), plusChar.outsideTouching());
+            if (minusChar.touchInside()) {
+                // this part belongs to the boundary,
+                // it has the outside on its plus side and the inside on its minus side
+                plusOutside = minusChar.insideTouching();
+                splitters = new NodesSet<S>();
+                splitters.addAll(minusChar.getInsideSplitters());
+                splitters.addAll(plusChar.getOutsideSplitters());
+            }
+        }
+
+        if (plusChar.touchInside()) {
+            // plusChar.insideTouching() corresponds to a subset of the cut sub-hyperplane
+            // known to have inside cells on its plus side, we want to check if parts
+            // of this subset do have outside cells on their minus side
+            final Characterization<S> minusChar = new Characterization<S>(node.getMinus(), plusChar.insideTouching());
+            if (minusChar.touchOutside()) {
+                // this part belongs to the boundary,
+                // it has the inside on its plus side and the outside on its minus side
+                plusInside = minusChar.outsideTouching();
+                if (splitters == null) {
+                    splitters = new NodesSet<S>();
+                }
+                splitters.addAll(minusChar.getOutsideSplitters());
+                splitters.addAll(plusChar.getInsideSplitters());
+            }
+        }
+
+        if (splitters != null) {
+            // the parent nodes are natural splitters for boundary sub-hyperplanes
+            for (BSPTree<S> up = node.getParent(); up != null; up = up.getParent()) {
+                splitters.add(up);
+            }
+        }
+
+        // set the boundary attribute at non-leaf nodes
+        node.setAttribute(new BoundaryAttribute<S>(plusOutside, plusInside, splitters));
+
+    }
+
+    /** {@inheritDoc} */
+    public void visitLeafNode(BSPTree<S> node) {
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/partitioning/BoundaryProjection.java b/src/main/java/org/apache/commons/math3/geometry/partitioning/BoundaryProjection.java
new file mode 100644
index 0000000..03526e4
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/partitioning/BoundaryProjection.java
@@ -0,0 +1,83 @@
+/*
+ * 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.math3.geometry.partitioning;
+
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.Space;
+
+/** Class holding the result of point projection on region boundary.
+ * <p>This class is a simple placeholder, it does not provide any
+ * processing methods.</p>
+ * <p>Instances of this class are guaranteed to be immutable</p>
+ * @param <S> Type of the space.
+ * @since 3.3
+ * @see AbstractRegion#projectToBoundary(Point)
+ */
+public class BoundaryProjection<S extends Space> {
+
+    /** Original point. */
+    private final Point<S> original;
+
+    /** Projected point. */
+    private final Point<S> projected;
+
+    /** Offset of the point with respect to the boundary it is projected on. */
+    private final double offset;
+
+    /** Constructor from raw elements.
+     * @param original original point
+     * @param projected projected point
+     * @param offset offset of the point with respect to the boundary it is projected on
+     */
+    public BoundaryProjection(final Point<S> original, final Point<S> projected, final double offset) {
+        this.original  = original;
+        this.projected = projected;
+        this.offset    = offset;
+    }
+
+    /** Get the original point.
+     * @return original point
+     */
+    public Point<S> getOriginal() {
+        return original;
+    }
+
+    /** Projected point.
+     * @return projected point, or null if there are no boundary
+     */
+    public Point<S> getProjected() {
+        return projected;
+    }
+
+    /** Offset of the point with respect to the boundary it is projected on.
+     * <p>
+     * The offset with respect to the boundary is negative if the {@link
+     * #getOriginal() original point} is inside the region, and positive otherwise.
+     * </p>
+     * <p>
+     * If there are no boundary, the value is set to either {@code
+     * Double.POSITIVE_INFINITY} if the region is empty (i.e. all points are
+     * outside of the region) or {@code Double.NEGATIVE_INFINITY} if the region
+     * covers the whole space (i.e. all points are inside of the region).
+     * </p>
+     * @return offset of the point with respect to the boundary it is projected on
+     */
+    public double getOffset() {
+        return offset;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/partitioning/BoundaryProjector.java b/src/main/java/org/apache/commons/math3/geometry/partitioning/BoundaryProjector.java
new file mode 100644
index 0000000..486bbf1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/partitioning/BoundaryProjector.java
@@ -0,0 +1,200 @@
+/*
+ * 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.math3.geometry.partitioning;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.Space;
+import org.apache.commons.math3.geometry.partitioning.Region.Location;
+import org.apache.commons.math3.util.FastMath;
+
+/** Local tree visitor to compute projection on boundary.
+ * @param <S> Type of the space.
+ * @param <T> Type of the sub-space.
+ * @since 3.3
+ */
+class BoundaryProjector<S extends Space, T extends Space> implements BSPTreeVisitor<S> {
+
+    /** Original point. */
+    private final Point<S> original;
+
+    /** Current best projected point. */
+    private Point<S> projected;
+
+    /** Leaf node closest to the test point. */
+    private BSPTree<S> leaf;
+
+    /** Current offset. */
+    private double offset;
+
+    /** Simple constructor.
+     * @param original original point
+     */
+    BoundaryProjector(final Point<S> original) {
+        this.original  = original;
+        this.projected = null;
+        this.leaf      = null;
+        this.offset    = Double.POSITIVE_INFINITY;
+    }
+
+    /** {@inheritDoc} */
+    public Order visitOrder(final BSPTree<S> node) {
+        // we want to visit the tree so that the first encountered
+        // leaf is the one closest to the test point
+        if (node.getCut().getHyperplane().getOffset(original) <= 0) {
+            return Order.MINUS_SUB_PLUS;
+        } else {
+            return Order.PLUS_SUB_MINUS;
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void visitInternalNode(final BSPTree<S> node) {
+
+        // project the point on the cut sub-hyperplane
+        final Hyperplane<S> hyperplane = node.getCut().getHyperplane();
+        final double signedOffset = hyperplane.getOffset(original);
+        if (FastMath.abs(signedOffset) < offset) {
+
+            // project point
+            final Point<S> regular = hyperplane.project(original);
+
+            // get boundary parts
+            final List<Region<T>> boundaryParts = boundaryRegions(node);
+
+            // check if regular projection really belongs to the boundary
+            boolean regularFound = false;
+            for (final Region<T> part : boundaryParts) {
+                if (!regularFound && belongsToPart(regular, hyperplane, part)) {
+                    // the projected point lies in the boundary
+                    projected    = regular;
+                    offset       = FastMath.abs(signedOffset);
+                    regularFound = true;
+                }
+            }
+
+            if (!regularFound) {
+                // the regular projected point is not on boundary,
+                // so we have to check further if a singular point
+                // (i.e. a vertex in 2D case) is a possible projection
+                for (final Region<T> part : boundaryParts) {
+                    final Point<S> spI = singularProjection(regular, hyperplane, part);
+                    if (spI != null) {
+                        final double distance = original.distance(spI);
+                        if (distance < offset) {
+                            projected = spI;
+                            offset    = distance;
+                        }
+                    }
+                }
+
+            }
+
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    public void visitLeafNode(final BSPTree<S> node) {
+        if (leaf == null) {
+            // this is the first leaf we visit,
+            // it is the closest one to the original point
+            leaf = node;
+        }
+    }
+
+    /** Get the projection.
+     * @return projection
+     */
+    public BoundaryProjection<S> getProjection() {
+
+        // fix offset sign
+        offset = FastMath.copySign(offset, (Boolean) leaf.getAttribute() ? -1 : +1);
+
+        return new BoundaryProjection<S>(original, projected, offset);
+
+    }
+
+    /** Extract the regions of the boundary on an internal node.
+     * @param node internal node
+     * @return regions in the node sub-hyperplane
+     */
+    private List<Region<T>> boundaryRegions(final BSPTree<S> node) {
+
+        final List<Region<T>> regions = new ArrayList<Region<T>>(2);
+
+        @SuppressWarnings("unchecked")
+        final BoundaryAttribute<S> ba = (BoundaryAttribute<S>) node.getAttribute();
+        addRegion(ba.getPlusInside(),  regions);
+        addRegion(ba.getPlusOutside(), regions);
+
+        return regions;
+
+    }
+
+    /** Add a boundary region to a list.
+     * @param sub sub-hyperplane defining the region
+     * @param list to fill up
+     */
+    private void addRegion(final SubHyperplane<S> sub, final List<Region<T>> list) {
+        if (sub != null) {
+            @SuppressWarnings("unchecked")
+            final Region<T> region = ((AbstractSubHyperplane<S, T>) sub).getRemainingRegion();
+            if (region != null) {
+                list.add(region);
+            }
+        }
+    }
+
+    /** Check if a projected point lies on a boundary part.
+     * @param point projected point to check
+     * @param hyperplane hyperplane into which the point was projected
+     * @param part boundary part
+     * @return true if point lies on the boundary part
+     */
+    private boolean belongsToPart(final Point<S> point, final Hyperplane<S> hyperplane,
+                                  final Region<T> part) {
+
+        // there is a non-null sub-space, we can dive into smaller dimensions
+        @SuppressWarnings("unchecked")
+        final Embedding<S, T> embedding = (Embedding<S, T>) hyperplane;
+        return part.checkPoint(embedding.toSubSpace(point)) != Location.OUTSIDE;
+
+    }
+
+    /** Get the projection to the closest boundary singular point.
+     * @param point projected point to check
+     * @param hyperplane hyperplane into which the point was projected
+     * @param part boundary part
+     * @return projection to a singular point of boundary part (may be null)
+     */
+    private Point<S> singularProjection(final Point<S> point, final Hyperplane<S> hyperplane,
+                                        final Region<T> part) {
+
+        // there is a non-null sub-space, we can dive into smaller dimensions
+        @SuppressWarnings("unchecked")
+        final Embedding<S, T> embedding = (Embedding<S, T>) hyperplane;
+        final BoundaryProjection<T> bp = part.projectToBoundary(embedding.toSubSpace(point));
+
+        // back to initial dimension
+        return (bp.getProjected() == null) ? null : embedding.toSpace(bp.getProjected());
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/partitioning/BoundarySizeVisitor.java b/src/main/java/org/apache/commons/math3/geometry/partitioning/BoundarySizeVisitor.java
new file mode 100644
index 0000000..054838b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/partitioning/BoundarySizeVisitor.java
@@ -0,0 +1,65 @@
+/*
+ * 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.math3.geometry.partitioning;
+
+import org.apache.commons.math3.geometry.Space;
+
+/** Visitor computing the boundary size.
+ * @param <S> Type of the space.
+ * @since 3.0
+ */
+class BoundarySizeVisitor<S extends Space> implements BSPTreeVisitor<S> {
+
+    /** Size of the boundary. */
+    private double boundarySize;
+
+    /** Simple constructor.
+     */
+    BoundarySizeVisitor() {
+        boundarySize = 0;
+    }
+
+    /** {@inheritDoc}*/
+    public Order visitOrder(final BSPTree<S> node) {
+        return Order.MINUS_SUB_PLUS;
+    }
+
+    /** {@inheritDoc}*/
+    public void visitInternalNode(final BSPTree<S> node) {
+        @SuppressWarnings("unchecked")
+        final BoundaryAttribute<S> attribute =
+            (BoundaryAttribute<S>) node.getAttribute();
+        if (attribute.getPlusOutside() != null) {
+            boundarySize += attribute.getPlusOutside().getSize();
+        }
+        if (attribute.getPlusInside() != null) {
+            boundarySize += attribute.getPlusInside().getSize();
+        }
+    }
+
+    /** {@inheritDoc}*/
+    public void visitLeafNode(final BSPTree<S> node) {
+    }
+
+    /** Get the size of the boundary.
+     * @return size of the boundary
+     */
+    public double getSize() {
+        return boundarySize;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/partitioning/Characterization.java b/src/main/java/org/apache/commons/math3/geometry/partitioning/Characterization.java
new file mode 100644
index 0000000..f8ec2f9
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/partitioning/Characterization.java
@@ -0,0 +1,190 @@
+/*
+ * 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.math3.geometry.partitioning;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.math3.exception.MathInternalError;
+import org.apache.commons.math3.geometry.Space;
+
+/** Cut sub-hyperplanes characterization with respect to inside/outside cells.
+ * @see BoundaryBuilder
+ * @param <S> Type of the space.
+ * @since 3.4
+ */
+class Characterization<S extends Space> {
+
+    /** Part of the cut sub-hyperplane that touch outside cells. */
+    private SubHyperplane<S> outsideTouching;
+
+    /** Part of the cut sub-hyperplane that touch inside cells. */
+    private SubHyperplane<S> insideTouching;
+
+    /** Nodes that were used to split the outside touching part. */
+    private final NodesSet<S> outsideSplitters;
+
+    /** Nodes that were used to split the outside touching part. */
+    private final NodesSet<S> insideSplitters;
+
+    /** Simple constructor.
+     * <p>Characterization consists in splitting the specified
+     * sub-hyperplane into several parts lying in inside and outside
+     * cells of the tree. The principle is to compute characterization
+     * twice for each cut sub-hyperplane in the tree, once on the plus
+     * node and once on the minus node. The parts that have the same flag
+     * (inside/inside or outside/outside) do not belong to the boundary
+     * while parts that have different flags (inside/outside or
+     * outside/inside) do belong to the boundary.</p>
+     * @param node current BSP tree node
+     * @param sub sub-hyperplane to characterize
+     */
+    Characterization(final BSPTree<S> node, final SubHyperplane<S> sub) {
+        outsideTouching  = null;
+        insideTouching   = null;
+        outsideSplitters = new NodesSet<S>();
+        insideSplitters  = new NodesSet<S>();
+        characterize(node, sub, new ArrayList<BSPTree<S>>());
+    }
+
+    /** Filter the parts of an hyperplane belonging to the boundary.
+     * <p>The filtering consist in splitting the specified
+     * sub-hyperplane into several parts lying in inside and outside
+     * cells of the tree. The principle is to call this method twice for
+     * each cut sub-hyperplane in the tree, once on the plus node and
+     * once on the minus node. The parts that have the same flag
+     * (inside/inside or outside/outside) do not belong to the boundary
+     * while parts that have different flags (inside/outside or
+     * outside/inside) do belong to the boundary.</p>
+     * @param node current BSP tree node
+     * @param sub sub-hyperplane to characterize
+     * @param splitters nodes that did split the current one
+     */
+    private void characterize(final BSPTree<S> node, final SubHyperplane<S> sub,
+                              final List<BSPTree<S>> splitters) {
+        if (node.getCut() == null) {
+            // we have reached a leaf node
+            final boolean inside = (Boolean) node.getAttribute();
+            if (inside) {
+                addInsideTouching(sub, splitters);
+            } else {
+                addOutsideTouching(sub, splitters);
+            }
+        } else {
+            final Hyperplane<S> hyperplane = node.getCut().getHyperplane();
+            final SubHyperplane.SplitSubHyperplane<S> split = sub.split(hyperplane);
+            switch (split.getSide()) {
+            case PLUS:
+                characterize(node.getPlus(),  sub, splitters);
+                break;
+            case MINUS:
+                characterize(node.getMinus(), sub, splitters);
+                break;
+            case BOTH:
+                splitters.add(node);
+                characterize(node.getPlus(),  split.getPlus(),  splitters);
+                characterize(node.getMinus(), split.getMinus(), splitters);
+                splitters.remove(splitters.size() - 1);
+                break;
+            default:
+                // this should not happen
+                throw new MathInternalError();
+            }
+        }
+    }
+
+    /** Add a part of the cut sub-hyperplane known to touch an outside cell.
+     * @param sub part of the cut sub-hyperplane known to touch an outside cell
+     * @param splitters sub-hyperplanes that did split the current one
+     */
+    private void addOutsideTouching(final SubHyperplane<S> sub,
+                                    final List<BSPTree<S>> splitters) {
+        if (outsideTouching == null) {
+            outsideTouching = sub;
+        } else {
+            outsideTouching = outsideTouching.reunite(sub);
+        }
+        outsideSplitters.addAll(splitters);
+    }
+
+    /** Add a part of the cut sub-hyperplane known to touch an inside cell.
+     * @param sub part of the cut sub-hyperplane known to touch an inside cell
+     * @param splitters sub-hyperplanes that did split the current one
+     */
+    private void addInsideTouching(final SubHyperplane<S> sub,
+                                   final List<BSPTree<S>> splitters) {
+        if (insideTouching == null) {
+            insideTouching = sub;
+        } else {
+            insideTouching = insideTouching.reunite(sub);
+        }
+        insideSplitters.addAll(splitters);
+    }
+
+    /** Check if the cut sub-hyperplane touches outside cells.
+     * @return true if the cut sub-hyperplane touches outside cells
+     */
+    public boolean touchOutside() {
+        return outsideTouching != null && !outsideTouching.isEmpty();
+    }
+
+    /** Get all the parts of the cut sub-hyperplane known to touch outside cells.
+     * @return parts of the cut sub-hyperplane known to touch outside cells
+     * (may be null or empty)
+     */
+    public SubHyperplane<S> outsideTouching() {
+        return outsideTouching;
+    }
+
+    /** Get the nodes that were used to split the outside touching part.
+     * <p>
+     * Splitting nodes are internal nodes (i.e. they have a non-null
+     * cut sub-hyperplane).
+     * </p>
+     * @return nodes that were used to split the outside touching part
+     */
+    public NodesSet<S> getOutsideSplitters() {
+        return outsideSplitters;
+    }
+
+    /** Check if the cut sub-hyperplane touches inside cells.
+     * @return true if the cut sub-hyperplane touches inside cells
+     */
+    public boolean touchInside() {
+        return insideTouching != null && !insideTouching.isEmpty();
+    }
+
+    /** Get all the parts of the cut sub-hyperplane known to touch inside cells.
+     * @return parts of the cut sub-hyperplane known to touch inside cells
+     * (may be null or empty)
+     */
+    public SubHyperplane<S> insideTouching() {
+        return insideTouching;
+    }
+
+    /** Get the nodes that were used to split the inside touching part.
+     * <p>
+     * Splitting nodes are internal nodes (i.e. they have a non-null
+     * cut sub-hyperplane).
+     * </p>
+     * @return nodes that were used to split the inside touching part
+     */
+    public NodesSet<S> getInsideSplitters() {
+        return insideSplitters;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/partitioning/Embedding.java b/src/main/java/org/apache/commons/math3/geometry/partitioning/Embedding.java
new file mode 100644
index 0000000..74e2c00
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/partitioning/Embedding.java
@@ -0,0 +1,68 @@
+/*
+ * 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.math3.geometry.partitioning;
+
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.Space;
+
+/** This interface defines mappers between a space and one of its sub-spaces.
+
+ * <p>Sub-spaces are the lower dimensions subsets of a n-dimensions
+ * space. The (n-1)-dimension sub-spaces are specific sub-spaces known
+ * as {@link Hyperplane hyperplanes}. This interface can be used regardless
+ * of the dimensions differences. As an example, {@link
+ * org.apache.commons.math3.geometry.euclidean.threed.Line Line} in 3D
+ * implements Embedding<{@link
+ * org.apache.commons.math3.geometry.euclidean.threed.Vector3D Vector3D}, {link
+ * org.apache.commons.math3.geometry.euclidean.oned.Vector1D Vector1D>, i.e. it
+ * maps directly dimensions 3 and 1.</p>
+
+ * <p>In the 3D euclidean space, hyperplanes are 2D planes, and the 1D
+ * sub-spaces are lines.</p>
+
+ * <p>
+ * Note that this interface is <em>not</em> intended to be implemented
+ * by Apache Commons Math users, it is only intended to be implemented
+ * within the library itself. New methods may be added even for minor
+ * versions, which breaks compatibility for external implementations.
+ * </p>
+
+ * @param <S> Type of the embedding space.
+ * @param <T> Type of the embedded sub-space.
+
+ * @see Hyperplane
+ * @since 3.0
+ */
+public interface Embedding<S extends Space, T extends Space> {
+
+    /** Transform a space point into a sub-space point.
+     * @param point n-dimension point of the space
+     * @return (n-1)-dimension point of the sub-space corresponding to
+     * the specified space point
+     * @see #toSpace
+     */
+    Point<T> toSubSpace(Point<S> point);
+
+    /** Transform a sub-space point into a space point.
+     * @param point (n-1)-dimension point of the sub-space
+     * @return n-dimension point of the space corresponding to the
+     * specified sub-space point
+     * @see #toSubSpace
+     */
+    Point<S> toSpace(Point<T> point);
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/partitioning/Hyperplane.java b/src/main/java/org/apache/commons/math3/geometry/partitioning/Hyperplane.java
new file mode 100644
index 0000000..f90c3bc
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/partitioning/Hyperplane.java
@@ -0,0 +1,98 @@
+/*
+ * 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.math3.geometry.partitioning;
+
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.Space;
+
+/** This interface represents an hyperplane of a space.
+
+ * <p>The most prominent place where hyperplane appears in space
+ * partitioning is as cutters. Each partitioning node in a {@link
+ * BSPTree BSP tree} has a cut {@link SubHyperplane sub-hyperplane}
+ * which is either an hyperplane or a part of an hyperplane. In an
+ * n-dimensions euclidean space, an hyperplane is an (n-1)-dimensions
+ * hyperplane (for example a traditional plane in the 3D euclidean
+ * space). They can be more exotic objects in specific fields, for
+ * example a circle on the surface of the unit sphere.</p>
+
+ * <p>
+ * Note that this interface is <em>not</em> intended to be implemented
+ * by Apache Commons Math users, it is only intended to be implemented
+ * within the library itself. New methods may be added even for minor
+ * versions, which breaks compatibility for external implementations.
+ * </p>
+
+ * @param <S> Type of the space.
+
+ * @since 3.0
+ */
+public interface Hyperplane<S extends Space> {
+
+    /** Copy the instance.
+     * <p>The instance created is completely independant of the original
+     * one. A deep copy is used, none of the underlying objects are
+     * shared (except for immutable objects).</p>
+     * @return a new hyperplane, copy of the instance
+     */
+    Hyperplane<S> copySelf();
+
+    /** Get the offset (oriented distance) of a point.
+     * <p>The offset is 0 if the point is on the underlying hyperplane,
+     * it is positive if the point is on one particular side of the
+     * hyperplane, and it is negative if the point is on the other side,
+     * according to the hyperplane natural orientation.</p>
+     * @param point point to check
+     * @return offset of the point
+     */
+    double getOffset(Point<S> point);
+
+    /** Project a point to the hyperplane.
+     * @param point point to project
+     * @return projected point
+     * @since 3.3
+     */
+    Point<S> project(Point<S> point);
+
+    /** Get the tolerance below which points are considered to belong to the hyperplane.
+     * @return tolerance below which points are considered to belong to the hyperplane
+     * @since 3.3
+     */
+    double getTolerance();
+
+    /** Check if the instance has the same orientation as another hyperplane.
+     * <p>This method is expected to be called on parallel hyperplanes. The
+     * method should <em>not</em> re-check for parallelism, only for
+     * orientation, typically by testing something like the sign of the
+     * dot-products of normals.</p>
+     * @param other other hyperplane to check against the instance
+     * @return true if the instance and the other hyperplane have
+     * the same orientation
+     */
+    boolean sameOrientationAs(Hyperplane<S> other);
+
+    /** Build a sub-hyperplane covering the whole hyperplane.
+     * @return a sub-hyperplane covering the whole hyperplane
+     */
+    SubHyperplane<S> wholeHyperplane();
+
+    /** Build a region covering the whole space.
+     * @return a region containing the instance
+     */
+    Region<S> wholeSpace();
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/partitioning/InsideFinder.java b/src/main/java/org/apache/commons/math3/geometry/partitioning/InsideFinder.java
new file mode 100644
index 0000000..b1db90a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/partitioning/InsideFinder.java
@@ -0,0 +1,150 @@
+/*
+ * 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.math3.geometry.partitioning;
+
+import org.apache.commons.math3.geometry.Space;
+
+/** Utility class checking if inside nodes can be found
+ * on the plus and minus sides of an hyperplane.
+ * @param <S> Type of the space.
+ * @since 3.4
+ */
+class InsideFinder<S extends Space> {
+
+    /** Region on which to operate. */
+    private final Region<S> region;
+
+    /** Indicator of inside leaf nodes found on the plus side. */
+    private boolean plusFound;
+
+    /** Indicator of inside leaf nodes found on the plus side. */
+    private boolean minusFound;
+
+    /** Simple constructor.
+     * @param region region on which to operate
+     */
+    InsideFinder(final Region<S> region) {
+        this.region = region;
+        plusFound  = false;
+        minusFound = false;
+    }
+
+    /** Search recursively for inside leaf nodes on each side of the given hyperplane.
+
+     * <p>The algorithm used here is directly derived from the one
+     * described in section III (<i>Binary Partitioning of a BSP
+     * Tree</i>) of the Bruce Naylor, John Amanatides and William
+     * Thibault paper <a
+     * href="http://www.cs.yorku.ca/~amana/research/bsptSetOp.pdf">Merging
+     * BSP Trees Yields Polyhedral Set Operations</a> Proc. Siggraph
+     * '90, Computer Graphics 24(4), August 1990, pp 115-124, published
+     * by the Association for Computing Machinery (ACM)..</p>
+
+     * @param node current BSP tree node
+     * @param sub sub-hyperplane
+     */
+    public void recurseSides(final BSPTree<S> node, final SubHyperplane<S> sub) {
+
+        if (node.getCut() == null) {
+            if ((Boolean) node.getAttribute()) {
+                // this is an inside cell expanding across the hyperplane
+                plusFound  = true;
+                minusFound = true;
+            }
+            return;
+        }
+
+        final Hyperplane<S> hyperplane = node.getCut().getHyperplane();
+        final SubHyperplane.SplitSubHyperplane<S> split = sub.split(hyperplane);
+        switch (split.getSide()) {
+        case PLUS :
+            // the sub-hyperplane is entirely in the plus sub-tree
+            if (node.getCut().split(sub.getHyperplane()).getSide() == Side.PLUS) {
+                if (!region.isEmpty(node.getMinus())) {
+                    plusFound  = true;
+                }
+            } else {
+                if (!region.isEmpty(node.getMinus())) {
+                    minusFound = true;
+                }
+            }
+            if (!(plusFound && minusFound)) {
+                recurseSides(node.getPlus(), sub);
+            }
+            break;
+        case MINUS :
+            // the sub-hyperplane is entirely in the minus sub-tree
+            if (node.getCut().split(sub.getHyperplane()).getSide() == Side.PLUS) {
+                if (!region.isEmpty(node.getPlus())) {
+                    plusFound  = true;
+                }
+            } else {
+                if (!region.isEmpty(node.getPlus())) {
+                    minusFound = true;
+                }
+            }
+            if (!(plusFound && minusFound)) {
+                recurseSides(node.getMinus(), sub);
+            }
+            break;
+        case BOTH :
+            // the sub-hyperplane extends in both sub-trees
+
+            // explore first the plus sub-tree
+            recurseSides(node.getPlus(), split.getPlus());
+
+            // if needed, explore the minus sub-tree
+            if (!(plusFound && minusFound)) {
+                recurseSides(node.getMinus(), split.getMinus());
+            }
+            break;
+        default :
+            // the sub-hyperplane and the cut sub-hyperplane share the same hyperplane
+            if (node.getCut().getHyperplane().sameOrientationAs(sub.getHyperplane())) {
+                if ((node.getPlus().getCut() != null) || ((Boolean) node.getPlus().getAttribute())) {
+                    plusFound  = true;
+                }
+                if ((node.getMinus().getCut() != null) || ((Boolean) node.getMinus().getAttribute())) {
+                    minusFound = true;
+                }
+            } else {
+                if ((node.getPlus().getCut() != null) || ((Boolean) node.getPlus().getAttribute())) {
+                    minusFound = true;
+                }
+                if ((node.getMinus().getCut() != null) || ((Boolean) node.getMinus().getAttribute())) {
+                    plusFound  = true;
+                }
+            }
+        }
+
+    }
+
+    /** Check if inside leaf nodes have been found on the plus side.
+     * @return true if inside leaf nodes have been found on the plus side
+     */
+    public boolean plusFound() {
+        return plusFound;
+    }
+
+    /** Check if inside leaf nodes have been found on the minus side.
+     * @return true if inside leaf nodes have been found on the minus side
+     */
+    public boolean minusFound() {
+        return minusFound;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/partitioning/NodesSet.java b/src/main/java/org/apache/commons/math3/geometry/partitioning/NodesSet.java
new file mode 100644
index 0000000..688279a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/partitioning/NodesSet.java
@@ -0,0 +1,72 @@
+/*
+ * 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.math3.geometry.partitioning;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.commons.math3.geometry.Space;
+
+/** Set of {@link BSPTree BSP tree} nodes.
+ * @see BoundaryAttribute
+ * @param <S> Type of the space.
+ * @since 3.4
+ */
+public class NodesSet<S extends Space> implements Iterable<BSPTree<S>> {
+
+    /** List of sub-hyperplanes. */
+    private List<BSPTree<S>> list;
+
+    /** Simple constructor.
+     */
+    public NodesSet() {
+        list = new ArrayList<BSPTree<S>>();
+    }
+
+    /** Add a node if not already known.
+     * @param node node to add
+     */
+    public void add(final BSPTree<S> node) {
+
+        for (final BSPTree<S> existing : list) {
+            if (node == existing) {
+                // the node is already known, don't add it
+                return;
+            }
+        }
+
+        // the node was not known, add it
+        list.add(node);
+
+    }
+
+    /** Add nodes if they are not already known.
+     * @param iterator nodes iterator
+     */
+    public void addAll(final Iterable<BSPTree<S>> iterator) {
+        for (final BSPTree<S> node : iterator) {
+            add(node);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public Iterator<BSPTree<S>> iterator() {
+        return list.iterator();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/partitioning/Region.java b/src/main/java/org/apache/commons/math3/geometry/partitioning/Region.java
new file mode 100644
index 0000000..9ff3946
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/partitioning/Region.java
@@ -0,0 +1,221 @@
+/*
+ * 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.math3.geometry.partitioning;
+
+import org.apache.commons.math3.geometry.Space;
+import org.apache.commons.math3.geometry.Point;
+
+/** This interface represents a region of a space as a partition.
+
+ * <p>Region are subsets of a space, they can be infinite (whole
+ * space, half space, infinite stripe ...) or finite (polygons in 2D,
+ * polyhedrons in 3D ...). Their main characteristic is to separate
+ * points that are considered to be <em>inside</em> the region from
+ * points considered to be <em>outside</em> of it. In between, there
+ * may be points on the <em>boundary</em> of the region.</p>
+
+ * <p>This implementation is limited to regions for which the boundary
+ * is composed of several {@link SubHyperplane sub-hyperplanes},
+ * including regions with no boundary at all: the whole space and the
+ * empty region. They are not necessarily finite and not necessarily
+ * path-connected. They can contain holes.</p>
+
+ * <p>Regions can be combined using the traditional sets operations :
+ * union, intersection, difference and symetric difference (exclusive
+ * or) for the binary operations, complement for the unary
+ * operation.</p>
+
+ * <p>
+ * Note that this interface is <em>not</em> intended to be implemented
+ * by Apache Commons Math users, it is only intended to be implemented
+ * within the library itself. New methods may be added even for minor
+ * versions, which breaks compatibility for external implementations.
+ * </p>
+
+ * @param <S> Type of the space.
+
+ * @since 3.0
+ */
+public interface Region<S extends Space> {
+
+    /** Enumerate for the location of a point with respect to the region. */
+    enum Location {
+        /** Code for points inside the partition. */
+        INSIDE,
+
+        /** Code for points outside of the partition. */
+        OUTSIDE,
+
+        /** Code for points on the partition boundary. */
+        BOUNDARY;
+    }
+
+    /** Build a region using the instance as a prototype.
+     * <p>This method allow to create new instances without knowing
+     * exactly the type of the region. It is an application of the
+     * prototype design pattern.</p>
+     * <p>The leaf nodes of the BSP tree <em>must</em> have a
+     * {@code Boolean} attribute representing the inside status of
+     * the corresponding cell (true for inside cells, false for outside
+     * cells). In order to avoid building too many small objects, it is
+     * recommended to use the predefined constants
+     * {@code Boolean.TRUE} and {@code Boolean.FALSE}. The
+     * tree also <em>must</em> have either null internal nodes or
+     * internal nodes representing the boundary as specified in the
+     * {@link #getTree getTree} method).</p>
+     * @param newTree inside/outside BSP tree representing the new region
+     * @return the built region
+     */
+    Region<S> buildNew(BSPTree<S> newTree);
+
+    /** Copy the instance.
+     * <p>The instance created is completely independant of the original
+     * one. A deep copy is used, none of the underlying objects are
+     * shared (except for the underlying tree {@code Boolean}
+     * attributes and immutable objects).</p>
+     * @return a new region, copy of the instance
+     */
+    Region<S> copySelf();
+
+    /** Check if the instance is empty.
+     * @return true if the instance is empty
+     */
+    boolean isEmpty();
+
+    /** Check if the sub-tree starting at a given node is empty.
+     * @param node root node of the sub-tree (<em>must</em> have {@link
+     * Region Region} tree semantics, i.e. the leaf nodes must have
+     * {@code Boolean} attributes representing an inside/outside
+     * property)
+     * @return true if the sub-tree starting at the given node is empty
+     */
+    boolean isEmpty(final BSPTree<S> node);
+
+    /** Check if the instance covers the full space.
+     * @return true if the instance covers the full space
+     */
+    boolean isFull();
+
+    /** Check if the sub-tree starting at a given node covers the full space.
+     * @param node root node of the sub-tree (<em>must</em> have {@link
+     * Region Region} tree semantics, i.e. the leaf nodes must have
+     * {@code Boolean} attributes representing an inside/outside
+     * property)
+     * @return true if the sub-tree starting at the given node covers the full space
+     */
+    boolean isFull(final BSPTree<S> node);
+
+    /** Check if the instance entirely contains another region.
+     * @param region region to check against the instance
+     * @return true if the instance contains the specified tree
+     */
+    boolean contains(final Region<S> region);
+
+    /** Check a point with respect to the region.
+     * @param point point to check
+     * @return a code representing the point status: either {@link
+     * Location#INSIDE}, {@link Location#OUTSIDE} or {@link Location#BOUNDARY}
+     */
+    Location checkPoint(final Point<S> point);
+
+    /** Project a point on the boundary of the region.
+     * @param point point to check
+     * @return projection of the point on the boundary
+     * @since 3.3
+     */
+    BoundaryProjection<S> projectToBoundary(final Point<S> point);
+
+    /** Get the underlying BSP tree.
+
+     * <p>Regions are represented by an underlying inside/outside BSP
+     * tree whose leaf attributes are {@code Boolean} instances
+     * representing inside leaf cells if the attribute value is
+     * {@code true} and outside leaf cells if the attribute is
+     * {@code false}. These leaf attributes are always present and
+     * guaranteed to be non null.</p>
+
+     * <p>In addition to the leaf attributes, the internal nodes which
+     * correspond to cells split by cut sub-hyperplanes may contain
+     * {@link BoundaryAttribute BoundaryAttribute} objects representing
+     * the parts of the corresponding cut sub-hyperplane that belong to
+     * the boundary. When the boundary attributes have been computed,
+     * all internal nodes are guaranteed to have non-null
+     * attributes, however some {@link BoundaryAttribute
+     * BoundaryAttribute} instances may have their {@link
+     * BoundaryAttribute#getPlusInside() getPlusInside} and {@link
+     * BoundaryAttribute#getPlusOutside() getPlusOutside} methods both
+     * returning null if the corresponding cut sub-hyperplane does not
+     * have any parts belonging to the boundary.</p>
+
+     * <p>Since computing the boundary is not always required and can be
+     * time-consuming for large trees, these internal nodes attributes
+     * are computed using lazy evaluation only when required by setting
+     * the {@code includeBoundaryAttributes} argument to
+     * {@code true}. Once computed, these attributes remain in the
+     * tree, which implies that in this case, further calls to the
+     * method for the same region will always include these attributes
+     * regardless of the value of the
+     * {@code includeBoundaryAttributes} argument.</p>
+
+     * @param includeBoundaryAttributes if true, the boundary attributes
+     * at internal nodes are guaranteed to be included (they may be
+     * included even if the argument is false, if they have already been
+     * computed due to a previous call)
+     * @return underlying BSP tree
+     * @see BoundaryAttribute
+     */
+    BSPTree<S> getTree(final boolean includeBoundaryAttributes);
+
+    /** Get the size of the boundary.
+     * @return the size of the boundary (this is 0 in 1D, a length in
+     * 2D, an area in 3D ...)
+     */
+    double getBoundarySize();
+
+    /** Get the size of the instance.
+     * @return the size of the instance (this is a length in 1D, an area
+     * in 2D, a volume in 3D ...)
+     */
+    double getSize();
+
+    /** Get the barycenter of the instance.
+     * @return an object representing the barycenter
+     */
+    Point<S> getBarycenter();
+
+    /** Compute the relative position of the instance with respect to an
+     * hyperplane.
+     * @param hyperplane reference hyperplane
+     * @return one of {@link Side#PLUS Side.PLUS}, {@link Side#MINUS
+     * Side.MINUS}, {@link Side#BOTH Side.BOTH} or {@link Side#HYPER
+     * Side.HYPER} (the latter result can occur only if the tree
+     * contains only one cut hyperplane)
+     * @deprecated as of 3.6, this method which was only intended for
+     * internal use is not used anymore
+     */
+    @Deprecated
+    Side side(final Hyperplane<S> hyperplane);
+
+    /** Get the parts of a sub-hyperplane that are contained in the region.
+     * <p>The parts of the sub-hyperplane that belong to the boundary are
+     * <em>not</em> included in the resulting parts.</p>
+     * @param sub sub-hyperplane traversing the region
+     * @return filtered sub-hyperplane
+     */
+    SubHyperplane<S> intersection(final SubHyperplane<S> sub);
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/partitioning/RegionFactory.java b/src/main/java/org/apache/commons/math3/geometry/partitioning/RegionFactory.java
new file mode 100644
index 0000000..688ffde
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/partitioning/RegionFactory.java
@@ -0,0 +1,378 @@
+/*
+ * 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.math3.geometry.partitioning;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.Space;
+import org.apache.commons.math3.geometry.partitioning.BSPTree.VanishingCutHandler;
+import org.apache.commons.math3.geometry.partitioning.Region.Location;
+import org.apache.commons.math3.geometry.partitioning.SubHyperplane.SplitSubHyperplane;
+
+/** This class is a factory for {@link Region}.
+
+ * @param <S> Type of the space.
+
+ * @since 3.0
+ */
+public class RegionFactory<S extends Space> {
+
+    /** Visitor removing internal nodes attributes. */
+    private final NodesCleaner nodeCleaner;
+
+    /** Simple constructor.
+     */
+    public RegionFactory() {
+        nodeCleaner = new NodesCleaner();
+    }
+
+    /** Build a convex region from a collection of bounding hyperplanes.
+     * @param hyperplanes collection of bounding hyperplanes
+     * @return a new convex region, or null if the collection is empty
+     */
+    public Region<S> buildConvex(final Hyperplane<S> ... hyperplanes) {
+        if ((hyperplanes == null) || (hyperplanes.length == 0)) {
+            return null;
+        }
+
+        // use the first hyperplane to build the right class
+        final Region<S> region = hyperplanes[0].wholeSpace();
+
+        // chop off parts of the space
+        BSPTree<S> node = region.getTree(false);
+        node.setAttribute(Boolean.TRUE);
+        for (final Hyperplane<S> hyperplane : hyperplanes) {
+            if (node.insertCut(hyperplane)) {
+                node.setAttribute(null);
+                node.getPlus().setAttribute(Boolean.FALSE);
+                node = node.getMinus();
+                node.setAttribute(Boolean.TRUE);
+            } else {
+                // the hyperplane could not be inserted in the current leaf node
+                // either it is completely outside (which means the input hyperplanes
+                // are wrong), or it is parallel to a previous hyperplane
+                SubHyperplane<S> s = hyperplane.wholeHyperplane();
+                for (BSPTree<S> tree = node; tree.getParent() != null && s != null; tree = tree.getParent()) {
+                    final Hyperplane<S>         other = tree.getParent().getCut().getHyperplane();
+                    final SplitSubHyperplane<S> split = s.split(other);
+                    switch (split.getSide()) {
+                        case HYPER :
+                            // the hyperplane is parallel to a previous hyperplane
+                            if (!hyperplane.sameOrientationAs(other)) {
+                                // this hyperplane is opposite to the other one,
+                                // the region is thinner than the tolerance, we consider it empty
+                                return getComplement(hyperplanes[0].wholeSpace());
+                            }
+                            // the hyperplane is an extension of an already known hyperplane, we just ignore it
+                            break;
+                        case PLUS :
+                        // the hyperplane is outside of the current convex zone,
+                        // the input hyperplanes are inconsistent
+                        throw new MathIllegalArgumentException(LocalizedFormats.NOT_CONVEX_HYPERPLANES);
+                        default :
+                            s = split.getMinus();
+                    }
+                }
+            }
+        }
+
+        return region;
+
+    }
+
+    /** Compute the union of two regions.
+     * @param region1 first region (will be unusable after the operation as
+     * parts of it will be reused in the new region)
+     * @param region2 second region (will be unusable after the operation as
+     * parts of it will be reused in the new region)
+     * @return a new region, result of {@code region1 union region2}
+     */
+    public Region<S> union(final Region<S> region1, final Region<S> region2) {
+        final BSPTree<S> tree =
+            region1.getTree(false).merge(region2.getTree(false), new UnionMerger());
+        tree.visit(nodeCleaner);
+        return region1.buildNew(tree);
+    }
+
+    /** Compute the intersection of two regions.
+     * @param region1 first region (will be unusable after the operation as
+     * parts of it will be reused in the new region)
+     * @param region2 second region (will be unusable after the operation as
+     * parts of it will be reused in the new region)
+     * @return a new region, result of {@code region1 intersection region2}
+     */
+    public Region<S> intersection(final Region<S> region1, final Region<S> region2) {
+        final BSPTree<S> tree =
+            region1.getTree(false).merge(region2.getTree(false), new IntersectionMerger());
+        tree.visit(nodeCleaner);
+        return region1.buildNew(tree);
+    }
+
+    /** Compute the symmetric difference (exclusive or) of two regions.
+     * @param region1 first region (will be unusable after the operation as
+     * parts of it will be reused in the new region)
+     * @param region2 second region (will be unusable after the operation as
+     * parts of it will be reused in the new region)
+     * @return a new region, result of {@code region1 xor region2}
+     */
+    public Region<S> xor(final Region<S> region1, final Region<S> region2) {
+        final BSPTree<S> tree =
+            region1.getTree(false).merge(region2.getTree(false), new XorMerger());
+        tree.visit(nodeCleaner);
+        return region1.buildNew(tree);
+    }
+
+    /** Compute the difference of two regions.
+     * @param region1 first region (will be unusable after the operation as
+     * parts of it will be reused in the new region)
+     * @param region2 second region (will be unusable after the operation as
+     * parts of it will be reused in the new region)
+     * @return a new region, result of {@code region1 minus region2}
+     */
+    public Region<S> difference(final Region<S> region1, final Region<S> region2) {
+        final BSPTree<S> tree =
+            region1.getTree(false).merge(region2.getTree(false), new DifferenceMerger(region1, region2));
+        tree.visit(nodeCleaner);
+        return region1.buildNew(tree);
+    }
+
+    /** Get the complement of the region (exchanged interior/exterior).
+     * @param region region to complement, it will not modified, a new
+     * region independent region will be built
+     * @return a new region, complement of the specified one
+     */
+    /** Get the complement of the region (exchanged interior/exterior).
+     * @param region region to complement, it will not modified, a new
+     * region independent region will be built
+     * @return a new region, complement of the specified one
+     */
+    public Region<S> getComplement(final Region<S> region) {
+        return region.buildNew(recurseComplement(region.getTree(false)));
+    }
+
+    /** Recursively build the complement of a BSP tree.
+     * @param node current node of the original tree
+     * @return new tree, complement of the node
+     */
+    private BSPTree<S> recurseComplement(final BSPTree<S> node) {
+
+        // transform the tree, except for boundary attribute splitters
+        final Map<BSPTree<S>, BSPTree<S>> map = new HashMap<BSPTree<S>, BSPTree<S>>();
+        final BSPTree<S> transformedTree = recurseComplement(node, map);
+
+        // set up the boundary attributes splitters
+        for (final Map.Entry<BSPTree<S>, BSPTree<S>> entry : map.entrySet()) {
+            if (entry.getKey().getCut() != null) {
+                @SuppressWarnings("unchecked")
+                BoundaryAttribute<S> original = (BoundaryAttribute<S>) entry.getKey().getAttribute();
+                if (original != null) {
+                    @SuppressWarnings("unchecked")
+                    BoundaryAttribute<S> transformed = (BoundaryAttribute<S>) entry.getValue().getAttribute();
+                    for (final BSPTree<S> splitter : original.getSplitters()) {
+                        transformed.getSplitters().add(map.get(splitter));
+                    }
+                }
+            }
+        }
+
+        return transformedTree;
+
+    }
+
+    /** Recursively build the complement of a BSP tree.
+     * @param node current node of the original tree
+     * @param map transformed nodes map
+     * @return new tree, complement of the node
+     */
+    private BSPTree<S> recurseComplement(final BSPTree<S> node,
+                                         final Map<BSPTree<S>, BSPTree<S>> map) {
+
+        final BSPTree<S> transformedNode;
+        if (node.getCut() == null) {
+            transformedNode = new BSPTree<S>(((Boolean) node.getAttribute()) ? Boolean.FALSE : Boolean.TRUE);
+        } else {
+
+            @SuppressWarnings("unchecked")
+            BoundaryAttribute<S> attribute = (BoundaryAttribute<S>) node.getAttribute();
+            if (attribute != null) {
+                final SubHyperplane<S> plusOutside =
+                        (attribute.getPlusInside() == null) ? null : attribute.getPlusInside().copySelf();
+                final SubHyperplane<S> plusInside  =
+                        (attribute.getPlusOutside() == null) ? null : attribute.getPlusOutside().copySelf();
+                // we start with an empty list of splitters, it will be filled in out of recursion
+                attribute = new BoundaryAttribute<S>(plusOutside, plusInside, new NodesSet<S>());
+            }
+
+            transformedNode = new BSPTree<S>(node.getCut().copySelf(),
+                                             recurseComplement(node.getPlus(),  map),
+                                             recurseComplement(node.getMinus(), map),
+                                             attribute);
+        }
+
+        map.put(node, transformedNode);
+        return transformedNode;
+
+    }
+
+    /** BSP tree leaf merger computing union of two regions. */
+    private class UnionMerger implements BSPTree.LeafMerger<S> {
+        /** {@inheritDoc} */
+        public BSPTree<S> merge(final BSPTree<S> leaf, final BSPTree<S> tree,
+                                final BSPTree<S> parentTree,
+                                final boolean isPlusChild, final boolean leafFromInstance) {
+            if ((Boolean) leaf.getAttribute()) {
+                // the leaf node represents an inside cell
+                leaf.insertInTree(parentTree, isPlusChild, new VanishingToLeaf(true));
+                return leaf;
+            }
+            // the leaf node represents an outside cell
+            tree.insertInTree(parentTree, isPlusChild, new VanishingToLeaf(false));
+            return tree;
+        }
+    }
+
+    /** BSP tree leaf merger computing intersection of two regions. */
+    private class IntersectionMerger implements BSPTree.LeafMerger<S> {
+        /** {@inheritDoc} */
+        public BSPTree<S> merge(final BSPTree<S> leaf, final BSPTree<S> tree,
+                                final BSPTree<S> parentTree,
+                                final boolean isPlusChild, final boolean leafFromInstance) {
+            if ((Boolean) leaf.getAttribute()) {
+                // the leaf node represents an inside cell
+                tree.insertInTree(parentTree, isPlusChild, new VanishingToLeaf(true));
+                return tree;
+            }
+            // the leaf node represents an outside cell
+            leaf.insertInTree(parentTree, isPlusChild, new VanishingToLeaf(false));
+            return leaf;
+        }
+    }
+
+    /** BSP tree leaf merger computing symmetric difference (exclusive or) of two regions. */
+    private class XorMerger implements BSPTree.LeafMerger<S> {
+        /** {@inheritDoc} */
+        public BSPTree<S> merge(final BSPTree<S> leaf, final BSPTree<S> tree,
+                                final BSPTree<S> parentTree, final boolean isPlusChild,
+                                final boolean leafFromInstance) {
+            BSPTree<S> t = tree;
+            if ((Boolean) leaf.getAttribute()) {
+                // the leaf node represents an inside cell
+                t = recurseComplement(t);
+            }
+            t.insertInTree(parentTree, isPlusChild, new VanishingToLeaf(true));
+            return t;
+        }
+    }
+
+    /** BSP tree leaf merger computing difference of two regions. */
+    private class DifferenceMerger implements BSPTree.LeafMerger<S>, VanishingCutHandler<S> {
+
+        /** Region to subtract from. */
+        private final Region<S> region1;
+
+        /** Region to subtract. */
+        private final Region<S> region2;
+
+        /** Simple constructor.
+         * @param region1 region to subtract from
+         * @param region2 region to subtract
+         */
+        DifferenceMerger(final Region<S> region1, final Region<S> region2) {
+            this.region1 = region1.copySelf();
+            this.region2 = region2.copySelf();
+        }
+
+        /** {@inheritDoc} */
+        public BSPTree<S> merge(final BSPTree<S> leaf, final BSPTree<S> tree,
+                                final BSPTree<S> parentTree, final boolean isPlusChild,
+                                final boolean leafFromInstance) {
+            if ((Boolean) leaf.getAttribute()) {
+                // the leaf node represents an inside cell
+                final BSPTree<S> argTree =
+                    recurseComplement(leafFromInstance ? tree : leaf);
+                argTree.insertInTree(parentTree, isPlusChild, this);
+                return argTree;
+            }
+            // the leaf node represents an outside cell
+            final BSPTree<S> instanceTree =
+                leafFromInstance ? leaf : tree;
+            instanceTree.insertInTree(parentTree, isPlusChild, this);
+            return instanceTree;
+        }
+
+        /** {@inheritDoc} */
+        public BSPTree<S> fixNode(final BSPTree<S> node) {
+            // get a representative point in the degenerate cell
+            final BSPTree<S> cell = node.pruneAroundConvexCell(Boolean.TRUE, Boolean.FALSE, null);
+            final Region<S> r = region1.buildNew(cell);
+            final Point<S> p = r.getBarycenter();
+            return new BSPTree<S>(region1.checkPoint(p) == Location.INSIDE &&
+                                  region2.checkPoint(p) == Location.OUTSIDE);
+        }
+
+    }
+
+    /** Visitor removing internal nodes attributes. */
+    private class NodesCleaner implements  BSPTreeVisitor<S> {
+
+        /** {@inheritDoc} */
+        public Order visitOrder(final BSPTree<S> node) {
+            return Order.PLUS_SUB_MINUS;
+        }
+
+        /** {@inheritDoc} */
+        public void visitInternalNode(final BSPTree<S> node) {
+            node.setAttribute(null);
+        }
+
+        /** {@inheritDoc} */
+        public void visitLeafNode(final BSPTree<S> node) {
+        }
+
+    }
+
+    /** Handler replacing nodes with vanishing cuts with leaf nodes. */
+    private class VanishingToLeaf implements VanishingCutHandler<S> {
+
+        /** Inside/outside indocator to use for ambiguous nodes. */
+        private final boolean inside;
+
+        /** Simple constructor.
+         * @param inside inside/outside indicator to use for ambiguous nodes
+         */
+        VanishingToLeaf(final boolean inside) {
+            this.inside = inside;
+        }
+
+        /** {@inheritDoc} */
+        public BSPTree<S> fixNode(final BSPTree<S> node) {
+            if (node.getPlus().getAttribute().equals(node.getMinus().getAttribute())) {
+                // no ambiguity
+                return new BSPTree<S>(node.getPlus().getAttribute());
+            } else {
+                // ambiguous node
+                return new BSPTree<S>(inside);
+            }
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/partitioning/Side.java b/src/main/java/org/apache/commons/math3/geometry/partitioning/Side.java
new file mode 100644
index 0000000..c9a1357
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/partitioning/Side.java
@@ -0,0 +1,37 @@
+/*
+ * 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.math3.geometry.partitioning;
+
+/** Enumerate representing the location of an element with respect to an
+ * {@link Hyperplane hyperplane} of a space.
+ * @since 3.0
+ */
+public enum Side {
+
+    /** Code for the plus side of the hyperplane. */
+    PLUS,
+
+    /** Code for the minus side of the hyperplane. */
+    MINUS,
+
+    /** Code for elements crossing the hyperplane from plus to minus side. */
+    BOTH,
+
+    /** Code for the hyperplane itself. */
+    HYPER;
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/partitioning/SubHyperplane.java b/src/main/java/org/apache/commons/math3/geometry/partitioning/SubHyperplane.java
new file mode 100644
index 0000000..2069f6f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/partitioning/SubHyperplane.java
@@ -0,0 +1,155 @@
+/*
+ * 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.math3.geometry.partitioning;
+
+import org.apache.commons.math3.geometry.Space;
+
+/** This interface represents the remaining parts of an hyperplane after
+ * other parts have been chopped off.
+
+ * <p>sub-hyperplanes are obtained when parts of an {@link
+ * Hyperplane hyperplane} are chopped off by other hyperplanes that
+ * intersect it. The remaining part is a convex region. Such objects
+ * appear in {@link BSPTree BSP trees} as the intersection of a cut
+ * hyperplane with the convex region which it splits, the chopping
+ * hyperplanes are the cut hyperplanes closer to the tree root.</p>
+
+ * <p>
+ * Note that this interface is <em>not</em> intended to be implemented
+ * by Apache Commons Math users, it is only intended to be implemented
+ * within the library itself. New methods may be added even for minor
+ * versions, which breaks compatibility for external implementations.
+ * </p>
+
+ * @param <S> Type of the embedding space.
+
+ * @since 3.0
+ */
+public interface SubHyperplane<S extends Space> {
+
+    /** Copy the instance.
+     * <p>The instance created is completely independent of the original
+     * one. A deep copy is used, none of the underlying objects are
+     * shared (except for the nodes attributes and immutable
+     * objects).</p>
+     * @return a new sub-hyperplane, copy of the instance
+     */
+    SubHyperplane<S> copySelf();
+
+    /** Get the underlying hyperplane.
+     * @return underlying hyperplane
+     */
+    Hyperplane<S> getHyperplane();
+
+    /** Check if the instance is empty.
+     * @return true if the instance is empty
+     */
+    boolean isEmpty();
+
+    /** Get the size of the instance.
+     * @return the size of the instance (this is a length in 1D, an area
+     * in 2D, a volume in 3D ...)
+     */
+    double getSize();
+
+    /** Compute the relative position of the instance with respect
+     * to an hyperplane.
+     * @param hyperplane hyperplane to check instance against
+     * @return one of {@link Side#PLUS}, {@link Side#MINUS}, {@link Side#BOTH},
+     * {@link Side#HYPER}
+     * @deprecated as of 3.6, replaced with {@link #split(Hyperplane)}.{@link SplitSubHyperplane#getSide()}
+     */
+    @Deprecated
+    Side side(Hyperplane<S> hyperplane);
+
+    /** Split the instance in two parts by an hyperplane.
+     * @param hyperplane splitting hyperplane
+     * @return an object containing both the part of the instance
+     * on the plus side of the hyperplane and the part of the
+     * instance on the minus side of the hyperplane
+     */
+    SplitSubHyperplane<S> split(Hyperplane<S> hyperplane);
+
+    /** Compute the union of the instance and another sub-hyperplane.
+     * @param other other sub-hyperplane to union (<em>must</em> be in the
+     * same hyperplane as the instance)
+     * @return a new sub-hyperplane, union of the instance and other
+     */
+    SubHyperplane<S> reunite(SubHyperplane<S> other);
+
+    /** Class holding the results of the {@link #split split} method.
+     * @param <U> Type of the embedding space.
+     */
+    class SplitSubHyperplane<U extends Space> {
+
+        /** Part of the sub-hyperplane on the plus side of the splitting hyperplane. */
+        private final SubHyperplane<U> plus;
+
+        /** Part of the sub-hyperplane on the minus side of the splitting hyperplane. */
+        private final SubHyperplane<U> minus;
+
+        /** Build a SplitSubHyperplane from its parts.
+         * @param plus part of the sub-hyperplane on the plus side of the
+         * splitting hyperplane
+         * @param minus part of the sub-hyperplane on the minus side of the
+         * splitting hyperplane
+         */
+        public SplitSubHyperplane(final SubHyperplane<U> plus,
+                                  final SubHyperplane<U> minus) {
+            this.plus  = plus;
+            this.minus = minus;
+        }
+
+        /** Get the part of the sub-hyperplane on the plus side of the splitting hyperplane.
+         * @return part of the sub-hyperplane on the plus side of the splitting hyperplane
+         */
+        public SubHyperplane<U> getPlus() {
+            return plus;
+        }
+
+        /** Get the part of the sub-hyperplane on the minus side of the splitting hyperplane.
+         * @return part of the sub-hyperplane on the minus side of the splitting hyperplane
+         */
+        public SubHyperplane<U> getMinus() {
+            return minus;
+        }
+
+        /** Get the side of the split sub-hyperplane with respect to its splitter.
+         * @return {@link Side#PLUS} if only {@link #getPlus()} is neither null nor empty,
+         * {@link Side#MINUS} if only {@link #getMinus()} is neither null nor empty,
+         * {@link Side#BOTH} if both {@link #getPlus()} and {@link #getMinus()}
+         * are neither null nor empty or {@link Side#HYPER} if both {@link #getPlus()} and
+         * {@link #getMinus()} are either null or empty
+         * @since 3.6
+         */
+        public Side getSide() {
+            if (plus != null && !plus.isEmpty()) {
+                if (minus != null && !minus.isEmpty()) {
+                    return Side.BOTH;
+                } else {
+                    return Side.PLUS;
+                }
+            } else if (minus != null && !minus.isEmpty()) {
+                return Side.MINUS;
+            } else {
+                return Side.HYPER;
+            }
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/partitioning/Transform.java b/src/main/java/org/apache/commons/math3/geometry/partitioning/Transform.java
new file mode 100644
index 0000000..ba0c1dd
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/partitioning/Transform.java
@@ -0,0 +1,80 @@
+/*
+ * 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.math3.geometry.partitioning;
+
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.Space;
+
+
+/** This interface represents an inversible affine transform in a space.
+ * <p>Inversible affine transform include for example scalings,
+ * translations, rotations.</p>
+
+ * <p>Transforms are dimension-specific. The consistency rules between
+ * the three {@code apply} methods are the following ones for a
+ * transformed defined for dimension D:</p>
+ * <ul>
+ *   <li>
+ *     the transform can be applied to a point in the
+ *     D-dimension space using its {@link #apply(Point)}
+ *     method
+ *   </li>
+ *   <li>
+ *     the transform can be applied to a (D-1)-dimension
+ *     hyperplane in the D-dimension space using its
+ *     {@link #apply(Hyperplane)} method
+ *   </li>
+ *   <li>
+ *     the transform can be applied to a (D-2)-dimension
+ *     sub-hyperplane in a (D-1)-dimension hyperplane using
+ *     its {@link #apply(SubHyperplane, Hyperplane, Hyperplane)}
+ *     method
+ *   </li>
+ * </ul>
+
+ * @param <S> Type of the embedding space.
+ * @param <T> Type of the embedded sub-space.
+
+ * @since 3.0
+ */
+public interface Transform<S extends Space, T extends Space> {
+
+    /** Transform a point of a space.
+     * @param point point to transform
+     * @return a new object representing the transformed point
+     */
+    Point<S> apply(Point<S> point);
+
+    /** Transform an hyperplane of a space.
+     * @param hyperplane hyperplane to transform
+     * @return a new object representing the transformed hyperplane
+     */
+    Hyperplane<S> apply(Hyperplane<S> hyperplane);
+
+    /** Transform a sub-hyperplane embedded in an hyperplane.
+     * @param sub sub-hyperplane to transform
+     * @param original hyperplane in which the sub-hyperplane is
+     * defined (this is the original hyperplane, the transform has
+     * <em>not</em> been applied to it)
+     * @param transformed hyperplane in which the sub-hyperplane is
+     * defined (this is the transformed hyperplane, the transform
+     * <em>has</em> been applied to it)
+     * @return a new object representing the transformed sub-hyperplane
+     */
+    SubHyperplane<T> apply(SubHyperplane<T> sub, Hyperplane<S> original, Hyperplane<S> transformed);
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/partitioning/package-info.java b/src/main/java/org/apache/commons/math3/geometry/partitioning/package-info.java
new file mode 100644
index 0000000..6e63c73
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/partitioning/package-info.java
@@ -0,0 +1,114 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * This package provides classes to implement Binary Space Partition trees.
+ *
+ * <p>
+ * {@link org.apache.commons.math3.geometry.partitioning.BSPTree BSP trees}
+ * are an efficient way to represent parts of space and in particular
+ * polytopes (line segments in 1D, polygons in 2D and polyhedrons in 3D)
+ * and to operate on them. The main principle is to recursively subdivide
+ * the space using simple hyperplanes (points in 1D, lines in 2D, planes
+ * in 3D).
+ * </p>
+ *
+ * <p>
+ * We start with a tree composed of a single node without any cut
+ * hyperplane: it represents the complete space, which is a convex
+ * part. If we add a cut hyperplane to this node, this represents a
+ * partition with the hyperplane at the node level and two half spaces at
+ * each side of the cut hyperplane. These half-spaces are represented by
+ * two child nodes without any cut hyperplanes associated, the plus child
+ * which represents the half space on the plus side of the cut hyperplane
+ * and the minus child on the other side. Continuing the subdivisions, we
+ * end up with a tree having internal nodes that are associated with a
+ * cut hyperplane and leaf nodes without any hyperplane which correspond
+ * to convex parts.
+ * </p>
+ *
+ * <p>
+ * When BSP trees are used to represent polytopes, the convex parts are
+ * known to be completely inside or outside the polytope as long as there
+ * is no facet in the part (which is obviously the case if the cut
+ * hyperplanes have been chosen as the underlying hyperplanes of the
+ * facets (this is called an autopartition) and if the subdivision
+ * process has been continued until all facets have been processed. It is
+ * important to note that the polytope is <em>not</em> defined by a
+ * single part, but by several convex ones. This is the property that
+ * allows BSP-trees to represent non-convex polytopes despites all parts
+ * are convex. The {@link
+ * org.apache.commons.math3.geometry.partitioning.Region Region} class is
+ * devoted to this representation, it is build on top of the {@link
+ * org.apache.commons.math3.geometry.partitioning.BSPTree BSPTree} class using
+ * boolean objects as the leaf nodes attributes to represent the
+ * inside/outside property of each leaf part, and also adds various
+ * methods dealing with boundaries (i.e. the separation between the
+ * inside and the outside parts).
+ * </p>
+ *
+ * <p>
+ * Rather than simply associating the internal nodes with an hyperplane,
+ * we consider <em>sub-hyperplanes</em> which correspond to the part of
+ * the hyperplane that is inside the convex part defined by all the
+ * parent nodes (this implies that the sub-hyperplane at root node is in
+ * fact a complete hyperplane, because there is no parent to bound
+ * it). Since the parts are convex, the sub-hyperplanes are convex, in
+ * 3D the convex parts are convex polyhedrons, and the sub-hyperplanes
+ * are convex polygons that cut these polyhedrons in two
+ * sub-polyhedrons. Using this definition, a BSP tree completely
+ * partitions the space. Each point either belongs to one of the
+ * sub-hyperplanes in an internal node or belongs to one of the leaf
+ * convex parts.
+ * </p>
+ *
+ * <p>
+ * In order to determine where a point is, it is sufficient to check its
+ * position with respect to the root cut hyperplane, to select the
+ * corresponding child tree and to repeat the procedure recursively,
+ * until either the point appears to be exactly on one of the hyperplanes
+ * in the middle of the tree or to be in one of the leaf parts. For
+ * this operation, it is sufficient to consider the complete hyperplanes,
+ * there is no need to check the points with the boundary of the
+ * sub-hyperplanes, because this check has in fact already been realized
+ * by the recursive descent in the tree. This is very easy to do and very
+ * efficient, especially if the tree is well balanced (the cost is
+ * <code>O(log(n))</code> where <code>n</code> is the number of facets)
+ * or if the first tree levels close to the root discriminate large parts
+ * of the total space.
+ * </p>
+ *
+ * <p>
+ * One of the main sources for the development of this package was Bruce
+ * Naylor, John Amanatides and William Thibault paper <a
+ * href="http://www.cs.yorku.ca/~amana/research/bsptSetOp.pdf">Merging
+ * BSP Trees Yields Polyhedral Set Operations</a> Proc. Siggraph '90,
+ * Computer Graphics 24(4), August 1990, pp 115-124, published by the
+ * Association for Computing Machinery (ACM). The same paper can also be
+ * found <a
+ * href="http://www.cs.utexas.edu/users/fussell/courses/cs384g/bsp_treemerge.pdf">here</a>.
+ * </p>
+ *
+ * <p>
+ * Note that the interfaces defined in this package are <em>not</em> intended to
+ * be implemented by Apache Commons Math users, they are only intended to be
+ * implemented within the library itself. New methods may be added even for
+ * minor versions, which breaks compatibility for external implementations.
+ * </p>
+ *
+ */
+package org.apache.commons.math3.geometry.partitioning;
diff --git a/src/main/java/org/apache/commons/math3/geometry/partitioning/utilities/AVLTree.java b/src/main/java/org/apache/commons/math3/geometry/partitioning/utilities/AVLTree.java
new file mode 100644
index 0000000..00c9d3e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/partitioning/utilities/AVLTree.java
@@ -0,0 +1,634 @@
+/*
+ * 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.math3.geometry.partitioning.utilities;
+
+/** This class implements AVL trees.
+ *
+ * <p>The purpose of this class is to sort elements while allowing
+ * duplicate elements (i.e. such that {@code a.equals(b)} is
+ * true). The {@code SortedSet} interface does not allow this, so
+ * a specific class is needed. Null elements are not allowed.</p>
+ *
+ * <p>Since the {@code equals} method is not sufficient to
+ * differentiate elements, the {@link #delete delete} method is
+ * implemented using the equality operator.</p>
+ *
+ * <p>In order to clearly mark the methods provided here do not have
+ * the same semantics as the ones specified in the
+ * {@code SortedSet} interface, different names are used
+ * ({@code add} has been replaced by {@link #insert insert} and
+ * {@code remove} has been replaced by {@link #delete
+ * delete}).</p>
+ *
+ * <p>This class is based on the C implementation Georg Kraml has put
+ * in the public domain. Unfortunately, his <a
+ * href="www.purists.org/georg/avltree/index.html">page</a> seems not
+ * to exist any more.</p>
+ *
+ * @param <T> the type of the elements
+ *
+ * @since 3.0
+ * @deprecated as of 3.4, this class is not used anymore and considered
+ * to be out of scope of Apache Commons Math
+ */
+@Deprecated
+public class AVLTree<T extends Comparable<T>> {
+
+    /** Top level node. */
+    private Node top;
+
+    /** Build an empty tree.
+     */
+    public AVLTree() {
+        top = null;
+    }
+
+    /** Insert an element in the tree.
+     * @param element element to insert (silently ignored if null)
+     */
+    public void insert(final T element) {
+        if (element != null) {
+            if (top == null) {
+                top = new Node(element, null);
+            } else {
+                top.insert(element);
+            }
+        }
+    }
+
+    /** Delete an element from the tree.
+     * <p>The element is deleted only if there is a node {@code n}
+     * containing exactly the element instance specified, i.e. for which
+     * {@code n.getElement() == element}. This is purposely
+     * <em>different</em> from the specification of the
+     * {@code java.util.Set} {@code remove} method (in fact,
+     * this is the reason why a specific class has been developed).</p>
+     * @param element element to delete (silently ignored if null)
+     * @return true if the element was deleted from the tree
+     */
+    public boolean delete(final T element) {
+        if (element != null) {
+            for (Node node = getNotSmaller(element); node != null; node = node.getNext()) {
+                // loop over all elements neither smaller nor larger
+                // than the specified one
+                if (node.element == element) {
+                    node.delete();
+                    return true;
+                } else if (node.element.compareTo(element) > 0) {
+                    // all the remaining elements are known to be larger,
+                    // the element is not in the tree
+                    return false;
+                }
+            }
+        }
+        return false;
+    }
+
+    /** Check if the tree is empty.
+     * @return true if the tree is empty
+     */
+    public boolean isEmpty() {
+        return top == null;
+    }
+
+
+    /** Get the number of elements of the tree.
+     * @return number of elements contained in the tree
+     */
+    public int size() {
+        return (top == null) ? 0 : top.size();
+    }
+
+    /** Get the node whose element is the smallest one in the tree.
+     * @return the tree node containing the smallest element in the tree
+     * or null if the tree is empty
+     * @see #getLargest
+     * @see #getNotSmaller
+     * @see #getNotLarger
+     * @see Node#getPrevious
+     * @see Node#getNext
+     */
+    public Node getSmallest() {
+        return (top == null) ? null : top.getSmallest();
+    }
+
+    /** Get the node whose element is the largest one in the tree.
+     * @return the tree node containing the largest element in the tree
+     * or null if the tree is empty
+     * @see #getSmallest
+     * @see #getNotSmaller
+     * @see #getNotLarger
+     * @see Node#getPrevious
+     * @see Node#getNext
+     */
+    public Node getLargest() {
+        return (top == null) ? null : top.getLargest();
+    }
+
+    /** Get the node whose element is not smaller than the reference object.
+     * @param reference reference object (may not be in the tree)
+     * @return the tree node containing the smallest element not smaller
+     * than the reference object or null if either the tree is empty or
+     * all its elements are smaller than the reference object
+     * @see #getSmallest
+     * @see #getLargest
+     * @see #getNotLarger
+     * @see Node#getPrevious
+     * @see Node#getNext
+     */
+    public Node getNotSmaller(final T reference) {
+        Node candidate = null;
+        for (Node node = top; node != null;) {
+            if (node.element.compareTo(reference) < 0) {
+                if (node.right == null) {
+                    return candidate;
+                }
+                node = node.right;
+            } else {
+                candidate = node;
+                if (node.left == null) {
+                    return candidate;
+                }
+                node = node.left;
+            }
+        }
+        return null;
+    }
+
+    /** Get the node whose element is not larger than the reference object.
+     * @param reference reference object (may not be in the tree)
+     * @return the tree node containing the largest element not larger
+     * than the reference object (in which case the node is guaranteed
+     * not to be empty) or null if either the tree is empty or all its
+     * elements are larger than the reference object
+     * @see #getSmallest
+     * @see #getLargest
+     * @see #getNotSmaller
+     * @see Node#getPrevious
+     * @see Node#getNext
+     */
+    public Node getNotLarger(final T reference) {
+        Node candidate = null;
+        for (Node node = top; node != null;) {
+            if (node.element.compareTo(reference) > 0) {
+                if (node.left == null) {
+                    return candidate;
+                }
+                node = node.left;
+            } else {
+                candidate = node;
+                if (node.right == null) {
+                    return candidate;
+                }
+                node = node.right;
+            }
+        }
+        return null;
+    }
+
+    /** Enum for tree skew factor. */
+    private enum Skew {
+        /** Code for left high trees. */
+        LEFT_HIGH,
+
+        /** Code for right high trees. */
+        RIGHT_HIGH,
+
+        /** Code for Skew.BALANCED trees. */
+        BALANCED;
+    }
+
+    /** This class implements AVL trees nodes.
+     * <p>AVL tree nodes implement all the logical structure of the
+     * tree. Nodes are created by the {@link AVLTree AVLTree} class.</p>
+     * <p>The nodes are not independant from each other but must obey
+     * specific balancing constraints and the tree structure is
+     * rearranged as elements are inserted or deleted from the tree. The
+     * creation, modification and tree-related navigation methods have
+     * therefore restricted access. Only the order-related navigation,
+     * reading and delete methods are public.</p>
+     * @see AVLTree
+     */
+    public class Node {
+
+        /** Element contained in the current node. */
+        private T element;
+
+        /** Left sub-tree. */
+        private Node left;
+
+        /** Right sub-tree. */
+        private Node right;
+
+        /** Parent tree. */
+        private Node parent;
+
+        /** Skew factor. */
+        private Skew skew;
+
+        /** Build a node for a specified element.
+         * @param element element
+         * @param parent parent node
+         */
+        Node(final T element, final Node parent) {
+            this.element = element;
+            left         = null;
+            right        = null;
+            this.parent  = parent;
+            skew         = Skew.BALANCED;
+        }
+
+        /** Get the contained element.
+         * @return element contained in the node
+         */
+        public T getElement() {
+            return element;
+        }
+
+        /** Get the number of elements of the tree rooted at this node.
+         * @return number of elements contained in the tree rooted at this node
+         */
+        int size() {
+            return 1 + ((left  == null) ? 0 : left.size()) + ((right == null) ? 0 : right.size());
+        }
+
+        /** Get the node whose element is the smallest one in the tree
+         * rooted at this node.
+         * @return the tree node containing the smallest element in the
+         * tree rooted at this node or null if the tree is empty
+         * @see #getLargest
+         */
+        Node getSmallest() {
+            Node node = this;
+            while (node.left != null) {
+                node = node.left;
+            }
+            return node;
+        }
+
+        /** Get the node whose element is the largest one in the tree
+         * rooted at this node.
+         * @return the tree node containing the largest element in the
+         * tree rooted at this node or null if the tree is empty
+         * @see #getSmallest
+         */
+        Node getLargest() {
+            Node node = this;
+            while (node.right != null) {
+                node = node.right;
+            }
+            return node;
+        }
+
+        /** Get the node containing the next smaller or equal element.
+         * @return node containing the next smaller or equal element or
+         * null if there is no smaller or equal element in the tree
+         * @see #getNext
+         */
+        public Node getPrevious() {
+
+            if (left != null) {
+                final Node node = left.getLargest();
+                if (node != null) {
+                    return node;
+                }
+            }
+
+            for (Node node = this; node.parent != null; node = node.parent) {
+                if (node != node.parent.left) {
+                    return node.parent;
+                }
+            }
+
+            return null;
+
+        }
+
+        /** Get the node containing the next larger or equal element.
+         * @return node containing the next larger or equal element (in
+         * which case the node is guaranteed not to be empty) or null if
+         * there is no larger or equal element in the tree
+         * @see #getPrevious
+         */
+        public Node getNext() {
+
+            if (right != null) {
+                final Node node = right.getSmallest();
+                if (node != null) {
+                    return node;
+                }
+            }
+
+            for (Node node = this; node.parent != null; node = node.parent) {
+                if (node != node.parent.right) {
+                    return node.parent;
+                }
+            }
+
+            return null;
+
+        }
+
+        /** Insert an element in a sub-tree.
+         * @param newElement element to insert
+         * @return true if the parent tree should be re-Skew.BALANCED
+         */
+        boolean insert(final T newElement) {
+            if (newElement.compareTo(this.element) < 0) {
+                // the inserted element is smaller than the node
+                if (left == null) {
+                    left = new Node(newElement, this);
+                    return rebalanceLeftGrown();
+                }
+                return left.insert(newElement) ? rebalanceLeftGrown() : false;
+            }
+
+            // the inserted element is equal to or greater than the node
+            if (right == null) {
+                right = new Node(newElement, this);
+                return rebalanceRightGrown();
+            }
+            return right.insert(newElement) ? rebalanceRightGrown() : false;
+
+        }
+
+        /** Delete the node from the tree.
+         */
+        public void delete() {
+            if ((parent == null) && (left == null) && (right == null)) {
+                // this was the last node, the tree is now empty
+                element = null;
+                top     = null;
+            } else {
+
+                Node node;
+                Node child;
+                boolean leftShrunk;
+                if ((left == null) && (right == null)) {
+                    node       = this;
+                    element    = null;
+                    leftShrunk = node == node.parent.left;
+                    child      = null;
+                } else {
+                    node       = (left != null) ? left.getLargest() : right.getSmallest();
+                    element    = node.element;
+                    leftShrunk = node == node.parent.left;
+                    child      = (node.left != null) ? node.left : node.right;
+                }
+
+                node = node.parent;
+                if (leftShrunk) {
+                    node.left = child;
+                } else {
+                    node.right = child;
+                }
+                if (child != null) {
+                    child.parent = node;
+                }
+
+                while (leftShrunk ? node.rebalanceLeftShrunk() : node.rebalanceRightShrunk()) {
+                    if (node.parent == null) {
+                        return;
+                    }
+                    leftShrunk = node == node.parent.left;
+                    node = node.parent;
+                }
+
+            }
+        }
+
+        /** Re-balance the instance as left sub-tree has grown.
+         * @return true if the parent tree should be reSkew.BALANCED too
+         */
+        private boolean rebalanceLeftGrown() {
+            switch (skew) {
+            case LEFT_HIGH:
+                if (left.skew == Skew.LEFT_HIGH) {
+                    rotateCW();
+                    skew       = Skew.BALANCED;
+                    right.skew = Skew.BALANCED;
+                } else {
+                    final Skew s = left.right.skew;
+                    left.rotateCCW();
+                    rotateCW();
+                    switch(s) {
+                    case LEFT_HIGH:
+                        left.skew  = Skew.BALANCED;
+                        right.skew = Skew.RIGHT_HIGH;
+                        break;
+                    case RIGHT_HIGH:
+                        left.skew  = Skew.LEFT_HIGH;
+                        right.skew = Skew.BALANCED;
+                        break;
+                    default:
+                        left.skew  = Skew.BALANCED;
+                        right.skew = Skew.BALANCED;
+                    }
+                    skew = Skew.BALANCED;
+                }
+                return false;
+            case RIGHT_HIGH:
+                skew = Skew.BALANCED;
+                return false;
+            default:
+                skew = Skew.LEFT_HIGH;
+                return true;
+            }
+        }
+
+        /** Re-balance the instance as right sub-tree has grown.
+         * @return true if the parent tree should be reSkew.BALANCED too
+         */
+        private boolean rebalanceRightGrown() {
+            switch (skew) {
+            case LEFT_HIGH:
+                skew = Skew.BALANCED;
+                return false;
+            case RIGHT_HIGH:
+                if (right.skew == Skew.RIGHT_HIGH) {
+                    rotateCCW();
+                    skew      = Skew.BALANCED;
+                    left.skew = Skew.BALANCED;
+                } else {
+                    final Skew s = right.left.skew;
+                    right.rotateCW();
+                    rotateCCW();
+                    switch (s) {
+                    case LEFT_HIGH:
+                        left.skew  = Skew.BALANCED;
+                        right.skew = Skew.RIGHT_HIGH;
+                        break;
+                    case RIGHT_HIGH:
+                        left.skew  = Skew.LEFT_HIGH;
+                        right.skew = Skew.BALANCED;
+                        break;
+                    default:
+                        left.skew  = Skew.BALANCED;
+                        right.skew = Skew.BALANCED;
+                    }
+                    skew = Skew.BALANCED;
+                }
+                return false;
+            default:
+                skew = Skew.RIGHT_HIGH;
+                return true;
+            }
+        }
+
+        /** Re-balance the instance as left sub-tree has shrunk.
+         * @return true if the parent tree should be reSkew.BALANCED too
+         */
+        private boolean rebalanceLeftShrunk() {
+            switch (skew) {
+            case LEFT_HIGH:
+                skew = Skew.BALANCED;
+                return true;
+            case RIGHT_HIGH:
+                if (right.skew == Skew.RIGHT_HIGH) {
+                    rotateCCW();
+                    skew      = Skew.BALANCED;
+                    left.skew = Skew.BALANCED;
+                    return true;
+                } else if (right.skew == Skew.BALANCED) {
+                    rotateCCW();
+                    skew      = Skew.LEFT_HIGH;
+                    left.skew = Skew.RIGHT_HIGH;
+                    return false;
+                } else {
+                    final Skew s = right.left.skew;
+                    right.rotateCW();
+                    rotateCCW();
+                    switch (s) {
+                    case LEFT_HIGH:
+                        left.skew  = Skew.BALANCED;
+                        right.skew = Skew.RIGHT_HIGH;
+                        break;
+                    case RIGHT_HIGH:
+                        left.skew  = Skew.LEFT_HIGH;
+                        right.skew = Skew.BALANCED;
+                        break;
+                    default:
+                        left.skew  = Skew.BALANCED;
+                        right.skew = Skew.BALANCED;
+                    }
+                    skew = Skew.BALANCED;
+                    return true;
+                }
+            default:
+                skew = Skew.RIGHT_HIGH;
+                return false;
+            }
+        }
+
+        /** Re-balance the instance as right sub-tree has shrunk.
+         * @return true if the parent tree should be reSkew.BALANCED too
+         */
+        private boolean rebalanceRightShrunk() {
+            switch (skew) {
+            case RIGHT_HIGH:
+                skew = Skew.BALANCED;
+                return true;
+            case LEFT_HIGH:
+                if (left.skew == Skew.LEFT_HIGH) {
+                    rotateCW();
+                    skew       = Skew.BALANCED;
+                    right.skew = Skew.BALANCED;
+                    return true;
+                } else if (left.skew == Skew.BALANCED) {
+                    rotateCW();
+                    skew       = Skew.RIGHT_HIGH;
+                    right.skew = Skew.LEFT_HIGH;
+                    return false;
+                } else {
+                    final Skew s = left.right.skew;
+                    left.rotateCCW();
+                    rotateCW();
+                    switch (s) {
+                    case LEFT_HIGH:
+                        left.skew  = Skew.BALANCED;
+                        right.skew = Skew.RIGHT_HIGH;
+                        break;
+                    case RIGHT_HIGH:
+                        left.skew  = Skew.LEFT_HIGH;
+                        right.skew = Skew.BALANCED;
+                        break;
+                    default:
+                        left.skew  = Skew.BALANCED;
+                        right.skew = Skew.BALANCED;
+                    }
+                    skew = Skew.BALANCED;
+                    return true;
+                }
+            default:
+                skew = Skew.LEFT_HIGH;
+                return false;
+            }
+        }
+
+        /** Perform a clockwise rotation rooted at the instance.
+         * <p>The skew factor are not updated by this method, they
+         * <em>must</em> be updated by the caller</p>
+         */
+        private void rotateCW() {
+
+            final T tmpElt       = element;
+            element              = left.element;
+            left.element         = tmpElt;
+
+            final Node tmpNode   = left;
+            left                 = tmpNode.left;
+            tmpNode.left         = tmpNode.right;
+            tmpNode.right        = right;
+            right                = tmpNode;
+
+            if (left != null) {
+                left.parent = this;
+            }
+            if (right.right != null) {
+                right.right.parent = right;
+            }
+
+        }
+
+        /** Perform a counter-clockwise rotation rooted at the instance.
+         * <p>The skew factor are not updated by this method, they
+         * <em>must</em> be updated by the caller</p>
+         */
+        private void rotateCCW() {
+
+            final T tmpElt        = element;
+            element               = right.element;
+            right.element         = tmpElt;
+
+            final Node tmpNode    = right;
+            right                 = tmpNode.right;
+            tmpNode.right         = tmpNode.left;
+            tmpNode.left          = left;
+            left                  = tmpNode;
+
+            if (right != null) {
+                right.parent = this;
+            }
+            if (left.left != null) {
+                left.left.parent = left;
+            }
+
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/partitioning/utilities/OrderedTuple.java b/src/main/java/org/apache/commons/math3/geometry/partitioning/utilities/OrderedTuple.java
new file mode 100644
index 0000000..2dad2d7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/partitioning/utilities/OrderedTuple.java
@@ -0,0 +1,431 @@
+/*
+ * 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.math3.geometry.partitioning.utilities;
+
+import java.util.Arrays;
+
+import org.apache.commons.math3.util.FastMath;
+
+/** This class implements an ordering operation for T-uples.
+ *
+ * <p>Ordering is done by encoding all components of the T-uple into a
+ * single scalar value and using this value as the sorting
+ * key. Encoding is performed using the method invented by Georg
+ * Cantor in 1877 when he proved it was possible to establish a
+ * bijection between a line and a plane. The binary representations of
+ * the components of the T-uple are mixed together to form a single
+ * scalar. This means that the 2<sup>k</sup> bit of component 0 is
+ * followed by the 2<sup>k</sup> bit of component 1, then by the
+ * 2<sup>k</sup> bit of component 2 up to the 2<sup>k</sup> bit of
+ * component {@code t}, which is followed by the 2<sup>k-1</sup>
+ * bit of component 0, followed by the 2<sup>k-1</sup> bit of
+ * component 1 ... The binary representations are extended as needed
+ * to handle numbers with different scales and a suitable
+ * 2<sup>p</sup> offset is added to the components in order to avoid
+ * negative numbers (this offset is adjusted as needed during the
+ * comparison operations).</p>
+ *
+ * <p>The more interesting property of the encoding method for our
+ * purpose is that it allows to select all the points that are in a
+ * given range. This is depicted in dimension 2 by the following
+ * picture:</p>
+ *
+ * <img src="doc-files/OrderedTuple.png" />
+ *
+ * <p>This picture shows a set of 100000 random 2-D pairs having their
+ * first component between -50 and +150 and their second component
+ * between -350 and +50. We wanted to extract all pairs having their
+ * first component between +30 and +70 and their second component
+ * between -120 and -30. We built the lower left point at coordinates
+ * (30, -120) and the upper right point at coordinates (70, -30). All
+ * points smaller than the lower left point are drawn in red and all
+ * points larger than the upper right point are drawn in blue. The
+ * green points are between the two limits. This picture shows that
+ * all the desired points are selected, along with spurious points. In
+ * this case, we get 15790 points, 4420 of which really belonging to
+ * the desired rectangle. It is possible to extract very small
+ * subsets. As an example extracting from the same 100000 points set
+ * the points having their first component between +30 and +31 and
+ * their second component between -91 and -90, we get a subset of 11
+ * points, 2 of which really belonging to the desired rectangle.</p>
+ *
+ * <p>the previous selection technique can be applied in all
+ * dimensions, still using two points to define the interval. The
+ * first point will have all its components set to their lower bounds
+ * while the second point will have all its components set to their
+ * upper bounds.</p>
+ *
+ * <p>T-uples with negative infinite or positive infinite components
+ * are sorted logically.</p>
+ *
+ * <p>Since the specification of the {@code Comparator} interface
+ * allows only {@code ClassCastException} errors, some arbitrary
+ * choices have been made to handle specific cases. The rationale for
+ * these choices is to keep <em>regular</em> and consistent T-uples
+ * together.</p>
+ * <ul>
+ * <li>instances with different dimensions are sorted according to
+ * their dimension regardless of their components values</li>
+ * <li>instances with {@code Double.NaN} components are sorted
+ * after all other ones (even after instances with positive infinite
+ * components</li>
+ * <li>instances with both positive and negative infinite components
+ * are considered as if they had {@code Double.NaN}
+ * components</li>
+ * </ul>
+ *
+ * @since 3.0
+ * @deprecated as of 3.4, this class is not used anymore and considered
+ * to be out of scope of Apache Commons Math
+ */
+@Deprecated
+public class OrderedTuple implements Comparable<OrderedTuple> {
+
+    /** Sign bit mask. */
+    private static final long SIGN_MASK     = 0x8000000000000000L;
+
+    /** Exponent bits mask. */
+    private static final long EXPONENT_MASK = 0x7ff0000000000000L;
+
+    /** Mantissa bits mask. */
+    private static final long MANTISSA_MASK = 0x000fffffffffffffL;
+
+    /** Implicit MSB for normalized numbers. */
+    private static final long IMPLICIT_ONE  = 0x0010000000000000L;
+
+    /** Double components of the T-uple. */
+    private double[] components;
+
+    /** Offset scale. */
+    private int offset;
+
+    /** Least Significant Bit scale. */
+    private int lsb;
+
+    /** Ordering encoding of the double components. */
+    private long[] encoding;
+
+    /** Positive infinity marker. */
+    private boolean posInf;
+
+    /** Negative infinity marker. */
+    private boolean negInf;
+
+    /** Not A Number marker. */
+    private boolean nan;
+
+    /** Build an ordered T-uple from its components.
+     * @param components double components of the T-uple
+     */
+    public OrderedTuple(final double ... components) {
+        this.components = components.clone();
+        int msb = Integer.MIN_VALUE;
+        lsb     = Integer.MAX_VALUE;
+        posInf  = false;
+        negInf  = false;
+        nan     = false;
+        for (int i = 0; i < components.length; ++i) {
+            if (Double.isInfinite(components[i])) {
+                if (components[i] < 0) {
+                    negInf = true;
+                } else {
+                    posInf = true;
+                }
+            } else if (Double.isNaN(components[i])) {
+                nan = true;
+            } else {
+                final long b = Double.doubleToLongBits(components[i]);
+                final long m = mantissa(b);
+                if (m != 0) {
+                    final int e = exponent(b);
+                    msb = FastMath.max(msb, e + computeMSB(m));
+                    lsb = FastMath.min(lsb, e + computeLSB(m));
+                }
+            }
+        }
+
+        if (posInf && negInf) {
+            // instance cannot be sorted logically
+            posInf = false;
+            negInf = false;
+            nan    = true;
+        }
+
+        if (lsb <= msb) {
+            // encode the T-upple with the specified offset
+            encode(msb + 16);
+        } else {
+            encoding = new long[] {
+                0x0L
+            };
+        }
+
+    }
+
+    /** Encode the T-uple with a given offset.
+     * @param minOffset minimal scale of the offset to add to all
+     * components (must be greater than the MSBs of all components)
+     */
+    private void encode(final int minOffset) {
+
+        // choose an offset with some margins
+        offset  = minOffset + 31;
+        offset -= offset % 32;
+
+        if ((encoding != null) && (encoding.length == 1) && (encoding[0] == 0x0L)) {
+            // the components are all zeroes
+            return;
+        }
+
+        // allocate an integer array to encode the components (we use only
+        // 63 bits per element because there is no unsigned long in Java)
+        final int neededBits  = offset + 1 - lsb;
+        final int neededLongs = (neededBits + 62) / 63;
+        encoding = new long[components.length * neededLongs];
+
+        // mix the bits from all components
+        int  eIndex = 0;
+        int  shift  = 62;
+        long word   = 0x0L;
+        for (int k = offset; eIndex < encoding.length; --k) {
+            for (int vIndex = 0; vIndex < components.length; ++vIndex) {
+                if (getBit(vIndex, k) != 0) {
+                    word |= 0x1L << shift;
+                }
+                if (shift-- == 0) {
+                    encoding[eIndex++] = word;
+                    word  = 0x0L;
+                    shift = 62;
+                }
+            }
+        }
+
+    }
+
+    /** Compares this ordered T-uple with the specified object.
+
+     * <p>The ordering method is detailed in the general description of
+     * the class. Its main property is to be consistent with distance:
+     * geometrically close T-uples stay close to each other when stored
+     * in a sorted collection using this comparison method.</p>
+
+     * <p>T-uples with negative infinite, positive infinite are sorted
+     * logically.</p>
+
+     * <p>Some arbitrary choices have been made to handle specific
+     * cases. The rationale for these choices is to keep
+     * <em>normal</em> and consistent T-uples together.</p>
+     * <ul>
+     * <li>instances with different dimensions are sorted according to
+     * their dimension regardless of their components values</li>
+     * <li>instances with {@code Double.NaN} components are sorted
+     * after all other ones (evan after instances with positive infinite
+     * components</li>
+     * <li>instances with both positive and negative infinite components
+     * are considered as if they had {@code Double.NaN}
+     * components</li>
+     * </ul>
+
+     * @param ot T-uple to compare instance with
+     * @return a negative integer if the instance is less than the
+     * object, zero if they are equal, or a positive integer if the
+     * instance is greater than the object
+
+     */
+    public int compareTo(final OrderedTuple ot) {
+        if (components.length == ot.components.length) {
+            if (nan) {
+                return +1;
+            } else if (ot.nan) {
+                return -1;
+            } else if (negInf || ot.posInf) {
+                return -1;
+            } else if (posInf || ot.negInf) {
+                return +1;
+            } else {
+
+                if (offset < ot.offset) {
+                    encode(ot.offset);
+                } else if (offset > ot.offset) {
+                    ot.encode(offset);
+                }
+
+                final int limit = FastMath.min(encoding.length, ot.encoding.length);
+                for (int i = 0; i < limit; ++i) {
+                    if (encoding[i] < ot.encoding[i]) {
+                        return -1;
+                    } else if (encoding[i] > ot.encoding[i]) {
+                        return +1;
+                    }
+                }
+
+                if (encoding.length < ot.encoding.length) {
+                    return -1;
+                } else if (encoding.length > ot.encoding.length) {
+                    return +1;
+                } else {
+                    return 0;
+                }
+
+            }
+        }
+
+        return components.length - ot.components.length;
+
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(final Object other) {
+        if (this == other) {
+            return true;
+        } else if (other instanceof OrderedTuple) {
+            return compareTo((OrderedTuple) other) == 0;
+        } else {
+            return false;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        // the following constants are arbitrary small primes
+        final int multiplier = 37;
+        final int trueHash   = 97;
+        final int falseHash  = 71;
+
+        // hash fields and combine them
+        // (we rely on the multiplier to have different combined weights
+        //  for all int fields and all boolean fields)
+        int hash = Arrays.hashCode(components);
+        hash = hash * multiplier + offset;
+        hash = hash * multiplier + lsb;
+        hash = hash * multiplier + (posInf ? trueHash : falseHash);
+        hash = hash * multiplier + (negInf ? trueHash : falseHash);
+        hash = hash * multiplier + (nan    ? trueHash : falseHash);
+
+        return hash;
+
+    }
+
+    /** Get the components array.
+     * @return array containing the T-uple components
+     */
+    public double[] getComponents() {
+        return components.clone();
+    }
+
+    /** Extract the sign from the bits of a double.
+     * @param bits binary representation of the double
+     * @return sign bit (zero if positive, non zero if negative)
+     */
+    private static long sign(final long bits) {
+        return bits & SIGN_MASK;
+    }
+
+    /** Extract the exponent from the bits of a double.
+     * @param bits binary representation of the double
+     * @return exponent
+     */
+    private static int exponent(final long bits) {
+        return ((int) ((bits & EXPONENT_MASK) >> 52)) - 1075;
+    }
+
+    /** Extract the mantissa from the bits of a double.
+     * @param bits binary representation of the double
+     * @return mantissa
+     */
+    private static long mantissa(final long bits) {
+        return ((bits & EXPONENT_MASK) == 0) ?
+               ((bits & MANTISSA_MASK) << 1) :          // subnormal number
+               (IMPLICIT_ONE | (bits & MANTISSA_MASK)); // normal number
+    }
+
+    /** Compute the most significant bit of a long.
+     * @param l long from which the most significant bit is requested
+     * @return scale of the most significant bit of {@code l},
+     * or 0 if {@code l} is zero
+     * @see #computeLSB
+     */
+    private static int computeMSB(final long l) {
+
+        long ll = l;
+        long mask  = 0xffffffffL;
+        int  scale = 32;
+        int  msb   = 0;
+
+        while (scale != 0) {
+            if ((ll & mask) != ll) {
+                msb |= scale;
+                ll >>= scale;
+            }
+            scale >>= 1;
+            mask >>= scale;
+        }
+
+        return msb;
+
+    }
+
+    /** Compute the least significant bit of a long.
+     * @param l long from which the least significant bit is requested
+     * @return scale of the least significant bit of {@code l},
+     * or 63 if {@code l} is zero
+     * @see #computeMSB
+     */
+    private static int computeLSB(final long l) {
+
+        long ll = l;
+        long mask  = 0xffffffff00000000L;
+        int  scale = 32;
+        int  lsb   = 0;
+
+        while (scale != 0) {
+            if ((ll & mask) == ll) {
+                lsb |= scale;
+                ll >>= scale;
+            }
+            scale >>= 1;
+            mask >>= scale;
+        }
+
+        return lsb;
+
+    }
+
+    /** Get a bit from the mantissa of a double.
+     * @param i index of the component
+     * @param k scale of the requested bit
+     * @return the specified bit (either 0 or 1), after the offset has
+     * been added to the double
+     */
+    private int getBit(final int i, final int k) {
+        final long bits = Double.doubleToLongBits(components[i]);
+        final int e = exponent(bits);
+        if ((k < e) || (k > offset)) {
+            return 0;
+        } else if (k == offset) {
+            return (sign(bits) == 0L) ? 1 : 0;
+        } else if (k > (e + 52)) {
+            return (sign(bits) == 0L) ? 0 : 1;
+        } else {
+            final long m = (sign(bits) == 0L) ? mantissa(bits) : -mantissa(bits);
+            return (int) ((m >> (k - e)) & 0x1L);
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/partitioning/utilities/doc-files/OrderedTuple.png b/src/main/java/org/apache/commons/math3/geometry/partitioning/utilities/doc-files/OrderedTuple.png
new file mode 100644
index 0000000..4eca233
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/partitioning/utilities/doc-files/OrderedTuple.png
Binary files differ
diff --git a/src/main/java/org/apache/commons/math3/geometry/partitioning/utilities/package-info.java b/src/main/java/org/apache/commons/math3/geometry/partitioning/utilities/package-info.java
new file mode 100644
index 0000000..31f57f1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/partitioning/utilities/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * <p>
+ * This package provides multidimensional ordering features for partitioning.
+ * </p>
+ *
+ */
+package org.apache.commons.math3.geometry.partitioning.utilities;
diff --git a/src/main/java/org/apache/commons/math3/geometry/spherical/oned/Arc.java b/src/main/java/org/apache/commons/math3/geometry/spherical/oned/Arc.java
new file mode 100644
index 0000000..af0388e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/spherical/oned/Arc.java
@@ -0,0 +1,132 @@
+/*
+ * 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.math3.geometry.spherical.oned;
+
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.geometry.partitioning.Region.Location;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+import org.apache.commons.math3.util.Precision;
+
+
+/** This class represents an arc on a circle.
+ * @see ArcsSet
+ * @since 3.3
+ */
+public class Arc {
+
+    /** The lower angular bound of the arc. */
+    private final double lower;
+
+    /** The upper angular bound of the arc. */
+    private final double upper;
+
+    /** Middle point of the arc. */
+    private final double middle;
+
+    /** Tolerance below which angles are considered identical. */
+    private final double tolerance;
+
+    /** Simple constructor.
+     * <p>
+     * If either {@code lower} is equals to {@code upper} or
+     * the interval exceeds \( 2 \pi \), the arc is considered
+     * to be the full circle and its initial defining boundaries
+     * will be forgotten. {@code lower} is not allowed to be
+     * greater than {@code upper} (an exception is thrown in this case).
+     * {@code lower} will be canonicalized between 0 and \( 2 \pi \), and
+     * upper shifted accordingly, so the {@link #getInf()} and {@link #getSup()}
+     * may not return the value used at instance construction.
+     * </p>
+     * @param lower lower angular bound of the arc
+     * @param upper upper angular bound of the arc
+     * @param tolerance tolerance below which angles are considered identical
+     * @exception NumberIsTooLargeException if lower is greater than upper
+     */
+    public Arc(final double lower, final double upper, final double tolerance)
+        throws NumberIsTooLargeException {
+        this.tolerance = tolerance;
+        if (Precision.equals(lower, upper, 0) || (upper - lower) >= MathUtils.TWO_PI) {
+            // the arc must cover the whole circle
+            this.lower  = 0;
+            this.upper  = MathUtils.TWO_PI;
+            this.middle = FastMath.PI;
+        } else  if (lower <= upper) {
+            this.lower  = MathUtils.normalizeAngle(lower, FastMath.PI);
+            this.upper  = this.lower + (upper - lower);
+            this.middle = 0.5 * (this.lower + this.upper);
+        } else {
+            throw new NumberIsTooLargeException(LocalizedFormats.ENDPOINTS_NOT_AN_INTERVAL,
+                                                lower, upper, true);
+        }
+    }
+
+    /** Get the lower angular bound of the arc.
+     * @return lower angular bound of the arc,
+     * always between 0 and \( 2 \pi \)
+     */
+    public double getInf() {
+        return lower;
+    }
+
+    /** Get the upper angular bound of the arc.
+     * @return upper angular bound of the arc,
+     * always between {@link #getInf()} and {@link #getInf()} \( + 2 \pi \)
+     */
+    public double getSup() {
+        return upper;
+    }
+
+    /** Get the angular size of the arc.
+     * @return angular size of the arc
+     */
+    public double getSize() {
+        return upper - lower;
+    }
+
+    /** Get the barycenter of the arc.
+     * @return barycenter of the arc
+     */
+    public double getBarycenter() {
+        return middle;
+    }
+
+    /** Get the tolerance below which angles are considered identical.
+     * @return tolerance below which angles are considered identical
+     */
+    public double getTolerance() {
+        return tolerance;
+    }
+
+    /** Check a point with respect to the arc.
+     * @param point point to check
+     * @return a code representing the point status: either {@link
+     * Location#INSIDE}, {@link Location#OUTSIDE} or {@link Location#BOUNDARY}
+     */
+    public Location checkPoint(final double point) {
+        final double normalizedPoint = MathUtils.normalizeAngle(point, middle);
+        if (normalizedPoint < lower - tolerance || normalizedPoint > upper + tolerance) {
+            return Location.OUTSIDE;
+        } else if (normalizedPoint > lower + tolerance && normalizedPoint < upper - tolerance) {
+            return Location.INSIDE;
+        } else {
+            return (getSize() >= MathUtils.TWO_PI - tolerance) ? Location.INSIDE : Location.BOUNDARY;
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/spherical/oned/ArcsSet.java b/src/main/java/org/apache/commons/math3/geometry/spherical/oned/ArcsSet.java
new file mode 100644
index 0000000..0a00aa7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/spherical/oned/ArcsSet.java
@@ -0,0 +1,955 @@
+/*
+ * 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.math3.geometry.spherical.oned;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.MathInternalError;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.partitioning.AbstractRegion;
+import org.apache.commons.math3.geometry.partitioning.BSPTree;
+import org.apache.commons.math3.geometry.partitioning.BoundaryProjection;
+import org.apache.commons.math3.geometry.partitioning.Side;
+import org.apache.commons.math3.geometry.partitioning.SubHyperplane;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+import org.apache.commons.math3.util.Precision;
+
+/** This class represents a region of a circle: a set of arcs.
+ * <p>
+ * Note that due to the wrapping around \(2 \pi\), barycenter is
+ * ill-defined here. It was defined only in order to fulfill
+ * the requirements of the {@link
+ * org.apache.commons.math3.geometry.partitioning.Region Region}
+ * interface, but its use is discouraged.
+ * </p>
+ * @since 3.3
+ */
+public class ArcsSet extends AbstractRegion<Sphere1D, Sphere1D> implements Iterable<double[]> {
+
+    /** Build an arcs set representing the whole circle.
+     * @param tolerance tolerance below which close sub-arcs are merged together
+     */
+    public ArcsSet(final double tolerance) {
+        super(tolerance);
+    }
+
+    /** Build an arcs set corresponding to a single arc.
+     * <p>
+     * If either {@code lower} is equals to {@code upper} or
+     * the interval exceeds \( 2 \pi \), the arc is considered
+     * to be the full circle and its initial defining boundaries
+     * will be forgotten. {@code lower} is not allowed to be greater
+     * than {@code upper} (an exception is thrown in this case).
+     * </p>
+     * @param lower lower bound of the arc
+     * @param upper upper bound of the arc
+     * @param tolerance tolerance below which close sub-arcs are merged together
+     * @exception NumberIsTooLargeException if lower is greater than upper
+     */
+    public ArcsSet(final double lower, final double upper, final double tolerance)
+        throws NumberIsTooLargeException {
+        super(buildTree(lower, upper, tolerance), tolerance);
+    }
+
+    /** Build an arcs set from an inside/outside BSP tree.
+     * <p>The leaf nodes of the BSP tree <em>must</em> have a
+     * {@code Boolean} attribute representing the inside status of
+     * the corresponding cell (true for inside cells, false for outside
+     * cells). In order to avoid building too many small objects, it is
+     * recommended to use the predefined constants
+     * {@code Boolean.TRUE} and {@code Boolean.FALSE}</p>
+     * @param tree inside/outside BSP tree representing the arcs set
+     * @param tolerance tolerance below which close sub-arcs are merged together
+     * @exception InconsistentStateAt2PiWrapping if the tree leaf nodes are not
+     * consistent across the \( 0, 2 \pi \) crossing
+     */
+    public ArcsSet(final BSPTree<Sphere1D> tree, final double tolerance)
+        throws InconsistentStateAt2PiWrapping {
+        super(tree, tolerance);
+        check2PiConsistency();
+    }
+
+    /** Build an arcs set from a Boundary REPresentation (B-rep).
+     * <p>The boundary is provided as a collection of {@link
+     * SubHyperplane sub-hyperplanes}. Each sub-hyperplane has the
+     * interior part of the region on its minus side and the exterior on
+     * its plus side.</p>
+     * <p>The boundary elements can be in any order, and can form
+     * several non-connected sets (like for example polygons with holes
+     * or a set of disjoints polyhedrons considered as a whole). In
+     * fact, the elements do not even need to be connected together
+     * (their topological connections are not used here). However, if the
+     * boundary does not really separate an inside open from an outside
+     * open (open having here its topological meaning), then subsequent
+     * calls to the {@link
+     * org.apache.commons.math3.geometry.partitioning.Region#checkPoint(org.apache.commons.math3.geometry.Point)
+     * checkPoint} method will not be meaningful anymore.</p>
+     * <p>If the boundary is empty, the region will represent the whole
+     * space.</p>
+     * @param boundary collection of boundary elements
+     * @param tolerance tolerance below which close sub-arcs are merged together
+     * @exception InconsistentStateAt2PiWrapping if the tree leaf nodes are not
+     * consistent across the \( 0, 2 \pi \) crossing
+     */
+    public ArcsSet(final Collection<SubHyperplane<Sphere1D>> boundary, final double tolerance)
+        throws InconsistentStateAt2PiWrapping {
+        super(boundary, tolerance);
+        check2PiConsistency();
+    }
+
+    /** Build an inside/outside tree representing a single arc.
+     * @param lower lower angular bound of the arc
+     * @param upper upper angular bound of the arc
+     * @param tolerance tolerance below which close sub-arcs are merged together
+     * @return the built tree
+     * @exception NumberIsTooLargeException if lower is greater than upper
+     */
+    private static BSPTree<Sphere1D> buildTree(final double lower, final double upper,
+                                               final double tolerance)
+        throws NumberIsTooLargeException {
+
+        if (Precision.equals(lower, upper, 0) || (upper - lower) >= MathUtils.TWO_PI) {
+            // the tree must cover the whole circle
+            return new BSPTree<Sphere1D>(Boolean.TRUE);
+        } else  if (lower > upper) {
+            throw new NumberIsTooLargeException(LocalizedFormats.ENDPOINTS_NOT_AN_INTERVAL,
+                                                lower, upper, true);
+        }
+
+        // this is a regular arc, covering only part of the circle
+        final double normalizedLower = MathUtils.normalizeAngle(lower, FastMath.PI);
+        final double normalizedUpper = normalizedLower + (upper - lower);
+        final SubHyperplane<Sphere1D> lowerCut =
+                new LimitAngle(new S1Point(normalizedLower), false, tolerance).wholeHyperplane();
+
+        if (normalizedUpper <= MathUtils.TWO_PI) {
+            // simple arc starting after 0 and ending before 2 \pi
+            final SubHyperplane<Sphere1D> upperCut =
+                    new LimitAngle(new S1Point(normalizedUpper), true, tolerance).wholeHyperplane();
+            return new BSPTree<Sphere1D>(lowerCut,
+                                         new BSPTree<Sphere1D>(Boolean.FALSE),
+                                         new BSPTree<Sphere1D>(upperCut,
+                                                               new BSPTree<Sphere1D>(Boolean.FALSE),
+                                                               new BSPTree<Sphere1D>(Boolean.TRUE),
+                                                               null),
+                                         null);
+        } else {
+            // arc wrapping around 2 \pi
+            final SubHyperplane<Sphere1D> upperCut =
+                    new LimitAngle(new S1Point(normalizedUpper - MathUtils.TWO_PI), true, tolerance).wholeHyperplane();
+            return new BSPTree<Sphere1D>(lowerCut,
+                                         new BSPTree<Sphere1D>(upperCut,
+                                                               new BSPTree<Sphere1D>(Boolean.FALSE),
+                                                               new BSPTree<Sphere1D>(Boolean.TRUE),
+                                                               null),
+                                         new BSPTree<Sphere1D>(Boolean.TRUE),
+                                         null);
+        }
+
+    }
+
+    /** Check consistency.
+    * @exception InconsistentStateAt2PiWrapping if the tree leaf nodes are not
+    * consistent across the \( 0, 2 \pi \) crossing
+    */
+    private void check2PiConsistency() throws InconsistentStateAt2PiWrapping {
+
+        // start search at the tree root
+        BSPTree<Sphere1D> root = getTree(false);
+        if (root.getCut() == null) {
+            return;
+        }
+
+        // find the inside/outside state before the smallest internal node
+        final Boolean stateBefore = (Boolean) getFirstLeaf(root).getAttribute();
+
+        // find the inside/outside state after the largest internal node
+        final Boolean stateAfter = (Boolean) getLastLeaf(root).getAttribute();
+
+        if (stateBefore ^ stateAfter) {
+            throw new InconsistentStateAt2PiWrapping();
+        }
+
+    }
+
+    /** Get the first leaf node of a tree.
+     * @param root tree root
+     * @return first leaf node (i.e. node corresponding to the region just after 0.0 radians)
+     */
+    private BSPTree<Sphere1D> getFirstLeaf(final BSPTree<Sphere1D> root) {
+
+        if (root.getCut() == null) {
+            return root;
+        }
+
+        // find the smallest internal node
+        BSPTree<Sphere1D> smallest = null;
+        for (BSPTree<Sphere1D> n = root; n != null; n = previousInternalNode(n)) {
+            smallest = n;
+        }
+
+        return leafBefore(smallest);
+
+    }
+
+    /** Get the last leaf node of a tree.
+     * @param root tree root
+     * @return last leaf node (i.e. node corresponding to the region just before \( 2 \pi \) radians)
+     */
+    private BSPTree<Sphere1D> getLastLeaf(final BSPTree<Sphere1D> root) {
+
+        if (root.getCut() == null) {
+            return root;
+        }
+
+        // find the largest internal node
+        BSPTree<Sphere1D> largest = null;
+        for (BSPTree<Sphere1D> n = root; n != null; n = nextInternalNode(n)) {
+            largest = n;
+        }
+
+        return leafAfter(largest);
+
+    }
+
+    /** Get the node corresponding to the first arc start.
+     * @return smallest internal node (i.e. first after 0.0 radians, in trigonometric direction),
+     * or null if there are no internal nodes (i.e. the set is either empty or covers the full circle)
+     */
+    private BSPTree<Sphere1D> getFirstArcStart() {
+
+        // start search at the tree root
+        BSPTree<Sphere1D> node = getTree(false);
+        if (node.getCut() == null) {
+            return null;
+        }
+
+        // walk tree until we find the smallest internal node
+        node = getFirstLeaf(node).getParent();
+
+        // walk tree until we find an arc start
+        while (node != null && !isArcStart(node)) {
+            node = nextInternalNode(node);
+        }
+
+        return node;
+
+    }
+
+    /** Check if an internal node corresponds to the start angle of an arc.
+     * @param node internal node to check
+     * @return true if the node corresponds to the start angle of an arc
+     */
+    private boolean isArcStart(final BSPTree<Sphere1D> node) {
+
+        if ((Boolean) leafBefore(node).getAttribute()) {
+            // it has an inside cell before it, it may end an arc but not start it
+            return false;
+        }
+
+        if (!(Boolean) leafAfter(node).getAttribute()) {
+            // it has an outside cell after it, it is a dummy cut away from real arcs
+            return false;
+        }
+
+        // the cell has an outside before and an inside after it
+        // it is the start of an arc
+        return true;
+
+    }
+
+    /** Check if an internal node corresponds to the end angle of an arc.
+     * @param node internal node to check
+     * @return true if the node corresponds to the end angle of an arc
+     */
+    private boolean isArcEnd(final BSPTree<Sphere1D> node) {
+
+        if (!(Boolean) leafBefore(node).getAttribute()) {
+            // it has an outside cell before it, it may start an arc but not end it
+            return false;
+        }
+
+        if ((Boolean) leafAfter(node).getAttribute()) {
+            // it has an inside cell after it, it is a dummy cut in the middle of an arc
+            return false;
+        }
+
+        // the cell has an inside before and an outside after it
+        // it is the end of an arc
+        return true;
+
+    }
+
+    /** Get the next internal node.
+     * @param node current internal node
+     * @return next internal node in trigonometric order, or null
+     * if this is the last internal node
+     */
+    private BSPTree<Sphere1D> nextInternalNode(BSPTree<Sphere1D> node) {
+
+        if (childAfter(node).getCut() != null) {
+            // the next node is in the sub-tree
+            return leafAfter(node).getParent();
+        }
+
+        // there is nothing left deeper in the tree, we backtrack
+        while (isAfterParent(node)) {
+            node = node.getParent();
+        }
+        return node.getParent();
+
+    }
+
+    /** Get the previous internal node.
+     * @param node current internal node
+     * @return previous internal node in trigonometric order, or null
+     * if this is the first internal node
+     */
+    private BSPTree<Sphere1D> previousInternalNode(BSPTree<Sphere1D> node) {
+
+        if (childBefore(node).getCut() != null) {
+            // the next node is in the sub-tree
+            return leafBefore(node).getParent();
+        }
+
+        // there is nothing left deeper in the tree, we backtrack
+        while (isBeforeParent(node)) {
+            node = node.getParent();
+        }
+        return node.getParent();
+
+    }
+
+    /** Find the leaf node just before an internal node.
+     * @param node internal node at which the sub-tree starts
+     * @return leaf node just before the internal node
+     */
+    private BSPTree<Sphere1D> leafBefore(BSPTree<Sphere1D> node) {
+
+        node = childBefore(node);
+        while (node.getCut() != null) {
+            node = childAfter(node);
+        }
+
+        return node;
+
+    }
+
+    /** Find the leaf node just after an internal node.
+     * @param node internal node at which the sub-tree starts
+     * @return leaf node just after the internal node
+     */
+    private BSPTree<Sphere1D> leafAfter(BSPTree<Sphere1D> node) {
+
+        node = childAfter(node);
+        while (node.getCut() != null) {
+            node = childBefore(node);
+        }
+
+        return node;
+
+    }
+
+    /** Check if a node is the child before its parent in trigonometric order.
+     * @param node child node considered
+     * @return true is the node has a parent end is before it in trigonometric order
+     */
+    private boolean isBeforeParent(final BSPTree<Sphere1D> node) {
+        final BSPTree<Sphere1D> parent = node.getParent();
+        if (parent == null) {
+            return false;
+        } else {
+            return node == childBefore(parent);
+        }
+    }
+
+    /** Check if a node is the child after its parent in trigonometric order.
+     * @param node child node considered
+     * @return true is the node has a parent end is after it in trigonometric order
+     */
+    private boolean isAfterParent(final BSPTree<Sphere1D> node) {
+        final BSPTree<Sphere1D> parent = node.getParent();
+        if (parent == null) {
+            return false;
+        } else {
+            return node == childAfter(parent);
+        }
+    }
+
+    /** Find the child node just before an internal node.
+     * @param node internal node at which the sub-tree starts
+     * @return child node just before the internal node
+     */
+    private BSPTree<Sphere1D> childBefore(BSPTree<Sphere1D> node) {
+        if (isDirect(node)) {
+            // smaller angles are on minus side, larger angles are on plus side
+            return node.getMinus();
+        } else {
+            // smaller angles are on plus side, larger angles are on minus side
+            return node.getPlus();
+        }
+    }
+
+    /** Find the child node just after an internal node.
+     * @param node internal node at which the sub-tree starts
+     * @return child node just after the internal node
+     */
+    private BSPTree<Sphere1D> childAfter(BSPTree<Sphere1D> node) {
+        if (isDirect(node)) {
+            // smaller angles are on minus side, larger angles are on plus side
+            return node.getPlus();
+        } else {
+            // smaller angles are on plus side, larger angles are on minus side
+            return node.getMinus();
+        }
+    }
+
+    /** Check if an internal node has a direct limit angle.
+     * @param node internal node to check
+     * @return true if the limit angle is direct
+     */
+    private boolean isDirect(final BSPTree<Sphere1D> node) {
+        return ((LimitAngle) node.getCut().getHyperplane()).isDirect();
+    }
+
+    /** Get the limit angle of an internal node.
+     * @param node internal node to check
+     * @return limit angle
+     */
+    private double getAngle(final BSPTree<Sphere1D> node) {
+        return ((LimitAngle) node.getCut().getHyperplane()).getLocation().getAlpha();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ArcsSet buildNew(final BSPTree<Sphere1D> tree) {
+        return new ArcsSet(tree, getTolerance());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void computeGeometricalProperties() {
+        if (getTree(false).getCut() == null) {
+            setBarycenter(S1Point.NaN);
+            setSize(((Boolean) getTree(false).getAttribute()) ? MathUtils.TWO_PI : 0);
+        } else {
+            double size = 0.0;
+            double sum  = 0.0;
+            for (final double[] a : this) {
+                final double length = a[1] - a[0];
+                size += length;
+                sum  += length * (a[0] + a[1]);
+            }
+            setSize(size);
+            if (Precision.equals(size, MathUtils.TWO_PI, 0)) {
+                setBarycenter(S1Point.NaN);
+            } else if (size >= Precision.SAFE_MIN) {
+                setBarycenter(new S1Point(sum / (2 * size)));
+            } else {
+                final LimitAngle limit = (LimitAngle) getTree(false).getCut().getHyperplane();
+                setBarycenter(limit.getLocation());
+            }
+        }
+    }
+
+    /** {@inheritDoc}
+     * @since 3.3
+     */
+    @Override
+    public BoundaryProjection<Sphere1D> projectToBoundary(final Point<Sphere1D> point) {
+
+        // get position of test point
+        final double alpha = ((S1Point) point).getAlpha();
+
+        boolean wrapFirst = false;
+        double first      = Double.NaN;
+        double previous   = Double.NaN;
+        for (final double[] a : this) {
+
+            if (Double.isNaN(first)) {
+                // remember the first angle in case we need it later
+                first = a[0];
+            }
+
+            if (!wrapFirst) {
+                if (alpha < a[0]) {
+                    // the test point lies between the previous and the current arcs
+                    // offset will be positive
+                    if (Double.isNaN(previous)) {
+                        // we need to wrap around the circle
+                        wrapFirst = true;
+                    } else {
+                        final double previousOffset = alpha - previous;
+                        final double currentOffset  = a[0] - alpha;
+                        if (previousOffset < currentOffset) {
+                            return new BoundaryProjection<Sphere1D>(point, new S1Point(previous), previousOffset);
+                        } else {
+                            return new BoundaryProjection<Sphere1D>(point, new S1Point(a[0]), currentOffset);
+                        }
+                    }
+                } else if (alpha <= a[1]) {
+                    // the test point lies within the current arc
+                    // offset will be negative
+                    final double offset0 = a[0] - alpha;
+                    final double offset1 = alpha - a[1];
+                    if (offset0 < offset1) {
+                        return new BoundaryProjection<Sphere1D>(point, new S1Point(a[1]), offset1);
+                    } else {
+                        return new BoundaryProjection<Sphere1D>(point, new S1Point(a[0]), offset0);
+                    }
+                }
+            }
+            previous = a[1];
+        }
+
+        if (Double.isNaN(previous)) {
+
+            // there are no points at all in the arcs set
+            return new BoundaryProjection<Sphere1D>(point, null, MathUtils.TWO_PI);
+
+        } else {
+
+            // the test point if before first arc and after last arc,
+            // somewhere around the 0/2 \pi crossing
+            if (wrapFirst) {
+                // the test point is between 0 and first
+                final double previousOffset = alpha - (previous - MathUtils.TWO_PI);
+                final double currentOffset  = first - alpha;
+                if (previousOffset < currentOffset) {
+                    return new BoundaryProjection<Sphere1D>(point, new S1Point(previous), previousOffset);
+                } else {
+                    return new BoundaryProjection<Sphere1D>(point, new S1Point(first), currentOffset);
+                }
+            } else {
+                // the test point is between last and 2\pi
+                final double previousOffset = alpha - previous;
+                final double currentOffset  = first + MathUtils.TWO_PI - alpha;
+                if (previousOffset < currentOffset) {
+                    return new BoundaryProjection<Sphere1D>(point, new S1Point(previous), previousOffset);
+                } else {
+                    return new BoundaryProjection<Sphere1D>(point, new S1Point(first), currentOffset);
+                }
+            }
+
+        }
+
+    }
+
+    /** Build an ordered list of arcs representing the instance.
+     * <p>This method builds this arcs set as an ordered list of
+     * {@link Arc Arc} elements. An empty tree will build an empty list
+     * while a tree representing the whole circle will build a one
+     * element list with bounds set to \( 0 and 2 \pi \).</p>
+     * @return a new ordered list containing {@link Arc Arc} elements
+     */
+    public List<Arc> asList() {
+        final List<Arc> list = new ArrayList<Arc>();
+        for (final double[] a : this) {
+            list.add(new Arc(a[0], a[1], getTolerance()));
+        }
+        return list;
+    }
+
+    /** {@inheritDoc}
+     * <p>
+     * The iterator returns the limit angles pairs of sub-arcs in trigonometric order.
+     * </p>
+     * <p>
+     * The iterator does <em>not</em> support the optional {@code remove} operation.
+     * </p>
+     */
+    public Iterator<double[]> iterator() {
+        return new SubArcsIterator();
+    }
+
+    /** Local iterator for sub-arcs. */
+    private class SubArcsIterator implements Iterator<double[]> {
+
+        /** Start of the first arc. */
+        private final BSPTree<Sphere1D> firstStart;
+
+        /** Current node. */
+        private BSPTree<Sphere1D> current;
+
+        /** Sub-arc no yet returned. */
+        private double[] pending;
+
+        /** Simple constructor.
+         */
+        SubArcsIterator() {
+
+            firstStart = getFirstArcStart();
+            current    = firstStart;
+
+            if (firstStart == null) {
+                // all the leaf tree nodes share the same inside/outside status
+                if ((Boolean) getFirstLeaf(getTree(false)).getAttribute()) {
+                    // it is an inside node, it represents the full circle
+                    pending = new double[] {
+                        0, MathUtils.TWO_PI
+                    };
+                } else {
+                    pending = null;
+                }
+            } else {
+                selectPending();
+            }
+        }
+
+        /** Walk the tree to select the pending sub-arc.
+         */
+        private void selectPending() {
+
+            // look for the start of the arc
+            BSPTree<Sphere1D> start = current;
+            while (start != null && !isArcStart(start)) {
+                start = nextInternalNode(start);
+            }
+
+            if (start == null) {
+                // we have exhausted the iterator
+                current = null;
+                pending = null;
+                return;
+            }
+
+            // look for the end of the arc
+            BSPTree<Sphere1D> end = start;
+            while (end != null && !isArcEnd(end)) {
+                end = nextInternalNode(end);
+            }
+
+            if (end != null) {
+
+                // we have identified the arc
+                pending = new double[] {
+                    getAngle(start), getAngle(end)
+                };
+
+                // prepare search for next arc
+                current = end;
+
+            } else {
+
+                // the final arc wraps around 2\pi, its end is before the first start
+                end = firstStart;
+                while (end != null && !isArcEnd(end)) {
+                    end = previousInternalNode(end);
+                }
+                if (end == null) {
+                    // this should never happen
+                    throw new MathInternalError();
+                }
+
+                // we have identified the last arc
+                pending = new double[] {
+                    getAngle(start), getAngle(end) + MathUtils.TWO_PI
+                };
+
+                // there won't be any other arcs
+                current = null;
+
+            }
+
+        }
+
+        /** {@inheritDoc} */
+        public boolean hasNext() {
+            return pending != null;
+        }
+
+        /** {@inheritDoc} */
+        public double[] next() {
+            if (pending == null) {
+                throw new NoSuchElementException();
+            }
+            final double[] next = pending;
+            selectPending();
+            return next;
+        }
+
+        /** {@inheritDoc} */
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+
+    }
+
+    /** Compute the relative position of the instance with respect
+     * to an arc.
+     * <p>
+     * The {@link Side#MINUS} side of the arc is the one covered by the arc.
+     * </p>
+     * @param arc arc to check instance against
+     * @return one of {@link Side#PLUS}, {@link Side#MINUS}, {@link Side#BOTH}
+     * or {@link Side#HYPER}
+     * @deprecated as of 3.6, replaced with {@link #split(Arc)}.{@link Split#getSide()}
+     */
+    @Deprecated
+    public Side side(final Arc arc) {
+        return split(arc).getSide();
+    }
+
+    /** Split the instance in two parts by an arc.
+     * @param arc splitting arc
+     * @return an object containing both the part of the instance
+     * on the plus side of the arc and the part of the
+     * instance on the minus side of the arc
+     */
+    public Split split(final Arc arc) {
+
+        final List<Double> minus = new ArrayList<Double>();
+        final List<Double>  plus = new ArrayList<Double>();
+
+        final double reference = FastMath.PI + arc.getInf();
+        final double arcLength = arc.getSup() - arc.getInf();
+
+        for (final double[] a : this) {
+            final double syncedStart = MathUtils.normalizeAngle(a[0], reference) - arc.getInf();
+            final double arcOffset   = a[0] - syncedStart;
+            final double syncedEnd   = a[1] - arcOffset;
+            if (syncedStart < arcLength) {
+                // the start point a[0] is in the minus part of the arc
+                minus.add(a[0]);
+                if (syncedEnd > arcLength) {
+                    // the end point a[1] is past the end of the arc
+                    // so we leave the minus part and enter the plus part
+                    final double minusToPlus = arcLength + arcOffset;
+                    minus.add(minusToPlus);
+                    plus.add(minusToPlus);
+                    if (syncedEnd > MathUtils.TWO_PI) {
+                        // in fact the end point a[1] goes far enough that we
+                        // leave the plus part of the arc and enter the minus part again
+                        final double plusToMinus = MathUtils.TWO_PI + arcOffset;
+                        plus.add(plusToMinus);
+                        minus.add(plusToMinus);
+                        minus.add(a[1]);
+                    } else {
+                        // the end point a[1] is in the plus part of the arc
+                        plus.add(a[1]);
+                    }
+                } else {
+                    // the end point a[1] is in the minus part of the arc
+                    minus.add(a[1]);
+                }
+            } else {
+                // the start point a[0] is in the plus part of the arc
+                plus.add(a[0]);
+                if (syncedEnd > MathUtils.TWO_PI) {
+                    // the end point a[1] wraps around to the start of the arc
+                    // so we leave the plus part and enter the minus part
+                    final double plusToMinus = MathUtils.TWO_PI + arcOffset;
+                    plus.add(plusToMinus);
+                    minus.add(plusToMinus);
+                    if (syncedEnd > MathUtils.TWO_PI + arcLength) {
+                        // in fact the end point a[1] goes far enough that we
+                        // leave the minus part of the arc and enter the plus part again
+                        final double minusToPlus = MathUtils.TWO_PI + arcLength + arcOffset;
+                        minus.add(minusToPlus);
+                        plus.add(minusToPlus);
+                        plus.add(a[1]);
+                    } else {
+                        // the end point a[1] is in the minus part of the arc
+                        minus.add(a[1]);
+                    }
+                } else {
+                    // the end point a[1] is in the plus part of the arc
+                    plus.add(a[1]);
+                }
+            }
+        }
+
+        return new Split(createSplitPart(plus), createSplitPart(minus));
+
+    }
+
+    /** Add an arc limit to a BSP tree under construction.
+     * @param tree BSP tree under construction
+     * @param alpha arc limit
+     * @param isStart if true, the limit is the start of an arc
+     */
+    private void addArcLimit(final BSPTree<Sphere1D> tree, final double alpha, final boolean isStart) {
+
+        final LimitAngle limit = new LimitAngle(new S1Point(alpha), !isStart, getTolerance());
+        final BSPTree<Sphere1D> node = tree.getCell(limit.getLocation(), getTolerance());
+        if (node.getCut() != null) {
+            // this should never happen
+            throw new MathInternalError();
+        }
+
+        node.insertCut(limit);
+        node.setAttribute(null);
+        node.getPlus().setAttribute(Boolean.FALSE);
+        node.getMinus().setAttribute(Boolean.TRUE);
+
+    }
+
+    /** Create a split part.
+     * <p>
+     * As per construction, the list of limit angles is known to have
+     * an even number of entries, with start angles at even indices and
+     * end angles at odd indices.
+     * </p>
+     * @param limits limit angles of the split part
+     * @return split part (may be null)
+     */
+    private ArcsSet createSplitPart(final List<Double> limits) {
+        if (limits.isEmpty()) {
+            return null;
+        } else {
+
+            // collapse close limit angles
+            for (int i = 0; i < limits.size(); ++i) {
+                final int    j  = (i + 1) % limits.size();
+                final double lA = limits.get(i);
+                final double lB = MathUtils.normalizeAngle(limits.get(j), lA);
+                if (FastMath.abs(lB - lA) <= getTolerance()) {
+                    // the two limits are too close to each other, we remove both of them
+                    if (j > 0) {
+                        // regular case, the two entries are consecutive ones
+                        limits.remove(j);
+                        limits.remove(i);
+                        i = i - 1;
+                    } else {
+                        // special case, i the the last entry and j is the first entry
+                        // we have wrapped around list end
+                        final double lEnd   = limits.remove(limits.size() - 1);
+                        final double lStart = limits.remove(0);
+                        if (limits.isEmpty()) {
+                            // the ends were the only limits, is it a full circle or an empty circle?
+                            if (lEnd - lStart > FastMath.PI) {
+                                // it was full circle
+                                return new ArcsSet(new BSPTree<Sphere1D>(Boolean.TRUE), getTolerance());
+                            } else {
+                                // it was an empty circle
+                                return null;
+                            }
+                        } else {
+                            // we have removed the first interval start, so our list
+                            // currently starts with an interval end, which is wrong
+                            // we need to move this interval end to the end of the list
+                            limits.add(limits.remove(0) + MathUtils.TWO_PI);
+                        }
+                    }
+                }
+            }
+
+            // build the tree by adding all angular sectors
+            BSPTree<Sphere1D> tree = new BSPTree<Sphere1D>(Boolean.FALSE);
+            for (int i = 0; i < limits.size() - 1; i += 2) {
+                addArcLimit(tree, limits.get(i),     true);
+                addArcLimit(tree, limits.get(i + 1), false);
+            }
+
+            if (tree.getCut() == null) {
+                // we did not insert anything
+                return null;
+            }
+
+            return new ArcsSet(tree, getTolerance());
+
+        }
+    }
+
+    /** Class holding the results of the {@link #split split} method.
+     */
+    public static class Split {
+
+        /** Part of the arcs set on the plus side of the splitting arc. */
+        private final ArcsSet plus;
+
+        /** Part of the arcs set on the minus side of the splitting arc. */
+        private final ArcsSet minus;
+
+        /** Build a Split from its parts.
+         * @param plus part of the arcs set on the plus side of the
+         * splitting arc
+         * @param minus part of the arcs set on the minus side of the
+         * splitting arc
+         */
+        private Split(final ArcsSet plus, final ArcsSet minus) {
+            this.plus  = plus;
+            this.minus = minus;
+        }
+
+        /** Get the part of the arcs set on the plus side of the splitting arc.
+         * @return part of the arcs set on the plus side of the splitting arc
+         */
+        public ArcsSet getPlus() {
+            return plus;
+        }
+
+        /** Get the part of the arcs set on the minus side of the splitting arc.
+         * @return part of the arcs set on the minus side of the splitting arc
+         */
+        public ArcsSet getMinus() {
+            return minus;
+        }
+
+        /** Get the side of the split arc with respect to its splitter.
+         * @return {@link Side#PLUS} if only {@link #getPlus()} returns non-null,
+         * {@link Side#MINUS} if only {@link #getMinus()} returns non-null,
+         * {@link Side#BOTH} if both {@link #getPlus()} and {@link #getMinus()}
+         * return non-null or {@link Side#HYPER} if both {@link #getPlus()} and
+         * {@link #getMinus()} return null
+         * @since 3.6
+         */
+        public Side getSide() {
+            if (plus != null) {
+                if (minus != null) {
+                    return Side.BOTH;
+                } else {
+                    return Side.PLUS;
+                }
+            } else if (minus != null) {
+                return Side.MINUS;
+            } else {
+                return Side.HYPER;
+            }
+        }
+
+    }
+
+    /** Specialized exception for inconsistent BSP tree state inconsistency.
+     * <p>
+     * This exception is thrown at {@link ArcsSet} construction time when the
+     * {@link org.apache.commons.math3.geometry.partitioning.Region.Location inside/outside}
+     * state is not consistent at the 0, \(2 \pi \) crossing.
+     * </p>
+     */
+    public static class InconsistentStateAt2PiWrapping extends MathIllegalArgumentException {
+
+        /** Serializable UID. */
+        private static final long serialVersionUID = 20140107L;
+
+        /** Simple constructor.
+         */
+        public InconsistentStateAt2PiWrapping() {
+            super(LocalizedFormats.INCONSISTENT_STATE_AT_2_PI_WRAPPING);
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/spherical/oned/LimitAngle.java b/src/main/java/org/apache/commons/math3/geometry/spherical/oned/LimitAngle.java
new file mode 100644
index 0000000..748a142
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/spherical/oned/LimitAngle.java
@@ -0,0 +1,127 @@
+/*
+ * 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.math3.geometry.spherical.oned;
+
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.partitioning.Hyperplane;
+
+/** This class represents a 1D oriented hyperplane on the circle.
+ * <p>An hyperplane on the 1-sphere is an angle with an orientation.</p>
+ * <p>Instances of this class are guaranteed to be immutable.</p>
+ * @since 3.3
+ */
+public class LimitAngle implements Hyperplane<Sphere1D> {
+
+    /** Angle location. */
+    private S1Point location;
+
+    /** Orientation. */
+    private boolean direct;
+
+    /** Tolerance below which angles are considered identical. */
+    private final double tolerance;
+
+    /** Simple constructor.
+     * @param location location of the hyperplane
+     * @param direct if true, the plus side of the hyperplane is towards
+     * angles greater than {@code location}
+     * @param tolerance tolerance below which angles are considered identical
+     */
+    public LimitAngle(final S1Point location, final boolean direct, final double tolerance) {
+        this.location  = location;
+        this.direct    = direct;
+        this.tolerance = tolerance;
+    }
+
+    /** Copy the instance.
+     * <p>Since instances are immutable, this method directly returns
+     * the instance.</p>
+     * @return the instance itself
+     */
+    public LimitAngle copySelf() {
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    public double getOffset(final Point<Sphere1D> point) {
+        final double delta = ((S1Point) point).getAlpha() - location.getAlpha();
+        return direct ? delta : -delta;
+    }
+
+    /** Check if the hyperplane orientation is direct.
+     * @return true if the plus side of the hyperplane is towards
+     * angles greater than hyperplane location
+     */
+    public boolean isDirect() {
+        return direct;
+    }
+
+    /** Get the reverse of the instance.
+     * <p>Get a limit angle with reversed orientation with respect to the
+     * instance. A new object is built, the instance is untouched.</p>
+     * @return a new limit angle, with orientation opposite to the instance orientation
+     */
+    public LimitAngle getReverse() {
+        return new LimitAngle(location, !direct, tolerance);
+    }
+
+    /** Build a region covering the whole hyperplane.
+     * <p>Since this class represent zero dimension spaces which does
+     * not have lower dimension sub-spaces, this method returns a dummy
+     * implementation of a {@link
+     * org.apache.commons.math3.geometry.partitioning.SubHyperplane SubHyperplane}.
+     * This implementation is only used to allow the {@link
+     * org.apache.commons.math3.geometry.partitioning.SubHyperplane
+     * SubHyperplane} class implementation to work properly, it should
+     * <em>not</em> be used otherwise.</p>
+     * @return a dummy sub hyperplane
+     */
+    public SubLimitAngle wholeHyperplane() {
+        return new SubLimitAngle(this, null);
+    }
+
+    /** Build a region covering the whole space.
+     * @return a region containing the instance (really an {@link
+     * ArcsSet IntervalsSet} instance)
+     */
+    public ArcsSet wholeSpace() {
+        return new ArcsSet(tolerance);
+    }
+
+    /** {@inheritDoc} */
+    public boolean sameOrientationAs(final Hyperplane<Sphere1D> other) {
+        return !(direct ^ ((LimitAngle) other).direct);
+    }
+
+    /** Get the hyperplane location on the circle.
+     * @return the hyperplane location
+     */
+    public S1Point getLocation() {
+        return location;
+    }
+
+    /** {@inheritDoc} */
+    public Point<Sphere1D> project(Point<Sphere1D> point) {
+        return location;
+    }
+
+    /** {@inheritDoc} */
+    public double getTolerance() {
+        return tolerance;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/spherical/oned/S1Point.java b/src/main/java/org/apache/commons/math3/geometry/spherical/oned/S1Point.java
new file mode 100644
index 0000000..263a559
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/spherical/oned/S1Point.java
@@ -0,0 +1,157 @@
+/*
+ * 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.math3.geometry.spherical.oned;
+
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.Space;
+import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+
+/** This class represents a point on the 1-sphere.
+ * <p>Instances of this class are guaranteed to be immutable.</p>
+ * @since 3.3
+ */
+public class S1Point implements Point<Sphere1D> {
+
+   // CHECKSTYLE: stop ConstantName
+    /** A vector with all coordinates set to NaN. */
+    public static final S1Point NaN = new S1Point(Double.NaN, Vector2D.NaN);
+    // CHECKSTYLE: resume ConstantName
+
+    /** Serializable UID. */
+    private static final long serialVersionUID = 20131218L;
+
+    /** Azimuthal angle \( \alpha \). */
+    private final double alpha;
+
+    /** Corresponding 2D normalized vector. */
+    private final Vector2D vector;
+
+    /** Simple constructor.
+     * Build a vector from its coordinates
+     * @param alpha azimuthal angle \( \alpha \)
+     * @see #getAlpha()
+     */
+    public S1Point(final double alpha) {
+        this(MathUtils.normalizeAngle(alpha, FastMath.PI),
+             new Vector2D(FastMath.cos(alpha), FastMath.sin(alpha)));
+    }
+
+    /** Build a point from its internal components.
+     * @param alpha azimuthal angle \( \alpha \)
+     * @param vector corresponding vector
+     */
+    private S1Point(final double alpha, final Vector2D vector) {
+        this.alpha  = alpha;
+        this.vector = vector;
+    }
+
+    /** Get the azimuthal angle \( \alpha \).
+     * @return azimuthal angle \( \alpha \)
+     * @see #S1Point(double)
+     */
+    public double getAlpha() {
+        return alpha;
+    }
+
+    /** Get the corresponding normalized vector in the 2D euclidean space.
+     * @return normalized vector
+     */
+    public Vector2D getVector() {
+        return vector;
+    }
+
+    /** {@inheritDoc} */
+    public Space getSpace() {
+        return Sphere1D.getInstance();
+    }
+
+    /** {@inheritDoc} */
+    public boolean isNaN() {
+        return Double.isNaN(alpha);
+    }
+
+    /** {@inheritDoc} */
+    public double distance(final Point<Sphere1D> point) {
+        return distance(this, (S1Point) point);
+    }
+
+    /** Compute the distance (angular separation) between two points.
+     * @param p1 first vector
+     * @param p2 second vector
+     * @return the angular separation between p1 and p2
+     */
+    public static double distance(S1Point p1, S1Point p2) {
+        return Vector2D.angle(p1.vector, p2.vector);
+    }
+
+    /**
+     * Test for the equality of two points on the 2-sphere.
+     * <p>
+     * If all coordinates of two points are exactly the same, and none are
+     * <code>Double.NaN</code>, the two points are considered to be equal.
+     * </p>
+     * <p>
+     * <code>NaN</code> coordinates are considered to affect globally the vector
+     * and be equals to each other - i.e, if either (or all) coordinates of the
+     * 2D vector are equal to <code>Double.NaN</code>, the 2D vector is equal to
+     * {@link #NaN}.
+     * </p>
+     *
+     * @param other Object to test for equality to this
+     * @return true if two points on the 2-sphere objects are equal, false if
+     *         object is null, not an instance of S2Point, or
+     *         not equal to this S2Point instance
+     *
+     */
+    @Override
+    public boolean equals(Object other) {
+
+        if (this == other) {
+            return true;
+        }
+
+        if (other instanceof S1Point) {
+            final S1Point rhs = (S1Point) other;
+            if (rhs.isNaN()) {
+                return this.isNaN();
+            }
+
+            return alpha == rhs.alpha;
+        }
+
+        return false;
+
+    }
+
+    /**
+     * Get a hashCode for the 2D vector.
+     * <p>
+     * All NaN values have the same hash code.</p>
+     *
+     * @return a hash code value for this object
+     */
+    @Override
+    public int hashCode() {
+        if (isNaN()) {
+            return 542;
+        }
+        return 1759 * MathUtils.hash(alpha);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/spherical/oned/Sphere1D.java b/src/main/java/org/apache/commons/math3/geometry/spherical/oned/Sphere1D.java
new file mode 100644
index 0000000..ce5c7cd
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/spherical/oned/Sphere1D.java
@@ -0,0 +1,106 @@
+/*
+ * 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.math3.geometry.spherical.oned;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.exception.MathUnsupportedOperationException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.geometry.Space;
+
+/**
+ * This class implements a one-dimensional sphere (i.e. a circle).
+ * <p>
+ * We use here the topologists definition of the 1-sphere (see
+ * <a href="http://mathworld.wolfram.com/Sphere.html">Sphere</a> on
+ * MathWorld), i.e. the 1-sphere is the one-dimensional closed curve
+ * defined in 2D as x<sup>2</sup>+y<sup>2</sup>=1.
+ * </p>
+ * @since 3.3
+ */
+public class Sphere1D implements Serializable, Space {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 20131218L;
+
+    /** Private constructor for the singleton.
+     */
+    private Sphere1D() {
+    }
+
+    /** Get the unique instance.
+     * @return the unique instance
+     */
+    public static Sphere1D getInstance() {
+        return LazyHolder.INSTANCE;
+    }
+
+    /** {@inheritDoc} */
+    public int getDimension() {
+        return 1;
+    }
+
+    /** {@inheritDoc}
+     * <p>
+     * As the 1-dimension sphere does not have proper sub-spaces,
+     * this method always throws a {@link NoSubSpaceException}
+     * </p>
+     * @return nothing
+     * @throws NoSubSpaceException in all cases
+     */
+    public Space getSubSpace() throws NoSubSpaceException {
+        throw new NoSubSpaceException();
+    }
+
+    // CHECKSTYLE: stop HideUtilityClassConstructor
+    /** Holder for the instance.
+     * <p>We use here the Initialization On Demand Holder Idiom.</p>
+     */
+    private static class LazyHolder {
+        /** Cached field instance. */
+        private static final Sphere1D INSTANCE = new Sphere1D();
+    }
+    // CHECKSTYLE: resume HideUtilityClassConstructor
+
+    /** Handle deserialization of the singleton.
+     * @return the singleton instance
+     */
+    private Object readResolve() {
+        // return the singleton instance
+        return LazyHolder.INSTANCE;
+    }
+
+    /** Specialized exception for inexistent sub-space.
+     * <p>
+     * This exception is thrown when attempting to get the sub-space of a one-dimensional space
+     * </p>
+     */
+    public static class NoSubSpaceException extends MathUnsupportedOperationException {
+
+        /** Serializable UID. */
+        private static final long serialVersionUID = 20140225L;
+
+        /** Simple constructor.
+         */
+        public NoSubSpaceException() {
+            super(LocalizedFormats.NOT_SUPPORTED_IN_DIMENSION_N, 1);
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/spherical/oned/SubLimitAngle.java b/src/main/java/org/apache/commons/math3/geometry/spherical/oned/SubLimitAngle.java
new file mode 100644
index 0000000..ebd3627
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/spherical/oned/SubLimitAngle.java
@@ -0,0 +1,66 @@
+/*
+ * 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.math3.geometry.spherical.oned;
+
+import org.apache.commons.math3.geometry.partitioning.AbstractSubHyperplane;
+import org.apache.commons.math3.geometry.partitioning.Hyperplane;
+import org.apache.commons.math3.geometry.partitioning.Region;
+
+/** This class represents sub-hyperplane for {@link LimitAngle}.
+ * <p>Instances of this class are guaranteed to be immutable.</p>
+ * @since 3.3
+ */
+public class SubLimitAngle extends AbstractSubHyperplane<Sphere1D, Sphere1D> {
+
+    /** Simple constructor.
+     * @param hyperplane underlying hyperplane
+     * @param remainingRegion remaining region of the hyperplane
+     */
+    public SubLimitAngle(final Hyperplane<Sphere1D> hyperplane,
+                         final Region<Sphere1D> remainingRegion) {
+        super(hyperplane, remainingRegion);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getSize() {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isEmpty() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected AbstractSubHyperplane<Sphere1D, Sphere1D> buildNew(final Hyperplane<Sphere1D> hyperplane,
+                                                                 final Region<Sphere1D> remainingRegion) {
+        return new SubLimitAngle(hyperplane, remainingRegion);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public SplitSubHyperplane<Sphere1D> split(final Hyperplane<Sphere1D> hyperplane) {
+        final double global = hyperplane.getOffset(((LimitAngle) getHyperplane()).getLocation());
+        return (global < -1.0e-10) ?
+                                    new SplitSubHyperplane<Sphere1D>(null, this) :
+                                    new SplitSubHyperplane<Sphere1D>(this, null);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/spherical/oned/package-info.java b/src/main/java/org/apache/commons/math3/geometry/spherical/oned/package-info.java
new file mode 100644
index 0000000..d54bc0b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/spherical/oned/package-info.java
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * <p>
+ * This package provides basic geometry components on the 1-sphere.
+ * </p>
+ * <p>
+ * We use here the topologists definition of the 1-sphere (see
+ * <a href="http://mathworld.wolfram.com/Sphere.html">Sphere</a> on
+ * MathWorld), i.e. the 1-sphere is the one-dimensional closed curve
+ * defined in 2D as x<sup>2</sup>+y<sup>2</sup>=1.
+ * </p>
+ *
+ */
+package org.apache.commons.math3.geometry.spherical.oned;
diff --git a/src/main/java/org/apache/commons/math3/geometry/spherical/twod/Circle.java b/src/main/java/org/apache/commons/math3/geometry/spherical/twod/Circle.java
new file mode 100644
index 0000000..a34db6d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/spherical/twod/Circle.java
@@ -0,0 +1,326 @@
+/*
+ * 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.math3.geometry.spherical.twod;
+
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.euclidean.threed.Rotation;
+import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
+import org.apache.commons.math3.geometry.partitioning.Embedding;
+import org.apache.commons.math3.geometry.partitioning.Hyperplane;
+import org.apache.commons.math3.geometry.partitioning.SubHyperplane;
+import org.apache.commons.math3.geometry.partitioning.Transform;
+import org.apache.commons.math3.geometry.spherical.oned.Arc;
+import org.apache.commons.math3.geometry.spherical.oned.ArcsSet;
+import org.apache.commons.math3.geometry.spherical.oned.S1Point;
+import org.apache.commons.math3.geometry.spherical.oned.Sphere1D;
+import org.apache.commons.math3.util.FastMath;
+
+/** This class represents an oriented great circle on the 2-sphere.
+
+ * <p>An oriented circle can be defined by a center point. The circle
+ * is the the set of points that are in the normal plan the center.</p>
+
+ * <p>Since it is oriented the two spherical caps at its two sides are
+ * unambiguously identified as a left cap and a right cap. This can be
+ * used to identify the interior and the exterior in a simple way by
+ * local properties only when part of a line is used to define part of
+ * a spherical polygon boundary.</p>
+
+ * @since 3.3
+ */
+public class Circle implements Hyperplane<Sphere2D>, Embedding<Sphere2D, Sphere1D> {
+
+    /** Pole or circle center. */
+    private Vector3D pole;
+
+    /** First axis in the equator plane, origin of the phase angles. */
+    private Vector3D x;
+
+    /** Second axis in the equator plane, in quadrature with respect to x. */
+    private Vector3D y;
+
+    /** Tolerance below which close sub-arcs are merged together. */
+    private final double tolerance;
+
+    /** Build a great circle from its pole.
+     * <p>The circle is oriented in the trigonometric direction around pole.</p>
+     * @param pole circle pole
+     * @param tolerance tolerance below which close sub-arcs are merged together
+     */
+    public Circle(final Vector3D pole, final double tolerance) {
+        reset(pole);
+        this.tolerance = tolerance;
+    }
+
+    /** Build a great circle from two non-aligned points.
+     * <p>The circle is oriented from first to second point using the path smaller than \( \pi \).</p>
+     * @param first first point contained in the great circle
+     * @param second second point contained in the great circle
+     * @param tolerance tolerance below which close sub-arcs are merged together
+     */
+    public Circle(final S2Point first, final S2Point second, final double tolerance) {
+        reset(first.getVector().crossProduct(second.getVector()));
+        this.tolerance = tolerance;
+    }
+
+    /** Build a circle from its internal components.
+     * <p>The circle is oriented in the trigonometric direction around center.</p>
+     * @param pole circle pole
+     * @param x first axis in the equator plane
+     * @param y second axis in the equator plane
+     * @param tolerance tolerance below which close sub-arcs are merged together
+     */
+    private Circle(final Vector3D pole, final Vector3D x, final Vector3D y,
+                   final double tolerance) {
+        this.pole      = pole;
+        this.x         = x;
+        this.y         = y;
+        this.tolerance = tolerance;
+    }
+
+    /** Copy constructor.
+     * <p>The created instance is completely independent from the
+     * original instance, it is a deep copy.</p>
+     * @param circle circle to copy
+     */
+    public Circle(final Circle circle) {
+        this(circle.pole, circle.x, circle.y, circle.tolerance);
+    }
+
+    /** {@inheritDoc} */
+    public Circle copySelf() {
+        return new Circle(this);
+    }
+
+    /** Reset the instance as if built from a pole.
+     * <p>The circle is oriented in the trigonometric direction around pole.</p>
+     * @param newPole circle pole
+     */
+    public void reset(final Vector3D newPole) {
+        this.pole = newPole.normalize();
+        this.x    = newPole.orthogonal();
+        this.y    = Vector3D.crossProduct(newPole, x).normalize();
+    }
+
+    /** Revert the instance.
+     */
+    public void revertSelf() {
+        // x remains the same
+        y    = y.negate();
+        pole = pole.negate();
+    }
+
+    /** Get the reverse of the instance.
+     * <p>Get a circle with reversed orientation with respect to the
+     * instance. A new object is built, the instance is untouched.</p>
+     * @return a new circle, with orientation opposite to the instance orientation
+     */
+    public Circle getReverse() {
+        return new Circle(pole.negate(), x, y.negate(), tolerance);
+    }
+
+    /** {@inheritDoc} */
+    public Point<Sphere2D> project(Point<Sphere2D> point) {
+        return toSpace(toSubSpace(point));
+    }
+
+    /** {@inheritDoc} */
+    public double getTolerance() {
+        return tolerance;
+    }
+
+    /** {@inheritDoc}
+     * @see #getPhase(Vector3D)
+     */
+    public S1Point toSubSpace(final Point<Sphere2D> point) {
+        return new S1Point(getPhase(((S2Point) point).getVector()));
+    }
+
+    /** Get the phase angle of a direction.
+     * <p>
+     * The direction may not belong to the circle as the
+     * phase is computed for the meridian plane between the circle
+     * pole and the direction.
+     * </p>
+     * @param direction direction for which phase is requested
+     * @return phase angle of the direction around the circle
+     * @see #toSubSpace(Point)
+     */
+    public double getPhase(final Vector3D direction) {
+        return FastMath.PI + FastMath.atan2(-direction.dotProduct(y), -direction.dotProduct(x));
+    }
+
+    /** {@inheritDoc}
+     * @see #getPointAt(double)
+     */
+    public S2Point toSpace(final Point<Sphere1D> point) {
+        return new S2Point(getPointAt(((S1Point) point).getAlpha()));
+    }
+
+    /** Get a circle point from its phase around the circle.
+     * @param alpha phase around the circle
+     * @return circle point on the sphere
+     * @see #toSpace(Point)
+     * @see #getXAxis()
+     * @see #getYAxis()
+     */
+    public Vector3D getPointAt(final double alpha) {
+        return new Vector3D(FastMath.cos(alpha), x, FastMath.sin(alpha), y);
+    }
+
+    /** Get the X axis of the circle.
+     * <p>
+     * This method returns the same value as {@link #getPointAt(double)
+     * getPointAt(0.0)} but it does not do any computation and always
+     * return the same instance.
+     * </p>
+     * @return an arbitrary x axis on the circle
+     * @see #getPointAt(double)
+     * @see #getYAxis()
+     * @see #getPole()
+     */
+    public Vector3D getXAxis() {
+        return x;
+    }
+
+    /** Get the Y axis of the circle.
+     * <p>
+     * This method returns the same value as {@link #getPointAt(double)
+     * getPointAt(0.5 * FastMath.PI)} but it does not do any computation and always
+     * return the same instance.
+     * </p>
+     * @return an arbitrary y axis point on the circle
+     * @see #getPointAt(double)
+     * @see #getXAxis()
+     * @see #getPole()
+     */
+    public Vector3D getYAxis() {
+        return y;
+    }
+
+    /** Get the pole of the circle.
+     * <p>
+     * As the circle is a great circle, the pole does <em>not</em>
+     * belong to it.
+     * </p>
+     * @return pole of the circle
+     * @see #getXAxis()
+     * @see #getYAxis()
+     */
+    public Vector3D getPole() {
+        return pole;
+    }
+
+    /** Get the arc of the instance that lies inside the other circle.
+     * @param other other circle
+     * @return arc of the instance that lies inside the other circle
+     */
+    public Arc getInsideArc(final Circle other) {
+        final double alpha  = getPhase(other.pole);
+        final double halfPi = 0.5 * FastMath.PI;
+        return new Arc(alpha - halfPi, alpha + halfPi, tolerance);
+    }
+
+    /** {@inheritDoc} */
+    public SubCircle wholeHyperplane() {
+        return new SubCircle(this, new ArcsSet(tolerance));
+    }
+
+    /** Build a region covering the whole space.
+     * @return a region containing the instance (really a {@link
+     * SphericalPolygonsSet SphericalPolygonsSet} instance)
+     */
+    public SphericalPolygonsSet wholeSpace() {
+        return new SphericalPolygonsSet(tolerance);
+    }
+
+    /** {@inheritDoc}
+     * @see #getOffset(Vector3D)
+     */
+    public double getOffset(final Point<Sphere2D> point) {
+        return getOffset(((S2Point) point).getVector());
+    }
+
+    /** Get the offset (oriented distance) of a direction.
+     * <p>The offset is defined as the angular distance between the
+     * circle center and the direction minus the circle radius. It
+     * is therefore 0 on the circle, positive for directions outside of
+     * the cone delimited by the circle, and negative inside the cone.</p>
+     * @param direction direction to check
+     * @return offset of the direction
+     * @see #getOffset(Point)
+     */
+    public double getOffset(final Vector3D direction) {
+        return Vector3D.angle(pole, direction) - 0.5 * FastMath.PI;
+    }
+
+    /** {@inheritDoc} */
+    public boolean sameOrientationAs(final Hyperplane<Sphere2D> other) {
+        final Circle otherC = (Circle) other;
+        return Vector3D.dotProduct(pole, otherC.pole) >= 0.0;
+    }
+
+    /** Get a {@link org.apache.commons.math3.geometry.partitioning.Transform
+     * Transform} embedding a 3D rotation.
+     * @param rotation rotation to use
+     * @return a new transform that can be applied to either {@link
+     * Point Point}, {@link Circle Line} or {@link
+     * org.apache.commons.math3.geometry.partitioning.SubHyperplane
+     * SubHyperplane} instances
+     */
+    public static Transform<Sphere2D, Sphere1D> getTransform(final Rotation rotation) {
+        return new CircleTransform(rotation);
+    }
+
+    /** Class embedding a 3D rotation. */
+    private static class CircleTransform implements Transform<Sphere2D, Sphere1D> {
+
+        /** Underlying rotation. */
+        private final Rotation rotation;
+
+        /** Build a transform from a {@code Rotation}.
+         * @param rotation rotation to use
+         */
+        CircleTransform(final Rotation rotation) {
+            this.rotation = rotation;
+        }
+
+        /** {@inheritDoc} */
+        public S2Point apply(final Point<Sphere2D> point) {
+            return new S2Point(rotation.applyTo(((S2Point) point).getVector()));
+        }
+
+        /** {@inheritDoc} */
+        public Circle apply(final Hyperplane<Sphere2D> hyperplane) {
+            final Circle circle = (Circle) hyperplane;
+            return new Circle(rotation.applyTo(circle.pole),
+                              rotation.applyTo(circle.x),
+                              rotation.applyTo(circle.y),
+                              circle.tolerance);
+        }
+
+        /** {@inheritDoc} */
+        public SubHyperplane<Sphere1D> apply(final SubHyperplane<Sphere1D> sub,
+                                             final Hyperplane<Sphere2D> original,
+                                             final Hyperplane<Sphere2D> transformed) {
+            // as the circle is rotated, the limit angles are rotated too
+            return sub;
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/spherical/twod/Edge.java b/src/main/java/org/apache/commons/math3/geometry/spherical/twod/Edge.java
new file mode 100644
index 0000000..a9ccb08
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/spherical/twod/Edge.java
@@ -0,0 +1,222 @@
+/*
+ * 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.math3.geometry.spherical.twod;
+
+import java.util.List;
+
+import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
+import org.apache.commons.math3.geometry.spherical.oned.Arc;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+
+/** Spherical polygons boundary edge.
+ * @see SphericalPolygonsSet#getBoundaryLoops()
+ * @see Vertex
+ * @since 3.3
+ */
+public class Edge {
+
+    /** Start vertex. */
+    private final Vertex start;
+
+    /** End vertex. */
+    private Vertex end;
+
+    /** Length of the arc. */
+    private final double length;
+
+    /** Circle supporting the edge. */
+    private final Circle circle;
+
+    /** Build an edge not contained in any node yet.
+     * @param start start vertex
+     * @param end end vertex
+     * @param length length of the arc (it can be greater than \( \pi \))
+     * @param circle circle supporting the edge
+     */
+    Edge(final Vertex start, final Vertex end, final double length, final Circle circle) {
+
+        this.start  = start;
+        this.end    = end;
+        this.length = length;
+        this.circle = circle;
+
+        // connect the vertices back to the edge
+        start.setOutgoing(this);
+        end.setIncoming(this);
+
+    }
+
+    /** Get start vertex.
+     * @return start vertex
+     */
+    public Vertex getStart() {
+        return start;
+    }
+
+    /** Get end vertex.
+     * @return end vertex
+     */
+    public Vertex getEnd() {
+        return end;
+    }
+
+    /** Get the length of the arc.
+     * @return length of the arc (can be greater than \( \pi \))
+     */
+    public double getLength() {
+        return length;
+    }
+
+    /** Get the circle supporting this edge.
+     * @return circle supporting this edge
+     */
+    public Circle getCircle() {
+        return circle;
+    }
+
+    /** Get an intermediate point.
+     * <p>
+     * The angle along the edge should normally be between 0 and {@link #getLength()}
+     * in order to remain within edge limits. However, there are no checks on the
+     * value of the angle, so user can rebuild the full circle on which an edge is
+     * defined if they want.
+     * </p>
+     * @param alpha angle along the edge, counted from {@link #getStart()}
+     * @return an intermediate point
+     */
+    public Vector3D getPointAt(final double alpha) {
+        return circle.getPointAt(alpha + circle.getPhase(start.getLocation().getVector()));
+    }
+
+    /** Connect the instance with a following edge.
+     * @param next edge following the instance
+     */
+    void setNextEdge(final Edge next) {
+        end = next.getStart();
+        end.setIncoming(this);
+        end.bindWith(getCircle());
+    }
+
+    /** Split the edge.
+     * <p>
+     * Once split, this edge is not referenced anymore by the vertices,
+     * it is replaced by the two or three sub-edges and intermediate splitting
+     * vertices are introduced to connect these sub-edges together.
+     * </p>
+     * @param splitCircle circle splitting the edge in several parts
+     * @param outsideList list where to put parts that are outside of the split circle
+     * @param insideList list where to put parts that are inside the split circle
+     */
+    void split(final Circle splitCircle,
+                       final List<Edge> outsideList, final List<Edge> insideList) {
+
+        // get the inside arc, synchronizing its phase with the edge itself
+        final double edgeStart        = circle.getPhase(start.getLocation().getVector());
+        final Arc    arc              = circle.getInsideArc(splitCircle);
+        final double arcRelativeStart = MathUtils.normalizeAngle(arc.getInf(), edgeStart + FastMath.PI) - edgeStart;
+        final double arcRelativeEnd   = arcRelativeStart + arc.getSize();
+        final double unwrappedEnd     = arcRelativeEnd - MathUtils.TWO_PI;
+
+        // build the sub-edges
+        final double tolerance = circle.getTolerance();
+        Vertex previousVertex = start;
+        if (unwrappedEnd >= length - tolerance) {
+
+            // the edge is entirely contained inside the circle
+            // we don't split anything
+            insideList.add(this);
+
+        } else {
+
+            // there are at least some parts of the edge that should be outside
+            // (even is they are later be filtered out as being too small)
+            double alreadyManagedLength = 0;
+            if (unwrappedEnd >= 0) {
+                // the start of the edge is inside the circle
+                previousVertex = addSubEdge(previousVertex,
+                                            new Vertex(new S2Point(circle.getPointAt(edgeStart + unwrappedEnd))),
+                                            unwrappedEnd, insideList, splitCircle);
+                alreadyManagedLength = unwrappedEnd;
+            }
+
+            if (arcRelativeStart >= length - tolerance) {
+                // the edge ends while still outside of the circle
+                if (unwrappedEnd >= 0) {
+                    previousVertex = addSubEdge(previousVertex, end,
+                                                length - alreadyManagedLength, outsideList, splitCircle);
+                } else {
+                    // the edge is entirely outside of the circle
+                    // we don't split anything
+                    outsideList.add(this);
+                }
+            } else {
+                // the edge is long enough to enter inside the circle
+                previousVertex = addSubEdge(previousVertex,
+                                            new Vertex(new S2Point(circle.getPointAt(edgeStart + arcRelativeStart))),
+                                            arcRelativeStart - alreadyManagedLength, outsideList, splitCircle);
+                alreadyManagedLength = arcRelativeStart;
+
+                if (arcRelativeEnd >= length - tolerance) {
+                    // the edge ends while still inside of the circle
+                    previousVertex = addSubEdge(previousVertex, end,
+                                                length - alreadyManagedLength, insideList, splitCircle);
+                } else {
+                    // the edge is long enough to exit outside of the circle
+                    previousVertex = addSubEdge(previousVertex,
+                                                new Vertex(new S2Point(circle.getPointAt(edgeStart + arcRelativeStart))),
+                                                arcRelativeStart - alreadyManagedLength, insideList, splitCircle);
+                    alreadyManagedLength = arcRelativeStart;
+                    previousVertex = addSubEdge(previousVertex, end,
+                                                length - alreadyManagedLength, outsideList, splitCircle);
+                }
+            }
+
+        }
+
+    }
+
+    /** Add a sub-edge to a list if long enough.
+     * <p>
+     * If the length of the sub-edge to add is smaller than the {@link Circle#getTolerance()}
+     * tolerance of the support circle, it will be ignored.
+     * </p>
+     * @param subStart start of the sub-edge
+     * @param subEnd end of the sub-edge
+     * @param subLength length of the sub-edge
+     * @param splitCircle circle splitting the edge in several parts
+     * @param list list where to put the sub-edge
+     * @return end vertex of the edge ({@code subEnd} if the edge was long enough and really
+     * added, {@code subStart} if the edge was too small and therefore ignored)
+     */
+    private Vertex addSubEdge(final Vertex subStart, final Vertex subEnd, final double subLength,
+                              final List<Edge> list, final Circle splitCircle) {
+
+        if (subLength <= circle.getTolerance()) {
+            // the edge is too short, we ignore it
+            return subStart;
+        }
+
+        // really add the edge
+        subEnd.bindWith(splitCircle);
+        final Edge edge = new Edge(subStart, subEnd, subLength, circle);
+        list.add(edge);
+        return subEnd;
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/spherical/twod/EdgesBuilder.java b/src/main/java/org/apache/commons/math3/geometry/spherical/twod/EdgesBuilder.java
new file mode 100644
index 0000000..844cfb1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/spherical/twod/EdgesBuilder.java
@@ -0,0 +1,169 @@
+/*
+ * 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.math3.geometry.spherical.twod;
+
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
+import org.apache.commons.math3.geometry.partitioning.BSPTree;
+import org.apache.commons.math3.geometry.partitioning.BSPTreeVisitor;
+import org.apache.commons.math3.geometry.partitioning.BoundaryAttribute;
+import org.apache.commons.math3.geometry.spherical.oned.Arc;
+import org.apache.commons.math3.geometry.spherical.oned.ArcsSet;
+import org.apache.commons.math3.geometry.spherical.oned.S1Point;
+
+/** Visitor building edges.
+ * @since 3.3
+ */
+class EdgesBuilder implements BSPTreeVisitor<Sphere2D> {
+
+    /** Root of the tree. */
+    private final BSPTree<Sphere2D> root;
+
+    /** Tolerance below which points are consider to be identical. */
+    private final double tolerance;
+
+    /** Built edges and their associated nodes. */
+    private final Map<Edge, BSPTree<Sphere2D>> edgeToNode;
+
+    /** Reversed map. */
+    private final Map<BSPTree<Sphere2D>, List<Edge>> nodeToEdgesList;
+
+    /** Simple constructor.
+     * @param root tree root
+     * @param tolerance below which points are consider to be identical
+     */
+    EdgesBuilder(final BSPTree<Sphere2D> root, final double tolerance) {
+        this.root            = root;
+        this.tolerance       = tolerance;
+        this.edgeToNode      = new IdentityHashMap<Edge, BSPTree<Sphere2D>>();
+        this.nodeToEdgesList = new IdentityHashMap<BSPTree<Sphere2D>, List<Edge>>();
+    }
+
+    /** {@inheritDoc} */
+    public Order visitOrder(final BSPTree<Sphere2D> node) {
+        return Order.MINUS_SUB_PLUS;
+    }
+
+    /** {@inheritDoc} */
+    public void visitInternalNode(final BSPTree<Sphere2D> node) {
+        nodeToEdgesList.put(node, new ArrayList<Edge>());
+        @SuppressWarnings("unchecked")
+        final BoundaryAttribute<Sphere2D> attribute = (BoundaryAttribute<Sphere2D>) node.getAttribute();
+        if (attribute.getPlusOutside() != null) {
+            addContribution((SubCircle) attribute.getPlusOutside(), false, node);
+        }
+        if (attribute.getPlusInside() != null) {
+            addContribution((SubCircle) attribute.getPlusInside(), true, node);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void visitLeafNode(final BSPTree<Sphere2D> node) {
+    }
+
+    /** Add the contribution of a boundary edge.
+     * @param sub boundary facet
+     * @param reversed if true, the facet has the inside on its plus side
+     * @param node node to which the edge belongs
+     */
+    private void addContribution(final SubCircle sub, final boolean reversed,
+                                 final BSPTree<Sphere2D> node) {
+        final Circle circle  = (Circle) sub.getHyperplane();
+        final List<Arc> arcs = ((ArcsSet) sub.getRemainingRegion()).asList();
+        for (final Arc a : arcs) {
+            final Vertex start = new Vertex((S2Point) circle.toSpace(new S1Point(a.getInf())));
+            final Vertex end   = new Vertex((S2Point) circle.toSpace(new S1Point(a.getSup())));
+            start.bindWith(circle);
+            end.bindWith(circle);
+            final Edge edge;
+            if (reversed) {
+                edge = new Edge(end, start, a.getSize(), circle.getReverse());
+            } else {
+                edge = new Edge(start, end, a.getSize(), circle);
+            }
+            edgeToNode.put(edge, node);
+            nodeToEdgesList.get(node).add(edge);
+        }
+    }
+
+    /** Get the edge that should naturally follow another one.
+     * @param previous edge to be continued
+     * @return other edge, starting where the previous one ends (they
+     * have not been connected yet)
+     * @exception MathIllegalStateException if there is not a single other edge
+     */
+    private Edge getFollowingEdge(final Edge previous)
+        throws MathIllegalStateException {
+
+        // get the candidate nodes
+        final S2Point point = previous.getEnd().getLocation();
+        final List<BSPTree<Sphere2D>> candidates = root.getCloseCuts(point, tolerance);
+
+        // the following edge we are looking for must start from one of the candidates nodes
+        double closest = tolerance;
+        Edge following = null;
+        for (final BSPTree<Sphere2D> node : candidates) {
+            for (final Edge edge : nodeToEdgesList.get(node)) {
+                if (edge != previous && edge.getStart().getIncoming() == null) {
+                    final Vector3D edgeStart = edge.getStart().getLocation().getVector();
+                    final double gap         = Vector3D.angle(point.getVector(), edgeStart);
+                    if (gap <= closest) {
+                        closest   = gap;
+                        following = edge;
+                    }
+                }
+            }
+        }
+
+        if (following == null) {
+            final Vector3D previousStart = previous.getStart().getLocation().getVector();
+            if (Vector3D.angle(point.getVector(), previousStart) <= tolerance) {
+                // the edge connects back to itself
+                return previous;
+            }
+
+            // this should never happen
+            throw new MathIllegalStateException(LocalizedFormats.OUTLINE_BOUNDARY_LOOP_OPEN);
+
+        }
+
+        return following;
+
+    }
+
+    /** Get the boundary edges.
+     * @return boundary edges
+     * @exception MathIllegalStateException if there is not a single other edge
+     */
+    public List<Edge> getEdges() throws MathIllegalStateException {
+
+        // connect the edges
+        for (final Edge previous : edgeToNode.keySet()) {
+            previous.setNextEdge(getFollowingEdge(previous));
+        }
+
+        return new ArrayList<Edge>(edgeToNode.keySet());
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/spherical/twod/PropertiesComputer.java b/src/main/java/org/apache/commons/math3/geometry/spherical/twod/PropertiesComputer.java
new file mode 100644
index 0000000..593180f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/spherical/twod/PropertiesComputer.java
@@ -0,0 +1,173 @@
+/*
+ * 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.math3.geometry.spherical.twod;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.math3.exception.MathInternalError;
+import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
+import org.apache.commons.math3.geometry.partitioning.BSPTree;
+import org.apache.commons.math3.geometry.partitioning.BSPTreeVisitor;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+
+/** Visitor computing geometrical properties.
+ * @since 3.3
+ */
+class PropertiesComputer implements BSPTreeVisitor<Sphere2D> {
+
+    /** Tolerance below which points are consider to be identical. */
+    private final double tolerance;
+
+    /** Summed area. */
+    private double summedArea;
+
+    /** Summed barycenter. */
+    private Vector3D summedBarycenter;
+
+    /** List of points strictly inside convex cells. */
+    private final List<Vector3D> convexCellsInsidePoints;
+
+    /** Simple constructor.
+     * @param tolerance below which points are consider to be identical
+     */
+    PropertiesComputer(final double tolerance) {
+        this.tolerance              = tolerance;
+        this.summedArea             = 0;
+        this.summedBarycenter       = Vector3D.ZERO;
+        this.convexCellsInsidePoints = new ArrayList<Vector3D>();
+    }
+
+    /** {@inheritDoc} */
+    public Order visitOrder(final BSPTree<Sphere2D> node) {
+        return Order.MINUS_SUB_PLUS;
+    }
+
+    /** {@inheritDoc} */
+    public void visitInternalNode(final BSPTree<Sphere2D> node) {
+        // nothing to do here
+    }
+
+    /** {@inheritDoc} */
+    public void visitLeafNode(final BSPTree<Sphere2D> node) {
+        if ((Boolean) node.getAttribute()) {
+
+            // transform this inside leaf cell into a simple convex polygon
+            final SphericalPolygonsSet convex =
+                    new SphericalPolygonsSet(node.pruneAroundConvexCell(Boolean.TRUE,
+                                                                        Boolean.FALSE,
+                                                                        null),
+                                             tolerance);
+
+            // extract the start of the single loop boundary of the convex cell
+            final List<Vertex> boundary = convex.getBoundaryLoops();
+            if (boundary.size() != 1) {
+                // this should never happen
+                throw new MathInternalError();
+            }
+
+            // compute the geometrical properties of the convex cell
+            final double area  = convexCellArea(boundary.get(0));
+            final Vector3D barycenter = convexCellBarycenter(boundary.get(0));
+            convexCellsInsidePoints.add(barycenter);
+
+            // add the cell contribution to the global properties
+            summedArea      += area;
+            summedBarycenter = new Vector3D(1, summedBarycenter, area, barycenter);
+
+        }
+    }
+
+    /** Compute convex cell area.
+     * @param start start vertex of the convex cell boundary
+     * @return area
+     */
+    private double convexCellArea(final Vertex start) {
+
+        int n = 0;
+        double sum = 0;
+
+        // loop around the cell
+        for (Edge e = start.getOutgoing(); n == 0 || e.getStart() != start; e = e.getEnd().getOutgoing()) {
+
+            // find path interior angle at vertex
+            final Vector3D previousPole = e.getCircle().getPole();
+            final Vector3D nextPole     = e.getEnd().getOutgoing().getCircle().getPole();
+            final Vector3D point        = e.getEnd().getLocation().getVector();
+            double alpha = FastMath.atan2(Vector3D.dotProduct(nextPole, Vector3D.crossProduct(point, previousPole)),
+                                          -Vector3D.dotProduct(nextPole, previousPole));
+            if (alpha < 0) {
+                alpha += MathUtils.TWO_PI;
+            }
+            sum += alpha;
+            n++;
+        }
+
+        // compute area using extended Girard theorem
+        // see Spherical Trigonometry: For the Use of Colleges and Schools by I. Todhunter
+        // article 99 in chapter VIII Area Of a Spherical Triangle. Spherical Excess.
+        // book available from project Gutenberg at http://www.gutenberg.org/ebooks/19770
+        return sum - (n - 2) * FastMath.PI;
+
+    }
+
+    /** Compute convex cell barycenter.
+     * @param start start vertex of the convex cell boundary
+     * @return barycenter
+     */
+    private Vector3D convexCellBarycenter(final Vertex start) {
+
+        int n = 0;
+        Vector3D sumB = Vector3D.ZERO;
+
+        // loop around the cell
+        for (Edge e = start.getOutgoing(); n == 0 || e.getStart() != start; e = e.getEnd().getOutgoing()) {
+            sumB = new Vector3D(1, sumB, e.getLength(), e.getCircle().getPole());
+            n++;
+        }
+
+        return sumB.normalize();
+
+    }
+
+    /** Get the area.
+     * @return area
+     */
+    public double getArea() {
+        return summedArea;
+    }
+
+    /** Get the barycenter.
+     * @return barycenter
+     */
+    public S2Point getBarycenter() {
+        if (summedBarycenter.getNormSq() == 0) {
+            return S2Point.NaN;
+        } else {
+            return new S2Point(summedBarycenter);
+        }
+    }
+
+    /** Get the points strictly inside convex cells.
+     * @return points strictly inside convex cells
+     */
+    public List<Vector3D> getConvexCellsInsidePoints() {
+        return convexCellsInsidePoints;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/spherical/twod/S2Point.java b/src/main/java/org/apache/commons/math3/geometry/spherical/twod/S2Point.java
new file mode 100644
index 0000000..677e830
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/spherical/twod/S2Point.java
@@ -0,0 +1,237 @@
+/*
+ * 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.math3.geometry.spherical.twod;
+
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.geometry.Point;
+import org.apache.commons.math3.geometry.Space;
+import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+
+/** This class represents a point on the 2-sphere.
+ * <p>
+ * We use the mathematical convention to use the azimuthal angle \( \theta \)
+ * in the x-y plane as the first coordinate, and the polar angle \( \varphi \)
+ * as the second coordinate (see <a
+ * href="http://mathworld.wolfram.com/SphericalCoordinates.html">Spherical
+ * Coordinates</a> in MathWorld).
+ * </p>
+ * <p>Instances of this class are guaranteed to be immutable.</p>
+ * @since 3.3
+ */
+public class S2Point implements Point<Sphere2D> {
+
+    /** +I (coordinates: \( \theta = 0, \varphi = \pi/2 \)). */
+    public static final S2Point PLUS_I = new S2Point(0, 0.5 * FastMath.PI, Vector3D.PLUS_I);
+
+    /** +J (coordinates: \( \theta = \pi/2, \varphi = \pi/2 \))). */
+    public static final S2Point PLUS_J = new S2Point(0.5 * FastMath.PI, 0.5 * FastMath.PI, Vector3D.PLUS_J);
+
+    /** +K (coordinates: \( \theta = any angle, \varphi = 0 \)). */
+    public static final S2Point PLUS_K = new S2Point(0, 0, Vector3D.PLUS_K);
+
+    /** -I (coordinates: \( \theta = \pi, \varphi = \pi/2 \)). */
+    public static final S2Point MINUS_I = new S2Point(FastMath.PI, 0.5 * FastMath.PI, Vector3D.MINUS_I);
+
+    /** -J (coordinates: \( \theta = 3\pi/2, \varphi = \pi/2 \)). */
+    public static final S2Point MINUS_J = new S2Point(1.5 * FastMath.PI, 0.5 * FastMath.PI, Vector3D.MINUS_J);
+
+    /** -K (coordinates: \( \theta = any angle, \varphi = \pi \)). */
+    public static final S2Point MINUS_K = new S2Point(0, FastMath.PI, Vector3D.MINUS_K);
+
+    // CHECKSTYLE: stop ConstantName
+    /** A vector with all coordinates set to NaN. */
+    public static final S2Point NaN = new S2Point(Double.NaN, Double.NaN, Vector3D.NaN);
+    // CHECKSTYLE: resume ConstantName
+
+    /** Serializable UID. */
+    private static final long serialVersionUID = 20131218L;
+
+    /** Azimuthal angle \( \theta \) in the x-y plane. */
+    private final double theta;
+
+    /** Polar angle \( \varphi \). */
+    private final double phi;
+
+    /** Corresponding 3D normalized vector. */
+    private final Vector3D vector;
+
+    /** Simple constructor.
+     * Build a vector from its spherical coordinates
+     * @param theta azimuthal angle \( \theta \) in the x-y plane
+     * @param phi polar angle \( \varphi \)
+     * @see #getTheta()
+     * @see #getPhi()
+     * @exception OutOfRangeException if \( \varphi \) is not in the [\( 0; \pi \)] range
+     */
+    public S2Point(final double theta, final double phi)
+        throws OutOfRangeException {
+        this(theta, phi, vector(theta, phi));
+    }
+
+    /** Simple constructor.
+     * Build a vector from its underlying 3D vector
+     * @param vector 3D vector
+     * @exception MathArithmeticException if vector norm is zero
+     */
+    public S2Point(final Vector3D vector) throws MathArithmeticException {
+        this(FastMath.atan2(vector.getY(), vector.getX()), Vector3D.angle(Vector3D.PLUS_K, vector),
+             vector.normalize());
+    }
+
+    /** Build a point from its internal components.
+     * @param theta azimuthal angle \( \theta \) in the x-y plane
+     * @param phi polar angle \( \varphi \)
+     * @param vector corresponding vector
+     */
+    private S2Point(final double theta, final double phi, final Vector3D vector) {
+        this.theta  = theta;
+        this.phi    = phi;
+        this.vector = vector;
+    }
+
+    /** Build the normalized vector corresponding to spherical coordinates.
+     * @param theta azimuthal angle \( \theta \) in the x-y plane
+     * @param phi polar angle \( \varphi \)
+     * @return normalized vector
+     * @exception OutOfRangeException if \( \varphi \) is not in the [\( 0; \pi \)] range
+     */
+    private static Vector3D vector(final double theta, final double phi)
+       throws OutOfRangeException {
+
+        if (phi < 0 || phi > FastMath.PI) {
+            throw new OutOfRangeException(phi, 0, FastMath.PI);
+        }
+
+        final double cosTheta = FastMath.cos(theta);
+        final double sinTheta = FastMath.sin(theta);
+        final double cosPhi   = FastMath.cos(phi);
+        final double sinPhi   = FastMath.sin(phi);
+
+        return new Vector3D(cosTheta * sinPhi, sinTheta * sinPhi, cosPhi);
+
+    }
+
+    /** Get the azimuthal angle \( \theta \) in the x-y plane.
+     * @return azimuthal angle \( \theta \) in the x-y plane
+     * @see #S2Point(double, double)
+     */
+    public double getTheta() {
+        return theta;
+    }
+
+    /** Get the polar angle \( \varphi \).
+     * @return polar angle \( \varphi \)
+     * @see #S2Point(double, double)
+     */
+    public double getPhi() {
+        return phi;
+    }
+
+    /** Get the corresponding normalized vector in the 3D euclidean space.
+     * @return normalized vector
+     */
+    public Vector3D getVector() {
+        return vector;
+    }
+
+    /** {@inheritDoc} */
+    public Space getSpace() {
+        return Sphere2D.getInstance();
+    }
+
+    /** {@inheritDoc} */
+    public boolean isNaN() {
+        return Double.isNaN(theta) || Double.isNaN(phi);
+    }
+
+    /** Get the opposite of the instance.
+     * @return a new vector which is opposite to the instance
+     */
+    public S2Point negate() {
+        return new S2Point(-theta, FastMath.PI - phi, vector.negate());
+    }
+
+    /** {@inheritDoc} */
+    public double distance(final Point<Sphere2D> point) {
+        return distance(this, (S2Point) point);
+    }
+
+    /** Compute the distance (angular separation) between two points.
+     * @param p1 first vector
+     * @param p2 second vector
+     * @return the angular separation between p1 and p2
+     */
+    public static double distance(S2Point p1, S2Point p2) {
+        return Vector3D.angle(p1.vector, p2.vector);
+    }
+
+    /**
+     * Test for the equality of two points on the 2-sphere.
+     * <p>
+     * If all coordinates of two points are exactly the same, and none are
+     * <code>Double.NaN</code>, the two points are considered to be equal.
+     * </p>
+     * <p>
+     * <code>NaN</code> coordinates are considered to affect globally the vector
+     * and be equals to each other - i.e, if either (or all) coordinates of the
+     * 2D vector are equal to <code>Double.NaN</code>, the 2D vector is equal to
+     * {@link #NaN}.
+     * </p>
+     *
+     * @param other Object to test for equality to this
+     * @return true if two points on the 2-sphere objects are equal, false if
+     *         object is null, not an instance of S2Point, or
+     *         not equal to this S2Point instance
+     *
+     */
+    @Override
+    public boolean equals(Object other) {
+
+        if (this == other) {
+            return true;
+        }
+
+        if (other instanceof S2Point) {
+            final S2Point rhs = (S2Point) other;
+            if (rhs.isNaN()) {
+                return this.isNaN();
+            }
+
+            return (theta == rhs.theta) && (phi == rhs.phi);
+        }
+        return false;
+    }
+
+    /**
+     * Get a hashCode for the 2D vector.
+     * <p>
+     * All NaN values have the same hash code.</p>
+     *
+     * @return a hash code value for this object
+     */
+    @Override
+    public int hashCode() {
+        if (isNaN()) {
+            return 542;
+        }
+        return 134 * (37 * MathUtils.hash(theta) +  MathUtils.hash(phi));
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/spherical/twod/Sphere2D.java b/src/main/java/org/apache/commons/math3/geometry/spherical/twod/Sphere2D.java
new file mode 100644
index 0000000..93ff04e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/spherical/twod/Sphere2D.java
@@ -0,0 +1,80 @@
+/*
+ * 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.math3.geometry.spherical.twod;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.geometry.Space;
+import org.apache.commons.math3.geometry.spherical.oned.Sphere1D;
+
+/**
+ * This class implements a two-dimensional sphere (i.e. the regular sphere).
+ * <p>
+ * We use here the topologists definition of the 2-sphere (see
+ * <a href="http://mathworld.wolfram.com/Sphere.html">Sphere</a> on
+ * MathWorld), i.e. the 2-sphere is the two-dimensional surface
+ * defined in 3D as x<sup>2</sup>+y<sup>2</sup>+z<sup>2</sup>=1.
+ * </p>
+ * @since 3.3
+ */
+public class Sphere2D implements Serializable, Space {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 20131218L;
+
+    /** Private constructor for the singleton.
+     */
+    private Sphere2D() {
+    }
+
+    /** Get the unique instance.
+     * @return the unique instance
+     */
+    public static Sphere2D getInstance() {
+        return LazyHolder.INSTANCE;
+    }
+
+    /** {@inheritDoc} */
+    public int getDimension() {
+        return 2;
+    }
+
+    /** {@inheritDoc} */
+    public Sphere1D getSubSpace() {
+        return Sphere1D.getInstance();
+    }
+
+    // CHECKSTYLE: stop HideUtilityClassConstructor
+    /** Holder for the instance.
+     * <p>We use here the Initialization On Demand Holder Idiom.</p>
+     */
+    private static class LazyHolder {
+        /** Cached field instance. */
+        private static final Sphere2D INSTANCE = new Sphere2D();
+    }
+    // CHECKSTYLE: resume HideUtilityClassConstructor
+
+    /** Handle deserialization of the singleton.
+     * @return the singleton instance
+     */
+    private Object readResolve() {
+        // return the singleton instance
+        return LazyHolder.INSTANCE;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/spherical/twod/SphericalPolygonsSet.java b/src/main/java/org/apache/commons/math3/geometry/spherical/twod/SphericalPolygonsSet.java
new file mode 100644
index 0000000..8e41659
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/spherical/twod/SphericalPolygonsSet.java
@@ -0,0 +1,565 @@
+/*
+ * 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.math3.geometry.spherical.twod;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.geometry.enclosing.EnclosingBall;
+import org.apache.commons.math3.geometry.enclosing.WelzlEncloser;
+import org.apache.commons.math3.geometry.euclidean.threed.Euclidean3D;
+import org.apache.commons.math3.geometry.euclidean.threed.Rotation;
+import org.apache.commons.math3.geometry.euclidean.threed.RotationConvention;
+import org.apache.commons.math3.geometry.euclidean.threed.SphereGenerator;
+import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
+import org.apache.commons.math3.geometry.partitioning.AbstractRegion;
+import org.apache.commons.math3.geometry.partitioning.BSPTree;
+import org.apache.commons.math3.geometry.partitioning.BoundaryProjection;
+import org.apache.commons.math3.geometry.partitioning.RegionFactory;
+import org.apache.commons.math3.geometry.partitioning.SubHyperplane;
+import org.apache.commons.math3.geometry.spherical.oned.Sphere1D;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+
+/** This class represents a region on the 2-sphere: a set of spherical polygons.
+ * @since 3.3
+ */
+public class SphericalPolygonsSet extends AbstractRegion<Sphere2D, Sphere1D> {
+
+    /** Boundary defined as an array of closed loops start vertices. */
+    private List<Vertex> loops;
+
+    /** Build a polygons set representing the whole real 2-sphere.
+     * @param tolerance below which points are consider to be identical
+     */
+    public SphericalPolygonsSet(final double tolerance) {
+        super(tolerance);
+    }
+
+    /** Build a polygons set representing a hemisphere.
+     * @param pole pole of the hemisphere (the pole is in the inside half)
+     * @param tolerance below which points are consider to be identical
+     */
+    public SphericalPolygonsSet(final Vector3D pole, final double tolerance) {
+        super(new BSPTree<Sphere2D>(new Circle(pole, tolerance).wholeHyperplane(),
+                                    new BSPTree<Sphere2D>(Boolean.FALSE),
+                                    new BSPTree<Sphere2D>(Boolean.TRUE),
+                                    null),
+              tolerance);
+    }
+
+    /** Build a polygons set representing a regular polygon.
+     * @param center center of the polygon (the center is in the inside half)
+     * @param meridian point defining the reference meridian for first polygon vertex
+     * @param outsideRadius distance of the vertices to the center
+     * @param n number of sides of the polygon
+     * @param tolerance below which points are consider to be identical
+     */
+    public SphericalPolygonsSet(final Vector3D center, final Vector3D meridian,
+                                final double outsideRadius, final int n,
+                                final double tolerance) {
+        this(tolerance, createRegularPolygonVertices(center, meridian, outsideRadius, n));
+    }
+
+    /** Build a polygons set from a BSP tree.
+     * <p>The leaf nodes of the BSP tree <em>must</em> have a
+     * {@code Boolean} attribute representing the inside status of
+     * the corresponding cell (true for inside cells, false for outside
+     * cells). In order to avoid building too many small objects, it is
+     * recommended to use the predefined constants
+     * {@code Boolean.TRUE} and {@code Boolean.FALSE}</p>
+     * @param tree inside/outside BSP tree representing the region
+     * @param tolerance below which points are consider to be identical
+     */
+    public SphericalPolygonsSet(final BSPTree<Sphere2D> tree, final double tolerance) {
+        super(tree, tolerance);
+    }
+
+    /** Build a polygons set from a Boundary REPresentation (B-rep).
+     * <p>The boundary is provided as a collection of {@link
+     * SubHyperplane sub-hyperplanes}. Each sub-hyperplane has the
+     * interior part of the region on its minus side and the exterior on
+     * its plus side.</p>
+     * <p>The boundary elements can be in any order, and can form
+     * several non-connected sets (like for example polygons with holes
+     * or a set of disjoint polygons considered as a whole). In
+     * fact, the elements do not even need to be connected together
+     * (their topological connections are not used here). However, if the
+     * boundary does not really separate an inside open from an outside
+     * open (open having here its topological meaning), then subsequent
+     * calls to the {@link
+     * org.apache.commons.math3.geometry.partitioning.Region#checkPoint(org.apache.commons.math3.geometry.Point)
+     * checkPoint} method will not be meaningful anymore.</p>
+     * <p>If the boundary is empty, the region will represent the whole
+     * space.</p>
+     * @param boundary collection of boundary elements, as a
+     * collection of {@link SubHyperplane SubHyperplane} objects
+     * @param tolerance below which points are consider to be identical
+     */
+    public SphericalPolygonsSet(final Collection<SubHyperplane<Sphere2D>> boundary, final double tolerance) {
+        super(boundary, tolerance);
+    }
+
+    /** Build a polygon from a simple list of vertices.
+     * <p>The boundary is provided as a list of points considering to
+     * represent the vertices of a simple loop. The interior part of the
+     * region is on the left side of this path and the exterior is on its
+     * right side.</p>
+     * <p>This constructor does not handle polygons with a boundary
+     * forming several disconnected paths (such as polygons with holes).</p>
+     * <p>For cases where this simple constructor applies, it is expected to
+     * be numerically more robust than the {@link #SphericalPolygonsSet(Collection,
+     * double) general constructor} using {@link SubHyperplane subhyperplanes}.</p>
+     * <p>If the list is empty, the region will represent the whole
+     * space.</p>
+     * <p>
+     * Polygons with thin pikes or dents are inherently difficult to handle because
+     * they involve circles with almost opposite directions at some vertices. Polygons
+     * whose vertices come from some physical measurement with noise are also
+     * difficult because an edge that should be straight may be broken in lots of
+     * different pieces with almost equal directions. In both cases, computing the
+     * circles intersections is not numerically robust due to the almost 0 or almost
+     * &pi; angle. Such cases need to carefully adjust the {@code hyperplaneThickness}
+     * parameter. A too small value would often lead to completely wrong polygons
+     * with large area wrongly identified as inside or outside. Large values are
+     * often much safer. As a rule of thumb, a value slightly below the size of the
+     * most accurate detail needed is a good value for the {@code hyperplaneThickness}
+     * parameter.
+     * </p>
+     * @param hyperplaneThickness tolerance below which points are considered to
+     * belong to the hyperplane (which is therefore more a slab)
+     * @param vertices vertices of the simple loop boundary
+     */
+    public SphericalPolygonsSet(final double hyperplaneThickness, final S2Point ... vertices) {
+        super(verticesToTree(hyperplaneThickness, vertices), hyperplaneThickness);
+    }
+
+    /** Build the vertices representing a regular polygon.
+     * @param center center of the polygon (the center is in the inside half)
+     * @param meridian point defining the reference meridian for first polygon vertex
+     * @param outsideRadius distance of the vertices to the center
+     * @param n number of sides of the polygon
+     * @return vertices array
+     */
+    private static S2Point[] createRegularPolygonVertices(final Vector3D center, final Vector3D meridian,
+                                                          final double outsideRadius, final int n) {
+        final S2Point[] array = new S2Point[n];
+        final Rotation r0 = new Rotation(Vector3D.crossProduct(center, meridian),
+                                         outsideRadius, RotationConvention.VECTOR_OPERATOR);
+        array[0] = new S2Point(r0.applyTo(center));
+
+        final Rotation r = new Rotation(center, MathUtils.TWO_PI / n, RotationConvention.VECTOR_OPERATOR);
+        for (int i = 1; i < n; ++i) {
+            array[i] = new S2Point(r.applyTo(array[i - 1].getVector()));
+        }
+
+        return array;
+    }
+
+    /** Build the BSP tree of a polygons set from a simple list of vertices.
+     * <p>The boundary is provided as a list of points considering to
+     * represent the vertices of a simple loop. The interior part of the
+     * region is on the left side of this path and the exterior is on its
+     * right side.</p>
+     * <p>This constructor does not handle polygons with a boundary
+     * forming several disconnected paths (such as polygons with holes).</p>
+     * <p>This constructor handles only polygons with edges strictly shorter
+     * than \( \pi \). If longer edges are needed, they need to be broken up
+     * in smaller sub-edges so this constraint holds.</p>
+     * <p>For cases where this simple constructor applies, it is expected to
+     * be numerically more robust than the {@link #PolygonsSet(Collection) general
+     * constructor} using {@link SubHyperplane subhyperplanes}.</p>
+     * @param hyperplaneThickness tolerance below which points are consider to
+     * belong to the hyperplane (which is therefore more a slab)
+     * @param vertices vertices of the simple loop boundary
+     * @return the BSP tree of the input vertices
+     */
+    private static BSPTree<Sphere2D> verticesToTree(final double hyperplaneThickness,
+                                                    final S2Point ... vertices) {
+
+        final int n = vertices.length;
+        if (n == 0) {
+            // the tree represents the whole space
+            return new BSPTree<Sphere2D>(Boolean.TRUE);
+        }
+
+        // build the vertices
+        final Vertex[] vArray = new Vertex[n];
+        for (int i = 0; i < n; ++i) {
+            vArray[i] = new Vertex(vertices[i]);
+        }
+
+        // build the edges
+        List<Edge> edges = new ArrayList<Edge>(n);
+        Vertex end = vArray[n - 1];
+        for (int i = 0; i < n; ++i) {
+
+            // get the endpoints of the edge
+            final Vertex start = end;
+            end = vArray[i];
+
+            // get the circle supporting the edge, taking care not to recreate it
+            // if it was already created earlier due to another edge being aligned
+            // with the current one
+            Circle circle = start.sharedCircleWith(end);
+            if (circle == null) {
+                circle = new Circle(start.getLocation(), end.getLocation(), hyperplaneThickness);
+            }
+
+            // create the edge and store it
+            edges.add(new Edge(start, end,
+                               Vector3D.angle(start.getLocation().getVector(),
+                                              end.getLocation().getVector()),
+                               circle));
+
+            // check if another vertex also happens to be on this circle
+            for (final Vertex vertex : vArray) {
+                if (vertex != start && vertex != end &&
+                    FastMath.abs(circle.getOffset(vertex.getLocation())) <= hyperplaneThickness) {
+                    vertex.bindWith(circle);
+                }
+            }
+
+        }
+
+        // build the tree top-down
+        final BSPTree<Sphere2D> tree = new BSPTree<Sphere2D>();
+        insertEdges(hyperplaneThickness, tree, edges);
+
+        return tree;
+
+    }
+
+    /** Recursively build a tree by inserting cut sub-hyperplanes.
+     * @param hyperplaneThickness tolerance below which points are considered to
+     * belong to the hyperplane (which is therefore more a slab)
+     * @param node current tree node (it is a leaf node at the beginning
+     * of the call)
+     * @param edges list of edges to insert in the cell defined by this node
+     * (excluding edges not belonging to the cell defined by this node)
+     */
+    private static void insertEdges(final double hyperplaneThickness,
+                                    final BSPTree<Sphere2D> node,
+                                    final List<Edge> edges) {
+
+        // find an edge with an hyperplane that can be inserted in the node
+        int index = 0;
+        Edge inserted = null;
+        while (inserted == null && index < edges.size()) {
+            inserted = edges.get(index++);
+            if (!node.insertCut(inserted.getCircle())) {
+                inserted = null;
+            }
+        }
+
+        if (inserted == null) {
+            // no suitable edge was found, the node remains a leaf node
+            // we need to set its inside/outside boolean indicator
+            final BSPTree<Sphere2D> parent = node.getParent();
+            if (parent == null || node == parent.getMinus()) {
+                node.setAttribute(Boolean.TRUE);
+            } else {
+                node.setAttribute(Boolean.FALSE);
+            }
+            return;
+        }
+
+        // we have split the node by inserting an edge as a cut sub-hyperplane
+        // distribute the remaining edges in the two sub-trees
+        final List<Edge> outsideList = new ArrayList<Edge>();
+        final List<Edge> insideList  = new ArrayList<Edge>();
+        for (final Edge edge : edges) {
+            if (edge != inserted) {
+                edge.split(inserted.getCircle(), outsideList, insideList);
+            }
+        }
+
+        // recurse through lower levels
+        if (!outsideList.isEmpty()) {
+            insertEdges(hyperplaneThickness, node.getPlus(), outsideList);
+        } else {
+            node.getPlus().setAttribute(Boolean.FALSE);
+        }
+        if (!insideList.isEmpty()) {
+            insertEdges(hyperplaneThickness, node.getMinus(),  insideList);
+        } else {
+            node.getMinus().setAttribute(Boolean.TRUE);
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public SphericalPolygonsSet buildNew(final BSPTree<Sphere2D> tree) {
+        return new SphericalPolygonsSet(tree, getTolerance());
+    }
+
+    /** {@inheritDoc}
+     * @exception MathIllegalStateException if the tolerance setting does not allow to build
+     * a clean non-ambiguous boundary
+     */
+    @Override
+    protected void computeGeometricalProperties() throws MathIllegalStateException {
+
+        final BSPTree<Sphere2D> tree = getTree(true);
+
+        if (tree.getCut() == null) {
+
+            // the instance has a single cell without any boundaries
+
+            if (tree.getCut() == null && (Boolean) tree.getAttribute()) {
+                // the instance covers the whole space
+                setSize(4 * FastMath.PI);
+                setBarycenter(new S2Point(0, 0));
+            } else {
+                setSize(0);
+                setBarycenter(S2Point.NaN);
+            }
+
+        } else {
+
+            // the instance has a boundary
+            final PropertiesComputer pc = new PropertiesComputer(getTolerance());
+            tree.visit(pc);
+            setSize(pc.getArea());
+            setBarycenter(pc.getBarycenter());
+
+        }
+
+    }
+
+    /** Get the boundary loops of the polygon.
+     * <p>The polygon boundary can be represented as a list of closed loops,
+     * each loop being given by exactly one of its vertices. From each loop
+     * start vertex, one can follow the loop by finding the outgoing edge,
+     * then the end vertex, then the next outgoing edge ... until the start
+     * vertex of the loop (exactly the same instance) is found again once
+     * the full loop has been visited.</p>
+     * <p>If the polygon has no boundary at all, a zero length loop
+     * array will be returned.</p>
+     * <p>If the polygon is a simple one-piece polygon, then the returned
+     * array will contain a single vertex.
+     * </p>
+     * <p>All edges in the various loops have the inside of the region on
+     * their left side (i.e. toward their pole) and the outside on their
+     * right side (i.e. away from their pole) when moving in the underlying
+     * circle direction. This means that the closed loops obey the direct
+     * trigonometric orientation.</p>
+     * @return boundary of the polygon, organized as an unmodifiable list of loops start vertices.
+     * @exception MathIllegalStateException if the tolerance setting does not allow to build
+     * a clean non-ambiguous boundary
+     * @see Vertex
+     * @see Edge
+     */
+    public List<Vertex> getBoundaryLoops() throws MathIllegalStateException {
+
+        if (loops == null) {
+            if (getTree(false).getCut() == null) {
+                loops = Collections.emptyList();
+            } else {
+
+                // sort the arcs according to their start point
+                final BSPTree<Sphere2D> root = getTree(true);
+                final EdgesBuilder visitor = new EdgesBuilder(root, getTolerance());
+                root.visit(visitor);
+                final List<Edge> edges = visitor.getEdges();
+
+
+                // convert the list of all edges into a list of start vertices
+                loops = new ArrayList<Vertex>();
+                while (!edges.isEmpty()) {
+
+                    // this is an edge belonging to a new loop, store it
+                    Edge edge = edges.get(0);
+                    final Vertex startVertex = edge.getStart();
+                    loops.add(startVertex);
+
+                    // remove all remaining edges in the same loop
+                    do {
+
+                        // remove one edge
+                        for (final Iterator<Edge> iterator = edges.iterator(); iterator.hasNext();) {
+                            if (iterator.next() == edge) {
+                                iterator.remove();
+                                break;
+                            }
+                        }
+
+                        // go to next edge following the boundary loop
+                        edge = edge.getEnd().getOutgoing();
+
+                    } while (edge.getStart() != startVertex);
+
+                }
+
+            }
+        }
+
+        return Collections.unmodifiableList(loops);
+
+    }
+
+    /** Get a spherical cap enclosing the polygon.
+     * <p>
+     * This method is intended as a first test to quickly identify points
+     * that are guaranteed to be outside of the region, hence performing a full
+     * {@link #checkPoint(org.apache.commons.math3.geometry.Vector) checkPoint}
+     * only if the point status remains undecided after the quick check. It is
+     * is therefore mostly useful to speed up computation for small polygons with
+     * complex shapes (say a country boundary on Earth), as the spherical cap will
+     * be small and hence will reliably identify a large part of the sphere as outside,
+     * whereas the full check can be more computing intensive. A typical use case is
+     * therefore:
+     * </p>
+     * <pre>
+     *   // compute region, plus an enclosing spherical cap
+     *   SphericalPolygonsSet complexShape = ...;
+     *   EnclosingBall<Sphere2D, S2Point> cap = complexShape.getEnclosingCap();
+     *
+     *   // check lots of points
+     *   for (Vector3D p : points) {
+     *
+     *     final Location l;
+     *     if (cap.contains(p)) {
+     *       // we cannot be sure where the point is
+     *       // we need to perform the full computation
+     *       l = complexShape.checkPoint(v);
+     *     } else {
+     *       // no need to do further computation,
+     *       // we already know the point is outside
+     *       l = Location.OUTSIDE;
+     *     }
+     *
+     *     // use l ...
+     *
+     *   }
+     * </pre>
+     * <p>
+     * In the special cases of empty or whole sphere polygons, special
+     * spherical caps are returned, with angular radius set to negative
+     * or positive infinity so the {@link
+     * EnclosingBall#contains(org.apache.commons.math3.geometry.Point) ball.contains(point)}
+     * method return always false or true.
+     * </p>
+     * <p>
+     * This method is <em>not</em> guaranteed to return the smallest enclosing cap.
+     * </p>
+     * @return a spherical cap enclosing the polygon
+     */
+    public EnclosingBall<Sphere2D, S2Point> getEnclosingCap() {
+
+        // handle special cases first
+        if (isEmpty()) {
+            return new EnclosingBall<Sphere2D, S2Point>(S2Point.PLUS_K, Double.NEGATIVE_INFINITY);
+        }
+        if (isFull()) {
+            return new EnclosingBall<Sphere2D, S2Point>(S2Point.PLUS_K, Double.POSITIVE_INFINITY);
+        }
+
+        // as the polygons is neither empty nor full, it has some boundaries and cut hyperplanes
+        final BSPTree<Sphere2D> root = getTree(false);
+        if (isEmpty(root.getMinus()) && isFull(root.getPlus())) {
+            // the polygon covers an hemisphere, and its boundary is one 2π long edge
+            final Circle circle = (Circle) root.getCut().getHyperplane();
+            return new EnclosingBall<Sphere2D, S2Point>(new S2Point(circle.getPole()).negate(),
+                                                        0.5 * FastMath.PI);
+        }
+        if (isFull(root.getMinus()) && isEmpty(root.getPlus())) {
+            // the polygon covers an hemisphere, and its boundary is one 2π long edge
+            final Circle circle = (Circle) root.getCut().getHyperplane();
+            return new EnclosingBall<Sphere2D, S2Point>(new S2Point(circle.getPole()),
+                                                        0.5 * FastMath.PI);
+        }
+
+        // gather some inside points, to be used by the encloser
+        final List<Vector3D> points = getInsidePoints();
+
+        // extract points from the boundary loops, to be used by the encloser as well
+        final List<Vertex> boundary = getBoundaryLoops();
+        for (final Vertex loopStart : boundary) {
+            int count = 0;
+            for (Vertex v = loopStart; count == 0 || v != loopStart; v = v.getOutgoing().getEnd()) {
+                ++count;
+                points.add(v.getLocation().getVector());
+            }
+        }
+
+        // find the smallest enclosing 3D sphere
+        final SphereGenerator generator = new SphereGenerator();
+        final WelzlEncloser<Euclidean3D, Vector3D> encloser =
+                new WelzlEncloser<Euclidean3D, Vector3D>(getTolerance(), generator);
+        EnclosingBall<Euclidean3D, Vector3D> enclosing3D = encloser.enclose(points);
+        final Vector3D[] support3D = enclosing3D.getSupport();
+
+        // convert to 3D sphere to spherical cap
+        final double r = enclosing3D.getRadius();
+        final double h = enclosing3D.getCenter().getNorm();
+        if (h < getTolerance()) {
+            // the 3D sphere is centered on the unit sphere and covers it
+            // fall back to a crude approximation, based only on outside convex cells
+            EnclosingBall<Sphere2D, S2Point> enclosingS2 =
+                    new EnclosingBall<Sphere2D, S2Point>(S2Point.PLUS_K, Double.POSITIVE_INFINITY);
+            for (Vector3D outsidePoint : getOutsidePoints()) {
+                final S2Point outsideS2 = new S2Point(outsidePoint);
+                final BoundaryProjection<Sphere2D> projection = projectToBoundary(outsideS2);
+                if (FastMath.PI - projection.getOffset() < enclosingS2.getRadius()) {
+                    enclosingS2 = new EnclosingBall<Sphere2D, S2Point>(outsideS2.negate(),
+                                                                       FastMath.PI - projection.getOffset(),
+                                                                       (S2Point) projection.getProjected());
+                }
+            }
+            return enclosingS2;
+        }
+        final S2Point[] support = new S2Point[support3D.length];
+        for (int i = 0; i < support3D.length; ++i) {
+            support[i] = new S2Point(support3D[i]);
+        }
+
+        final EnclosingBall<Sphere2D, S2Point> enclosingS2 =
+                new EnclosingBall<Sphere2D, S2Point>(new S2Point(enclosing3D.getCenter()),
+                                                     FastMath.acos((1 + h * h - r * r) / (2 * h)),
+                                                     support);
+
+        return enclosingS2;
+
+    }
+
+    /** Gather some inside points.
+     * @return list of points known to be strictly in all inside convex cells
+     */
+    private List<Vector3D> getInsidePoints() {
+        final PropertiesComputer pc = new PropertiesComputer(getTolerance());
+        getTree(true).visit(pc);
+        return pc.getConvexCellsInsidePoints();
+    }
+
+    /** Gather some outside points.
+     * @return list of points known to be strictly in all outside convex cells
+     */
+    private List<Vector3D> getOutsidePoints() {
+        final SphericalPolygonsSet complement =
+                (SphericalPolygonsSet) new RegionFactory<Sphere2D>().getComplement(this);
+        final PropertiesComputer pc = new PropertiesComputer(getTolerance());
+        complement.getTree(true).visit(pc);
+        return pc.getConvexCellsInsidePoints();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/spherical/twod/SubCircle.java b/src/main/java/org/apache/commons/math3/geometry/spherical/twod/SubCircle.java
new file mode 100644
index 0000000..97164cc
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/spherical/twod/SubCircle.java
@@ -0,0 +1,72 @@
+/*
+ * 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.math3.geometry.spherical.twod;
+
+import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
+import org.apache.commons.math3.geometry.partitioning.AbstractSubHyperplane;
+import org.apache.commons.math3.geometry.partitioning.Hyperplane;
+import org.apache.commons.math3.geometry.partitioning.Region;
+import org.apache.commons.math3.geometry.spherical.oned.Arc;
+import org.apache.commons.math3.geometry.spherical.oned.ArcsSet;
+import org.apache.commons.math3.geometry.spherical.oned.Sphere1D;
+import org.apache.commons.math3.util.FastMath;
+
+/** This class represents a sub-hyperplane for {@link Circle}.
+ * @since 3.3
+ */
+public class SubCircle extends AbstractSubHyperplane<Sphere2D, Sphere1D> {
+
+    /** Simple constructor.
+     * @param hyperplane underlying hyperplane
+     * @param remainingRegion remaining region of the hyperplane
+     */
+    public SubCircle(final Hyperplane<Sphere2D> hyperplane,
+                     final Region<Sphere1D> remainingRegion) {
+        super(hyperplane, remainingRegion);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected AbstractSubHyperplane<Sphere2D, Sphere1D> buildNew(final Hyperplane<Sphere2D> hyperplane,
+                                                                 final Region<Sphere1D> remainingRegion) {
+        return new SubCircle(hyperplane, remainingRegion);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public SplitSubHyperplane<Sphere2D> split(final Hyperplane<Sphere2D> hyperplane) {
+
+        final Circle thisCircle   = (Circle) getHyperplane();
+        final Circle otherCircle  = (Circle) hyperplane;
+        final double angle = Vector3D.angle(thisCircle.getPole(), otherCircle.getPole());
+
+        if (angle < thisCircle.getTolerance() || angle > FastMath.PI - thisCircle.getTolerance()) {
+            // the two circles are aligned or opposite
+            return new SplitSubHyperplane<Sphere2D>(null, null);
+        } else {
+            // the two circles intersect each other
+            final Arc    arc          = thisCircle.getInsideArc(otherCircle);
+            final ArcsSet.Split split = ((ArcsSet) getRemainingRegion()).split(arc);
+            final ArcsSet plus        = split.getPlus();
+            final ArcsSet minus       = split.getMinus();
+            return new SplitSubHyperplane<Sphere2D>(plus  == null ? null : new SubCircle(thisCircle.copySelf(), plus),
+                                                    minus == null ? null : new SubCircle(thisCircle.copySelf(), minus));
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/spherical/twod/Vertex.java b/src/main/java/org/apache/commons/math3/geometry/spherical/twod/Vertex.java
new file mode 100644
index 0000000..3003da8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/spherical/twod/Vertex.java
@@ -0,0 +1,124 @@
+/*
+ * 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.math3.geometry.spherical.twod;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Spherical polygons boundary vertex.
+ * @see SphericalPolygonsSet#getBoundaryLoops()
+ * @see Edge
+ * @since 3.3
+ */
+public class Vertex {
+
+    /** Vertex location. */
+    private final S2Point location;
+
+    /** Incoming edge. */
+    private Edge incoming;
+
+    /** Outgoing edge. */
+    private Edge outgoing;
+
+    /** Circles bound with this vertex. */
+    private final List<Circle> circles;
+
+    /** Build a non-processed vertex not owned by any node yet.
+     * @param location vertex location
+     */
+    Vertex(final S2Point location) {
+        this.location = location;
+        this.incoming = null;
+        this.outgoing = null;
+        this.circles  = new ArrayList<Circle>();
+    }
+
+    /** Get Vertex location.
+     * @return vertex location
+     */
+    public S2Point getLocation() {
+        return location;
+    }
+
+    /** Bind a circle considered to contain this vertex.
+     * @param circle circle to bind with this vertex
+     */
+    void bindWith(final Circle circle) {
+        circles.add(circle);
+    }
+
+    /** Get the common circle bound with both the instance and another vertex, if any.
+     * <p>
+     * When two vertices are both bound to the same circle, this means they are
+     * already handled by node associated with this circle, so there is no need
+     * to create a cut hyperplane for them.
+     * </p>
+     * @param vertex other vertex to check instance against
+     * @return circle bound with both the instance and another vertex, or null if the
+     * two vertices do not share a circle yet
+     */
+    Circle sharedCircleWith(final Vertex vertex) {
+        for (final Circle circle1 : circles) {
+            for (final Circle circle2 : vertex.circles) {
+                if (circle1 == circle2) {
+                    return circle1;
+                }
+            }
+        }
+        return null;
+    }
+
+    /** Set incoming edge.
+     * <p>
+     * The circle supporting the incoming edge is automatically bound
+     * with the instance.
+     * </p>
+     * @param incoming incoming edge
+     */
+    void setIncoming(final Edge incoming) {
+        this.incoming = incoming;
+        bindWith(incoming.getCircle());
+    }
+
+    /** Get incoming edge.
+     * @return incoming edge
+     */
+    public Edge getIncoming() {
+        return incoming;
+    }
+
+    /** Set outgoing edge.
+     * <p>
+     * The circle supporting the outgoing edge is automatically bound
+     * with the instance.
+     * </p>
+     * @param outgoing outgoing edge
+     */
+    void setOutgoing(final Edge outgoing) {
+        this.outgoing = outgoing;
+        bindWith(outgoing.getCircle());
+    }
+
+    /** Get outgoing edge.
+     * @return outgoing edge
+     */
+    public Edge getOutgoing() {
+        return outgoing;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/spherical/twod/package-info.java b/src/main/java/org/apache/commons/math3/geometry/spherical/twod/package-info.java
new file mode 100644
index 0000000..3f3c5b0
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/spherical/twod/package-info.java
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * <p>
+ * This package provides basic geometry components on the 2-sphere.
+ * </p>
+ * <p>
+ * We use here the topologists definition of the 2-sphere (see
+ * <a href="http://mathworld.wolfram.com/Sphere.html">Sphere</a> on
+ * MathWorld), i.e. the 2-sphere is the two-dimensional surface
+ * defined in 3D as x<sup>2</sup>+y<sup>2</sup>+z<sup>2</sup>=1.
+ * </p>
+ *
+ */
+package org.apache.commons.math3.geometry.spherical.twod;
diff --git a/src/main/java/org/apache/commons/math3/linear/AbstractFieldMatrix.java b/src/main/java/org/apache/commons/math3/linear/AbstractFieldMatrix.java
new file mode 100644
index 0000000..355d72d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/AbstractFieldMatrix.java
@@ -0,0 +1,1155 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.FieldElement;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.MathArrays;
+
+import java.util.ArrayList;
+
+/**
+ * Basic implementation of {@link FieldMatrix} methods regardless of the underlying storage.
+ *
+ * <p>All the methods implemented here use {@link #getEntry(int, int)} to access matrix elements.
+ * Derived class can provide faster implementations.
+ *
+ * @param <T> Type of the field elements.
+ * @since 2.0
+ */
+public abstract class AbstractFieldMatrix<T extends FieldElement<T>> implements FieldMatrix<T> {
+    /** Field to which the elements belong. */
+    private final Field<T> field;
+
+    /** Constructor for use with Serializable */
+    protected AbstractFieldMatrix() {
+        field = null;
+    }
+
+    /**
+     * Creates a matrix with no data
+     *
+     * @param field field to which the elements belong
+     */
+    protected AbstractFieldMatrix(final Field<T> field) {
+        this.field = field;
+    }
+
+    /**
+     * Create a new FieldMatrix<T> with the supplied row and column dimensions.
+     *
+     * @param field Field to which the elements belong.
+     * @param rowDimension Number of rows in the new matrix.
+     * @param columnDimension Number of columns in the new matrix.
+     * @throws NotStrictlyPositiveException if row or column dimension is not positive.
+     */
+    protected AbstractFieldMatrix(
+            final Field<T> field, final int rowDimension, final int columnDimension)
+            throws NotStrictlyPositiveException {
+        if (rowDimension <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.DIMENSION, rowDimension);
+        }
+        if (columnDimension <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.DIMENSION, columnDimension);
+        }
+        this.field = field;
+    }
+
+    /**
+     * Get the elements type from an array.
+     *
+     * @param <T> Type of the field elements.
+     * @param d Data array.
+     * @return the field to which the array elements belong.
+     * @throws NullArgumentException if the array is {@code null}.
+     * @throws NoDataException if the array is empty.
+     */
+    protected static <T extends FieldElement<T>> Field<T> extractField(final T[][] d)
+            throws NoDataException, NullArgumentException {
+        if (d == null) {
+            throw new NullArgumentException();
+        }
+        if (d.length == 0) {
+            throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_ROW);
+        }
+        if (d[0].length == 0) {
+            throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_COLUMN);
+        }
+        return d[0][0].getField();
+    }
+
+    /**
+     * Get the elements type from an array.
+     *
+     * @param <T> Type of the field elements.
+     * @param d Data array.
+     * @return the field to which the array elements belong.
+     * @throws NoDataException if array is empty.
+     */
+    protected static <T extends FieldElement<T>> Field<T> extractField(final T[] d)
+            throws NoDataException {
+        if (d.length == 0) {
+            throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_ROW);
+        }
+        return d[0].getField();
+    }
+
+    /**
+     * Build an array of elements.
+     *
+     * <p>Complete arrays are filled with field.getZero()
+     *
+     * @param <T> Type of the field elements
+     * @param field field to which array elements belong
+     * @param rows number of rows
+     * @param columns number of columns (may be negative to build partial arrays in the same way
+     *     <code>new Field[rows][]</code> works)
+     * @return a new array
+     * @deprecated as of 3.2, replaced by {@link MathArrays#buildArray(Field, int, int)}
+     */
+    @Deprecated
+    protected static <T extends FieldElement<T>> T[][] buildArray(
+            final Field<T> field, final int rows, final int columns) {
+        return MathArrays.buildArray(field, rows, columns);
+    }
+
+    /**
+     * Build an array of elements.
+     *
+     * <p>Arrays are filled with field.getZero()
+     *
+     * @param <T> the type of the field elements
+     * @param field field to which array elements belong
+     * @param length of the array
+     * @return a new array
+     * @deprecated as of 3.2, replaced by {@link MathArrays#buildArray(Field, int)}
+     */
+    @Deprecated
+    protected static <T extends FieldElement<T>> T[] buildArray(
+            final Field<T> field, final int length) {
+        return MathArrays.buildArray(field, length);
+    }
+
+    /** {@inheritDoc} */
+    public Field<T> getField() {
+        return field;
+    }
+
+    /** {@inheritDoc} */
+    public abstract FieldMatrix<T> createMatrix(final int rowDimension, final int columnDimension)
+            throws NotStrictlyPositiveException;
+
+    /** {@inheritDoc} */
+    public abstract FieldMatrix<T> copy();
+
+    /** {@inheritDoc} */
+    public FieldMatrix<T> add(FieldMatrix<T> m) throws MatrixDimensionMismatchException {
+        // safety check
+        checkAdditionCompatible(m);
+
+        final int rowCount = getRowDimension();
+        final int columnCount = getColumnDimension();
+        final FieldMatrix<T> out = createMatrix(rowCount, columnCount);
+        for (int row = 0; row < rowCount; ++row) {
+            for (int col = 0; col < columnCount; ++col) {
+                out.setEntry(row, col, getEntry(row, col).add(m.getEntry(row, col)));
+            }
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    public FieldMatrix<T> subtract(final FieldMatrix<T> m) throws MatrixDimensionMismatchException {
+        // safety check
+        checkSubtractionCompatible(m);
+
+        final int rowCount = getRowDimension();
+        final int columnCount = getColumnDimension();
+        final FieldMatrix<T> out = createMatrix(rowCount, columnCount);
+        for (int row = 0; row < rowCount; ++row) {
+            for (int col = 0; col < columnCount; ++col) {
+                out.setEntry(row, col, getEntry(row, col).subtract(m.getEntry(row, col)));
+            }
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    public FieldMatrix<T> scalarAdd(final T d) {
+
+        final int rowCount = getRowDimension();
+        final int columnCount = getColumnDimension();
+        final FieldMatrix<T> out = createMatrix(rowCount, columnCount);
+        for (int row = 0; row < rowCount; ++row) {
+            for (int col = 0; col < columnCount; ++col) {
+                out.setEntry(row, col, getEntry(row, col).add(d));
+            }
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    public FieldMatrix<T> scalarMultiply(final T d) {
+        final int rowCount = getRowDimension();
+        final int columnCount = getColumnDimension();
+        final FieldMatrix<T> out = createMatrix(rowCount, columnCount);
+        for (int row = 0; row < rowCount; ++row) {
+            for (int col = 0; col < columnCount; ++col) {
+                out.setEntry(row, col, getEntry(row, col).multiply(d));
+            }
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    public FieldMatrix<T> multiply(final FieldMatrix<T> m) throws DimensionMismatchException {
+        // safety check
+        checkMultiplicationCompatible(m);
+
+        final int nRows = getRowDimension();
+        final int nCols = m.getColumnDimension();
+        final int nSum = getColumnDimension();
+        final FieldMatrix<T> out = createMatrix(nRows, nCols);
+        for (int row = 0; row < nRows; ++row) {
+            for (int col = 0; col < nCols; ++col) {
+                T sum = field.getZero();
+                for (int i = 0; i < nSum; ++i) {
+                    sum = sum.add(getEntry(row, i).multiply(m.getEntry(i, col)));
+                }
+                out.setEntry(row, col, sum);
+            }
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    public FieldMatrix<T> preMultiply(final FieldMatrix<T> m) throws DimensionMismatchException {
+        return m.multiply(this);
+    }
+
+    /** {@inheritDoc} */
+    public FieldMatrix<T> power(final int p) throws NonSquareMatrixException, NotPositiveException {
+        if (p < 0) {
+            throw new NotPositiveException(p);
+        }
+
+        if (!isSquare()) {
+            throw new NonSquareMatrixException(getRowDimension(), getColumnDimension());
+        }
+
+        if (p == 0) {
+            return MatrixUtils.createFieldIdentityMatrix(this.getField(), this.getRowDimension());
+        }
+
+        if (p == 1) {
+            return this.copy();
+        }
+
+        final int power = p - 1;
+
+        /*
+         * Only log_2(p) operations is used by doing as follows:
+         * 5^214 = 5^128 * 5^64 * 5^16 * 5^4 * 5^2
+         *
+         * In general, the same approach is used for A^p.
+         */
+
+        final char[] binaryRepresentation = Integer.toBinaryString(power).toCharArray();
+        final ArrayList<Integer> nonZeroPositions = new ArrayList<Integer>();
+
+        for (int i = 0; i < binaryRepresentation.length; ++i) {
+            if (binaryRepresentation[i] == '1') {
+                final int pos = binaryRepresentation.length - i - 1;
+                nonZeroPositions.add(pos);
+            }
+        }
+
+        ArrayList<FieldMatrix<T>> results =
+                new ArrayList<FieldMatrix<T>>(binaryRepresentation.length);
+
+        results.add(0, this.copy());
+
+        for (int i = 1; i < binaryRepresentation.length; ++i) {
+            final FieldMatrix<T> s = results.get(i - 1);
+            final FieldMatrix<T> r = s.multiply(s);
+            results.add(i, r);
+        }
+
+        FieldMatrix<T> result = this.copy();
+
+        for (Integer i : nonZeroPositions) {
+            result = result.multiply(results.get(i));
+        }
+
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    public T[][] getData() {
+        final T[][] data = MathArrays.buildArray(field, getRowDimension(), getColumnDimension());
+
+        for (int i = 0; i < data.length; ++i) {
+            final T[] dataI = data[i];
+            for (int j = 0; j < dataI.length; ++j) {
+                dataI[j] = getEntry(i, j);
+            }
+        }
+
+        return data;
+    }
+
+    /** {@inheritDoc} */
+    public FieldMatrix<T> getSubMatrix(
+            final int startRow, final int endRow, final int startColumn, final int endColumn)
+            throws NumberIsTooSmallException, OutOfRangeException {
+        checkSubMatrixIndex(startRow, endRow, startColumn, endColumn);
+
+        final FieldMatrix<T> subMatrix =
+                createMatrix(endRow - startRow + 1, endColumn - startColumn + 1);
+        for (int i = startRow; i <= endRow; ++i) {
+            for (int j = startColumn; j <= endColumn; ++j) {
+                subMatrix.setEntry(i - startRow, j - startColumn, getEntry(i, j));
+            }
+        }
+
+        return subMatrix;
+    }
+
+    /** {@inheritDoc} */
+    public FieldMatrix<T> getSubMatrix(final int[] selectedRows, final int[] selectedColumns)
+            throws NoDataException, NullArgumentException, OutOfRangeException {
+
+        // safety checks
+        checkSubMatrixIndex(selectedRows, selectedColumns);
+
+        // copy entries
+        final FieldMatrix<T> subMatrix = createMatrix(selectedRows.length, selectedColumns.length);
+        subMatrix.walkInOptimizedOrder(
+                new DefaultFieldMatrixChangingVisitor<T>(field.getZero()) {
+
+                    /** {@inheritDoc} */
+                    @Override
+                    public T visit(final int row, final int column, final T value) {
+                        return getEntry(selectedRows[row], selectedColumns[column]);
+                    }
+                });
+
+        return subMatrix;
+    }
+
+    /** {@inheritDoc} */
+    public void copySubMatrix(
+            final int startRow,
+            final int endRow,
+            final int startColumn,
+            final int endColumn,
+            final T[][] destination)
+            throws MatrixDimensionMismatchException,
+                    NumberIsTooSmallException,
+                    OutOfRangeException {
+        // safety checks
+        checkSubMatrixIndex(startRow, endRow, startColumn, endColumn);
+        final int rowsCount = endRow + 1 - startRow;
+        final int columnsCount = endColumn + 1 - startColumn;
+        if ((destination.length < rowsCount) || (destination[0].length < columnsCount)) {
+            throw new MatrixDimensionMismatchException(
+                    destination.length, destination[0].length, rowsCount, columnsCount);
+        }
+
+        // copy entries
+        walkInOptimizedOrder(
+                new DefaultFieldMatrixPreservingVisitor<T>(field.getZero()) {
+
+                    /** Initial row index. */
+                    private int startRow;
+
+                    /** Initial column index. */
+                    private int startColumn;
+
+                    /** {@inheritDoc} */
+                    @Override
+                    public void start(
+                            final int rows,
+                            final int columns,
+                            final int startRow,
+                            final int endRow,
+                            final int startColumn,
+                            final int endColumn) {
+                        this.startRow = startRow;
+                        this.startColumn = startColumn;
+                    }
+
+                    /** {@inheritDoc} */
+                    @Override
+                    public void visit(final int row, final int column, final T value) {
+                        destination[row - startRow][column - startColumn] = value;
+                    }
+                },
+                startRow,
+                endRow,
+                startColumn,
+                endColumn);
+    }
+
+    /** {@inheritDoc} */
+    public void copySubMatrix(int[] selectedRows, int[] selectedColumns, T[][] destination)
+            throws MatrixDimensionMismatchException,
+                    NoDataException,
+                    NullArgumentException,
+                    OutOfRangeException {
+        // safety checks
+        checkSubMatrixIndex(selectedRows, selectedColumns);
+        if ((destination.length < selectedRows.length)
+                || (destination[0].length < selectedColumns.length)) {
+            throw new MatrixDimensionMismatchException(
+                    destination.length,
+                    destination[0].length,
+                    selectedRows.length,
+                    selectedColumns.length);
+        }
+
+        // copy entries
+        for (int i = 0; i < selectedRows.length; i++) {
+            final T[] destinationI = destination[i];
+            for (int j = 0; j < selectedColumns.length; j++) {
+                destinationI[j] = getEntry(selectedRows[i], selectedColumns[j]);
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void setSubMatrix(final T[][] subMatrix, final int row, final int column)
+            throws DimensionMismatchException,
+                    OutOfRangeException,
+                    NoDataException,
+                    NullArgumentException {
+        if (subMatrix == null) {
+            throw new NullArgumentException();
+        }
+        final int nRows = subMatrix.length;
+        if (nRows == 0) {
+            throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_ROW);
+        }
+
+        final int nCols = subMatrix[0].length;
+        if (nCols == 0) {
+            throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_COLUMN);
+        }
+
+        for (int r = 1; r < nRows; ++r) {
+            if (subMatrix[r].length != nCols) {
+                throw new DimensionMismatchException(nCols, subMatrix[r].length);
+            }
+        }
+
+        checkRowIndex(row);
+        checkColumnIndex(column);
+        checkRowIndex(nRows + row - 1);
+        checkColumnIndex(nCols + column - 1);
+
+        for (int i = 0; i < nRows; ++i) {
+            for (int j = 0; j < nCols; ++j) {
+                setEntry(row + i, column + j, subMatrix[i][j]);
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    public FieldMatrix<T> getRowMatrix(final int row) throws OutOfRangeException {
+        checkRowIndex(row);
+        final int nCols = getColumnDimension();
+        final FieldMatrix<T> out = createMatrix(1, nCols);
+        for (int i = 0; i < nCols; ++i) {
+            out.setEntry(0, i, getEntry(row, i));
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    public void setRowMatrix(final int row, final FieldMatrix<T> matrix)
+            throws OutOfRangeException, MatrixDimensionMismatchException {
+        checkRowIndex(row);
+        final int nCols = getColumnDimension();
+        if ((matrix.getRowDimension() != 1) || (matrix.getColumnDimension() != nCols)) {
+            throw new MatrixDimensionMismatchException(
+                    matrix.getRowDimension(), matrix.getColumnDimension(), 1, nCols);
+        }
+        for (int i = 0; i < nCols; ++i) {
+            setEntry(row, i, matrix.getEntry(0, i));
+        }
+    }
+
+    /** {@inheritDoc} */
+    public FieldMatrix<T> getColumnMatrix(final int column) throws OutOfRangeException {
+
+        checkColumnIndex(column);
+        final int nRows = getRowDimension();
+        final FieldMatrix<T> out = createMatrix(nRows, 1);
+        for (int i = 0; i < nRows; ++i) {
+            out.setEntry(i, 0, getEntry(i, column));
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    public void setColumnMatrix(final int column, final FieldMatrix<T> matrix)
+            throws OutOfRangeException, MatrixDimensionMismatchException {
+        checkColumnIndex(column);
+        final int nRows = getRowDimension();
+        if ((matrix.getRowDimension() != nRows) || (matrix.getColumnDimension() != 1)) {
+            throw new MatrixDimensionMismatchException(
+                    matrix.getRowDimension(), matrix.getColumnDimension(), nRows, 1);
+        }
+        for (int i = 0; i < nRows; ++i) {
+            setEntry(i, column, matrix.getEntry(i, 0));
+        }
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> getRowVector(final int row) throws OutOfRangeException {
+        return new ArrayFieldVector<T>(field, getRow(row), false);
+    }
+
+    /** {@inheritDoc} */
+    public void setRowVector(final int row, final FieldVector<T> vector)
+            throws OutOfRangeException, MatrixDimensionMismatchException {
+        checkRowIndex(row);
+        final int nCols = getColumnDimension();
+        if (vector.getDimension() != nCols) {
+            throw new MatrixDimensionMismatchException(1, vector.getDimension(), 1, nCols);
+        }
+        for (int i = 0; i < nCols; ++i) {
+            setEntry(row, i, vector.getEntry(i));
+        }
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> getColumnVector(final int column) throws OutOfRangeException {
+        return new ArrayFieldVector<T>(field, getColumn(column), false);
+    }
+
+    /** {@inheritDoc} */
+    public void setColumnVector(final int column, final FieldVector<T> vector)
+            throws OutOfRangeException, MatrixDimensionMismatchException {
+
+        checkColumnIndex(column);
+        final int nRows = getRowDimension();
+        if (vector.getDimension() != nRows) {
+            throw new MatrixDimensionMismatchException(vector.getDimension(), 1, nRows, 1);
+        }
+        for (int i = 0; i < nRows; ++i) {
+            setEntry(i, column, vector.getEntry(i));
+        }
+    }
+
+    /** {@inheritDoc} */
+    public T[] getRow(final int row) throws OutOfRangeException {
+        checkRowIndex(row);
+        final int nCols = getColumnDimension();
+        final T[] out = MathArrays.buildArray(field, nCols);
+        for (int i = 0; i < nCols; ++i) {
+            out[i] = getEntry(row, i);
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    public void setRow(final int row, final T[] array)
+            throws OutOfRangeException, MatrixDimensionMismatchException {
+        checkRowIndex(row);
+        final int nCols = getColumnDimension();
+        if (array.length != nCols) {
+            throw new MatrixDimensionMismatchException(1, array.length, 1, nCols);
+        }
+        for (int i = 0; i < nCols; ++i) {
+            setEntry(row, i, array[i]);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public T[] getColumn(final int column) throws OutOfRangeException {
+        checkColumnIndex(column);
+        final int nRows = getRowDimension();
+        final T[] out = MathArrays.buildArray(field, nRows);
+        for (int i = 0; i < nRows; ++i) {
+            out[i] = getEntry(i, column);
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    public void setColumn(final int column, final T[] array)
+            throws OutOfRangeException, MatrixDimensionMismatchException {
+        checkColumnIndex(column);
+        final int nRows = getRowDimension();
+        if (array.length != nRows) {
+            throw new MatrixDimensionMismatchException(array.length, 1, nRows, 1);
+        }
+        for (int i = 0; i < nRows; ++i) {
+            setEntry(i, column, array[i]);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public abstract T getEntry(int row, int column) throws OutOfRangeException;
+
+    /** {@inheritDoc} */
+    public abstract void setEntry(int row, int column, T value) throws OutOfRangeException;
+
+    /** {@inheritDoc} */
+    public abstract void addToEntry(int row, int column, T increment) throws OutOfRangeException;
+
+    /** {@inheritDoc} */
+    public abstract void multiplyEntry(int row, int column, T factor) throws OutOfRangeException;
+
+    /** {@inheritDoc} */
+    public FieldMatrix<T> transpose() {
+        final int nRows = getRowDimension();
+        final int nCols = getColumnDimension();
+        final FieldMatrix<T> out = createMatrix(nCols, nRows);
+        walkInOptimizedOrder(
+                new DefaultFieldMatrixPreservingVisitor<T>(field.getZero()) {
+                    /** {@inheritDoc} */
+                    @Override
+                    public void visit(final int row, final int column, final T value) {
+                        out.setEntry(column, row, value);
+                    }
+                });
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSquare() {
+        return getColumnDimension() == getRowDimension();
+    }
+
+    /** {@inheritDoc} */
+    public abstract int getRowDimension();
+
+    /** {@inheritDoc} */
+    public abstract int getColumnDimension();
+
+    /** {@inheritDoc} */
+    public T getTrace() throws NonSquareMatrixException {
+        final int nRows = getRowDimension();
+        final int nCols = getColumnDimension();
+        if (nRows != nCols) {
+            throw new NonSquareMatrixException(nRows, nCols);
+        }
+        T trace = field.getZero();
+        for (int i = 0; i < nRows; ++i) {
+            trace = trace.add(getEntry(i, i));
+        }
+        return trace;
+    }
+
+    /** {@inheritDoc} */
+    public T[] operate(final T[] v) throws DimensionMismatchException {
+
+        final int nRows = getRowDimension();
+        final int nCols = getColumnDimension();
+        if (v.length != nCols) {
+            throw new DimensionMismatchException(v.length, nCols);
+        }
+
+        final T[] out = MathArrays.buildArray(field, nRows);
+        for (int row = 0; row < nRows; ++row) {
+            T sum = field.getZero();
+            for (int i = 0; i < nCols; ++i) {
+                sum = sum.add(getEntry(row, i).multiply(v[i]));
+            }
+            out[row] = sum;
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> operate(final FieldVector<T> v) throws DimensionMismatchException {
+        try {
+            return new ArrayFieldVector<T>(
+                    field, operate(((ArrayFieldVector<T>) v).getDataRef()), false);
+        } catch (ClassCastException cce) {
+            final int nRows = getRowDimension();
+            final int nCols = getColumnDimension();
+            if (v.getDimension() != nCols) {
+                throw new DimensionMismatchException(v.getDimension(), nCols);
+            }
+
+            final T[] out = MathArrays.buildArray(field, nRows);
+            for (int row = 0; row < nRows; ++row) {
+                T sum = field.getZero();
+                for (int i = 0; i < nCols; ++i) {
+                    sum = sum.add(getEntry(row, i).multiply(v.getEntry(i)));
+                }
+                out[row] = sum;
+            }
+
+            return new ArrayFieldVector<T>(field, out, false);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public T[] preMultiply(final T[] v) throws DimensionMismatchException {
+
+        final int nRows = getRowDimension();
+        final int nCols = getColumnDimension();
+        if (v.length != nRows) {
+            throw new DimensionMismatchException(v.length, nRows);
+        }
+
+        final T[] out = MathArrays.buildArray(field, nCols);
+        for (int col = 0; col < nCols; ++col) {
+            T sum = field.getZero();
+            for (int i = 0; i < nRows; ++i) {
+                sum = sum.add(getEntry(i, col).multiply(v[i]));
+            }
+            out[col] = sum;
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> preMultiply(final FieldVector<T> v) throws DimensionMismatchException {
+        try {
+            return new ArrayFieldVector<T>(
+                    field, preMultiply(((ArrayFieldVector<T>) v).getDataRef()), false);
+        } catch (ClassCastException cce) {
+            final int nRows = getRowDimension();
+            final int nCols = getColumnDimension();
+            if (v.getDimension() != nRows) {
+                throw new DimensionMismatchException(v.getDimension(), nRows);
+            }
+
+            final T[] out = MathArrays.buildArray(field, nCols);
+            for (int col = 0; col < nCols; ++col) {
+                T sum = field.getZero();
+                for (int i = 0; i < nRows; ++i) {
+                    sum = sum.add(getEntry(i, col).multiply(v.getEntry(i)));
+                }
+                out[col] = sum;
+            }
+
+            return new ArrayFieldVector<T>(field, out, false);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public T walkInRowOrder(final FieldMatrixChangingVisitor<T> visitor) {
+        final int rows = getRowDimension();
+        final int columns = getColumnDimension();
+        visitor.start(rows, columns, 0, rows - 1, 0, columns - 1);
+        for (int row = 0; row < rows; ++row) {
+            for (int column = 0; column < columns; ++column) {
+                final T oldValue = getEntry(row, column);
+                final T newValue = visitor.visit(row, column, oldValue);
+                setEntry(row, column, newValue);
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    public T walkInRowOrder(final FieldMatrixPreservingVisitor<T> visitor) {
+        final int rows = getRowDimension();
+        final int columns = getColumnDimension();
+        visitor.start(rows, columns, 0, rows - 1, 0, columns - 1);
+        for (int row = 0; row < rows; ++row) {
+            for (int column = 0; column < columns; ++column) {
+                visitor.visit(row, column, getEntry(row, column));
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    public T walkInRowOrder(
+            final FieldMatrixChangingVisitor<T> visitor,
+            final int startRow,
+            final int endRow,
+            final int startColumn,
+            final int endColumn)
+            throws NumberIsTooSmallException, OutOfRangeException {
+        checkSubMatrixIndex(startRow, endRow, startColumn, endColumn);
+        visitor.start(
+                getRowDimension(), getColumnDimension(), startRow, endRow, startColumn, endColumn);
+        for (int row = startRow; row <= endRow; ++row) {
+            for (int column = startColumn; column <= endColumn; ++column) {
+                final T oldValue = getEntry(row, column);
+                final T newValue = visitor.visit(row, column, oldValue);
+                setEntry(row, column, newValue);
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    public T walkInRowOrder(
+            final FieldMatrixPreservingVisitor<T> visitor,
+            final int startRow,
+            final int endRow,
+            final int startColumn,
+            final int endColumn)
+            throws NumberIsTooSmallException, OutOfRangeException {
+        checkSubMatrixIndex(startRow, endRow, startColumn, endColumn);
+        visitor.start(
+                getRowDimension(), getColumnDimension(), startRow, endRow, startColumn, endColumn);
+        for (int row = startRow; row <= endRow; ++row) {
+            for (int column = startColumn; column <= endColumn; ++column) {
+                visitor.visit(row, column, getEntry(row, column));
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    public T walkInColumnOrder(final FieldMatrixChangingVisitor<T> visitor) {
+        final int rows = getRowDimension();
+        final int columns = getColumnDimension();
+        visitor.start(rows, columns, 0, rows - 1, 0, columns - 1);
+        for (int column = 0; column < columns; ++column) {
+            for (int row = 0; row < rows; ++row) {
+                final T oldValue = getEntry(row, column);
+                final T newValue = visitor.visit(row, column, oldValue);
+                setEntry(row, column, newValue);
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    public T walkInColumnOrder(final FieldMatrixPreservingVisitor<T> visitor) {
+        final int rows = getRowDimension();
+        final int columns = getColumnDimension();
+        visitor.start(rows, columns, 0, rows - 1, 0, columns - 1);
+        for (int column = 0; column < columns; ++column) {
+            for (int row = 0; row < rows; ++row) {
+                visitor.visit(row, column, getEntry(row, column));
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    public T walkInColumnOrder(
+            final FieldMatrixChangingVisitor<T> visitor,
+            final int startRow,
+            final int endRow,
+            final int startColumn,
+            final int endColumn)
+            throws NumberIsTooSmallException, OutOfRangeException {
+        checkSubMatrixIndex(startRow, endRow, startColumn, endColumn);
+        visitor.start(
+                getRowDimension(), getColumnDimension(), startRow, endRow, startColumn, endColumn);
+        for (int column = startColumn; column <= endColumn; ++column) {
+            for (int row = startRow; row <= endRow; ++row) {
+                final T oldValue = getEntry(row, column);
+                final T newValue = visitor.visit(row, column, oldValue);
+                setEntry(row, column, newValue);
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    public T walkInColumnOrder(
+            final FieldMatrixPreservingVisitor<T> visitor,
+            final int startRow,
+            final int endRow,
+            final int startColumn,
+            final int endColumn)
+            throws NumberIsTooSmallException, OutOfRangeException {
+        checkSubMatrixIndex(startRow, endRow, startColumn, endColumn);
+        visitor.start(
+                getRowDimension(), getColumnDimension(), startRow, endRow, startColumn, endColumn);
+        for (int column = startColumn; column <= endColumn; ++column) {
+            for (int row = startRow; row <= endRow; ++row) {
+                visitor.visit(row, column, getEntry(row, column));
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    public T walkInOptimizedOrder(final FieldMatrixChangingVisitor<T> visitor) {
+        return walkInRowOrder(visitor);
+    }
+
+    /** {@inheritDoc} */
+    public T walkInOptimizedOrder(final FieldMatrixPreservingVisitor<T> visitor) {
+        return walkInRowOrder(visitor);
+    }
+
+    /** {@inheritDoc} */
+    public T walkInOptimizedOrder(
+            final FieldMatrixChangingVisitor<T> visitor,
+            final int startRow,
+            final int endRow,
+            final int startColumn,
+            final int endColumn)
+            throws NumberIsTooSmallException, OutOfRangeException {
+        return walkInRowOrder(visitor, startRow, endRow, startColumn, endColumn);
+    }
+
+    /** {@inheritDoc} */
+    public T walkInOptimizedOrder(
+            final FieldMatrixPreservingVisitor<T> visitor,
+            final int startRow,
+            final int endRow,
+            final int startColumn,
+            final int endColumn)
+            throws NumberIsTooSmallException, OutOfRangeException {
+        return walkInRowOrder(visitor, startRow, endRow, startColumn, endColumn);
+    }
+
+    /**
+     * Get a string representation for this matrix.
+     *
+     * @return a string representation for this matrix
+     */
+    @Override
+    public String toString() {
+        final int nRows = getRowDimension();
+        final int nCols = getColumnDimension();
+        final StringBuffer res = new StringBuffer();
+        String fullClassName = getClass().getName();
+        String shortClassName = fullClassName.substring(fullClassName.lastIndexOf('.') + 1);
+        res.append(shortClassName).append("{");
+
+        for (int i = 0; i < nRows; ++i) {
+            if (i > 0) {
+                res.append(",");
+            }
+            res.append("{");
+            for (int j = 0; j < nCols; ++j) {
+                if (j > 0) {
+                    res.append(",");
+                }
+                res.append(getEntry(i, j));
+            }
+            res.append("}");
+        }
+
+        res.append("}");
+        return res.toString();
+    }
+
+    /**
+     * Returns true iff <code>object</code> is a <code>FieldMatrix</code> instance with the same
+     * dimensions as this and all corresponding matrix entries are equal.
+     *
+     * @param object the object to test equality against.
+     * @return true if object equals this
+     */
+    @Override
+    public boolean equals(final Object object) {
+        if (object == this) {
+            return true;
+        }
+        if (object instanceof FieldMatrix<?> == false) {
+            return false;
+        }
+        FieldMatrix<?> m = (FieldMatrix<?>) object;
+        final int nRows = getRowDimension();
+        final int nCols = getColumnDimension();
+        if (m.getColumnDimension() != nCols || m.getRowDimension() != nRows) {
+            return false;
+        }
+        for (int row = 0; row < nRows; ++row) {
+            for (int col = 0; col < nCols; ++col) {
+                if (!getEntry(row, col).equals(m.getEntry(row, col))) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Computes a hashcode for the matrix.
+     *
+     * @return hashcode for matrix
+     */
+    @Override
+    public int hashCode() {
+        int ret = 322562;
+        final int nRows = getRowDimension();
+        final int nCols = getColumnDimension();
+        ret = ret * 31 + nRows;
+        ret = ret * 31 + nCols;
+        for (int row = 0; row < nRows; ++row) {
+            for (int col = 0; col < nCols; ++col) {
+                ret = ret * 31 + (11 * (row + 1) + 17 * (col + 1)) * getEntry(row, col).hashCode();
+            }
+        }
+        return ret;
+    }
+
+    /**
+     * Check if a row index is valid.
+     *
+     * @param row Row index to check.
+     * @throws OutOfRangeException if {@code index} is not valid.
+     */
+    protected void checkRowIndex(final int row) throws OutOfRangeException {
+        if (row < 0 || row >= getRowDimension()) {
+            throw new OutOfRangeException(
+                    LocalizedFormats.ROW_INDEX, row, 0, getRowDimension() - 1);
+        }
+    }
+
+    /**
+     * Check if a column index is valid.
+     *
+     * @param column Column index to check.
+     * @throws OutOfRangeException if {@code index} is not valid.
+     */
+    protected void checkColumnIndex(final int column) throws OutOfRangeException {
+        if (column < 0 || column >= getColumnDimension()) {
+            throw new OutOfRangeException(
+                    LocalizedFormats.COLUMN_INDEX, column, 0, getColumnDimension() - 1);
+        }
+    }
+
+    /**
+     * Check if submatrix ranges indices are valid. Rows and columns are indicated counting from 0
+     * to n-1.
+     *
+     * @param startRow Initial row index.
+     * @param endRow Final row index.
+     * @param startColumn Initial column index.
+     * @param endColumn Final column index.
+     * @throws OutOfRangeException if the indices are not valid.
+     * @throws NumberIsTooSmallException if {@code endRow < startRow} or {@code endColumn <
+     *     startColumn}.
+     */
+    protected void checkSubMatrixIndex(
+            final int startRow, final int endRow, final int startColumn, final int endColumn)
+            throws NumberIsTooSmallException, OutOfRangeException {
+        checkRowIndex(startRow);
+        checkRowIndex(endRow);
+        if (endRow < startRow) {
+            throw new NumberIsTooSmallException(
+                    LocalizedFormats.INITIAL_ROW_AFTER_FINAL_ROW, endRow, startRow, true);
+        }
+
+        checkColumnIndex(startColumn);
+        checkColumnIndex(endColumn);
+        if (endColumn < startColumn) {
+            throw new NumberIsTooSmallException(
+                    LocalizedFormats.INITIAL_COLUMN_AFTER_FINAL_COLUMN,
+                    endColumn,
+                    startColumn,
+                    true);
+        }
+    }
+
+    /**
+     * Check if submatrix ranges indices are valid. Rows and columns are indicated counting from 0
+     * to n-1.
+     *
+     * @param selectedRows Array of row indices.
+     * @param selectedColumns Array of column indices.
+     * @throws NullArgumentException if the arrays are {@code null}.
+     * @throws NoDataException if the arrays have zero length.
+     * @throws OutOfRangeException if row or column selections are not valid.
+     */
+    protected void checkSubMatrixIndex(final int[] selectedRows, final int[] selectedColumns)
+            throws NoDataException, NullArgumentException, OutOfRangeException {
+        if (selectedRows == null || selectedColumns == null) {
+            throw new NullArgumentException();
+        }
+        if (selectedRows.length == 0 || selectedColumns.length == 0) {
+            throw new NoDataException();
+        }
+
+        for (final int row : selectedRows) {
+            checkRowIndex(row);
+        }
+        for (final int column : selectedColumns) {
+            checkColumnIndex(column);
+        }
+    }
+
+    /**
+     * Check if a matrix is addition compatible with the instance.
+     *
+     * @param m Matrix to check.
+     * @throws MatrixDimensionMismatchException if the matrix is not addition-compatible with
+     *     instance.
+     */
+    protected void checkAdditionCompatible(final FieldMatrix<T> m)
+            throws MatrixDimensionMismatchException {
+        if ((getRowDimension() != m.getRowDimension())
+                || (getColumnDimension() != m.getColumnDimension())) {
+            throw new MatrixDimensionMismatchException(
+                    m.getRowDimension(), m.getColumnDimension(),
+                    getRowDimension(), getColumnDimension());
+        }
+    }
+
+    /**
+     * Check if a matrix is subtraction compatible with the instance.
+     *
+     * @param m Matrix to check.
+     * @throws MatrixDimensionMismatchException if the matrix is not subtraction-compatible with
+     *     instance.
+     */
+    protected void checkSubtractionCompatible(final FieldMatrix<T> m)
+            throws MatrixDimensionMismatchException {
+        if ((getRowDimension() != m.getRowDimension())
+                || (getColumnDimension() != m.getColumnDimension())) {
+            throw new MatrixDimensionMismatchException(
+                    m.getRowDimension(), m.getColumnDimension(),
+                    getRowDimension(), getColumnDimension());
+        }
+    }
+
+    /**
+     * Check if a matrix is multiplication compatible with the instance.
+     *
+     * @param m Matrix to check.
+     * @throws DimensionMismatchException if the matrix is not multiplication-compatible with
+     *     instance.
+     */
+    protected void checkMultiplicationCompatible(final FieldMatrix<T> m)
+            throws DimensionMismatchException {
+        if (getColumnDimension() != m.getRowDimension()) {
+            throw new DimensionMismatchException(m.getRowDimension(), getColumnDimension());
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/AbstractRealMatrix.java b/src/main/java/org/apache/commons/math3/linear/AbstractRealMatrix.java
new file mode 100644
index 0000000..e3fd838
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/AbstractRealMatrix.java
@@ -0,0 +1,1010 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+/**
+ * Basic implementation of RealMatrix methods regardless of the underlying storage.
+ *
+ * <p>All the methods implemented here use {@link #getEntry(int, int)} to access matrix elements.
+ * Derived class can provide faster implementations.
+ *
+ * @since 2.0
+ */
+public abstract class AbstractRealMatrix extends RealLinearOperator implements RealMatrix {
+
+    /** Default format. */
+    private static final RealMatrixFormat DEFAULT_FORMAT = RealMatrixFormat.getInstance(Locale.US);
+
+    static {
+        // set the minimum fraction digits to 1 to keep compatibility
+        DEFAULT_FORMAT.getFormat().setMinimumFractionDigits(1);
+    }
+
+    /** Creates a matrix with no data */
+    protected AbstractRealMatrix() {}
+
+    /**
+     * Create a new RealMatrix with the supplied row and column dimensions.
+     *
+     * @param rowDimension the number of rows in the new matrix
+     * @param columnDimension the number of columns in the new matrix
+     * @throws NotStrictlyPositiveException if row or column dimension is not positive
+     */
+    protected AbstractRealMatrix(final int rowDimension, final int columnDimension)
+            throws NotStrictlyPositiveException {
+        if (rowDimension < 1) {
+            throw new NotStrictlyPositiveException(rowDimension);
+        }
+        if (columnDimension < 1) {
+            throw new NotStrictlyPositiveException(columnDimension);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix add(RealMatrix m) throws MatrixDimensionMismatchException {
+        MatrixUtils.checkAdditionCompatible(this, m);
+
+        final int rowCount = getRowDimension();
+        final int columnCount = getColumnDimension();
+        final RealMatrix out = createMatrix(rowCount, columnCount);
+        for (int row = 0; row < rowCount; ++row) {
+            for (int col = 0; col < columnCount; ++col) {
+                out.setEntry(row, col, getEntry(row, col) + m.getEntry(row, col));
+            }
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix subtract(final RealMatrix m) throws MatrixDimensionMismatchException {
+        MatrixUtils.checkSubtractionCompatible(this, m);
+
+        final int rowCount = getRowDimension();
+        final int columnCount = getColumnDimension();
+        final RealMatrix out = createMatrix(rowCount, columnCount);
+        for (int row = 0; row < rowCount; ++row) {
+            for (int col = 0; col < columnCount; ++col) {
+                out.setEntry(row, col, getEntry(row, col) - m.getEntry(row, col));
+            }
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix scalarAdd(final double d) {
+        final int rowCount = getRowDimension();
+        final int columnCount = getColumnDimension();
+        final RealMatrix out = createMatrix(rowCount, columnCount);
+        for (int row = 0; row < rowCount; ++row) {
+            for (int col = 0; col < columnCount; ++col) {
+                out.setEntry(row, col, getEntry(row, col) + d);
+            }
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix scalarMultiply(final double d) {
+        final int rowCount = getRowDimension();
+        final int columnCount = getColumnDimension();
+        final RealMatrix out = createMatrix(rowCount, columnCount);
+        for (int row = 0; row < rowCount; ++row) {
+            for (int col = 0; col < columnCount; ++col) {
+                out.setEntry(row, col, getEntry(row, col) * d);
+            }
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix multiply(final RealMatrix m) throws DimensionMismatchException {
+        MatrixUtils.checkMultiplicationCompatible(this, m);
+
+        final int nRows = getRowDimension();
+        final int nCols = m.getColumnDimension();
+        final int nSum = getColumnDimension();
+        final RealMatrix out = createMatrix(nRows, nCols);
+        for (int row = 0; row < nRows; ++row) {
+            for (int col = 0; col < nCols; ++col) {
+                double sum = 0;
+                for (int i = 0; i < nSum; ++i) {
+                    sum += getEntry(row, i) * m.getEntry(i, col);
+                }
+                out.setEntry(row, col, sum);
+            }
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix preMultiply(final RealMatrix m) throws DimensionMismatchException {
+        return m.multiply(this);
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix power(final int p) throws NotPositiveException, NonSquareMatrixException {
+        if (p < 0) {
+            throw new NotPositiveException(LocalizedFormats.NOT_POSITIVE_EXPONENT, p);
+        }
+
+        if (!isSquare()) {
+            throw new NonSquareMatrixException(getRowDimension(), getColumnDimension());
+        }
+
+        if (p == 0) {
+            return MatrixUtils.createRealIdentityMatrix(this.getRowDimension());
+        }
+
+        if (p == 1) {
+            return this.copy();
+        }
+
+        final int power = p - 1;
+
+        /*
+         * Only log_2(p) operations is used by doing as follows:
+         * 5^214 = 5^128 * 5^64 * 5^16 * 5^4 * 5^2
+         *
+         * In general, the same approach is used for A^p.
+         */
+
+        final char[] binaryRepresentation = Integer.toBinaryString(power).toCharArray();
+        final ArrayList<Integer> nonZeroPositions = new ArrayList<Integer>();
+        int maxI = -1;
+
+        for (int i = 0; i < binaryRepresentation.length; ++i) {
+            if (binaryRepresentation[i] == '1') {
+                final int pos = binaryRepresentation.length - i - 1;
+                nonZeroPositions.add(pos);
+
+                // The positions are taken in turn, so maxI is only changed once
+                if (maxI == -1) {
+                    maxI = pos;
+                }
+            }
+        }
+
+        RealMatrix[] results = new RealMatrix[maxI + 1];
+        results[0] = this.copy();
+
+        for (int i = 1; i <= maxI; ++i) {
+            results[i] = results[i - 1].multiply(results[i - 1]);
+        }
+
+        RealMatrix result = this.copy();
+
+        for (Integer i : nonZeroPositions) {
+            result = result.multiply(results[i]);
+        }
+
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    public double[][] getData() {
+        final double[][] data = new double[getRowDimension()][getColumnDimension()];
+
+        for (int i = 0; i < data.length; ++i) {
+            final double[] dataI = data[i];
+            for (int j = 0; j < dataI.length; ++j) {
+                dataI[j] = getEntry(i, j);
+            }
+        }
+
+        return data;
+    }
+
+    /** {@inheritDoc} */
+    public double getNorm() {
+        return walkInColumnOrder(
+                new RealMatrixPreservingVisitor() {
+
+                    /** Last row index. */
+                    private double endRow;
+
+                    /** Sum of absolute values on one column. */
+                    private double columnSum;
+
+                    /** Maximal sum across all columns. */
+                    private double maxColSum;
+
+                    /** {@inheritDoc} */
+                    public void start(
+                            final int rows,
+                            final int columns,
+                            final int startRow,
+                            final int endRow,
+                            final int startColumn,
+                            final int endColumn) {
+                        this.endRow = endRow;
+                        columnSum = 0;
+                        maxColSum = 0;
+                    }
+
+                    /** {@inheritDoc} */
+                    public void visit(final int row, final int column, final double value) {
+                        columnSum += FastMath.abs(value);
+                        if (row == endRow) {
+                            maxColSum = FastMath.max(maxColSum, columnSum);
+                            columnSum = 0;
+                        }
+                    }
+
+                    /** {@inheritDoc} */
+                    public double end() {
+                        return maxColSum;
+                    }
+                });
+    }
+
+    /** {@inheritDoc} */
+    public double getFrobeniusNorm() {
+        return walkInOptimizedOrder(
+                new RealMatrixPreservingVisitor() {
+
+                    /** Sum of squared entries. */
+                    private double sum;
+
+                    /** {@inheritDoc} */
+                    public void start(
+                            final int rows,
+                            final int columns,
+                            final int startRow,
+                            final int endRow,
+                            final int startColumn,
+                            final int endColumn) {
+                        sum = 0;
+                    }
+
+                    /** {@inheritDoc} */
+                    public void visit(final int row, final int column, final double value) {
+                        sum += value * value;
+                    }
+
+                    /** {@inheritDoc} */
+                    public double end() {
+                        return FastMath.sqrt(sum);
+                    }
+                });
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix getSubMatrix(
+            final int startRow, final int endRow, final int startColumn, final int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException {
+        MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn);
+
+        final RealMatrix subMatrix =
+                createMatrix(endRow - startRow + 1, endColumn - startColumn + 1);
+        for (int i = startRow; i <= endRow; ++i) {
+            for (int j = startColumn; j <= endColumn; ++j) {
+                subMatrix.setEntry(i - startRow, j - startColumn, getEntry(i, j));
+            }
+        }
+
+        return subMatrix;
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix getSubMatrix(final int[] selectedRows, final int[] selectedColumns)
+            throws NullArgumentException, NoDataException, OutOfRangeException {
+        MatrixUtils.checkSubMatrixIndex(this, selectedRows, selectedColumns);
+
+        final RealMatrix subMatrix = createMatrix(selectedRows.length, selectedColumns.length);
+        subMatrix.walkInOptimizedOrder(
+                new DefaultRealMatrixChangingVisitor() {
+
+                    /** {@inheritDoc} */
+                    @Override
+                    public double visit(final int row, final int column, final double value) {
+                        return getEntry(selectedRows[row], selectedColumns[column]);
+                    }
+                });
+
+        return subMatrix;
+    }
+
+    /** {@inheritDoc} */
+    public void copySubMatrix(
+            final int startRow,
+            final int endRow,
+            final int startColumn,
+            final int endColumn,
+            final double[][] destination)
+            throws OutOfRangeException,
+                    NumberIsTooSmallException,
+                    MatrixDimensionMismatchException {
+        MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn);
+        final int rowsCount = endRow + 1 - startRow;
+        final int columnsCount = endColumn + 1 - startColumn;
+        if ((destination.length < rowsCount) || (destination[0].length < columnsCount)) {
+            throw new MatrixDimensionMismatchException(
+                    destination.length, destination[0].length, rowsCount, columnsCount);
+        }
+
+        for (int i = 1; i < rowsCount; i++) {
+            if (destination[i].length < columnsCount) {
+                throw new MatrixDimensionMismatchException(
+                        destination.length, destination[i].length, rowsCount, columnsCount);
+            }
+        }
+
+        walkInOptimizedOrder(
+                new DefaultRealMatrixPreservingVisitor() {
+
+                    /** Initial row index. */
+                    private int startRow;
+
+                    /** Initial column index. */
+                    private int startColumn;
+
+                    /** {@inheritDoc} */
+                    @Override
+                    public void start(
+                            final int rows,
+                            final int columns,
+                            final int startRow,
+                            final int endRow,
+                            final int startColumn,
+                            final int endColumn) {
+                        this.startRow = startRow;
+                        this.startColumn = startColumn;
+                    }
+
+                    /** {@inheritDoc} */
+                    @Override
+                    public void visit(final int row, final int column, final double value) {
+                        destination[row - startRow][column - startColumn] = value;
+                    }
+                },
+                startRow,
+                endRow,
+                startColumn,
+                endColumn);
+    }
+
+    /** {@inheritDoc} */
+    public void copySubMatrix(int[] selectedRows, int[] selectedColumns, double[][] destination)
+            throws OutOfRangeException,
+                    NullArgumentException,
+                    NoDataException,
+                    MatrixDimensionMismatchException {
+        MatrixUtils.checkSubMatrixIndex(this, selectedRows, selectedColumns);
+        final int nCols = selectedColumns.length;
+        if ((destination.length < selectedRows.length) || (destination[0].length < nCols)) {
+            throw new MatrixDimensionMismatchException(
+                    destination.length, destination[0].length,
+                    selectedRows.length, selectedColumns.length);
+        }
+
+        for (int i = 0; i < selectedRows.length; i++) {
+            final double[] destinationI = destination[i];
+            if (destinationI.length < nCols) {
+                throw new MatrixDimensionMismatchException(
+                        destination.length, destinationI.length,
+                        selectedRows.length, selectedColumns.length);
+            }
+            for (int j = 0; j < selectedColumns.length; j++) {
+                destinationI[j] = getEntry(selectedRows[i], selectedColumns[j]);
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void setSubMatrix(final double[][] subMatrix, final int row, final int column)
+            throws NoDataException,
+                    OutOfRangeException,
+                    DimensionMismatchException,
+                    NullArgumentException {
+        MathUtils.checkNotNull(subMatrix);
+        final int nRows = subMatrix.length;
+        if (nRows == 0) {
+            throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_ROW);
+        }
+
+        final int nCols = subMatrix[0].length;
+        if (nCols == 0) {
+            throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_COLUMN);
+        }
+
+        for (int r = 1; r < nRows; ++r) {
+            if (subMatrix[r].length != nCols) {
+                throw new DimensionMismatchException(nCols, subMatrix[r].length);
+            }
+        }
+
+        MatrixUtils.checkRowIndex(this, row);
+        MatrixUtils.checkColumnIndex(this, column);
+        MatrixUtils.checkRowIndex(this, nRows + row - 1);
+        MatrixUtils.checkColumnIndex(this, nCols + column - 1);
+
+        for (int i = 0; i < nRows; ++i) {
+            for (int j = 0; j < nCols; ++j) {
+                setEntry(row + i, column + j, subMatrix[i][j]);
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix getRowMatrix(final int row) throws OutOfRangeException {
+        MatrixUtils.checkRowIndex(this, row);
+        final int nCols = getColumnDimension();
+        final RealMatrix out = createMatrix(1, nCols);
+        for (int i = 0; i < nCols; ++i) {
+            out.setEntry(0, i, getEntry(row, i));
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    public void setRowMatrix(final int row, final RealMatrix matrix)
+            throws OutOfRangeException, MatrixDimensionMismatchException {
+        MatrixUtils.checkRowIndex(this, row);
+        final int nCols = getColumnDimension();
+        if ((matrix.getRowDimension() != 1) || (matrix.getColumnDimension() != nCols)) {
+            throw new MatrixDimensionMismatchException(
+                    matrix.getRowDimension(), matrix.getColumnDimension(), 1, nCols);
+        }
+        for (int i = 0; i < nCols; ++i) {
+            setEntry(row, i, matrix.getEntry(0, i));
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix getColumnMatrix(final int column) throws OutOfRangeException {
+        MatrixUtils.checkColumnIndex(this, column);
+        final int nRows = getRowDimension();
+        final RealMatrix out = createMatrix(nRows, 1);
+        for (int i = 0; i < nRows; ++i) {
+            out.setEntry(i, 0, getEntry(i, column));
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    public void setColumnMatrix(final int column, final RealMatrix matrix)
+            throws OutOfRangeException, MatrixDimensionMismatchException {
+        MatrixUtils.checkColumnIndex(this, column);
+        final int nRows = getRowDimension();
+        if ((matrix.getRowDimension() != nRows) || (matrix.getColumnDimension() != 1)) {
+            throw new MatrixDimensionMismatchException(
+                    matrix.getRowDimension(), matrix.getColumnDimension(), nRows, 1);
+        }
+        for (int i = 0; i < nRows; ++i) {
+            setEntry(i, column, matrix.getEntry(i, 0));
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealVector getRowVector(final int row) throws OutOfRangeException {
+        return new ArrayRealVector(getRow(row), false);
+    }
+
+    /** {@inheritDoc} */
+    public void setRowVector(final int row, final RealVector vector)
+            throws OutOfRangeException, MatrixDimensionMismatchException {
+        MatrixUtils.checkRowIndex(this, row);
+        final int nCols = getColumnDimension();
+        if (vector.getDimension() != nCols) {
+            throw new MatrixDimensionMismatchException(1, vector.getDimension(), 1, nCols);
+        }
+        for (int i = 0; i < nCols; ++i) {
+            setEntry(row, i, vector.getEntry(i));
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealVector getColumnVector(final int column) throws OutOfRangeException {
+        return new ArrayRealVector(getColumn(column), false);
+    }
+
+    /** {@inheritDoc} */
+    public void setColumnVector(final int column, final RealVector vector)
+            throws OutOfRangeException, MatrixDimensionMismatchException {
+        MatrixUtils.checkColumnIndex(this, column);
+        final int nRows = getRowDimension();
+        if (vector.getDimension() != nRows) {
+            throw new MatrixDimensionMismatchException(vector.getDimension(), 1, nRows, 1);
+        }
+        for (int i = 0; i < nRows; ++i) {
+            setEntry(i, column, vector.getEntry(i));
+        }
+    }
+
+    /** {@inheritDoc} */
+    public double[] getRow(final int row) throws OutOfRangeException {
+        MatrixUtils.checkRowIndex(this, row);
+        final int nCols = getColumnDimension();
+        final double[] out = new double[nCols];
+        for (int i = 0; i < nCols; ++i) {
+            out[i] = getEntry(row, i);
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    public void setRow(final int row, final double[] array)
+            throws OutOfRangeException, MatrixDimensionMismatchException {
+        MatrixUtils.checkRowIndex(this, row);
+        final int nCols = getColumnDimension();
+        if (array.length != nCols) {
+            throw new MatrixDimensionMismatchException(1, array.length, 1, nCols);
+        }
+        for (int i = 0; i < nCols; ++i) {
+            setEntry(row, i, array[i]);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public double[] getColumn(final int column) throws OutOfRangeException {
+        MatrixUtils.checkColumnIndex(this, column);
+        final int nRows = getRowDimension();
+        final double[] out = new double[nRows];
+        for (int i = 0; i < nRows; ++i) {
+            out[i] = getEntry(i, column);
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    public void setColumn(final int column, final double[] array)
+            throws OutOfRangeException, MatrixDimensionMismatchException {
+        MatrixUtils.checkColumnIndex(this, column);
+        final int nRows = getRowDimension();
+        if (array.length != nRows) {
+            throw new MatrixDimensionMismatchException(array.length, 1, nRows, 1);
+        }
+        for (int i = 0; i < nRows; ++i) {
+            setEntry(i, column, array[i]);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void addToEntry(int row, int column, double increment) throws OutOfRangeException {
+        MatrixUtils.checkMatrixIndex(this, row, column);
+        setEntry(row, column, getEntry(row, column) + increment);
+    }
+
+    /** {@inheritDoc} */
+    public void multiplyEntry(int row, int column, double factor) throws OutOfRangeException {
+        MatrixUtils.checkMatrixIndex(this, row, column);
+        setEntry(row, column, getEntry(row, column) * factor);
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix transpose() {
+        final int nRows = getRowDimension();
+        final int nCols = getColumnDimension();
+        final RealMatrix out = createMatrix(nCols, nRows);
+        walkInOptimizedOrder(
+                new DefaultRealMatrixPreservingVisitor() {
+
+                    /** {@inheritDoc} */
+                    @Override
+                    public void visit(final int row, final int column, final double value) {
+                        out.setEntry(column, row, value);
+                    }
+                });
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSquare() {
+        return getColumnDimension() == getRowDimension();
+    }
+
+    /**
+     * Returns the number of rows of this matrix.
+     *
+     * @return the number of rows.
+     */
+    @Override
+    public abstract int getRowDimension();
+
+    /**
+     * Returns the number of columns of this matrix.
+     *
+     * @return the number of columns.
+     */
+    @Override
+    public abstract int getColumnDimension();
+
+    /** {@inheritDoc} */
+    public double getTrace() throws NonSquareMatrixException {
+        final int nRows = getRowDimension();
+        final int nCols = getColumnDimension();
+        if (nRows != nCols) {
+            throw new NonSquareMatrixException(nRows, nCols);
+        }
+        double trace = 0;
+        for (int i = 0; i < nRows; ++i) {
+            trace += getEntry(i, i);
+        }
+        return trace;
+    }
+
+    /** {@inheritDoc} */
+    public double[] operate(final double[] v) throws DimensionMismatchException {
+        final int nRows = getRowDimension();
+        final int nCols = getColumnDimension();
+        if (v.length != nCols) {
+            throw new DimensionMismatchException(v.length, nCols);
+        }
+
+        final double[] out = new double[nRows];
+        for (int row = 0; row < nRows; ++row) {
+            double sum = 0;
+            for (int i = 0; i < nCols; ++i) {
+                sum += getEntry(row, i) * v[i];
+            }
+            out[row] = sum;
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector operate(final RealVector v) throws DimensionMismatchException {
+        try {
+            return new ArrayRealVector(operate(((ArrayRealVector) v).getDataRef()), false);
+        } catch (ClassCastException cce) {
+            final int nRows = getRowDimension();
+            final int nCols = getColumnDimension();
+            if (v.getDimension() != nCols) {
+                throw new DimensionMismatchException(v.getDimension(), nCols);
+            }
+
+            final double[] out = new double[nRows];
+            for (int row = 0; row < nRows; ++row) {
+                double sum = 0;
+                for (int i = 0; i < nCols; ++i) {
+                    sum += getEntry(row, i) * v.getEntry(i);
+                }
+                out[row] = sum;
+            }
+
+            return new ArrayRealVector(out, false);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public double[] preMultiply(final double[] v) throws DimensionMismatchException {
+
+        final int nRows = getRowDimension();
+        final int nCols = getColumnDimension();
+        if (v.length != nRows) {
+            throw new DimensionMismatchException(v.length, nRows);
+        }
+
+        final double[] out = new double[nCols];
+        for (int col = 0; col < nCols; ++col) {
+            double sum = 0;
+            for (int i = 0; i < nRows; ++i) {
+                sum += getEntry(i, col) * v[i];
+            }
+            out[col] = sum;
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    public RealVector preMultiply(final RealVector v) throws DimensionMismatchException {
+        try {
+            return new ArrayRealVector(preMultiply(((ArrayRealVector) v).getDataRef()), false);
+        } catch (ClassCastException cce) {
+
+            final int nRows = getRowDimension();
+            final int nCols = getColumnDimension();
+            if (v.getDimension() != nRows) {
+                throw new DimensionMismatchException(v.getDimension(), nRows);
+            }
+
+            final double[] out = new double[nCols];
+            for (int col = 0; col < nCols; ++col) {
+                double sum = 0;
+                for (int i = 0; i < nRows; ++i) {
+                    sum += getEntry(i, col) * v.getEntry(i);
+                }
+                out[col] = sum;
+            }
+
+            return new ArrayRealVector(out, false);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public double walkInRowOrder(final RealMatrixChangingVisitor visitor) {
+        final int rows = getRowDimension();
+        final int columns = getColumnDimension();
+        visitor.start(rows, columns, 0, rows - 1, 0, columns - 1);
+        for (int row = 0; row < rows; ++row) {
+            for (int column = 0; column < columns; ++column) {
+                final double oldValue = getEntry(row, column);
+                final double newValue = visitor.visit(row, column, oldValue);
+                setEntry(row, column, newValue);
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    public double walkInRowOrder(final RealMatrixPreservingVisitor visitor) {
+        final int rows = getRowDimension();
+        final int columns = getColumnDimension();
+        visitor.start(rows, columns, 0, rows - 1, 0, columns - 1);
+        for (int row = 0; row < rows; ++row) {
+            for (int column = 0; column < columns; ++column) {
+                visitor.visit(row, column, getEntry(row, column));
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    public double walkInRowOrder(
+            final RealMatrixChangingVisitor visitor,
+            final int startRow,
+            final int endRow,
+            final int startColumn,
+            final int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException {
+        MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn);
+        visitor.start(
+                getRowDimension(), getColumnDimension(), startRow, endRow, startColumn, endColumn);
+        for (int row = startRow; row <= endRow; ++row) {
+            for (int column = startColumn; column <= endColumn; ++column) {
+                final double oldValue = getEntry(row, column);
+                final double newValue = visitor.visit(row, column, oldValue);
+                setEntry(row, column, newValue);
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    public double walkInRowOrder(
+            final RealMatrixPreservingVisitor visitor,
+            final int startRow,
+            final int endRow,
+            final int startColumn,
+            final int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException {
+        MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn);
+        visitor.start(
+                getRowDimension(), getColumnDimension(), startRow, endRow, startColumn, endColumn);
+        for (int row = startRow; row <= endRow; ++row) {
+            for (int column = startColumn; column <= endColumn; ++column) {
+                visitor.visit(row, column, getEntry(row, column));
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    public double walkInColumnOrder(final RealMatrixChangingVisitor visitor) {
+        final int rows = getRowDimension();
+        final int columns = getColumnDimension();
+        visitor.start(rows, columns, 0, rows - 1, 0, columns - 1);
+        for (int column = 0; column < columns; ++column) {
+            for (int row = 0; row < rows; ++row) {
+                final double oldValue = getEntry(row, column);
+                final double newValue = visitor.visit(row, column, oldValue);
+                setEntry(row, column, newValue);
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    public double walkInColumnOrder(final RealMatrixPreservingVisitor visitor) {
+        final int rows = getRowDimension();
+        final int columns = getColumnDimension();
+        visitor.start(rows, columns, 0, rows - 1, 0, columns - 1);
+        for (int column = 0; column < columns; ++column) {
+            for (int row = 0; row < rows; ++row) {
+                visitor.visit(row, column, getEntry(row, column));
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    public double walkInColumnOrder(
+            final RealMatrixChangingVisitor visitor,
+            final int startRow,
+            final int endRow,
+            final int startColumn,
+            final int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException {
+        MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn);
+        visitor.start(
+                getRowDimension(), getColumnDimension(), startRow, endRow, startColumn, endColumn);
+        for (int column = startColumn; column <= endColumn; ++column) {
+            for (int row = startRow; row <= endRow; ++row) {
+                final double oldValue = getEntry(row, column);
+                final double newValue = visitor.visit(row, column, oldValue);
+                setEntry(row, column, newValue);
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    public double walkInColumnOrder(
+            final RealMatrixPreservingVisitor visitor,
+            final int startRow,
+            final int endRow,
+            final int startColumn,
+            final int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException {
+        MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn);
+        visitor.start(
+                getRowDimension(), getColumnDimension(), startRow, endRow, startColumn, endColumn);
+        for (int column = startColumn; column <= endColumn; ++column) {
+            for (int row = startRow; row <= endRow; ++row) {
+                visitor.visit(row, column, getEntry(row, column));
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    public double walkInOptimizedOrder(final RealMatrixChangingVisitor visitor) {
+        return walkInRowOrder(visitor);
+    }
+
+    /** {@inheritDoc} */
+    public double walkInOptimizedOrder(final RealMatrixPreservingVisitor visitor) {
+        return walkInRowOrder(visitor);
+    }
+
+    /** {@inheritDoc} */
+    public double walkInOptimizedOrder(
+            final RealMatrixChangingVisitor visitor,
+            final int startRow,
+            final int endRow,
+            final int startColumn,
+            final int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException {
+        return walkInRowOrder(visitor, startRow, endRow, startColumn, endColumn);
+    }
+
+    /** {@inheritDoc} */
+    public double walkInOptimizedOrder(
+            final RealMatrixPreservingVisitor visitor,
+            final int startRow,
+            final int endRow,
+            final int startColumn,
+            final int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException {
+        return walkInRowOrder(visitor, startRow, endRow, startColumn, endColumn);
+    }
+
+    /**
+     * Get a string representation for this matrix.
+     *
+     * @return a string representation for this matrix
+     */
+    @Override
+    public String toString() {
+        final StringBuilder res = new StringBuilder();
+        String fullClassName = getClass().getName();
+        String shortClassName = fullClassName.substring(fullClassName.lastIndexOf('.') + 1);
+        res.append(shortClassName);
+        res.append(DEFAULT_FORMAT.format(this));
+        return res.toString();
+    }
+
+    /**
+     * Returns true iff <code>object</code> is a <code>RealMatrix</code> instance with the same
+     * dimensions as this and all corresponding matrix entries are equal.
+     *
+     * @param object the object to test equality against.
+     * @return true if object equals this
+     */
+    @Override
+    public boolean equals(final Object object) {
+        if (object == this) {
+            return true;
+        }
+        if (object instanceof RealMatrix == false) {
+            return false;
+        }
+        RealMatrix m = (RealMatrix) object;
+        final int nRows = getRowDimension();
+        final int nCols = getColumnDimension();
+        if (m.getColumnDimension() != nCols || m.getRowDimension() != nRows) {
+            return false;
+        }
+        for (int row = 0; row < nRows; ++row) {
+            for (int col = 0; col < nCols; ++col) {
+                if (getEntry(row, col) != m.getEntry(row, col)) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Computes a hashcode for the matrix.
+     *
+     * @return hashcode for matrix
+     */
+    @Override
+    public int hashCode() {
+        int ret = 7;
+        final int nRows = getRowDimension();
+        final int nCols = getColumnDimension();
+        ret = ret * 31 + nRows;
+        ret = ret * 31 + nCols;
+        for (int row = 0; row < nRows; ++row) {
+            for (int col = 0; col < nCols; ++col) {
+                ret =
+                        ret * 31
+                                + (11 * (row + 1) + 17 * (col + 1))
+                                        * MathUtils.hash(getEntry(row, col));
+            }
+        }
+        return ret;
+    }
+
+    /*
+     * Empty implementations of these methods are provided in order to allow for
+     * the use of the @Override tag with Java 1.5.
+     */
+
+    /** {@inheritDoc} */
+    public abstract RealMatrix createMatrix(int rowDimension, int columnDimension)
+            throws NotStrictlyPositiveException;
+
+    /** {@inheritDoc} */
+    public abstract RealMatrix copy();
+
+    /** {@inheritDoc} */
+    public abstract double getEntry(int row, int column) throws OutOfRangeException;
+
+    /** {@inheritDoc} */
+    public abstract void setEntry(int row, int column, double value) throws OutOfRangeException;
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/AnyMatrix.java b/src/main/java/org/apache/commons/math3/linear/AnyMatrix.java
new file mode 100644
index 0000000..1dcce88
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/AnyMatrix.java
@@ -0,0 +1,47 @@
+/*
+ * 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.math3.linear;
+
+/**
+ * Interface defining very basic matrix operations.
+ *
+ * @since 2.0
+ */
+public interface AnyMatrix {
+
+    /**
+     * Is this a square matrix?
+     *
+     * @return true if the matrix is square (rowDimension = columnDimension)
+     */
+    boolean isSquare();
+
+    /**
+     * Returns the number of rows in the matrix.
+     *
+     * @return rowDimension
+     */
+    int getRowDimension();
+
+    /**
+     * Returns the number of columns in the matrix.
+     *
+     * @return columnDimension
+     */
+    int getColumnDimension();
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/Array2DRowFieldMatrix.java b/src/main/java/org/apache/commons/math3/linear/Array2DRowFieldMatrix.java
new file mode 100644
index 0000000..1d9b4c5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/Array2DRowFieldMatrix.java
@@ -0,0 +1,612 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.FieldElement;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.util.MathUtils;
+
+import java.io.Serializable;
+
+/**
+ * Implementation of FieldMatrix<T> using a {@link FieldElement}[][] array to store entries.
+ *
+ * <p>As specified in the {@link FieldMatrix} interface, matrix element indexing is 0-based -- e.g.,
+ * <code>getEntry(0, 0)</code> returns the element in the first row, first column of the matrix.
+ * </ul>
+ *
+ * @param <T> the type of the field elements
+ */
+public class Array2DRowFieldMatrix<T extends FieldElement<T>> extends AbstractFieldMatrix<T>
+        implements Serializable {
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 7260756672015356458L;
+
+    /** Entries of the matrix */
+    private T[][] data;
+
+    /**
+     * Creates a matrix with no data
+     *
+     * @param field field to which the elements belong
+     */
+    public Array2DRowFieldMatrix(final Field<T> field) {
+        super(field);
+    }
+
+    /**
+     * Create a new {@code FieldMatrix<T>} with the supplied row and column dimensions.
+     *
+     * @param field Field to which the elements belong.
+     * @param rowDimension Number of rows in the new matrix.
+     * @param columnDimension Number of columns in the new matrix.
+     * @throws NotStrictlyPositiveException if row or column dimension is not positive.
+     */
+    public Array2DRowFieldMatrix(
+            final Field<T> field, final int rowDimension, final int columnDimension)
+            throws NotStrictlyPositiveException {
+        super(field, rowDimension, columnDimension);
+        data = MathArrays.buildArray(field, rowDimension, columnDimension);
+    }
+
+    /**
+     * Create a new {@code FieldMatrix<T>} using the input array as the underlying data array.
+     *
+     * <p>The input array is copied, not referenced. This constructor has the same effect as calling
+     * {@link #Array2DRowFieldMatrix(FieldElement[][], boolean)} with the second argument set to
+     * {@code true}.
+     *
+     * @param d Data for the new matrix.
+     * @throws DimensionMismatchException if {@code d} is not rectangular.
+     * @throws NullArgumentException if {@code d} is {@code null}.
+     * @throws NoDataException if there are not at least one row and one column.
+     * @see #Array2DRowFieldMatrix(FieldElement[][], boolean)
+     */
+    public Array2DRowFieldMatrix(final T[][] d)
+            throws DimensionMismatchException, NullArgumentException, NoDataException {
+        this(extractField(d), d);
+    }
+
+    /**
+     * Create a new {@code FieldMatrix<T>} using the input array as the underlying data array.
+     *
+     * <p>The input array is copied, not referenced. This constructor has the same effect as calling
+     * {@link #Array2DRowFieldMatrix(FieldElement[][], boolean)} with the second argument set to
+     * {@code true}.
+     *
+     * @param field Field to which the elements belong.
+     * @param d Data for the new matrix.
+     * @throws DimensionMismatchException if {@code d} is not rectangular.
+     * @throws NullArgumentException if {@code d} is {@code null}.
+     * @throws NoDataException if there are not at least one row and one column.
+     * @see #Array2DRowFieldMatrix(FieldElement[][], boolean)
+     */
+    public Array2DRowFieldMatrix(final Field<T> field, final T[][] d)
+            throws DimensionMismatchException, NullArgumentException, NoDataException {
+        super(field);
+        copyIn(d);
+    }
+
+    /**
+     * Create a new {@code FieldMatrix<T>} using the input array as the underlying data array.
+     *
+     * <p>If an array is built specially in order to be embedded in a {@code FieldMatrix<T>} and not
+     * used directly, the {@code copyArray} may be set to {@code false}. This will prevent the
+     * copying and improve performance as no new array will be built and no data will be copied.
+     *
+     * @param d Data for the new matrix.
+     * @param copyArray Whether to copy or reference the input array.
+     * @throws DimensionMismatchException if {@code d} is not rectangular.
+     * @throws NoDataException if there are not at least one row and one column.
+     * @throws NullArgumentException if {@code d} is {@code null}.
+     * @see #Array2DRowFieldMatrix(FieldElement[][])
+     */
+    public Array2DRowFieldMatrix(final T[][] d, final boolean copyArray)
+            throws DimensionMismatchException, NoDataException, NullArgumentException {
+        this(extractField(d), d, copyArray);
+    }
+
+    /**
+     * Create a new {@code FieldMatrix<T>} using the input array as the underlying data array.
+     *
+     * <p>If an array is built specially in order to be embedded in a {@code FieldMatrix<T>} and not
+     * used directly, the {@code copyArray} may be set to {@code false}. This will prevent the
+     * copying and improve performance as no new array will be built and no data will be copied.
+     *
+     * @param field Field to which the elements belong.
+     * @param d Data for the new matrix.
+     * @param copyArray Whether to copy or reference the input array.
+     * @throws DimensionMismatchException if {@code d} is not rectangular.
+     * @throws NoDataException if there are not at least one row and one column.
+     * @throws NullArgumentException if {@code d} is {@code null}.
+     * @see #Array2DRowFieldMatrix(FieldElement[][])
+     */
+    public Array2DRowFieldMatrix(final Field<T> field, final T[][] d, final boolean copyArray)
+            throws DimensionMismatchException, NoDataException, NullArgumentException {
+        super(field);
+        if (copyArray) {
+            copyIn(d);
+        } else {
+            MathUtils.checkNotNull(d);
+            final int nRows = d.length;
+            if (nRows == 0) {
+                throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_ROW);
+            }
+            final int nCols = d[0].length;
+            if (nCols == 0) {
+                throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_COLUMN);
+            }
+            for (int r = 1; r < nRows; r++) {
+                if (d[r].length != nCols) {
+                    throw new DimensionMismatchException(nCols, d[r].length);
+                }
+            }
+            data = d;
+        }
+    }
+
+    /**
+     * Create a new (column) {@code FieldMatrix<T>} using {@code v} as the data for the unique
+     * column of the created matrix. The input array is copied.
+     *
+     * @param v Column vector holding data for new matrix.
+     * @throws NoDataException if v is empty
+     */
+    public Array2DRowFieldMatrix(final T[] v) throws NoDataException {
+        this(extractField(v), v);
+    }
+
+    /**
+     * Create a new (column) {@code FieldMatrix<T>} using {@code v} as the data for the unique
+     * column of the created matrix. The input array is copied.
+     *
+     * @param field Field to which the elements belong.
+     * @param v Column vector holding data for new matrix.
+     */
+    public Array2DRowFieldMatrix(final Field<T> field, final T[] v) {
+        super(field);
+        final int nRows = v.length;
+        data = MathArrays.buildArray(getField(), nRows, 1);
+        for (int row = 0; row < nRows; row++) {
+            data[row][0] = v[row];
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public FieldMatrix<T> createMatrix(final int rowDimension, final int columnDimension)
+            throws NotStrictlyPositiveException {
+        return new Array2DRowFieldMatrix<T>(getField(), rowDimension, columnDimension);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public FieldMatrix<T> copy() {
+        return new Array2DRowFieldMatrix<T>(getField(), copyOut(), false);
+    }
+
+    /**
+     * Add {@code m} to this matrix.
+     *
+     * @param m Matrix to be added.
+     * @return {@code this} + m.
+     * @throws MatrixDimensionMismatchException if {@code m} is not the same size as this matrix.
+     */
+    public Array2DRowFieldMatrix<T> add(final Array2DRowFieldMatrix<T> m)
+            throws MatrixDimensionMismatchException {
+        // safety check
+        checkAdditionCompatible(m);
+
+        final int rowCount = getRowDimension();
+        final int columnCount = getColumnDimension();
+        final T[][] outData = MathArrays.buildArray(getField(), rowCount, columnCount);
+        for (int row = 0; row < rowCount; row++) {
+            final T[] dataRow = data[row];
+            final T[] mRow = m.data[row];
+            final T[] outDataRow = outData[row];
+            for (int col = 0; col < columnCount; col++) {
+                outDataRow[col] = dataRow[col].add(mRow[col]);
+            }
+        }
+
+        return new Array2DRowFieldMatrix<T>(getField(), outData, false);
+    }
+
+    /**
+     * Subtract {@code m} from this matrix.
+     *
+     * @param m Matrix to be subtracted.
+     * @return {@code this} + m.
+     * @throws MatrixDimensionMismatchException if {@code m} is not the same size as this matrix.
+     */
+    public Array2DRowFieldMatrix<T> subtract(final Array2DRowFieldMatrix<T> m)
+            throws MatrixDimensionMismatchException {
+        // safety check
+        checkSubtractionCompatible(m);
+
+        final int rowCount = getRowDimension();
+        final int columnCount = getColumnDimension();
+        final T[][] outData = MathArrays.buildArray(getField(), rowCount, columnCount);
+        for (int row = 0; row < rowCount; row++) {
+            final T[] dataRow = data[row];
+            final T[] mRow = m.data[row];
+            final T[] outDataRow = outData[row];
+            for (int col = 0; col < columnCount; col++) {
+                outDataRow[col] = dataRow[col].subtract(mRow[col]);
+            }
+        }
+
+        return new Array2DRowFieldMatrix<T>(getField(), outData, false);
+    }
+
+    /**
+     * Postmultiplying this matrix by {@code m}.
+     *
+     * @param m Matrix to postmultiply by.
+     * @return {@code this} * m.
+     * @throws DimensionMismatchException if the number of columns of this matrix is not equal to
+     *     the number of rows of {@code m}.
+     */
+    public Array2DRowFieldMatrix<T> multiply(final Array2DRowFieldMatrix<T> m)
+            throws DimensionMismatchException {
+        // safety check
+        checkMultiplicationCompatible(m);
+
+        final int nRows = this.getRowDimension();
+        final int nCols = m.getColumnDimension();
+        final int nSum = this.getColumnDimension();
+        final T[][] outData = MathArrays.buildArray(getField(), nRows, nCols);
+        for (int row = 0; row < nRows; row++) {
+            final T[] dataRow = data[row];
+            final T[] outDataRow = outData[row];
+            for (int col = 0; col < nCols; col++) {
+                T sum = getField().getZero();
+                for (int i = 0; i < nSum; i++) {
+                    sum = sum.add(dataRow[i].multiply(m.data[i][col]));
+                }
+                outDataRow[col] = sum;
+            }
+        }
+
+        return new Array2DRowFieldMatrix<T>(getField(), outData, false);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public T[][] getData() {
+        return copyOut();
+    }
+
+    /**
+     * Get a reference to the underlying data array. This methods returns internal data,
+     * <strong>not</strong> fresh copy of it.
+     *
+     * @return the 2-dimensional array of entries.
+     */
+    public T[][] getDataRef() {
+        return data;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setSubMatrix(final T[][] subMatrix, final int row, final int column)
+            throws OutOfRangeException,
+                    NullArgumentException,
+                    NoDataException,
+                    DimensionMismatchException {
+        if (data == null) {
+            if (row > 0) {
+                throw new MathIllegalStateException(
+                        LocalizedFormats.FIRST_ROWS_NOT_INITIALIZED_YET, row);
+            }
+            if (column > 0) {
+                throw new MathIllegalStateException(
+                        LocalizedFormats.FIRST_COLUMNS_NOT_INITIALIZED_YET, column);
+            }
+            final int nRows = subMatrix.length;
+            if (nRows == 0) {
+                throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_ROW);
+            }
+
+            final int nCols = subMatrix[0].length;
+            if (nCols == 0) {
+                throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_COLUMN);
+            }
+            data = MathArrays.buildArray(getField(), subMatrix.length, nCols);
+            for (int i = 0; i < data.length; ++i) {
+                if (subMatrix[i].length != nCols) {
+                    throw new DimensionMismatchException(nCols, subMatrix[i].length);
+                }
+                System.arraycopy(subMatrix[i], 0, data[i + row], column, nCols);
+            }
+        } else {
+            super.setSubMatrix(subMatrix, row, column);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public T getEntry(final int row, final int column) throws OutOfRangeException {
+        checkRowIndex(row);
+        checkColumnIndex(column);
+
+        return data[row][column];
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setEntry(final int row, final int column, final T value)
+            throws OutOfRangeException {
+        checkRowIndex(row);
+        checkColumnIndex(column);
+
+        data[row][column] = value;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addToEntry(final int row, final int column, final T increment)
+            throws OutOfRangeException {
+        checkRowIndex(row);
+        checkColumnIndex(column);
+
+        data[row][column] = data[row][column].add(increment);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void multiplyEntry(final int row, final int column, final T factor)
+            throws OutOfRangeException {
+        checkRowIndex(row);
+        checkColumnIndex(column);
+
+        data[row][column] = data[row][column].multiply(factor);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getRowDimension() {
+        return (data == null) ? 0 : data.length;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getColumnDimension() {
+        return ((data == null) || (data[0] == null)) ? 0 : data[0].length;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public T[] operate(final T[] v) throws DimensionMismatchException {
+        final int nRows = this.getRowDimension();
+        final int nCols = this.getColumnDimension();
+        if (v.length != nCols) {
+            throw new DimensionMismatchException(v.length, nCols);
+        }
+        final T[] out = MathArrays.buildArray(getField(), nRows);
+        for (int row = 0; row < nRows; row++) {
+            final T[] dataRow = data[row];
+            T sum = getField().getZero();
+            for (int i = 0; i < nCols; i++) {
+                sum = sum.add(dataRow[i].multiply(v[i]));
+            }
+            out[row] = sum;
+        }
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public T[] preMultiply(final T[] v) throws DimensionMismatchException {
+        final int nRows = getRowDimension();
+        final int nCols = getColumnDimension();
+        if (v.length != nRows) {
+            throw new DimensionMismatchException(v.length, nRows);
+        }
+
+        final T[] out = MathArrays.buildArray(getField(), nCols);
+        for (int col = 0; col < nCols; ++col) {
+            T sum = getField().getZero();
+            for (int i = 0; i < nRows; ++i) {
+                sum = sum.add(data[i][col].multiply(v[i]));
+            }
+            out[col] = sum;
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public T walkInRowOrder(final FieldMatrixChangingVisitor<T> visitor) {
+        final int rows = getRowDimension();
+        final int columns = getColumnDimension();
+        visitor.start(rows, columns, 0, rows - 1, 0, columns - 1);
+        for (int i = 0; i < rows; ++i) {
+            final T[] rowI = data[i];
+            for (int j = 0; j < columns; ++j) {
+                rowI[j] = visitor.visit(i, j, rowI[j]);
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public T walkInRowOrder(final FieldMatrixPreservingVisitor<T> visitor) {
+        final int rows = getRowDimension();
+        final int columns = getColumnDimension();
+        visitor.start(rows, columns, 0, rows - 1, 0, columns - 1);
+        for (int i = 0; i < rows; ++i) {
+            final T[] rowI = data[i];
+            for (int j = 0; j < columns; ++j) {
+                visitor.visit(i, j, rowI[j]);
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public T walkInRowOrder(
+            final FieldMatrixChangingVisitor<T> visitor,
+            final int startRow,
+            final int endRow,
+            final int startColumn,
+            final int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException {
+        checkSubMatrixIndex(startRow, endRow, startColumn, endColumn);
+        visitor.start(
+                getRowDimension(), getColumnDimension(), startRow, endRow, startColumn, endColumn);
+        for (int i = startRow; i <= endRow; ++i) {
+            final T[] rowI = data[i];
+            for (int j = startColumn; j <= endColumn; ++j) {
+                rowI[j] = visitor.visit(i, j, rowI[j]);
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public T walkInRowOrder(
+            final FieldMatrixPreservingVisitor<T> visitor,
+            final int startRow,
+            final int endRow,
+            final int startColumn,
+            final int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException {
+        checkSubMatrixIndex(startRow, endRow, startColumn, endColumn);
+        visitor.start(
+                getRowDimension(), getColumnDimension(), startRow, endRow, startColumn, endColumn);
+        for (int i = startRow; i <= endRow; ++i) {
+            final T[] rowI = data[i];
+            for (int j = startColumn; j <= endColumn; ++j) {
+                visitor.visit(i, j, rowI[j]);
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public T walkInColumnOrder(final FieldMatrixChangingVisitor<T> visitor) {
+        final int rows = getRowDimension();
+        final int columns = getColumnDimension();
+        visitor.start(rows, columns, 0, rows - 1, 0, columns - 1);
+        for (int j = 0; j < columns; ++j) {
+            for (int i = 0; i < rows; ++i) {
+                final T[] rowI = data[i];
+                rowI[j] = visitor.visit(i, j, rowI[j]);
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public T walkInColumnOrder(final FieldMatrixPreservingVisitor<T> visitor) {
+        final int rows = getRowDimension();
+        final int columns = getColumnDimension();
+        visitor.start(rows, columns, 0, rows - 1, 0, columns - 1);
+        for (int j = 0; j < columns; ++j) {
+            for (int i = 0; i < rows; ++i) {
+                visitor.visit(i, j, data[i][j]);
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public T walkInColumnOrder(
+            final FieldMatrixChangingVisitor<T> visitor,
+            final int startRow,
+            final int endRow,
+            final int startColumn,
+            final int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException {
+        checkSubMatrixIndex(startRow, endRow, startColumn, endColumn);
+        visitor.start(
+                getRowDimension(), getColumnDimension(), startRow, endRow, startColumn, endColumn);
+        for (int j = startColumn; j <= endColumn; ++j) {
+            for (int i = startRow; i <= endRow; ++i) {
+                final T[] rowI = data[i];
+                rowI[j] = visitor.visit(i, j, rowI[j]);
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public T walkInColumnOrder(
+            final FieldMatrixPreservingVisitor<T> visitor,
+            final int startRow,
+            final int endRow,
+            final int startColumn,
+            final int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException {
+        checkSubMatrixIndex(startRow, endRow, startColumn, endColumn);
+        visitor.start(
+                getRowDimension(), getColumnDimension(), startRow, endRow, startColumn, endColumn);
+        for (int j = startColumn; j <= endColumn; ++j) {
+            for (int i = startRow; i <= endRow; ++i) {
+                visitor.visit(i, j, data[i][j]);
+            }
+        }
+        return visitor.end();
+    }
+
+    /**
+     * Get a fresh copy of the underlying data array.
+     *
+     * @return a copy of the underlying data array.
+     */
+    private T[][] copyOut() {
+        final int nRows = this.getRowDimension();
+        final T[][] out = MathArrays.buildArray(getField(), nRows, getColumnDimension());
+        // can't copy 2-d array in one shot, otherwise get row references
+        for (int i = 0; i < nRows; i++) {
+            System.arraycopy(data[i], 0, out[i], 0, data[i].length);
+        }
+        return out;
+    }
+
+    /**
+     * Replace data with a fresh copy of the input array.
+     *
+     * @param in Data to copy.
+     * @throws NoDataException if the input array is empty.
+     * @throws DimensionMismatchException if the input array is not rectangular.
+     * @throws NullArgumentException if the input array is {@code null}.
+     */
+    private void copyIn(final T[][] in)
+            throws NullArgumentException, NoDataException, DimensionMismatchException {
+        setSubMatrix(in, 0, 0);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/Array2DRowRealMatrix.java b/src/main/java/org/apache/commons/math3/linear/Array2DRowRealMatrix.java
new file mode 100644
index 0000000..1780663
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/Array2DRowRealMatrix.java
@@ -0,0 +1,540 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.MathUtils;
+
+import java.io.Serializable;
+
+/** Implementation of {@link RealMatrix} using a {@code double[][]} array to store entries. */
+public class Array2DRowRealMatrix extends AbstractRealMatrix implements Serializable {
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -1067294169172445528L;
+
+    /** Entries of the matrix. */
+    private double data[][];
+
+    /** Creates a matrix with no data */
+    public Array2DRowRealMatrix() {}
+
+    /**
+     * Create a new RealMatrix with the supplied row and column dimensions.
+     *
+     * @param rowDimension Number of rows in the new matrix.
+     * @param columnDimension Number of columns in the new matrix.
+     * @throws NotStrictlyPositiveException if the row or column dimension is not positive.
+     */
+    public Array2DRowRealMatrix(final int rowDimension, final int columnDimension)
+            throws NotStrictlyPositiveException {
+        super(rowDimension, columnDimension);
+        data = new double[rowDimension][columnDimension];
+    }
+
+    /**
+     * Create a new {@code RealMatrix} using the input array as the underlying data array.
+     *
+     * <p>The input array is copied, not referenced. This constructor has the same effect as calling
+     * {@link #Array2DRowRealMatrix(double[][], boolean)} with the second argument set to {@code
+     * true}.
+     *
+     * @param d Data for the new matrix.
+     * @throws DimensionMismatchException if {@code d} is not rectangular.
+     * @throws NoDataException if {@code d} row or column dimension is zero.
+     * @throws NullArgumentException if {@code d} is {@code null}.
+     * @see #Array2DRowRealMatrix(double[][], boolean)
+     */
+    public Array2DRowRealMatrix(final double[][] d)
+            throws DimensionMismatchException, NoDataException, NullArgumentException {
+        copyIn(d);
+    }
+
+    /**
+     * Create a new RealMatrix using the input array as the underlying data array. If an array is
+     * built specially in order to be embedded in a RealMatrix and not used directly, the {@code
+     * copyArray} may be set to {@code false}. This will prevent the copying and improve performance
+     * as no new array will be built and no data will be copied.
+     *
+     * @param d Data for new matrix.
+     * @param copyArray if {@code true}, the input array will be copied, otherwise it will be
+     *     referenced.
+     * @throws DimensionMismatchException if {@code d} is not rectangular.
+     * @throws NoDataException if {@code d} row or column dimension is zero.
+     * @throws NullArgumentException if {@code d} is {@code null}.
+     * @see #Array2DRowRealMatrix(double[][])
+     */
+    public Array2DRowRealMatrix(final double[][] d, final boolean copyArray)
+            throws DimensionMismatchException, NoDataException, NullArgumentException {
+        if (copyArray) {
+            copyIn(d);
+        } else {
+            if (d == null) {
+                throw new NullArgumentException();
+            }
+            final int nRows = d.length;
+            if (nRows == 0) {
+                throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_ROW);
+            }
+            final int nCols = d[0].length;
+            if (nCols == 0) {
+                throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_COLUMN);
+            }
+            for (int r = 1; r < nRows; r++) {
+                if (d[r].length != nCols) {
+                    throw new DimensionMismatchException(d[r].length, nCols);
+                }
+            }
+            data = d;
+        }
+    }
+
+    /**
+     * Create a new (column) RealMatrix using {@code v} as the data for the unique column of the
+     * created matrix. The input array is copied.
+     *
+     * @param v Column vector holding data for new matrix.
+     */
+    public Array2DRowRealMatrix(final double[] v) {
+        final int nRows = v.length;
+        data = new double[nRows][1];
+        for (int row = 0; row < nRows; row++) {
+            data[row][0] = v[row];
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealMatrix createMatrix(final int rowDimension, final int columnDimension)
+            throws NotStrictlyPositiveException {
+        return new Array2DRowRealMatrix(rowDimension, columnDimension);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealMatrix copy() {
+        return new Array2DRowRealMatrix(copyOut(), false);
+    }
+
+    /**
+     * Compute the sum of {@code this} and {@code m}.
+     *
+     * @param m Matrix to be added.
+     * @return {@code this + m}.
+     * @throws MatrixDimensionMismatchException if {@code m} is not the same size as {@code this}.
+     */
+    public Array2DRowRealMatrix add(final Array2DRowRealMatrix m)
+            throws MatrixDimensionMismatchException {
+        // Safety check.
+        MatrixUtils.checkAdditionCompatible(this, m);
+
+        final int rowCount = getRowDimension();
+        final int columnCount = getColumnDimension();
+        final double[][] outData = new double[rowCount][columnCount];
+        for (int row = 0; row < rowCount; row++) {
+            final double[] dataRow = data[row];
+            final double[] mRow = m.data[row];
+            final double[] outDataRow = outData[row];
+            for (int col = 0; col < columnCount; col++) {
+                outDataRow[col] = dataRow[col] + mRow[col];
+            }
+        }
+
+        return new Array2DRowRealMatrix(outData, false);
+    }
+
+    /**
+     * Returns {@code this} minus {@code m}.
+     *
+     * @param m Matrix to be subtracted.
+     * @return {@code this - m}
+     * @throws MatrixDimensionMismatchException if {@code m} is not the same size as {@code this}.
+     */
+    public Array2DRowRealMatrix subtract(final Array2DRowRealMatrix m)
+            throws MatrixDimensionMismatchException {
+        MatrixUtils.checkSubtractionCompatible(this, m);
+
+        final int rowCount = getRowDimension();
+        final int columnCount = getColumnDimension();
+        final double[][] outData = new double[rowCount][columnCount];
+        for (int row = 0; row < rowCount; row++) {
+            final double[] dataRow = data[row];
+            final double[] mRow = m.data[row];
+            final double[] outDataRow = outData[row];
+            for (int col = 0; col < columnCount; col++) {
+                outDataRow[col] = dataRow[col] - mRow[col];
+            }
+        }
+
+        return new Array2DRowRealMatrix(outData, false);
+    }
+
+    /**
+     * Returns the result of postmultiplying {@code this} by {@code m}.
+     *
+     * @param m matrix to postmultiply by
+     * @return {@code this * m}
+     * @throws DimensionMismatchException if {@code columnDimension(this) != rowDimension(m)}
+     */
+    public Array2DRowRealMatrix multiply(final Array2DRowRealMatrix m)
+            throws DimensionMismatchException {
+        MatrixUtils.checkMultiplicationCompatible(this, m);
+
+        final int nRows = this.getRowDimension();
+        final int nCols = m.getColumnDimension();
+        final int nSum = this.getColumnDimension();
+
+        final double[][] outData = new double[nRows][nCols];
+        // Will hold a column of "m".
+        final double[] mCol = new double[nSum];
+        final double[][] mData = m.data;
+
+        // Multiply.
+        for (int col = 0; col < nCols; col++) {
+            // Copy all elements of column "col" of "m" so that
+            // will be in contiguous memory.
+            for (int mRow = 0; mRow < nSum; mRow++) {
+                mCol[mRow] = mData[mRow][col];
+            }
+
+            for (int row = 0; row < nRows; row++) {
+                final double[] dataRow = data[row];
+                double sum = 0;
+                for (int i = 0; i < nSum; i++) {
+                    sum += dataRow[i] * mCol[i];
+                }
+                outData[row][col] = sum;
+            }
+        }
+
+        return new Array2DRowRealMatrix(outData, false);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double[][] getData() {
+        return copyOut();
+    }
+
+    /**
+     * Get a reference to the underlying data array.
+     *
+     * @return 2-dimensional array of entries.
+     */
+    public double[][] getDataRef() {
+        return data;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setSubMatrix(final double[][] subMatrix, final int row, final int column)
+            throws NoDataException,
+                    OutOfRangeException,
+                    DimensionMismatchException,
+                    NullArgumentException {
+        if (data == null) {
+            if (row > 0) {
+                throw new MathIllegalStateException(
+                        LocalizedFormats.FIRST_ROWS_NOT_INITIALIZED_YET, row);
+            }
+            if (column > 0) {
+                throw new MathIllegalStateException(
+                        LocalizedFormats.FIRST_COLUMNS_NOT_INITIALIZED_YET, column);
+            }
+            MathUtils.checkNotNull(subMatrix);
+            final int nRows = subMatrix.length;
+            if (nRows == 0) {
+                throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_ROW);
+            }
+
+            final int nCols = subMatrix[0].length;
+            if (nCols == 0) {
+                throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_COLUMN);
+            }
+            data = new double[subMatrix.length][nCols];
+            for (int i = 0; i < data.length; ++i) {
+                if (subMatrix[i].length != nCols) {
+                    throw new DimensionMismatchException(subMatrix[i].length, nCols);
+                }
+                System.arraycopy(subMatrix[i], 0, data[i + row], column, nCols);
+            }
+        } else {
+            super.setSubMatrix(subMatrix, row, column);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getEntry(final int row, final int column) throws OutOfRangeException {
+        MatrixUtils.checkMatrixIndex(this, row, column);
+        return data[row][column];
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setEntry(final int row, final int column, final double value)
+            throws OutOfRangeException {
+        MatrixUtils.checkMatrixIndex(this, row, column);
+        data[row][column] = value;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addToEntry(final int row, final int column, final double increment)
+            throws OutOfRangeException {
+        MatrixUtils.checkMatrixIndex(this, row, column);
+        data[row][column] += increment;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void multiplyEntry(final int row, final int column, final double factor)
+            throws OutOfRangeException {
+        MatrixUtils.checkMatrixIndex(this, row, column);
+        data[row][column] *= factor;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getRowDimension() {
+        return (data == null) ? 0 : data.length;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getColumnDimension() {
+        return ((data == null) || (data[0] == null)) ? 0 : data[0].length;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double[] operate(final double[] v) throws DimensionMismatchException {
+        final int nRows = this.getRowDimension();
+        final int nCols = this.getColumnDimension();
+        if (v.length != nCols) {
+            throw new DimensionMismatchException(v.length, nCols);
+        }
+        final double[] out = new double[nRows];
+        for (int row = 0; row < nRows; row++) {
+            final double[] dataRow = data[row];
+            double sum = 0;
+            for (int i = 0; i < nCols; i++) {
+                sum += dataRow[i] * v[i];
+            }
+            out[row] = sum;
+        }
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double[] preMultiply(final double[] v) throws DimensionMismatchException {
+        final int nRows = getRowDimension();
+        final int nCols = getColumnDimension();
+        if (v.length != nRows) {
+            throw new DimensionMismatchException(v.length, nRows);
+        }
+
+        final double[] out = new double[nCols];
+        for (int col = 0; col < nCols; ++col) {
+            double sum = 0;
+            for (int i = 0; i < nRows; ++i) {
+                sum += data[i][col] * v[i];
+            }
+            out[col] = sum;
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double walkInRowOrder(final RealMatrixChangingVisitor visitor) {
+        final int rows = getRowDimension();
+        final int columns = getColumnDimension();
+        visitor.start(rows, columns, 0, rows - 1, 0, columns - 1);
+        for (int i = 0; i < rows; ++i) {
+            final double[] rowI = data[i];
+            for (int j = 0; j < columns; ++j) {
+                rowI[j] = visitor.visit(i, j, rowI[j]);
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double walkInRowOrder(final RealMatrixPreservingVisitor visitor) {
+        final int rows = getRowDimension();
+        final int columns = getColumnDimension();
+        visitor.start(rows, columns, 0, rows - 1, 0, columns - 1);
+        for (int i = 0; i < rows; ++i) {
+            final double[] rowI = data[i];
+            for (int j = 0; j < columns; ++j) {
+                visitor.visit(i, j, rowI[j]);
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double walkInRowOrder(
+            final RealMatrixChangingVisitor visitor,
+            final int startRow,
+            final int endRow,
+            final int startColumn,
+            final int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException {
+        MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn);
+        visitor.start(
+                getRowDimension(), getColumnDimension(), startRow, endRow, startColumn, endColumn);
+        for (int i = startRow; i <= endRow; ++i) {
+            final double[] rowI = data[i];
+            for (int j = startColumn; j <= endColumn; ++j) {
+                rowI[j] = visitor.visit(i, j, rowI[j]);
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double walkInRowOrder(
+            final RealMatrixPreservingVisitor visitor,
+            final int startRow,
+            final int endRow,
+            final int startColumn,
+            final int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException {
+        MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn);
+        visitor.start(
+                getRowDimension(), getColumnDimension(), startRow, endRow, startColumn, endColumn);
+        for (int i = startRow; i <= endRow; ++i) {
+            final double[] rowI = data[i];
+            for (int j = startColumn; j <= endColumn; ++j) {
+                visitor.visit(i, j, rowI[j]);
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double walkInColumnOrder(final RealMatrixChangingVisitor visitor) {
+        final int rows = getRowDimension();
+        final int columns = getColumnDimension();
+        visitor.start(rows, columns, 0, rows - 1, 0, columns - 1);
+        for (int j = 0; j < columns; ++j) {
+            for (int i = 0; i < rows; ++i) {
+                final double[] rowI = data[i];
+                rowI[j] = visitor.visit(i, j, rowI[j]);
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double walkInColumnOrder(final RealMatrixPreservingVisitor visitor) {
+        final int rows = getRowDimension();
+        final int columns = getColumnDimension();
+        visitor.start(rows, columns, 0, rows - 1, 0, columns - 1);
+        for (int j = 0; j < columns; ++j) {
+            for (int i = 0; i < rows; ++i) {
+                visitor.visit(i, j, data[i][j]);
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double walkInColumnOrder(
+            final RealMatrixChangingVisitor visitor,
+            final int startRow,
+            final int endRow,
+            final int startColumn,
+            final int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException {
+        MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn);
+        visitor.start(
+                getRowDimension(), getColumnDimension(), startRow, endRow, startColumn, endColumn);
+        for (int j = startColumn; j <= endColumn; ++j) {
+            for (int i = startRow; i <= endRow; ++i) {
+                final double[] rowI = data[i];
+                rowI[j] = visitor.visit(i, j, rowI[j]);
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double walkInColumnOrder(
+            final RealMatrixPreservingVisitor visitor,
+            final int startRow,
+            final int endRow,
+            final int startColumn,
+            final int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException {
+        MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn);
+        visitor.start(
+                getRowDimension(), getColumnDimension(), startRow, endRow, startColumn, endColumn);
+        for (int j = startColumn; j <= endColumn; ++j) {
+            for (int i = startRow; i <= endRow; ++i) {
+                visitor.visit(i, j, data[i][j]);
+            }
+        }
+        return visitor.end();
+    }
+
+    /**
+     * Get a fresh copy of the underlying data array.
+     *
+     * @return a copy of the underlying data array.
+     */
+    private double[][] copyOut() {
+        final int nRows = this.getRowDimension();
+        final double[][] out = new double[nRows][this.getColumnDimension()];
+        // can't copy 2-d array in one shot, otherwise get row references
+        for (int i = 0; i < nRows; i++) {
+            System.arraycopy(data[i], 0, out[i], 0, data[i].length);
+        }
+        return out;
+    }
+
+    /**
+     * Replace data with a fresh copy of the input array.
+     *
+     * @param in Data to copy.
+     * @throws NoDataException if the input array is empty.
+     * @throws DimensionMismatchException if the input array is not rectangular.
+     * @throws NullArgumentException if the input array is {@code null}.
+     */
+    private void copyIn(final double[][] in)
+            throws DimensionMismatchException, NoDataException, NullArgumentException {
+        setSubMatrix(in, 0, 0);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/ArrayFieldVector.java b/src/main/java/org/apache/commons/math3/linear/ArrayFieldVector.java
new file mode 100644
index 0000000..db90aaf
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/ArrayFieldVector.java
@@ -0,0 +1,1091 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.FieldElement;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.ZeroException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.util.MathUtils;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+/**
+ * This class implements the {@link FieldVector} interface with a {@link FieldElement} array.
+ *
+ * @param <T> the type of the field elements
+ * @since 2.0
+ */
+public class ArrayFieldVector<T extends FieldElement<T>> implements FieldVector<T>, Serializable {
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 7648186910365927050L;
+
+    /** Entries of the vector. */
+    private T[] data;
+
+    /** Field to which the elements belong. */
+    private final Field<T> field;
+
+    /**
+     * Build a 0-length vector. Zero-length vectors may be used to initialize construction of
+     * vectors by data gathering. We start with zero-length and use either the {@link
+     * #ArrayFieldVector(ArrayFieldVector, ArrayFieldVector)} constructor or one of the {@code
+     * append} methods ({@link #add(FieldVector)} or {@link #append(ArrayFieldVector)}) to gather
+     * data into this vector.
+     *
+     * @param field field to which the elements belong
+     */
+    public ArrayFieldVector(final Field<T> field) {
+        this(field, 0);
+    }
+
+    /**
+     * Construct a vector of zeroes.
+     *
+     * @param field Field to which the elements belong.
+     * @param size Size of the vector.
+     */
+    public ArrayFieldVector(Field<T> field, int size) {
+        this.field = field;
+        this.data = MathArrays.buildArray(field, size);
+    }
+
+    /**
+     * Construct a vector with preset values.
+     *
+     * @param size Size of the vector.
+     * @param preset All entries will be set with this value.
+     */
+    public ArrayFieldVector(int size, T preset) {
+        this(preset.getField(), size);
+        Arrays.fill(data, preset);
+    }
+
+    /**
+     * Construct a vector from an array, copying the input array. This constructor needs a non-empty
+     * {@code d} array to retrieve the field from its first element. This implies it cannot build 0
+     * length vectors. To build vectors from any size, one should use the {@link
+     * #ArrayFieldVector(Field, FieldElement[])} constructor.
+     *
+     * @param d Array.
+     * @throws NullArgumentException if {@code d} is {@code null}.
+     * @throws ZeroException if {@code d} is empty.
+     * @see #ArrayFieldVector(Field, FieldElement[])
+     */
+    public ArrayFieldVector(T[] d) throws NullArgumentException, ZeroException {
+        MathUtils.checkNotNull(d);
+        try {
+            field = d[0].getField();
+            data = d.clone();
+        } catch (ArrayIndexOutOfBoundsException e) {
+            throw new ZeroException(LocalizedFormats.VECTOR_MUST_HAVE_AT_LEAST_ONE_ELEMENT);
+        }
+    }
+
+    /**
+     * Construct a vector from an array, copying the input array.
+     *
+     * @param field Field to which the elements belong.
+     * @param d Array.
+     * @throws NullArgumentException if {@code d} is {@code null}.
+     * @see #ArrayFieldVector(FieldElement[])
+     */
+    public ArrayFieldVector(Field<T> field, T[] d) throws NullArgumentException {
+        MathUtils.checkNotNull(d);
+        this.field = field;
+        data = d.clone();
+    }
+
+    /**
+     * Create a new ArrayFieldVector using the input array as the underlying data array. If an array
+     * is built specially in order to be embedded in a ArrayFieldVector and not used directly, the
+     * {@code copyArray} may be set to {@code false}. This will prevent the copying and improve
+     * performance as no new array will be built and no data will be copied. This constructor needs
+     * a non-empty {@code d} array to retrieve the field from its first element. This implies it
+     * cannot build 0 length vectors. To build vectors from any size, one should use the {@link
+     * #ArrayFieldVector(Field, FieldElement[], boolean)} constructor.
+     *
+     * @param d Data for the new vector.
+     * @param copyArray If {@code true}, the input array will be copied, otherwise it will be
+     *     referenced.
+     * @throws NullArgumentException if {@code d} is {@code null}.
+     * @throws ZeroException if {@code d} is empty.
+     * @see #ArrayFieldVector(FieldElement[])
+     * @see #ArrayFieldVector(Field, FieldElement[], boolean)
+     */
+    public ArrayFieldVector(T[] d, boolean copyArray) throws NullArgumentException, ZeroException {
+        MathUtils.checkNotNull(d);
+        if (d.length == 0) {
+            throw new ZeroException(LocalizedFormats.VECTOR_MUST_HAVE_AT_LEAST_ONE_ELEMENT);
+        }
+        field = d[0].getField();
+        data = copyArray ? d.clone() : d;
+    }
+
+    /**
+     * Create a new ArrayFieldVector using the input array as the underlying data array. If an array
+     * is built specially in order to be embedded in a ArrayFieldVector and not used directly, the
+     * {@code copyArray} may be set to {@code false}. This will prevent the copying and improve
+     * performance as no new array will be built and no data will be copied.
+     *
+     * @param field Field to which the elements belong.
+     * @param d Data for the new vector.
+     * @param copyArray If {@code true}, the input array will be copied, otherwise it will be
+     *     referenced.
+     * @throws NullArgumentException if {@code d} is {@code null}.
+     * @see #ArrayFieldVector(FieldElement[], boolean)
+     */
+    public ArrayFieldVector(Field<T> field, T[] d, boolean copyArray) throws NullArgumentException {
+        MathUtils.checkNotNull(d);
+        this.field = field;
+        data = copyArray ? d.clone() : d;
+    }
+
+    /**
+     * Construct a vector from part of a array.
+     *
+     * @param d Array.
+     * @param pos Position of the first entry.
+     * @param size Number of entries to copy.
+     * @throws NullArgumentException if {@code d} is {@code null}.
+     * @throws NumberIsTooLargeException if the size of {@code d} is less than {@code pos + size}.
+     */
+    public ArrayFieldVector(T[] d, int pos, int size)
+            throws NullArgumentException, NumberIsTooLargeException {
+        MathUtils.checkNotNull(d);
+        if (d.length < pos + size) {
+            throw new NumberIsTooLargeException(pos + size, d.length, true);
+        }
+        field = d[0].getField();
+        data = MathArrays.buildArray(field, size);
+        System.arraycopy(d, pos, data, 0, size);
+    }
+
+    /**
+     * Construct a vector from part of a array.
+     *
+     * @param field Field to which the elements belong.
+     * @param d Array.
+     * @param pos Position of the first entry.
+     * @param size Number of entries to copy.
+     * @throws NullArgumentException if {@code d} is {@code null}.
+     * @throws NumberIsTooLargeException if the size of {@code d} is less than {@code pos + size}.
+     */
+    public ArrayFieldVector(Field<T> field, T[] d, int pos, int size)
+            throws NullArgumentException, NumberIsTooLargeException {
+        MathUtils.checkNotNull(d);
+        if (d.length < pos + size) {
+            throw new NumberIsTooLargeException(pos + size, d.length, true);
+        }
+        this.field = field;
+        data = MathArrays.buildArray(field, size);
+        System.arraycopy(d, pos, data, 0, size);
+    }
+
+    /**
+     * Construct a vector from another vector, using a deep copy.
+     *
+     * @param v Vector to copy.
+     * @throws NullArgumentException if {@code v} is {@code null}.
+     */
+    public ArrayFieldVector(FieldVector<T> v) throws NullArgumentException {
+        MathUtils.checkNotNull(v);
+        field = v.getField();
+        data = MathArrays.buildArray(field, v.getDimension());
+        for (int i = 0; i < data.length; ++i) {
+            data[i] = v.getEntry(i);
+        }
+    }
+
+    /**
+     * Construct a vector from another vector, using a deep copy.
+     *
+     * @param v Vector to copy.
+     * @throws NullArgumentException if {@code v} is {@code null}.
+     */
+    public ArrayFieldVector(ArrayFieldVector<T> v) throws NullArgumentException {
+        MathUtils.checkNotNull(v);
+        field = v.getField();
+        data = v.data.clone();
+    }
+
+    /**
+     * Construct a vector from another vector.
+     *
+     * @param v Vector to copy.
+     * @param deep If {@code true} perform a deep copy, otherwise perform a shallow copy
+     * @throws NullArgumentException if {@code v} is {@code null}.
+     */
+    public ArrayFieldVector(ArrayFieldVector<T> v, boolean deep) throws NullArgumentException {
+        MathUtils.checkNotNull(v);
+        field = v.getField();
+        data = deep ? v.data.clone() : v.data;
+    }
+
+    /**
+     * Construct a vector by appending one vector to another vector.
+     *
+     * @param v1 First vector (will be put in front of the new vector).
+     * @param v2 Second vector (will be put at back of the new vector).
+     * @throws NullArgumentException if {@code v1} or {@code v2} is {@code null}.
+     * @deprecated as of 3.2, replaced by {@link #ArrayFieldVector(FieldVector, FieldVector)}
+     */
+    @Deprecated
+    public ArrayFieldVector(ArrayFieldVector<T> v1, ArrayFieldVector<T> v2)
+            throws NullArgumentException {
+        this((FieldVector<T>) v1, (FieldVector<T>) v2);
+    }
+
+    /**
+     * Construct a vector by appending one vector to another vector.
+     *
+     * @param v1 First vector (will be put in front of the new vector).
+     * @param v2 Second vector (will be put at back of the new vector).
+     * @throws NullArgumentException if {@code v1} or {@code v2} is {@code null}.
+     * @since 3.2
+     */
+    public ArrayFieldVector(FieldVector<T> v1, FieldVector<T> v2) throws NullArgumentException {
+        MathUtils.checkNotNull(v1);
+        MathUtils.checkNotNull(v2);
+        field = v1.getField();
+        final T[] v1Data =
+                (v1 instanceof ArrayFieldVector) ? ((ArrayFieldVector<T>) v1).data : v1.toArray();
+        final T[] v2Data =
+                (v2 instanceof ArrayFieldVector) ? ((ArrayFieldVector<T>) v2).data : v2.toArray();
+        data = MathArrays.buildArray(field, v1Data.length + v2Data.length);
+        System.arraycopy(v1Data, 0, data, 0, v1Data.length);
+        System.arraycopy(v2Data, 0, data, v1Data.length, v2Data.length);
+    }
+
+    /**
+     * Construct a vector by appending one vector to another vector.
+     *
+     * @param v1 First vector (will be put in front of the new vector).
+     * @param v2 Second vector (will be put at back of the new vector).
+     * @throws NullArgumentException if {@code v1} or {@code v2} is {@code null}.
+     * @deprecated as of 3.2, replaced by {@link #ArrayFieldVector(FieldVector, FieldElement[])}
+     */
+    @Deprecated
+    public ArrayFieldVector(ArrayFieldVector<T> v1, T[] v2) throws NullArgumentException {
+        this((FieldVector<T>) v1, v2);
+    }
+
+    /**
+     * Construct a vector by appending one vector to another vector.
+     *
+     * @param v1 First vector (will be put in front of the new vector).
+     * @param v2 Second vector (will be put at back of the new vector).
+     * @throws NullArgumentException if {@code v1} or {@code v2} is {@code null}.
+     * @since 3.2
+     */
+    public ArrayFieldVector(FieldVector<T> v1, T[] v2) throws NullArgumentException {
+        MathUtils.checkNotNull(v1);
+        MathUtils.checkNotNull(v2);
+        field = v1.getField();
+        final T[] v1Data =
+                (v1 instanceof ArrayFieldVector) ? ((ArrayFieldVector<T>) v1).data : v1.toArray();
+        data = MathArrays.buildArray(field, v1Data.length + v2.length);
+        System.arraycopy(v1Data, 0, data, 0, v1Data.length);
+        System.arraycopy(v2, 0, data, v1Data.length, v2.length);
+    }
+
+    /**
+     * Construct a vector by appending one vector to another vector.
+     *
+     * @param v1 First vector (will be put in front of the new vector).
+     * @param v2 Second vector (will be put at back of the new vector).
+     * @throws NullArgumentException if {@code v1} or {@code v2} is {@code null}.
+     * @deprecated as of 3.2, replaced by {@link #ArrayFieldVector(FieldElement[], FieldVector)}
+     */
+    @Deprecated
+    public ArrayFieldVector(T[] v1, ArrayFieldVector<T> v2) throws NullArgumentException {
+        this(v1, (FieldVector<T>) v2);
+    }
+
+    /**
+     * Construct a vector by appending one vector to another vector.
+     *
+     * @param v1 First vector (will be put in front of the new vector).
+     * @param v2 Second vector (will be put at back of the new vector).
+     * @throws NullArgumentException if {@code v1} or {@code v2} is {@code null}.
+     * @since 3.2
+     */
+    public ArrayFieldVector(T[] v1, FieldVector<T> v2) throws NullArgumentException {
+        MathUtils.checkNotNull(v1);
+        MathUtils.checkNotNull(v2);
+        field = v2.getField();
+        final T[] v2Data =
+                (v2 instanceof ArrayFieldVector) ? ((ArrayFieldVector<T>) v2).data : v2.toArray();
+        data = MathArrays.buildArray(field, v1.length + v2Data.length);
+        System.arraycopy(v1, 0, data, 0, v1.length);
+        System.arraycopy(v2Data, 0, data, v1.length, v2Data.length);
+    }
+
+    /**
+     * Construct a vector by appending one vector to another vector. This constructor needs at least
+     * one non-empty array to retrieve the field from its first element. This implies it cannot
+     * build 0 length vectors. To build vectors from any size, one should use the {@link
+     * #ArrayFieldVector(Field, FieldElement[], FieldElement[])} constructor.
+     *
+     * @param v1 First vector (will be put in front of the new vector).
+     * @param v2 Second vector (will be put at back of the new vector).
+     * @throws NullArgumentException if {@code v1} or {@code v2} is {@code null}.
+     * @throws ZeroException if both arrays are empty.
+     * @see #ArrayFieldVector(Field, FieldElement[], FieldElement[])
+     */
+    public ArrayFieldVector(T[] v1, T[] v2) throws NullArgumentException, ZeroException {
+        MathUtils.checkNotNull(v1);
+        MathUtils.checkNotNull(v2);
+        if (v1.length + v2.length == 0) {
+            throw new ZeroException(LocalizedFormats.VECTOR_MUST_HAVE_AT_LEAST_ONE_ELEMENT);
+        }
+        data = MathArrays.buildArray(v1[0].getField(), v1.length + v2.length);
+        System.arraycopy(v1, 0, data, 0, v1.length);
+        System.arraycopy(v2, 0, data, v1.length, v2.length);
+        field = data[0].getField();
+    }
+
+    /**
+     * Construct a vector by appending one vector to another vector.
+     *
+     * @param field Field to which the elements belong.
+     * @param v1 First vector (will be put in front of the new vector).
+     * @param v2 Second vector (will be put at back of the new vector).
+     * @throws NullArgumentException if {@code v1} or {@code v2} is {@code null}.
+     * @throws ZeroException if both arrays are empty.
+     * @see #ArrayFieldVector(FieldElement[], FieldElement[])
+     */
+    public ArrayFieldVector(Field<T> field, T[] v1, T[] v2)
+            throws NullArgumentException, ZeroException {
+        MathUtils.checkNotNull(v1);
+        MathUtils.checkNotNull(v2);
+        if (v1.length + v2.length == 0) {
+            throw new ZeroException(LocalizedFormats.VECTOR_MUST_HAVE_AT_LEAST_ONE_ELEMENT);
+        }
+        data = MathArrays.buildArray(field, v1.length + v2.length);
+        System.arraycopy(v1, 0, data, 0, v1.length);
+        System.arraycopy(v2, 0, data, v1.length, v2.length);
+        this.field = field;
+    }
+
+    /** {@inheritDoc} */
+    public Field<T> getField() {
+        return field;
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> copy() {
+        return new ArrayFieldVector<T>(this, true);
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> add(FieldVector<T> v) throws DimensionMismatchException {
+        try {
+            return add((ArrayFieldVector<T>) v);
+        } catch (ClassCastException cce) {
+            checkVectorDimensions(v);
+            T[] out = MathArrays.buildArray(field, data.length);
+            for (int i = 0; i < data.length; i++) {
+                out[i] = data[i].add(v.getEntry(i));
+            }
+            return new ArrayFieldVector<T>(field, out, false);
+        }
+    }
+
+    /**
+     * Compute the sum of {@code this} and {@code v}.
+     *
+     * @param v vector to be added
+     * @return {@code this + v}
+     * @throws DimensionMismatchException if {@code v} is not the same size as {@code this}
+     */
+    public ArrayFieldVector<T> add(ArrayFieldVector<T> v) throws DimensionMismatchException {
+        checkVectorDimensions(v.data.length);
+        T[] out = MathArrays.buildArray(field, data.length);
+        for (int i = 0; i < data.length; i++) {
+            out[i] = data[i].add(v.data[i]);
+        }
+        return new ArrayFieldVector<T>(field, out, false);
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> subtract(FieldVector<T> v) throws DimensionMismatchException {
+        try {
+            return subtract((ArrayFieldVector<T>) v);
+        } catch (ClassCastException cce) {
+            checkVectorDimensions(v);
+            T[] out = MathArrays.buildArray(field, data.length);
+            for (int i = 0; i < data.length; i++) {
+                out[i] = data[i].subtract(v.getEntry(i));
+            }
+            return new ArrayFieldVector<T>(field, out, false);
+        }
+    }
+
+    /**
+     * Compute {@code this} minus {@code v}.
+     *
+     * @param v vector to be subtracted
+     * @return {@code this - v}
+     * @throws DimensionMismatchException if {@code v} is not the same size as {@code this}
+     */
+    public ArrayFieldVector<T> subtract(ArrayFieldVector<T> v) throws DimensionMismatchException {
+        checkVectorDimensions(v.data.length);
+        T[] out = MathArrays.buildArray(field, data.length);
+        for (int i = 0; i < data.length; i++) {
+            out[i] = data[i].subtract(v.data[i]);
+        }
+        return new ArrayFieldVector<T>(field, out, false);
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> mapAdd(T d) throws NullArgumentException {
+        T[] out = MathArrays.buildArray(field, data.length);
+        for (int i = 0; i < data.length; i++) {
+            out[i] = data[i].add(d);
+        }
+        return new ArrayFieldVector<T>(field, out, false);
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> mapAddToSelf(T d) throws NullArgumentException {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = data[i].add(d);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> mapSubtract(T d) throws NullArgumentException {
+        T[] out = MathArrays.buildArray(field, data.length);
+        for (int i = 0; i < data.length; i++) {
+            out[i] = data[i].subtract(d);
+        }
+        return new ArrayFieldVector<T>(field, out, false);
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> mapSubtractToSelf(T d) throws NullArgumentException {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = data[i].subtract(d);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> mapMultiply(T d) throws NullArgumentException {
+        T[] out = MathArrays.buildArray(field, data.length);
+        for (int i = 0; i < data.length; i++) {
+            out[i] = data[i].multiply(d);
+        }
+        return new ArrayFieldVector<T>(field, out, false);
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> mapMultiplyToSelf(T d) throws NullArgumentException {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = data[i].multiply(d);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> mapDivide(T d) throws NullArgumentException, MathArithmeticException {
+        MathUtils.checkNotNull(d);
+        T[] out = MathArrays.buildArray(field, data.length);
+        for (int i = 0; i < data.length; i++) {
+            out[i] = data[i].divide(d);
+        }
+        return new ArrayFieldVector<T>(field, out, false);
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> mapDivideToSelf(T d)
+            throws NullArgumentException, MathArithmeticException {
+        MathUtils.checkNotNull(d);
+        for (int i = 0; i < data.length; i++) {
+            data[i] = data[i].divide(d);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> mapInv() throws MathArithmeticException {
+        T[] out = MathArrays.buildArray(field, data.length);
+        final T one = field.getOne();
+        for (int i = 0; i < data.length; i++) {
+            try {
+                out[i] = one.divide(data[i]);
+            } catch (final MathArithmeticException e) {
+                throw new MathArithmeticException(LocalizedFormats.INDEX, i);
+            }
+        }
+        return new ArrayFieldVector<T>(field, out, false);
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> mapInvToSelf() throws MathArithmeticException {
+        final T one = field.getOne();
+        for (int i = 0; i < data.length; i++) {
+            try {
+                data[i] = one.divide(data[i]);
+            } catch (final MathArithmeticException e) {
+                throw new MathArithmeticException(LocalizedFormats.INDEX, i);
+            }
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> ebeMultiply(FieldVector<T> v) throws DimensionMismatchException {
+        try {
+            return ebeMultiply((ArrayFieldVector<T>) v);
+        } catch (ClassCastException cce) {
+            checkVectorDimensions(v);
+            T[] out = MathArrays.buildArray(field, data.length);
+            for (int i = 0; i < data.length; i++) {
+                out[i] = data[i].multiply(v.getEntry(i));
+            }
+            return new ArrayFieldVector<T>(field, out, false);
+        }
+    }
+
+    /**
+     * Element-by-element multiplication.
+     *
+     * @param v vector by which instance elements must be multiplied
+     * @return a vector containing {@code this[i] * v[i]} for all {@code i}
+     * @throws DimensionMismatchException if {@code v} is not the same size as {@code this}
+     */
+    public ArrayFieldVector<T> ebeMultiply(ArrayFieldVector<T> v)
+            throws DimensionMismatchException {
+        checkVectorDimensions(v.data.length);
+        T[] out = MathArrays.buildArray(field, data.length);
+        for (int i = 0; i < data.length; i++) {
+            out[i] = data[i].multiply(v.data[i]);
+        }
+        return new ArrayFieldVector<T>(field, out, false);
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> ebeDivide(FieldVector<T> v)
+            throws DimensionMismatchException, MathArithmeticException {
+        try {
+            return ebeDivide((ArrayFieldVector<T>) v);
+        } catch (ClassCastException cce) {
+            checkVectorDimensions(v);
+            T[] out = MathArrays.buildArray(field, data.length);
+            for (int i = 0; i < data.length; i++) {
+                try {
+                    out[i] = data[i].divide(v.getEntry(i));
+                } catch (final MathArithmeticException e) {
+                    throw new MathArithmeticException(LocalizedFormats.INDEX, i);
+                }
+            }
+            return new ArrayFieldVector<T>(field, out, false);
+        }
+    }
+
+    /**
+     * Element-by-element division.
+     *
+     * @param v vector by which instance elements must be divided
+     * @return a vector containing {@code this[i] / v[i]} for all {@code i}
+     * @throws DimensionMismatchException if {@code v} is not the same size as {@code this}
+     * @throws MathArithmeticException if one entry of {@code v} is zero.
+     */
+    public ArrayFieldVector<T> ebeDivide(ArrayFieldVector<T> v)
+            throws DimensionMismatchException, MathArithmeticException {
+        checkVectorDimensions(v.data.length);
+        T[] out = MathArrays.buildArray(field, data.length);
+        for (int i = 0; i < data.length; i++) {
+            try {
+                out[i] = data[i].divide(v.data[i]);
+            } catch (final MathArithmeticException e) {
+                throw new MathArithmeticException(LocalizedFormats.INDEX, i);
+            }
+        }
+        return new ArrayFieldVector<T>(field, out, false);
+    }
+
+    /** {@inheritDoc} */
+    public T[] getData() {
+        return data.clone();
+    }
+
+    /**
+     * Returns a reference to the underlying data array.
+     *
+     * <p>Does not make a fresh copy of the underlying data.
+     *
+     * @return array of entries
+     */
+    public T[] getDataRef() {
+        return data;
+    }
+
+    /** {@inheritDoc} */
+    public T dotProduct(FieldVector<T> v) throws DimensionMismatchException {
+        try {
+            return dotProduct((ArrayFieldVector<T>) v);
+        } catch (ClassCastException cce) {
+            checkVectorDimensions(v);
+            T dot = field.getZero();
+            for (int i = 0; i < data.length; i++) {
+                dot = dot.add(data[i].multiply(v.getEntry(i)));
+            }
+            return dot;
+        }
+    }
+
+    /**
+     * Compute the dot product.
+     *
+     * @param v vector with which dot product should be computed
+     * @return the scalar dot product of {@code this} and {@code v}
+     * @throws DimensionMismatchException if {@code v} is not the same size as {@code this}
+     */
+    public T dotProduct(ArrayFieldVector<T> v) throws DimensionMismatchException {
+        checkVectorDimensions(v.data.length);
+        T dot = field.getZero();
+        for (int i = 0; i < data.length; i++) {
+            dot = dot.add(data[i].multiply(v.data[i]));
+        }
+        return dot;
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> projection(FieldVector<T> v)
+            throws DimensionMismatchException, MathArithmeticException {
+        return v.mapMultiply(dotProduct(v).divide(v.dotProduct(v)));
+    }
+
+    /**
+     * Find the orthogonal projection of this vector onto another vector.
+     *
+     * @param v vector onto which {@code this} must be projected
+     * @return projection of {@code this} onto {@code v}
+     * @throws DimensionMismatchException if {@code v} is not the same size as {@code this}
+     * @throws MathArithmeticException if {@code v} is the null vector.
+     */
+    public ArrayFieldVector<T> projection(ArrayFieldVector<T> v)
+            throws DimensionMismatchException, MathArithmeticException {
+        return (ArrayFieldVector<T>) v.mapMultiply(dotProduct(v).divide(v.dotProduct(v)));
+    }
+
+    /** {@inheritDoc} */
+    public FieldMatrix<T> outerProduct(FieldVector<T> v) {
+        try {
+            return outerProduct((ArrayFieldVector<T>) v);
+        } catch (ClassCastException cce) {
+            final int m = data.length;
+            final int n = v.getDimension();
+            final FieldMatrix<T> out = new Array2DRowFieldMatrix<T>(field, m, n);
+            for (int i = 0; i < m; i++) {
+                for (int j = 0; j < n; j++) {
+                    out.setEntry(i, j, data[i].multiply(v.getEntry(j)));
+                }
+            }
+            return out;
+        }
+    }
+
+    /**
+     * Compute the outer product.
+     *
+     * @param v vector with which outer product should be computed
+     * @return the matrix outer product between instance and v
+     */
+    public FieldMatrix<T> outerProduct(ArrayFieldVector<T> v) {
+        final int m = data.length;
+        final int n = v.data.length;
+        final FieldMatrix<T> out = new Array2DRowFieldMatrix<T>(field, m, n);
+        for (int i = 0; i < m; i++) {
+            for (int j = 0; j < n; j++) {
+                out.setEntry(i, j, data[i].multiply(v.data[j]));
+            }
+        }
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    public T getEntry(int index) {
+        return data[index];
+    }
+
+    /** {@inheritDoc} */
+    public int getDimension() {
+        return data.length;
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> append(FieldVector<T> v) {
+        try {
+            return append((ArrayFieldVector<T>) v);
+        } catch (ClassCastException cce) {
+            return new ArrayFieldVector<T>(this, new ArrayFieldVector<T>(v));
+        }
+    }
+
+    /**
+     * Construct a vector by appending a vector to this vector.
+     *
+     * @param v vector to append to this one.
+     * @return a new vector
+     */
+    public ArrayFieldVector<T> append(ArrayFieldVector<T> v) {
+        return new ArrayFieldVector<T>(this, v);
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> append(T in) {
+        final T[] out = MathArrays.buildArray(field, data.length + 1);
+        System.arraycopy(data, 0, out, 0, data.length);
+        out[data.length] = in;
+        return new ArrayFieldVector<T>(field, out, false);
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> getSubVector(int index, int n)
+            throws OutOfRangeException, NotPositiveException {
+        if (n < 0) {
+            throw new NotPositiveException(
+                    LocalizedFormats.NUMBER_OF_ELEMENTS_SHOULD_BE_POSITIVE, n);
+        }
+        ArrayFieldVector<T> out = new ArrayFieldVector<T>(field, n);
+        try {
+            System.arraycopy(data, index, out.data, 0, n);
+        } catch (IndexOutOfBoundsException e) {
+            checkIndex(index);
+            checkIndex(index + n - 1);
+        }
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    public void setEntry(int index, T value) {
+        try {
+            data[index] = value;
+        } catch (IndexOutOfBoundsException e) {
+            checkIndex(index);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void setSubVector(int index, FieldVector<T> v) throws OutOfRangeException {
+        try {
+            try {
+                set(index, (ArrayFieldVector<T>) v);
+            } catch (ClassCastException cce) {
+                for (int i = index; i < index + v.getDimension(); ++i) {
+                    data[i] = v.getEntry(i - index);
+                }
+            }
+        } catch (IndexOutOfBoundsException e) {
+            checkIndex(index);
+            checkIndex(index + v.getDimension() - 1);
+        }
+    }
+
+    /**
+     * Set a set of consecutive elements.
+     *
+     * @param index index of first element to be set.
+     * @param v vector containing the values to set.
+     * @throws OutOfRangeException if the index is invalid.
+     */
+    public void set(int index, ArrayFieldVector<T> v) throws OutOfRangeException {
+        try {
+            System.arraycopy(v.data, 0, data, index, v.data.length);
+        } catch (IndexOutOfBoundsException e) {
+            checkIndex(index);
+            checkIndex(index + v.data.length - 1);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void set(T value) {
+        Arrays.fill(data, value);
+    }
+
+    /** {@inheritDoc} */
+    public T[] toArray() {
+        return data.clone();
+    }
+
+    /**
+     * Check if instance and specified vectors have the same dimension.
+     *
+     * @param v vector to compare instance with
+     * @exception DimensionMismatchException if the vectors do not have the same dimensions
+     */
+    protected void checkVectorDimensions(FieldVector<T> v) throws DimensionMismatchException {
+        checkVectorDimensions(v.getDimension());
+    }
+
+    /**
+     * Check if instance dimension is equal to some expected value.
+     *
+     * @param n Expected dimension.
+     * @throws DimensionMismatchException if the dimension is not equal to the size of {@code this}
+     *     vector.
+     */
+    protected void checkVectorDimensions(int n) throws DimensionMismatchException {
+        if (data.length != n) {
+            throw new DimensionMismatchException(data.length, n);
+        }
+    }
+
+    /**
+     * Visits (but does not alter) all entries of this vector in default order (increasing index).
+     *
+     * @param visitor the visitor to be used to process the entries of this vector
+     * @return the value returned by {@link FieldVectorPreservingVisitor#end()} at the end of the
+     *     walk
+     * @since 3.3
+     */
+    public T walkInDefaultOrder(final FieldVectorPreservingVisitor<T> visitor) {
+        final int dim = getDimension();
+        visitor.start(dim, 0, dim - 1);
+        for (int i = 0; i < dim; i++) {
+            visitor.visit(i, getEntry(i));
+        }
+        return visitor.end();
+    }
+
+    /**
+     * Visits (but does not alter) some entries of this vector in default order (increasing index).
+     *
+     * @param visitor visitor to be used to process the entries of this vector
+     * @param start the index of the first entry to be visited
+     * @param end the index of the last entry to be visited (inclusive)
+     * @return the value returned by {@link FieldVectorPreservingVisitor#end()} at the end of the
+     *     walk
+     * @throws NumberIsTooSmallException if {@code end < start}.
+     * @throws OutOfRangeException if the indices are not valid.
+     * @since 3.3
+     */
+    public T walkInDefaultOrder(
+            final FieldVectorPreservingVisitor<T> visitor, final int start, final int end)
+            throws NumberIsTooSmallException, OutOfRangeException {
+        checkIndices(start, end);
+        visitor.start(getDimension(), start, end);
+        for (int i = start; i <= end; i++) {
+            visitor.visit(i, getEntry(i));
+        }
+        return visitor.end();
+    }
+
+    /**
+     * Visits (but does not alter) all entries of this vector in optimized order. The order in which
+     * the entries are visited is selected so as to lead to the most efficient implementation; it
+     * might depend on the concrete implementation of this abstract class.
+     *
+     * @param visitor the visitor to be used to process the entries of this vector
+     * @return the value returned by {@link FieldVectorPreservingVisitor#end()} at the end of the
+     *     walk
+     * @since 3.3
+     */
+    public T walkInOptimizedOrder(final FieldVectorPreservingVisitor<T> visitor) {
+        return walkInDefaultOrder(visitor);
+    }
+
+    /**
+     * Visits (but does not alter) some entries of this vector in optimized order. The order in
+     * which the entries are visited is selected so as to lead to the most efficient implementation;
+     * it might depend on the concrete implementation of this abstract class.
+     *
+     * @param visitor visitor to be used to process the entries of this vector
+     * @param start the index of the first entry to be visited
+     * @param end the index of the last entry to be visited (inclusive)
+     * @return the value returned by {@link FieldVectorPreservingVisitor#end()} at the end of the
+     *     walk
+     * @throws NumberIsTooSmallException if {@code end < start}.
+     * @throws OutOfRangeException if the indices are not valid.
+     * @since 3.3
+     */
+    public T walkInOptimizedOrder(
+            final FieldVectorPreservingVisitor<T> visitor, final int start, final int end)
+            throws NumberIsTooSmallException, OutOfRangeException {
+        return walkInDefaultOrder(visitor, start, end);
+    }
+
+    /**
+     * Visits (and possibly alters) all entries of this vector in default order (increasing index).
+     *
+     * @param visitor the visitor to be used to process and modify the entries of this vector
+     * @return the value returned by {@link FieldVectorChangingVisitor#end()} at the end of the walk
+     * @since 3.3
+     */
+    public T walkInDefaultOrder(final FieldVectorChangingVisitor<T> visitor) {
+        final int dim = getDimension();
+        visitor.start(dim, 0, dim - 1);
+        for (int i = 0; i < dim; i++) {
+            setEntry(i, visitor.visit(i, getEntry(i)));
+        }
+        return visitor.end();
+    }
+
+    /**
+     * Visits (and possibly alters) some entries of this vector in default order (increasing index).
+     *
+     * @param visitor visitor to be used to process the entries of this vector
+     * @param start the index of the first entry to be visited
+     * @param end the index of the last entry to be visited (inclusive)
+     * @return the value returned by {@link FieldVectorChangingVisitor#end()} at the end of the walk
+     * @throws NumberIsTooSmallException if {@code end < start}.
+     * @throws OutOfRangeException if the indices are not valid.
+     * @since 3.3
+     */
+    public T walkInDefaultOrder(
+            final FieldVectorChangingVisitor<T> visitor, final int start, final int end)
+            throws NumberIsTooSmallException, OutOfRangeException {
+        checkIndices(start, end);
+        visitor.start(getDimension(), start, end);
+        for (int i = start; i <= end; i++) {
+            setEntry(i, visitor.visit(i, getEntry(i)));
+        }
+        return visitor.end();
+    }
+
+    /**
+     * Visits (and possibly alters) all entries of this vector in optimized order. The order in
+     * which the entries are visited is selected so as to lead to the most efficient implementation;
+     * it might depend on the concrete implementation of this abstract class.
+     *
+     * @param visitor the visitor to be used to process the entries of this vector
+     * @return the value returned by {@link FieldVectorChangingVisitor#end()} at the end of the walk
+     * @since 3.3
+     */
+    public T walkInOptimizedOrder(final FieldVectorChangingVisitor<T> visitor) {
+        return walkInDefaultOrder(visitor);
+    }
+
+    /**
+     * Visits (and possibly change) some entries of this vector in optimized order. The order in
+     * which the entries are visited is selected so as to lead to the most efficient implementation;
+     * it might depend on the concrete implementation of this abstract class.
+     *
+     * @param visitor visitor to be used to process the entries of this vector
+     * @param start the index of the first entry to be visited
+     * @param end the index of the last entry to be visited (inclusive)
+     * @return the value returned by {@link FieldVectorChangingVisitor#end()} at the end of the walk
+     * @throws NumberIsTooSmallException if {@code end < start}.
+     * @throws OutOfRangeException if the indices are not valid.
+     * @since 3.3
+     */
+    public T walkInOptimizedOrder(
+            final FieldVectorChangingVisitor<T> visitor, final int start, final int end)
+            throws NumberIsTooSmallException, OutOfRangeException {
+        return walkInDefaultOrder(visitor, start, end);
+    }
+
+    /**
+     * Test for the equality of two vectors.
+     *
+     * @param other Object to test for equality.
+     * @return {@code true} if two vector objects are equal, {@code false} otherwise.
+     */
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+        if (other == null) {
+            return false;
+        }
+
+        try {
+            @SuppressWarnings("unchecked") // May fail, but we ignore ClassCastException
+            FieldVector<T> rhs = (FieldVector<T>) other;
+            if (data.length != rhs.getDimension()) {
+                return false;
+            }
+
+            for (int i = 0; i < data.length; ++i) {
+                if (!data[i].equals(rhs.getEntry(i))) {
+                    return false;
+                }
+            }
+            return true;
+        } catch (ClassCastException ex) {
+            // ignore exception
+            return false;
+        }
+    }
+
+    /**
+     * Get a hashCode for the real vector.
+     *
+     * <p>All NaN values have the same hash code.
+     *
+     * @return a hash code value for this object
+     */
+    @Override
+    public int hashCode() {
+        int h = 3542;
+        for (final T a : data) {
+            h ^= a.hashCode();
+        }
+        return h;
+    }
+
+    /**
+     * Check if an index is valid.
+     *
+     * @param index Index to check.
+     * @exception OutOfRangeException if the index is not valid.
+     */
+    private void checkIndex(final int index) throws OutOfRangeException {
+        if (index < 0 || index >= getDimension()) {
+            throw new OutOfRangeException(LocalizedFormats.INDEX, index, 0, getDimension() - 1);
+        }
+    }
+
+    /**
+     * Checks that the indices of a subvector are valid.
+     *
+     * @param start the index of the first entry of the subvector
+     * @param end the index of the last entry of the subvector (inclusive)
+     * @throws OutOfRangeException if {@code start} of {@code end} are not valid
+     * @throws NumberIsTooSmallException if {@code end < start}
+     * @since 3.3
+     */
+    private void checkIndices(final int start, final int end)
+            throws NumberIsTooSmallException, OutOfRangeException {
+        final int dim = getDimension();
+        if ((start < 0) || (start >= dim)) {
+            throw new OutOfRangeException(LocalizedFormats.INDEX, start, 0, dim - 1);
+        }
+        if ((end < 0) || (end >= dim)) {
+            throw new OutOfRangeException(LocalizedFormats.INDEX, end, 0, dim - 1);
+        }
+        if (end < start) {
+            throw new NumberIsTooSmallException(
+                    LocalizedFormats.INITIAL_ROW_AFTER_FINAL_ROW, end, start, false);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/ArrayRealVector.java b/src/main/java/org/apache/commons/math3/linear/ArrayRealVector.java
new file mode 100644
index 0000000..f4d0e46
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/ArrayRealVector.java
@@ -0,0 +1,937 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Iterator;
+
+/**
+ * This class implements the {@link RealVector} interface with a double array.
+ *
+ * @since 2.0
+ */
+public class ArrayRealVector extends RealVector implements Serializable {
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -1097961340710804027L;
+
+    /** Default format. */
+    private static final RealVectorFormat DEFAULT_FORMAT = RealVectorFormat.getInstance();
+
+    /** Entries of the vector. */
+    private double data[];
+
+    /**
+     * Build a 0-length vector. Zero-length vectors may be used to initialized construction of
+     * vectors by data gathering. We start with zero-length and use either the {@link
+     * #ArrayRealVector(ArrayRealVector, ArrayRealVector)} constructor or one of the {@code append}
+     * method ({@link #append(double)}, {@link #append(ArrayRealVector)}) to gather data into this
+     * vector.
+     */
+    public ArrayRealVector() {
+        data = new double[0];
+    }
+
+    /**
+     * Construct a vector of zeroes.
+     *
+     * @param size Size of the vector.
+     */
+    public ArrayRealVector(int size) {
+        data = new double[size];
+    }
+
+    /**
+     * Construct a vector with preset values.
+     *
+     * @param size Size of the vector
+     * @param preset All entries will be set with this value.
+     */
+    public ArrayRealVector(int size, double preset) {
+        data = new double[size];
+        Arrays.fill(data, preset);
+    }
+
+    /**
+     * Construct a vector from an array, copying the input array.
+     *
+     * @param d Array.
+     */
+    public ArrayRealVector(double[] d) {
+        data = d.clone();
+    }
+
+    /**
+     * Create a new ArrayRealVector using the input array as the underlying data array. If an array
+     * is built specially in order to be embedded in a ArrayRealVector and not used directly, the
+     * {@code copyArray} may be set to {@code false}. This will prevent the copying and improve
+     * performance as no new array will be built and no data will be copied.
+     *
+     * @param d Data for the new vector.
+     * @param copyArray if {@code true}, the input array will be copied, otherwise it will be
+     *     referenced.
+     * @throws NullArgumentException if {@code d} is {@code null}.
+     * @see #ArrayRealVector(double[])
+     */
+    public ArrayRealVector(double[] d, boolean copyArray) throws NullArgumentException {
+        if (d == null) {
+            throw new NullArgumentException();
+        }
+        data = copyArray ? d.clone() : d;
+    }
+
+    /**
+     * Construct a vector from part of a array.
+     *
+     * @param d Array.
+     * @param pos Position of first entry.
+     * @param size Number of entries to copy.
+     * @throws NullArgumentException if {@code d} is {@code null}.
+     * @throws NumberIsTooLargeException if the size of {@code d} is less than {@code pos + size}.
+     */
+    public ArrayRealVector(double[] d, int pos, int size)
+            throws NullArgumentException, NumberIsTooLargeException {
+        if (d == null) {
+            throw new NullArgumentException();
+        }
+        if (d.length < pos + size) {
+            throw new NumberIsTooLargeException(pos + size, d.length, true);
+        }
+        data = new double[size];
+        System.arraycopy(d, pos, data, 0, size);
+    }
+
+    /**
+     * Construct a vector from an array.
+     *
+     * @param d Array of {@code Double}s.
+     */
+    public ArrayRealVector(Double[] d) {
+        data = new double[d.length];
+        for (int i = 0; i < d.length; i++) {
+            data[i] = d[i].doubleValue();
+        }
+    }
+
+    /**
+     * Construct a vector from part of an array.
+     *
+     * @param d Array.
+     * @param pos Position of first entry.
+     * @param size Number of entries to copy.
+     * @throws NullArgumentException if {@code d} is {@code null}.
+     * @throws NumberIsTooLargeException if the size of {@code d} is less than {@code pos + size}.
+     */
+    public ArrayRealVector(Double[] d, int pos, int size)
+            throws NullArgumentException, NumberIsTooLargeException {
+        if (d == null) {
+            throw new NullArgumentException();
+        }
+        if (d.length < pos + size) {
+            throw new NumberIsTooLargeException(pos + size, d.length, true);
+        }
+        data = new double[size];
+        for (int i = pos; i < pos + size; i++) {
+            data[i - pos] = d[i].doubleValue();
+        }
+    }
+
+    /**
+     * Construct a vector from another vector, using a deep copy.
+     *
+     * @param v vector to copy.
+     * @throws NullArgumentException if {@code v} is {@code null}.
+     */
+    public ArrayRealVector(RealVector v) throws NullArgumentException {
+        if (v == null) {
+            throw new NullArgumentException();
+        }
+        data = new double[v.getDimension()];
+        for (int i = 0; i < data.length; ++i) {
+            data[i] = v.getEntry(i);
+        }
+    }
+
+    /**
+     * Construct a vector from another vector, using a deep copy.
+     *
+     * @param v Vector to copy.
+     * @throws NullArgumentException if {@code v} is {@code null}.
+     */
+    public ArrayRealVector(ArrayRealVector v) throws NullArgumentException {
+        this(v, true);
+    }
+
+    /**
+     * Construct a vector from another vector.
+     *
+     * @param v Vector to copy.
+     * @param deep If {@code true} perform a deep copy, otherwise perform a shallow copy.
+     */
+    public ArrayRealVector(ArrayRealVector v, boolean deep) {
+        data = deep ? v.data.clone() : v.data;
+    }
+
+    /**
+     * Construct a vector by appending one vector to another vector.
+     *
+     * @param v1 First vector (will be put in front of the new vector).
+     * @param v2 Second vector (will be put at back of the new vector).
+     */
+    public ArrayRealVector(ArrayRealVector v1, ArrayRealVector v2) {
+        data = new double[v1.data.length + v2.data.length];
+        System.arraycopy(v1.data, 0, data, 0, v1.data.length);
+        System.arraycopy(v2.data, 0, data, v1.data.length, v2.data.length);
+    }
+
+    /**
+     * Construct a vector by appending one vector to another vector.
+     *
+     * @param v1 First vector (will be put in front of the new vector).
+     * @param v2 Second vector (will be put at back of the new vector).
+     */
+    public ArrayRealVector(ArrayRealVector v1, RealVector v2) {
+        final int l1 = v1.data.length;
+        final int l2 = v2.getDimension();
+        data = new double[l1 + l2];
+        System.arraycopy(v1.data, 0, data, 0, l1);
+        for (int i = 0; i < l2; ++i) {
+            data[l1 + i] = v2.getEntry(i);
+        }
+    }
+
+    /**
+     * Construct a vector by appending one vector to another vector.
+     *
+     * @param v1 First vector (will be put in front of the new vector).
+     * @param v2 Second vector (will be put at back of the new vector).
+     */
+    public ArrayRealVector(RealVector v1, ArrayRealVector v2) {
+        final int l1 = v1.getDimension();
+        final int l2 = v2.data.length;
+        data = new double[l1 + l2];
+        for (int i = 0; i < l1; ++i) {
+            data[i] = v1.getEntry(i);
+        }
+        System.arraycopy(v2.data, 0, data, l1, l2);
+    }
+
+    /**
+     * Construct a vector by appending one vector to another vector.
+     *
+     * @param v1 First vector (will be put in front of the new vector).
+     * @param v2 Second vector (will be put at back of the new vector).
+     */
+    public ArrayRealVector(ArrayRealVector v1, double[] v2) {
+        final int l1 = v1.getDimension();
+        final int l2 = v2.length;
+        data = new double[l1 + l2];
+        System.arraycopy(v1.data, 0, data, 0, l1);
+        System.arraycopy(v2, 0, data, l1, l2);
+    }
+
+    /**
+     * Construct a vector by appending one vector to another vector.
+     *
+     * @param v1 First vector (will be put in front of the new vector).
+     * @param v2 Second vector (will be put at back of the new vector).
+     */
+    public ArrayRealVector(double[] v1, ArrayRealVector v2) {
+        final int l1 = v1.length;
+        final int l2 = v2.getDimension();
+        data = new double[l1 + l2];
+        System.arraycopy(v1, 0, data, 0, l1);
+        System.arraycopy(v2.data, 0, data, l1, l2);
+    }
+
+    /**
+     * Construct a vector by appending one vector to another vector.
+     *
+     * @param v1 first vector (will be put in front of the new vector)
+     * @param v2 second vector (will be put at back of the new vector)
+     */
+    public ArrayRealVector(double[] v1, double[] v2) {
+        final int l1 = v1.length;
+        final int l2 = v2.length;
+        data = new double[l1 + l2];
+        System.arraycopy(v1, 0, data, 0, l1);
+        System.arraycopy(v2, 0, data, l1, l2);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ArrayRealVector copy() {
+        return new ArrayRealVector(this, true);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ArrayRealVector add(RealVector v) throws DimensionMismatchException {
+        if (v instanceof ArrayRealVector) {
+            final double[] vData = ((ArrayRealVector) v).data;
+            final int dim = vData.length;
+            checkVectorDimensions(dim);
+            ArrayRealVector result = new ArrayRealVector(dim);
+            double[] resultData = result.data;
+            for (int i = 0; i < dim; i++) {
+                resultData[i] = data[i] + vData[i];
+            }
+            return result;
+        } else {
+            checkVectorDimensions(v);
+            double[] out = data.clone();
+            Iterator<Entry> it = v.iterator();
+            while (it.hasNext()) {
+                final Entry e = it.next();
+                out[e.getIndex()] += e.getValue();
+            }
+            return new ArrayRealVector(out, false);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ArrayRealVector subtract(RealVector v) throws DimensionMismatchException {
+        if (v instanceof ArrayRealVector) {
+            final double[] vData = ((ArrayRealVector) v).data;
+            final int dim = vData.length;
+            checkVectorDimensions(dim);
+            ArrayRealVector result = new ArrayRealVector(dim);
+            double[] resultData = result.data;
+            for (int i = 0; i < dim; i++) {
+                resultData[i] = data[i] - vData[i];
+            }
+            return result;
+        } else {
+            checkVectorDimensions(v);
+            double[] out = data.clone();
+            Iterator<Entry> it = v.iterator();
+            while (it.hasNext()) {
+                final Entry e = it.next();
+                out[e.getIndex()] -= e.getValue();
+            }
+            return new ArrayRealVector(out, false);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ArrayRealVector map(UnivariateFunction function) {
+        return copy().mapToSelf(function);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ArrayRealVector mapToSelf(UnivariateFunction function) {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = function.value(data[i]);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector mapAddToSelf(double d) {
+        for (int i = 0; i < data.length; i++) {
+            data[i] += d;
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector mapSubtractToSelf(double d) {
+        for (int i = 0; i < data.length; i++) {
+            data[i] -= d;
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector mapMultiplyToSelf(double d) {
+        for (int i = 0; i < data.length; i++) {
+            data[i] *= d;
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector mapDivideToSelf(double d) {
+        for (int i = 0; i < data.length; i++) {
+            data[i] /= d;
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ArrayRealVector ebeMultiply(RealVector v) throws DimensionMismatchException {
+        if (v instanceof ArrayRealVector) {
+            final double[] vData = ((ArrayRealVector) v).data;
+            final int dim = vData.length;
+            checkVectorDimensions(dim);
+            ArrayRealVector result = new ArrayRealVector(dim);
+            double[] resultData = result.data;
+            for (int i = 0; i < dim; i++) {
+                resultData[i] = data[i] * vData[i];
+            }
+            return result;
+        } else {
+            checkVectorDimensions(v);
+            double[] out = data.clone();
+            for (int i = 0; i < data.length; i++) {
+                out[i] *= v.getEntry(i);
+            }
+            return new ArrayRealVector(out, false);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ArrayRealVector ebeDivide(RealVector v) throws DimensionMismatchException {
+        if (v instanceof ArrayRealVector) {
+            final double[] vData = ((ArrayRealVector) v).data;
+            final int dim = vData.length;
+            checkVectorDimensions(dim);
+            ArrayRealVector result = new ArrayRealVector(dim);
+            double[] resultData = result.data;
+            for (int i = 0; i < dim; i++) {
+                resultData[i] = data[i] / vData[i];
+            }
+            return result;
+        } else {
+            checkVectorDimensions(v);
+            double[] out = data.clone();
+            for (int i = 0; i < data.length; i++) {
+                out[i] /= v.getEntry(i);
+            }
+            return new ArrayRealVector(out, false);
+        }
+    }
+
+    /**
+     * Get a reference to the underlying data array. This method does not make a fresh copy of the
+     * underlying data.
+     *
+     * @return the array of entries.
+     */
+    public double[] getDataRef() {
+        return data;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double dotProduct(RealVector v) throws DimensionMismatchException {
+        if (v instanceof ArrayRealVector) {
+            final double[] vData = ((ArrayRealVector) v).data;
+            checkVectorDimensions(vData.length);
+            double dot = 0;
+            for (int i = 0; i < data.length; i++) {
+                dot += data[i] * vData[i];
+            }
+            return dot;
+        }
+        return super.dotProduct(v);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getNorm() {
+        double sum = 0;
+        for (double a : data) {
+            sum += a * a;
+        }
+        return FastMath.sqrt(sum);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getL1Norm() {
+        double sum = 0;
+        for (double a : data) {
+            sum += FastMath.abs(a);
+        }
+        return sum;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getLInfNorm() {
+        double max = 0;
+        for (double a : data) {
+            max = FastMath.max(max, FastMath.abs(a));
+        }
+        return max;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getDistance(RealVector v) throws DimensionMismatchException {
+        if (v instanceof ArrayRealVector) {
+            final double[] vData = ((ArrayRealVector) v).data;
+            checkVectorDimensions(vData.length);
+            double sum = 0;
+            for (int i = 0; i < data.length; ++i) {
+                final double delta = data[i] - vData[i];
+                sum += delta * delta;
+            }
+            return FastMath.sqrt(sum);
+        } else {
+            checkVectorDimensions(v);
+            double sum = 0;
+            for (int i = 0; i < data.length; ++i) {
+                final double delta = data[i] - v.getEntry(i);
+                sum += delta * delta;
+            }
+            return FastMath.sqrt(sum);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getL1Distance(RealVector v) throws DimensionMismatchException {
+        if (v instanceof ArrayRealVector) {
+            final double[] vData = ((ArrayRealVector) v).data;
+            checkVectorDimensions(vData.length);
+            double sum = 0;
+            for (int i = 0; i < data.length; ++i) {
+                final double delta = data[i] - vData[i];
+                sum += FastMath.abs(delta);
+            }
+            return sum;
+        } else {
+            checkVectorDimensions(v);
+            double sum = 0;
+            for (int i = 0; i < data.length; ++i) {
+                final double delta = data[i] - v.getEntry(i);
+                sum += FastMath.abs(delta);
+            }
+            return sum;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getLInfDistance(RealVector v) throws DimensionMismatchException {
+        if (v instanceof ArrayRealVector) {
+            final double[] vData = ((ArrayRealVector) v).data;
+            checkVectorDimensions(vData.length);
+            double max = 0;
+            for (int i = 0; i < data.length; ++i) {
+                final double delta = data[i] - vData[i];
+                max = FastMath.max(max, FastMath.abs(delta));
+            }
+            return max;
+        } else {
+            checkVectorDimensions(v);
+            double max = 0;
+            for (int i = 0; i < data.length; ++i) {
+                final double delta = data[i] - v.getEntry(i);
+                max = FastMath.max(max, FastMath.abs(delta));
+            }
+            return max;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealMatrix outerProduct(RealVector v) {
+        if (v instanceof ArrayRealVector) {
+            final double[] vData = ((ArrayRealVector) v).data;
+            final int m = data.length;
+            final int n = vData.length;
+            final RealMatrix out = MatrixUtils.createRealMatrix(m, n);
+            for (int i = 0; i < m; i++) {
+                for (int j = 0; j < n; j++) {
+                    out.setEntry(i, j, data[i] * vData[j]);
+                }
+            }
+            return out;
+        } else {
+            final int m = data.length;
+            final int n = v.getDimension();
+            final RealMatrix out = MatrixUtils.createRealMatrix(m, n);
+            for (int i = 0; i < m; i++) {
+                for (int j = 0; j < n; j++) {
+                    out.setEntry(i, j, data[i] * v.getEntry(j));
+                }
+            }
+            return out;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getEntry(int index) throws OutOfRangeException {
+        try {
+            return data[index];
+        } catch (IndexOutOfBoundsException e) {
+            throw new OutOfRangeException(LocalizedFormats.INDEX, index, 0, getDimension() - 1);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getDimension() {
+        return data.length;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector append(RealVector v) {
+        try {
+            return new ArrayRealVector(this, (ArrayRealVector) v);
+        } catch (ClassCastException cce) {
+            return new ArrayRealVector(this, v);
+        }
+    }
+
+    /**
+     * Construct a vector by appending a vector to this vector.
+     *
+     * @param v Vector to append to this one.
+     * @return a new vector.
+     */
+    public ArrayRealVector append(ArrayRealVector v) {
+        return new ArrayRealVector(this, v);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector append(double in) {
+        final double[] out = new double[data.length + 1];
+        System.arraycopy(data, 0, out, 0, data.length);
+        out[data.length] = in;
+        return new ArrayRealVector(out, false);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector getSubVector(int index, int n)
+            throws OutOfRangeException, NotPositiveException {
+        if (n < 0) {
+            throw new NotPositiveException(
+                    LocalizedFormats.NUMBER_OF_ELEMENTS_SHOULD_BE_POSITIVE, n);
+        }
+        ArrayRealVector out = new ArrayRealVector(n);
+        try {
+            System.arraycopy(data, index, out.data, 0, n);
+        } catch (IndexOutOfBoundsException e) {
+            checkIndex(index);
+            checkIndex(index + n - 1);
+        }
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setEntry(int index, double value) throws OutOfRangeException {
+        try {
+            data[index] = value;
+        } catch (IndexOutOfBoundsException e) {
+            checkIndex(index);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addToEntry(int index, double increment) throws OutOfRangeException {
+        try {
+            data[index] += increment;
+        } catch (IndexOutOfBoundsException e) {
+            throw new OutOfRangeException(LocalizedFormats.INDEX, index, 0, data.length - 1);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setSubVector(int index, RealVector v) throws OutOfRangeException {
+        if (v instanceof ArrayRealVector) {
+            setSubVector(index, ((ArrayRealVector) v).data);
+        } else {
+            try {
+                for (int i = index; i < index + v.getDimension(); ++i) {
+                    data[i] = v.getEntry(i - index);
+                }
+            } catch (IndexOutOfBoundsException e) {
+                checkIndex(index);
+                checkIndex(index + v.getDimension() - 1);
+            }
+        }
+    }
+
+    /**
+     * Set a set of consecutive elements.
+     *
+     * @param index Index of first element to be set.
+     * @param v Vector containing the values to set.
+     * @throws OutOfRangeException if the index is inconsistent with the vector size.
+     */
+    public void setSubVector(int index, double[] v) throws OutOfRangeException {
+        try {
+            System.arraycopy(v, 0, data, index, v.length);
+        } catch (IndexOutOfBoundsException e) {
+            checkIndex(index);
+            checkIndex(index + v.length - 1);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void set(double value) {
+        Arrays.fill(data, value);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double[] toArray() {
+        return data.clone();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return DEFAULT_FORMAT.format(this);
+    }
+
+    /**
+     * Check if instance and specified vectors have the same dimension.
+     *
+     * @param v Vector to compare instance with.
+     * @throws DimensionMismatchException if the vectors do not have the same dimension.
+     */
+    @Override
+    protected void checkVectorDimensions(RealVector v) throws DimensionMismatchException {
+        checkVectorDimensions(v.getDimension());
+    }
+
+    /**
+     * Check if instance dimension is equal to some expected value.
+     *
+     * @param n Expected dimension.
+     * @throws DimensionMismatchException if the dimension is inconsistent with vector size.
+     */
+    @Override
+    protected void checkVectorDimensions(int n) throws DimensionMismatchException {
+        if (data.length != n) {
+            throw new DimensionMismatchException(data.length, n);
+        }
+    }
+
+    /**
+     * Check if any coordinate of this vector is {@code NaN}.
+     *
+     * @return {@code true} if any coordinate of this vector is {@code NaN}, {@code false}
+     *     otherwise.
+     */
+    @Override
+    public boolean isNaN() {
+        for (double v : data) {
+            if (Double.isNaN(v)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Check whether any coordinate of this vector is infinite and none are {@code NaN}.
+     *
+     * @return {@code true} if any coordinate of this vector is infinite and none are {@code NaN},
+     *     {@code false} otherwise.
+     */
+    @Override
+    public boolean isInfinite() {
+        if (isNaN()) {
+            return false;
+        }
+
+        for (double v : data) {
+            if (Double.isInfinite(v)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+
+        if (!(other instanceof RealVector)) {
+            return false;
+        }
+
+        RealVector rhs = (RealVector) other;
+        if (data.length != rhs.getDimension()) {
+            return false;
+        }
+
+        if (rhs.isNaN()) {
+            return this.isNaN();
+        }
+
+        for (int i = 0; i < data.length; ++i) {
+            if (data[i] != rhs.getEntry(i)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /** {@inheritDoc} All {@code NaN} values have the same hash code. */
+    @Override
+    public int hashCode() {
+        if (isNaN()) {
+            return 9;
+        }
+        return MathUtils.hash(data);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ArrayRealVector combine(double a, double b, RealVector y)
+            throws DimensionMismatchException {
+        return copy().combineToSelf(a, b, y);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ArrayRealVector combineToSelf(double a, double b, RealVector y)
+            throws DimensionMismatchException {
+        if (y instanceof ArrayRealVector) {
+            final double[] yData = ((ArrayRealVector) y).data;
+            checkVectorDimensions(yData.length);
+            for (int i = 0; i < this.data.length; i++) {
+                data[i] = a * data[i] + b * yData[i];
+            }
+        } else {
+            checkVectorDimensions(y);
+            for (int i = 0; i < this.data.length; i++) {
+                data[i] = a * data[i] + b * y.getEntry(i);
+            }
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double walkInDefaultOrder(final RealVectorPreservingVisitor visitor) {
+        visitor.start(data.length, 0, data.length - 1);
+        for (int i = 0; i < data.length; i++) {
+            visitor.visit(i, data[i]);
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double walkInDefaultOrder(
+            final RealVectorPreservingVisitor visitor, final int start, final int end)
+            throws NumberIsTooSmallException, OutOfRangeException {
+        checkIndices(start, end);
+        visitor.start(data.length, start, end);
+        for (int i = start; i <= end; i++) {
+            visitor.visit(i, data[i]);
+        }
+        return visitor.end();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>In this implementation, the optimized order is the default order.
+     */
+    @Override
+    public double walkInOptimizedOrder(final RealVectorPreservingVisitor visitor) {
+        return walkInDefaultOrder(visitor);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>In this implementation, the optimized order is the default order.
+     */
+    @Override
+    public double walkInOptimizedOrder(
+            final RealVectorPreservingVisitor visitor, final int start, final int end)
+            throws NumberIsTooSmallException, OutOfRangeException {
+        return walkInDefaultOrder(visitor, start, end);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double walkInDefaultOrder(final RealVectorChangingVisitor visitor) {
+        visitor.start(data.length, 0, data.length - 1);
+        for (int i = 0; i < data.length; i++) {
+            data[i] = visitor.visit(i, data[i]);
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double walkInDefaultOrder(
+            final RealVectorChangingVisitor visitor, final int start, final int end)
+            throws NumberIsTooSmallException, OutOfRangeException {
+        checkIndices(start, end);
+        visitor.start(data.length, start, end);
+        for (int i = start; i <= end; i++) {
+            data[i] = visitor.visit(i, data[i]);
+        }
+        return visitor.end();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>In this implementation, the optimized order is the default order.
+     */
+    @Override
+    public double walkInOptimizedOrder(final RealVectorChangingVisitor visitor) {
+        return walkInDefaultOrder(visitor);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>In this implementation, the optimized order is the default order.
+     */
+    @Override
+    public double walkInOptimizedOrder(
+            final RealVectorChangingVisitor visitor, final int start, final int end)
+            throws NumberIsTooSmallException, OutOfRangeException {
+        return walkInDefaultOrder(visitor, start, end);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/BiDiagonalTransformer.java b/src/main/java/org/apache/commons/math3/linear/BiDiagonalTransformer.java
new file mode 100644
index 0000000..063b202
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/BiDiagonalTransformer.java
@@ -0,0 +1,388 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Class transforming any matrix to bi-diagonal shape.
+ *
+ * <p>Any m &times; n matrix A can be written as the product of three matrices: A = U &times; B
+ * &times; V<sup>T</sup> with U an m &times; m orthogonal matrix, B an m &times; n bi-diagonal
+ * matrix (lower diagonal if m &lt; n, upper diagonal otherwise), and V an n &times; n orthogonal
+ * matrix.
+ *
+ * <p>Transformation to bi-diagonal shape is often not a goal by itself, but it is an intermediate
+ * step in more general decomposition algorithms like {@link SingularValueDecomposition Singular
+ * Value Decomposition}. This class is therefore intended for internal use by the library and is not
+ * public. As a consequence of this explicitly limited scope, many methods directly returns
+ * references to internal arrays, not copies.
+ *
+ * @since 2.0
+ */
+class BiDiagonalTransformer {
+
+    /** Householder vectors. */
+    private final double householderVectors[][];
+
+    /** Main diagonal. */
+    private final double[] main;
+
+    /** Secondary diagonal. */
+    private final double[] secondary;
+
+    /** Cached value of U. */
+    private RealMatrix cachedU;
+
+    /** Cached value of B. */
+    private RealMatrix cachedB;
+
+    /** Cached value of V. */
+    private RealMatrix cachedV;
+
+    /**
+     * Build the transformation to bi-diagonal shape of a matrix.
+     *
+     * @param matrix the matrix to transform.
+     */
+    BiDiagonalTransformer(RealMatrix matrix) {
+
+        final int m = matrix.getRowDimension();
+        final int n = matrix.getColumnDimension();
+        final int p = FastMath.min(m, n);
+        householderVectors = matrix.getData();
+        main = new double[p];
+        secondary = new double[p - 1];
+        cachedU = null;
+        cachedB = null;
+        cachedV = null;
+
+        // transform matrix
+        if (m >= n) {
+            transformToUpperBiDiagonal();
+        } else {
+            transformToLowerBiDiagonal();
+        }
+    }
+
+    /**
+     * Returns the matrix U of the transform.
+     *
+     * <p>U is an orthogonal matrix, i.e. its transpose is also its inverse.
+     *
+     * @return the U matrix
+     */
+    public RealMatrix getU() {
+
+        if (cachedU == null) {
+
+            final int m = householderVectors.length;
+            final int n = householderVectors[0].length;
+            final int p = main.length;
+            final int diagOffset = (m >= n) ? 0 : 1;
+            final double[] diagonal = (m >= n) ? main : secondary;
+            double[][] ua = new double[m][m];
+
+            // fill up the part of the matrix not affected by Householder transforms
+            for (int k = m - 1; k >= p; --k) {
+                ua[k][k] = 1;
+            }
+
+            // build up first part of the matrix by applying Householder transforms
+            for (int k = p - 1; k >= diagOffset; --k) {
+                final double[] hK = householderVectors[k];
+                ua[k][k] = 1;
+                if (hK[k - diagOffset] != 0.0) {
+                    for (int j = k; j < m; ++j) {
+                        double alpha = 0;
+                        for (int i = k; i < m; ++i) {
+                            alpha -= ua[i][j] * householderVectors[i][k - diagOffset];
+                        }
+                        alpha /= diagonal[k - diagOffset] * hK[k - diagOffset];
+
+                        for (int i = k; i < m; ++i) {
+                            ua[i][j] += -alpha * householderVectors[i][k - diagOffset];
+                        }
+                    }
+                }
+            }
+            if (diagOffset > 0) {
+                ua[0][0] = 1;
+            }
+            cachedU = MatrixUtils.createRealMatrix(ua);
+        }
+
+        // return the cached matrix
+        return cachedU;
+    }
+
+    /**
+     * Returns the bi-diagonal matrix B of the transform.
+     *
+     * @return the B matrix
+     */
+    public RealMatrix getB() {
+
+        if (cachedB == null) {
+
+            final int m = householderVectors.length;
+            final int n = householderVectors[0].length;
+            double[][] ba = new double[m][n];
+            for (int i = 0; i < main.length; ++i) {
+                ba[i][i] = main[i];
+                if (m < n) {
+                    if (i > 0) {
+                        ba[i][i - 1] = secondary[i - 1];
+                    }
+                } else {
+                    if (i < main.length - 1) {
+                        ba[i][i + 1] = secondary[i];
+                    }
+                }
+            }
+            cachedB = MatrixUtils.createRealMatrix(ba);
+        }
+
+        // return the cached matrix
+        return cachedB;
+    }
+
+    /**
+     * Returns the matrix V of the transform.
+     *
+     * <p>V is an orthogonal matrix, i.e. its transpose is also its inverse.
+     *
+     * @return the V matrix
+     */
+    public RealMatrix getV() {
+
+        if (cachedV == null) {
+
+            final int m = householderVectors.length;
+            final int n = householderVectors[0].length;
+            final int p = main.length;
+            final int diagOffset = (m >= n) ? 1 : 0;
+            final double[] diagonal = (m >= n) ? secondary : main;
+            double[][] va = new double[n][n];
+
+            // fill up the part of the matrix not affected by Householder transforms
+            for (int k = n - 1; k >= p; --k) {
+                va[k][k] = 1;
+            }
+
+            // build up first part of the matrix by applying Householder transforms
+            for (int k = p - 1; k >= diagOffset; --k) {
+                final double[] hK = householderVectors[k - diagOffset];
+                va[k][k] = 1;
+                if (hK[k] != 0.0) {
+                    for (int j = k; j < n; ++j) {
+                        double beta = 0;
+                        for (int i = k; i < n; ++i) {
+                            beta -= va[i][j] * hK[i];
+                        }
+                        beta /= diagonal[k - diagOffset] * hK[k];
+
+                        for (int i = k; i < n; ++i) {
+                            va[i][j] += -beta * hK[i];
+                        }
+                    }
+                }
+            }
+            if (diagOffset > 0) {
+                va[0][0] = 1;
+            }
+            cachedV = MatrixUtils.createRealMatrix(va);
+        }
+
+        // return the cached matrix
+        return cachedV;
+    }
+
+    /**
+     * Get the Householder vectors of the transform.
+     *
+     * <p>Note that since this class is only intended for internal use, it returns directly a
+     * reference to its internal arrays, not a copy.
+     *
+     * @return the main diagonal elements of the B matrix
+     */
+    double[][] getHouseholderVectorsRef() {
+        return householderVectors;
+    }
+
+    /**
+     * Get the main diagonal elements of the matrix B of the transform.
+     *
+     * <p>Note that since this class is only intended for internal use, it returns directly a
+     * reference to its internal arrays, not a copy.
+     *
+     * @return the main diagonal elements of the B matrix
+     */
+    double[] getMainDiagonalRef() {
+        return main;
+    }
+
+    /**
+     * Get the secondary diagonal elements of the matrix B of the transform.
+     *
+     * <p>Note that since this class is only intended for internal use, it returns directly a
+     * reference to its internal arrays, not a copy.
+     *
+     * @return the secondary diagonal elements of the B matrix
+     */
+    double[] getSecondaryDiagonalRef() {
+        return secondary;
+    }
+
+    /**
+     * Check if the matrix is transformed to upper bi-diagonal.
+     *
+     * @return true if the matrix is transformed to upper bi-diagonal
+     */
+    boolean isUpperBiDiagonal() {
+        return householderVectors.length >= householderVectors[0].length;
+    }
+
+    /**
+     * Transform original matrix to upper bi-diagonal form.
+     *
+     * <p>Transformation is done using alternate Householder transforms on columns and rows.
+     */
+    private void transformToUpperBiDiagonal() {
+
+        final int m = householderVectors.length;
+        final int n = householderVectors[0].length;
+        for (int k = 0; k < n; k++) {
+
+            // zero-out a column
+            double xNormSqr = 0;
+            for (int i = k; i < m; ++i) {
+                final double c = householderVectors[i][k];
+                xNormSqr += c * c;
+            }
+            final double[] hK = householderVectors[k];
+            final double a = (hK[k] > 0) ? -FastMath.sqrt(xNormSqr) : FastMath.sqrt(xNormSqr);
+            main[k] = a;
+            if (a != 0.0) {
+                hK[k] -= a;
+                for (int j = k + 1; j < n; ++j) {
+                    double alpha = 0;
+                    for (int i = k; i < m; ++i) {
+                        final double[] hI = householderVectors[i];
+                        alpha -= hI[j] * hI[k];
+                    }
+                    alpha /= a * householderVectors[k][k];
+                    for (int i = k; i < m; ++i) {
+                        final double[] hI = householderVectors[i];
+                        hI[j] -= alpha * hI[k];
+                    }
+                }
+            }
+
+            if (k < n - 1) {
+                // zero-out a row
+                xNormSqr = 0;
+                for (int j = k + 1; j < n; ++j) {
+                    final double c = hK[j];
+                    xNormSqr += c * c;
+                }
+                final double b =
+                        (hK[k + 1] > 0) ? -FastMath.sqrt(xNormSqr) : FastMath.sqrt(xNormSqr);
+                secondary[k] = b;
+                if (b != 0.0) {
+                    hK[k + 1] -= b;
+                    for (int i = k + 1; i < m; ++i) {
+                        final double[] hI = householderVectors[i];
+                        double beta = 0;
+                        for (int j = k + 1; j < n; ++j) {
+                            beta -= hI[j] * hK[j];
+                        }
+                        beta /= b * hK[k + 1];
+                        for (int j = k + 1; j < n; ++j) {
+                            hI[j] -= beta * hK[j];
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Transform original matrix to lower bi-diagonal form.
+     *
+     * <p>Transformation is done using alternate Householder transforms on rows and columns.
+     */
+    private void transformToLowerBiDiagonal() {
+
+        final int m = householderVectors.length;
+        final int n = householderVectors[0].length;
+        for (int k = 0; k < m; k++) {
+
+            // zero-out a row
+            final double[] hK = householderVectors[k];
+            double xNormSqr = 0;
+            for (int j = k; j < n; ++j) {
+                final double c = hK[j];
+                xNormSqr += c * c;
+            }
+            final double a = (hK[k] > 0) ? -FastMath.sqrt(xNormSqr) : FastMath.sqrt(xNormSqr);
+            main[k] = a;
+            if (a != 0.0) {
+                hK[k] -= a;
+                for (int i = k + 1; i < m; ++i) {
+                    final double[] hI = householderVectors[i];
+                    double alpha = 0;
+                    for (int j = k; j < n; ++j) {
+                        alpha -= hI[j] * hK[j];
+                    }
+                    alpha /= a * householderVectors[k][k];
+                    for (int j = k; j < n; ++j) {
+                        hI[j] -= alpha * hK[j];
+                    }
+                }
+            }
+
+            if (k < m - 1) {
+                // zero-out a column
+                final double[] hKp1 = householderVectors[k + 1];
+                xNormSqr = 0;
+                for (int i = k + 1; i < m; ++i) {
+                    final double c = householderVectors[i][k];
+                    xNormSqr += c * c;
+                }
+                final double b = (hKp1[k] > 0) ? -FastMath.sqrt(xNormSqr) : FastMath.sqrt(xNormSqr);
+                secondary[k] = b;
+                if (b != 0.0) {
+                    hKp1[k] -= b;
+                    for (int j = k + 1; j < n; ++j) {
+                        double beta = 0;
+                        for (int i = k + 1; i < m; ++i) {
+                            final double[] hI = householderVectors[i];
+                            beta -= hI[j] * hI[k];
+                        }
+                        beta /= b * hKp1[k];
+                        for (int i = k + 1; i < m; ++i) {
+                            final double[] hI = householderVectors[i];
+                            hI[j] -= beta * hI[k];
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/BlockFieldMatrix.java b/src/main/java/org/apache/commons/math3/linear/BlockFieldMatrix.java
new file mode 100644
index 0000000..419a25f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/BlockFieldMatrix.java
@@ -0,0 +1,1664 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.FieldElement;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.util.MathUtils;
+
+import java.io.Serializable;
+
+/**
+ * Cache-friendly implementation of FieldMatrix using a flat arrays to store square blocks of the
+ * matrix.
+ *
+ * <p>This implementation is specially designed to be cache-friendly. Square blocks are stored as
+ * small arrays and allow efficient traversal of data both in row major direction and columns major
+ * direction, one block at a time. This greatly increases performances for algorithms that use
+ * crossed directions loops like multiplication or transposition.
+ *
+ * <p>The size of square blocks is a static parameter. It may be tuned according to the cache size
+ * of the target computer processor. As a rule of thumbs, it should be the largest value that allows
+ * three blocks to be simultaneously cached (this is necessary for example for matrix
+ * multiplication). The default value is to use 36x36 blocks.
+ *
+ * <p>The regular blocks represent {@link #BLOCK_SIZE} x {@link #BLOCK_SIZE} squares. Blocks at
+ * right hand side and bottom side which may be smaller to fit matrix dimensions. The square blocks
+ * are flattened in row major order in single dimension arrays which are therefore {@link
+ * #BLOCK_SIZE}<sup>2</sup> elements long for regular blocks. The blocks are themselves organized in
+ * row major order.
+ *
+ * <p>As an example, for a block size of 36x36, a 100x60 matrix would be stored in 6 blocks. Block 0
+ * would be a Field[1296] array holding the upper left 36x36 square, block 1 would be a Field[1296]
+ * array holding the upper center 36x36 square, block 2 would be a Field[1008] array holding the
+ * upper right 36x28 rectangle, block 3 would be a Field[864] array holding the lower left 24x36
+ * rectangle, block 4 would be a Field[864] array holding the lower center 24x36 rectangle and block
+ * 5 would be a Field[672] array holding the lower right 24x28 rectangle.
+ *
+ * <p>The layout complexity overhead versus simple mapping of matrices to java arrays is negligible
+ * for small matrices (about 1%). The gain from cache efficiency leads to up to 3-fold improvements
+ * for matrices of moderate to large size.
+ *
+ * @param <T> the type of the field elements
+ * @since 2.0
+ */
+public class BlockFieldMatrix<T extends FieldElement<T>> extends AbstractFieldMatrix<T>
+        implements Serializable {
+    /** Block size. */
+    public static final int BLOCK_SIZE = 36;
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -4602336630143123183L;
+
+    /** Blocks of matrix entries. */
+    private final T blocks[][];
+
+    /** Number of rows of the matrix. */
+    private final int rows;
+
+    /** Number of columns of the matrix. */
+    private final int columns;
+
+    /** Number of block rows of the matrix. */
+    private final int blockRows;
+
+    /** Number of block columns of the matrix. */
+    private final int blockColumns;
+
+    /**
+     * Create a new matrix with the supplied row and column dimensions.
+     *
+     * @param field Field to which the elements belong.
+     * @param rows Number of rows in the new matrix.
+     * @param columns Number of columns in the new matrix.
+     * @throws NotStrictlyPositiveException if row or column dimension is not positive.
+     */
+    public BlockFieldMatrix(final Field<T> field, final int rows, final int columns)
+            throws NotStrictlyPositiveException {
+        super(field, rows, columns);
+        this.rows = rows;
+        this.columns = columns;
+
+        // number of blocks
+        blockRows = (rows + BLOCK_SIZE - 1) / BLOCK_SIZE;
+        blockColumns = (columns + BLOCK_SIZE - 1) / BLOCK_SIZE;
+
+        // allocate storage blocks, taking care of smaller ones at right and bottom
+        blocks = createBlocksLayout(field, rows, columns);
+    }
+
+    /**
+     * Create a new dense matrix copying entries from raw layout data.
+     *
+     * <p>The input array <em>must</em> already be in raw layout.
+     *
+     * <p>Calling this constructor is equivalent to call:
+     *
+     * <pre>matrix = new BlockFieldMatrix<T>(getField(), rawData.length, rawData[0].length,
+     *                                   toBlocksLayout(rawData), false);</pre>
+     *
+     * @param rawData Data for the new matrix, in raw layout.
+     * @throws DimensionMismatchException if the {@code blockData} shape is inconsistent with block
+     *     layout.
+     * @see #BlockFieldMatrix(int, int, FieldElement[][], boolean)
+     */
+    public BlockFieldMatrix(final T[][] rawData) throws DimensionMismatchException {
+        this(rawData.length, rawData[0].length, toBlocksLayout(rawData), false);
+    }
+
+    /**
+     * Create a new dense matrix copying entries from block layout data.
+     *
+     * <p>The input array <em>must</em> already be in blocks layout.
+     *
+     * @param rows the number of rows in the new matrix
+     * @param columns the number of columns in the new matrix
+     * @param blockData data for new matrix
+     * @param copyArray if true, the input array will be copied, otherwise it will be referenced
+     * @throws DimensionMismatchException if the {@code blockData} shape is inconsistent with block
+     *     layout.
+     * @throws NotStrictlyPositiveException if row or column dimension is not positive.
+     * @see #createBlocksLayout(Field, int, int)
+     * @see #toBlocksLayout(FieldElement[][])
+     * @see #BlockFieldMatrix(FieldElement[][])
+     */
+    public BlockFieldMatrix(
+            final int rows, final int columns, final T[][] blockData, final boolean copyArray)
+            throws DimensionMismatchException, NotStrictlyPositiveException {
+        super(extractField(blockData), rows, columns);
+        this.rows = rows;
+        this.columns = columns;
+
+        // number of blocks
+        blockRows = (rows + BLOCK_SIZE - 1) / BLOCK_SIZE;
+        blockColumns = (columns + BLOCK_SIZE - 1) / BLOCK_SIZE;
+
+        if (copyArray) {
+            // allocate storage blocks, taking care of smaller ones at right and bottom
+            blocks = MathArrays.buildArray(getField(), blockRows * blockColumns, -1);
+        } else {
+            // reference existing array
+            blocks = blockData;
+        }
+
+        int index = 0;
+        for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
+            final int iHeight = blockHeight(iBlock);
+            for (int jBlock = 0; jBlock < blockColumns; ++jBlock, ++index) {
+                if (blockData[index].length != iHeight * blockWidth(jBlock)) {
+                    throw new DimensionMismatchException(
+                            blockData[index].length, iHeight * blockWidth(jBlock));
+                }
+                if (copyArray) {
+                    blocks[index] = blockData[index].clone();
+                }
+            }
+        }
+    }
+
+    /**
+     * Convert a data array from raw layout to blocks layout.
+     *
+     * <p>Raw layout is the straightforward layout where element at row i and column j is in array
+     * element <code>rawData[i][j]</code>. Blocks layout is the layout used in {@link
+     * BlockFieldMatrix} instances, where the matrix is split in square blocks (except at right and
+     * bottom side where blocks may be rectangular to fit matrix size) and each block is stored in a
+     * flattened one-dimensional array.
+     *
+     * <p>This method creates an array in blocks layout from an input array in raw layout. It can be
+     * used to provide the array argument of the {@link #BlockFieldMatrix(int, int,
+     * FieldElement[][], boolean)} constructor.
+     *
+     * @param <T> Type of the field elements.
+     * @param rawData Data array in raw layout.
+     * @return a new data array containing the same entries but in blocks layout
+     * @throws DimensionMismatchException if {@code rawData} is not rectangular (not all rows have
+     *     the same length).
+     * @see #createBlocksLayout(Field, int, int)
+     * @see #BlockFieldMatrix(int, int, FieldElement[][], boolean)
+     */
+    public static <T extends FieldElement<T>> T[][] toBlocksLayout(final T[][] rawData)
+            throws DimensionMismatchException {
+
+        final int rows = rawData.length;
+        final int columns = rawData[0].length;
+        final int blockRows = (rows + BLOCK_SIZE - 1) / BLOCK_SIZE;
+        final int blockColumns = (columns + BLOCK_SIZE - 1) / BLOCK_SIZE;
+
+        // safety checks
+        for (int i = 0; i < rawData.length; ++i) {
+            final int length = rawData[i].length;
+            if (length != columns) {
+                throw new DimensionMismatchException(columns, length);
+            }
+        }
+
+        // convert array
+        final Field<T> field = extractField(rawData);
+        final T[][] blocks = MathArrays.buildArray(field, blockRows * blockColumns, -1);
+        int blockIndex = 0;
+        for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
+            final int pStart = iBlock * BLOCK_SIZE;
+            final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows);
+            final int iHeight = pEnd - pStart;
+            for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
+                final int qStart = jBlock * BLOCK_SIZE;
+                final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns);
+                final int jWidth = qEnd - qStart;
+
+                // allocate new block
+                final T[] block = MathArrays.buildArray(field, iHeight * jWidth);
+                blocks[blockIndex] = block;
+
+                // copy data
+                int index = 0;
+                for (int p = pStart; p < pEnd; ++p) {
+                    System.arraycopy(rawData[p], qStart, block, index, jWidth);
+                    index += jWidth;
+                }
+
+                ++blockIndex;
+            }
+        }
+
+        return blocks;
+    }
+
+    /**
+     * Create a data array in blocks layout.
+     *
+     * <p>This method can be used to create the array argument of the {@link #BlockFieldMatrix(int,
+     * int, FieldElement[][], boolean)} constructor.
+     *
+     * @param <T> Type of the field elements.
+     * @param field Field to which the elements belong.
+     * @param rows Number of rows in the new matrix.
+     * @param columns Number of columns in the new matrix.
+     * @return a new data array in blocks layout.
+     * @see #toBlocksLayout(FieldElement[][])
+     * @see #BlockFieldMatrix(int, int, FieldElement[][], boolean)
+     */
+    public static <T extends FieldElement<T>> T[][] createBlocksLayout(
+            final Field<T> field, final int rows, final int columns) {
+        final int blockRows = (rows + BLOCK_SIZE - 1) / BLOCK_SIZE;
+        final int blockColumns = (columns + BLOCK_SIZE - 1) / BLOCK_SIZE;
+
+        final T[][] blocks = MathArrays.buildArray(field, blockRows * blockColumns, -1);
+        int blockIndex = 0;
+        for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
+            final int pStart = iBlock * BLOCK_SIZE;
+            final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows);
+            final int iHeight = pEnd - pStart;
+            for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
+                final int qStart = jBlock * BLOCK_SIZE;
+                final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns);
+                final int jWidth = qEnd - qStart;
+                blocks[blockIndex] = MathArrays.buildArray(field, iHeight * jWidth);
+                ++blockIndex;
+            }
+        }
+
+        return blocks;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public FieldMatrix<T> createMatrix(final int rowDimension, final int columnDimension)
+            throws NotStrictlyPositiveException {
+        return new BlockFieldMatrix<T>(getField(), rowDimension, columnDimension);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public FieldMatrix<T> copy() {
+
+        // create an empty matrix
+        BlockFieldMatrix<T> copied = new BlockFieldMatrix<T>(getField(), rows, columns);
+
+        // copy the blocks
+        for (int i = 0; i < blocks.length; ++i) {
+            System.arraycopy(blocks[i], 0, copied.blocks[i], 0, blocks[i].length);
+        }
+
+        return copied;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public FieldMatrix<T> add(final FieldMatrix<T> m) throws MatrixDimensionMismatchException {
+        try {
+            return add((BlockFieldMatrix<T>) m);
+        } catch (ClassCastException cce) {
+
+            // safety check
+            checkAdditionCompatible(m);
+
+            final BlockFieldMatrix<T> out = new BlockFieldMatrix<T>(getField(), rows, columns);
+
+            // perform addition block-wise, to ensure good cache behavior
+            int blockIndex = 0;
+            for (int iBlock = 0; iBlock < out.blockRows; ++iBlock) {
+                for (int jBlock = 0; jBlock < out.blockColumns; ++jBlock) {
+
+                    // perform addition on the current block
+                    final T[] outBlock = out.blocks[blockIndex];
+                    final T[] tBlock = blocks[blockIndex];
+                    final int pStart = iBlock * BLOCK_SIZE;
+                    final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows);
+                    final int qStart = jBlock * BLOCK_SIZE;
+                    final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns);
+                    int k = 0;
+                    for (int p = pStart; p < pEnd; ++p) {
+                        for (int q = qStart; q < qEnd; ++q) {
+                            outBlock[k] = tBlock[k].add(m.getEntry(p, q));
+                            ++k;
+                        }
+                    }
+
+                    // go to next block
+                    ++blockIndex;
+                }
+            }
+
+            return out;
+        }
+    }
+
+    /**
+     * Compute the sum of {@code this} and {@code m}.
+     *
+     * @param m matrix to be added
+     * @return {@code this + m}
+     * @throws MatrixDimensionMismatchException if {@code m} is not the same size as {@code this}
+     */
+    public BlockFieldMatrix<T> add(final BlockFieldMatrix<T> m)
+            throws MatrixDimensionMismatchException {
+
+        // safety check
+        checkAdditionCompatible(m);
+
+        final BlockFieldMatrix<T> out = new BlockFieldMatrix<T>(getField(), rows, columns);
+
+        // perform addition block-wise, to ensure good cache behavior
+        for (int blockIndex = 0; blockIndex < out.blocks.length; ++blockIndex) {
+            final T[] outBlock = out.blocks[blockIndex];
+            final T[] tBlock = blocks[blockIndex];
+            final T[] mBlock = m.blocks[blockIndex];
+            for (int k = 0; k < outBlock.length; ++k) {
+                outBlock[k] = tBlock[k].add(mBlock[k]);
+            }
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public FieldMatrix<T> subtract(final FieldMatrix<T> m) throws MatrixDimensionMismatchException {
+        try {
+            return subtract((BlockFieldMatrix<T>) m);
+        } catch (ClassCastException cce) {
+
+            // safety check
+            checkSubtractionCompatible(m);
+
+            final BlockFieldMatrix<T> out = new BlockFieldMatrix<T>(getField(), rows, columns);
+
+            // perform subtraction block-wise, to ensure good cache behavior
+            int blockIndex = 0;
+            for (int iBlock = 0; iBlock < out.blockRows; ++iBlock) {
+                for (int jBlock = 0; jBlock < out.blockColumns; ++jBlock) {
+
+                    // perform subtraction on the current block
+                    final T[] outBlock = out.blocks[blockIndex];
+                    final T[] tBlock = blocks[blockIndex];
+                    final int pStart = iBlock * BLOCK_SIZE;
+                    final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows);
+                    final int qStart = jBlock * BLOCK_SIZE;
+                    final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns);
+                    int k = 0;
+                    for (int p = pStart; p < pEnd; ++p) {
+                        for (int q = qStart; q < qEnd; ++q) {
+                            outBlock[k] = tBlock[k].subtract(m.getEntry(p, q));
+                            ++k;
+                        }
+                    }
+
+                    // go to next block
+                    ++blockIndex;
+                }
+            }
+
+            return out;
+        }
+    }
+
+    /**
+     * Compute {@code this - m}.
+     *
+     * @param m matrix to be subtracted
+     * @return {@code this - m}
+     * @throws MatrixDimensionMismatchException if {@code m} is not the same size as {@code this}
+     */
+    public BlockFieldMatrix<T> subtract(final BlockFieldMatrix<T> m)
+            throws MatrixDimensionMismatchException {
+        // safety check
+        checkSubtractionCompatible(m);
+
+        final BlockFieldMatrix<T> out = new BlockFieldMatrix<T>(getField(), rows, columns);
+
+        // perform subtraction block-wise, to ensure good cache behavior
+        for (int blockIndex = 0; blockIndex < out.blocks.length; ++blockIndex) {
+            final T[] outBlock = out.blocks[blockIndex];
+            final T[] tBlock = blocks[blockIndex];
+            final T[] mBlock = m.blocks[blockIndex];
+            for (int k = 0; k < outBlock.length; ++k) {
+                outBlock[k] = tBlock[k].subtract(mBlock[k]);
+            }
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public FieldMatrix<T> scalarAdd(final T d) {
+        final BlockFieldMatrix<T> out = new BlockFieldMatrix<T>(getField(), rows, columns);
+
+        // perform subtraction block-wise, to ensure good cache behavior
+        for (int blockIndex = 0; blockIndex < out.blocks.length; ++blockIndex) {
+            final T[] outBlock = out.blocks[blockIndex];
+            final T[] tBlock = blocks[blockIndex];
+            for (int k = 0; k < outBlock.length; ++k) {
+                outBlock[k] = tBlock[k].add(d);
+            }
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public FieldMatrix<T> scalarMultiply(final T d) {
+
+        final BlockFieldMatrix<T> out = new BlockFieldMatrix<T>(getField(), rows, columns);
+
+        // perform subtraction block-wise, to ensure good cache behavior
+        for (int blockIndex = 0; blockIndex < out.blocks.length; ++blockIndex) {
+            final T[] outBlock = out.blocks[blockIndex];
+            final T[] tBlock = blocks[blockIndex];
+            for (int k = 0; k < outBlock.length; ++k) {
+                outBlock[k] = tBlock[k].multiply(d);
+            }
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public FieldMatrix<T> multiply(final FieldMatrix<T> m) throws DimensionMismatchException {
+        try {
+            return multiply((BlockFieldMatrix<T>) m);
+        } catch (ClassCastException cce) {
+
+            // safety check
+            checkMultiplicationCompatible(m);
+
+            final BlockFieldMatrix<T> out =
+                    new BlockFieldMatrix<T>(getField(), rows, m.getColumnDimension());
+            final T zero = getField().getZero();
+
+            // perform multiplication block-wise, to ensure good cache behavior
+            int blockIndex = 0;
+            for (int iBlock = 0; iBlock < out.blockRows; ++iBlock) {
+
+                final int pStart = iBlock * BLOCK_SIZE;
+                final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows);
+
+                for (int jBlock = 0; jBlock < out.blockColumns; ++jBlock) {
+
+                    final int qStart = jBlock * BLOCK_SIZE;
+                    final int qEnd = FastMath.min(qStart + BLOCK_SIZE, m.getColumnDimension());
+
+                    // select current block
+                    final T[] outBlock = out.blocks[blockIndex];
+
+                    // perform multiplication on current block
+                    for (int kBlock = 0; kBlock < blockColumns; ++kBlock) {
+                        final int kWidth = blockWidth(kBlock);
+                        final T[] tBlock = blocks[iBlock * blockColumns + kBlock];
+                        final int rStart = kBlock * BLOCK_SIZE;
+                        int k = 0;
+                        for (int p = pStart; p < pEnd; ++p) {
+                            final int lStart = (p - pStart) * kWidth;
+                            final int lEnd = lStart + kWidth;
+                            for (int q = qStart; q < qEnd; ++q) {
+                                T sum = zero;
+                                int r = rStart;
+                                for (int l = lStart; l < lEnd; ++l) {
+                                    sum = sum.add(tBlock[l].multiply(m.getEntry(r, q)));
+                                    ++r;
+                                }
+                                outBlock[k] = outBlock[k].add(sum);
+                                ++k;
+                            }
+                        }
+                    }
+
+                    // go to next block
+                    ++blockIndex;
+                }
+            }
+
+            return out;
+        }
+    }
+
+    /**
+     * Returns the result of postmultiplying {@code this} by {@code m}.
+     *
+     * @param m matrix to postmultiply by
+     * @return {@code this * m}
+     * @throws DimensionMismatchException if the matrices are not compatible.
+     */
+    public BlockFieldMatrix<T> multiply(BlockFieldMatrix<T> m) throws DimensionMismatchException {
+
+        // safety check
+        checkMultiplicationCompatible(m);
+
+        final BlockFieldMatrix<T> out = new BlockFieldMatrix<T>(getField(), rows, m.columns);
+        final T zero = getField().getZero();
+
+        // perform multiplication block-wise, to ensure good cache behavior
+        int blockIndex = 0;
+        for (int iBlock = 0; iBlock < out.blockRows; ++iBlock) {
+
+            final int pStart = iBlock * BLOCK_SIZE;
+            final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows);
+
+            for (int jBlock = 0; jBlock < out.blockColumns; ++jBlock) {
+                final int jWidth = out.blockWidth(jBlock);
+                final int jWidth2 = jWidth + jWidth;
+                final int jWidth3 = jWidth2 + jWidth;
+                final int jWidth4 = jWidth3 + jWidth;
+
+                // select current block
+                final T[] outBlock = out.blocks[blockIndex];
+
+                // perform multiplication on current block
+                for (int kBlock = 0; kBlock < blockColumns; ++kBlock) {
+                    final int kWidth = blockWidth(kBlock);
+                    final T[] tBlock = blocks[iBlock * blockColumns + kBlock];
+                    final T[] mBlock = m.blocks[kBlock * m.blockColumns + jBlock];
+                    int k = 0;
+                    for (int p = pStart; p < pEnd; ++p) {
+                        final int lStart = (p - pStart) * kWidth;
+                        final int lEnd = lStart + kWidth;
+                        for (int nStart = 0; nStart < jWidth; ++nStart) {
+                            T sum = zero;
+                            int l = lStart;
+                            int n = nStart;
+                            while (l < lEnd - 3) {
+                                sum =
+                                        sum.add(tBlock[l].multiply(mBlock[n]))
+                                                .add(tBlock[l + 1].multiply(mBlock[n + jWidth]))
+                                                .add(tBlock[l + 2].multiply(mBlock[n + jWidth2]))
+                                                .add(tBlock[l + 3].multiply(mBlock[n + jWidth3]));
+                                l += 4;
+                                n += jWidth4;
+                            }
+                            while (l < lEnd) {
+                                sum = sum.add(tBlock[l++].multiply(mBlock[n]));
+                                n += jWidth;
+                            }
+                            outBlock[k] = outBlock[k].add(sum);
+                            ++k;
+                        }
+                    }
+                }
+
+                // go to next block
+                ++blockIndex;
+            }
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public T[][] getData() {
+
+        final T[][] data =
+                MathArrays.buildArray(getField(), getRowDimension(), getColumnDimension());
+        final int lastColumns = columns - (blockColumns - 1) * BLOCK_SIZE;
+
+        for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
+            final int pStart = iBlock * BLOCK_SIZE;
+            final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows);
+            int regularPos = 0;
+            int lastPos = 0;
+            for (int p = pStart; p < pEnd; ++p) {
+                final T[] dataP = data[p];
+                int blockIndex = iBlock * blockColumns;
+                int dataPos = 0;
+                for (int jBlock = 0; jBlock < blockColumns - 1; ++jBlock) {
+                    System.arraycopy(blocks[blockIndex++], regularPos, dataP, dataPos, BLOCK_SIZE);
+                    dataPos += BLOCK_SIZE;
+                }
+                System.arraycopy(blocks[blockIndex], lastPos, dataP, dataPos, lastColumns);
+                regularPos += BLOCK_SIZE;
+                lastPos += lastColumns;
+            }
+        }
+
+        return data;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public FieldMatrix<T> getSubMatrix(
+            final int startRow, final int endRow, final int startColumn, final int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException {
+        // safety checks
+        checkSubMatrixIndex(startRow, endRow, startColumn, endColumn);
+
+        // create the output matrix
+        final BlockFieldMatrix<T> out =
+                new BlockFieldMatrix<T>(
+                        getField(), endRow - startRow + 1, endColumn - startColumn + 1);
+
+        // compute blocks shifts
+        final int blockStartRow = startRow / BLOCK_SIZE;
+        final int rowsShift = startRow % BLOCK_SIZE;
+        final int blockStartColumn = startColumn / BLOCK_SIZE;
+        final int columnsShift = startColumn % BLOCK_SIZE;
+
+        // perform extraction block-wise, to ensure good cache behavior
+        int pBlock = blockStartRow;
+        for (int iBlock = 0; iBlock < out.blockRows; ++iBlock) {
+            final int iHeight = out.blockHeight(iBlock);
+            int qBlock = blockStartColumn;
+            for (int jBlock = 0; jBlock < out.blockColumns; ++jBlock) {
+                final int jWidth = out.blockWidth(jBlock);
+
+                // handle one block of the output matrix
+                final int outIndex = iBlock * out.blockColumns + jBlock;
+                final T[] outBlock = out.blocks[outIndex];
+                final int index = pBlock * blockColumns + qBlock;
+                final int width = blockWidth(qBlock);
+
+                final int heightExcess = iHeight + rowsShift - BLOCK_SIZE;
+                final int widthExcess = jWidth + columnsShift - BLOCK_SIZE;
+                if (heightExcess > 0) {
+                    // the submatrix block spans on two blocks rows from the original matrix
+                    if (widthExcess > 0) {
+                        // the submatrix block spans on two blocks columns from the original matrix
+                        final int width2 = blockWidth(qBlock + 1);
+                        copyBlockPart(
+                                blocks[index],
+                                width,
+                                rowsShift,
+                                BLOCK_SIZE,
+                                columnsShift,
+                                BLOCK_SIZE,
+                                outBlock,
+                                jWidth,
+                                0,
+                                0);
+                        copyBlockPart(
+                                blocks[index + 1],
+                                width2,
+                                rowsShift,
+                                BLOCK_SIZE,
+                                0,
+                                widthExcess,
+                                outBlock,
+                                jWidth,
+                                0,
+                                jWidth - widthExcess);
+                        copyBlockPart(
+                                blocks[index + blockColumns],
+                                width,
+                                0,
+                                heightExcess,
+                                columnsShift,
+                                BLOCK_SIZE,
+                                outBlock,
+                                jWidth,
+                                iHeight - heightExcess,
+                                0);
+                        copyBlockPart(
+                                blocks[index + blockColumns + 1],
+                                width2,
+                                0,
+                                heightExcess,
+                                0,
+                                widthExcess,
+                                outBlock,
+                                jWidth,
+                                iHeight - heightExcess,
+                                jWidth - widthExcess);
+                    } else {
+                        // the submatrix block spans on one block column from the original matrix
+                        copyBlockPart(
+                                blocks[index],
+                                width,
+                                rowsShift,
+                                BLOCK_SIZE,
+                                columnsShift,
+                                jWidth + columnsShift,
+                                outBlock,
+                                jWidth,
+                                0,
+                                0);
+                        copyBlockPart(
+                                blocks[index + blockColumns],
+                                width,
+                                0,
+                                heightExcess,
+                                columnsShift,
+                                jWidth + columnsShift,
+                                outBlock,
+                                jWidth,
+                                iHeight - heightExcess,
+                                0);
+                    }
+                } else {
+                    // the submatrix block spans on one block row from the original matrix
+                    if (widthExcess > 0) {
+                        // the submatrix block spans on two blocks columns from the original matrix
+                        final int width2 = blockWidth(qBlock + 1);
+                        copyBlockPart(
+                                blocks[index],
+                                width,
+                                rowsShift,
+                                iHeight + rowsShift,
+                                columnsShift,
+                                BLOCK_SIZE,
+                                outBlock,
+                                jWidth,
+                                0,
+                                0);
+                        copyBlockPart(
+                                blocks[index + 1],
+                                width2,
+                                rowsShift,
+                                iHeight + rowsShift,
+                                0,
+                                widthExcess,
+                                outBlock,
+                                jWidth,
+                                0,
+                                jWidth - widthExcess);
+                    } else {
+                        // the submatrix block spans on one block column from the original matrix
+                        copyBlockPart(
+                                blocks[index],
+                                width,
+                                rowsShift,
+                                iHeight + rowsShift,
+                                columnsShift,
+                                jWidth + columnsShift,
+                                outBlock,
+                                jWidth,
+                                0,
+                                0);
+                    }
+                }
+                ++qBlock;
+            }
+            ++pBlock;
+        }
+
+        return out;
+    }
+
+    /**
+     * Copy a part of a block into another one
+     *
+     * <p>This method can be called only when the specified part fits in both blocks, no
+     * verification is done here.
+     *
+     * @param srcBlock source block
+     * @param srcWidth source block width ({@link #BLOCK_SIZE} or smaller)
+     * @param srcStartRow start row in the source block
+     * @param srcEndRow end row (exclusive) in the source block
+     * @param srcStartColumn start column in the source block
+     * @param srcEndColumn end column (exclusive) in the source block
+     * @param dstBlock destination block
+     * @param dstWidth destination block width ({@link #BLOCK_SIZE} or smaller)
+     * @param dstStartRow start row in the destination block
+     * @param dstStartColumn start column in the destination block
+     */
+    private void copyBlockPart(
+            final T[] srcBlock,
+            final int srcWidth,
+            final int srcStartRow,
+            final int srcEndRow,
+            final int srcStartColumn,
+            final int srcEndColumn,
+            final T[] dstBlock,
+            final int dstWidth,
+            final int dstStartRow,
+            final int dstStartColumn) {
+        final int length = srcEndColumn - srcStartColumn;
+        int srcPos = srcStartRow * srcWidth + srcStartColumn;
+        int dstPos = dstStartRow * dstWidth + dstStartColumn;
+        for (int srcRow = srcStartRow; srcRow < srcEndRow; ++srcRow) {
+            System.arraycopy(srcBlock, srcPos, dstBlock, dstPos, length);
+            srcPos += srcWidth;
+            dstPos += dstWidth;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setSubMatrix(final T[][] subMatrix, final int row, final int column)
+            throws DimensionMismatchException,
+                    OutOfRangeException,
+                    NoDataException,
+                    NullArgumentException {
+        // safety checks
+        MathUtils.checkNotNull(subMatrix);
+        final int refLength = subMatrix[0].length;
+        if (refLength == 0) {
+            throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_COLUMN);
+        }
+        final int endRow = row + subMatrix.length - 1;
+        final int endColumn = column + refLength - 1;
+        checkSubMatrixIndex(row, endRow, column, endColumn);
+        for (final T[] subRow : subMatrix) {
+            if (subRow.length != refLength) {
+                throw new DimensionMismatchException(refLength, subRow.length);
+            }
+        }
+
+        // compute blocks bounds
+        final int blockStartRow = row / BLOCK_SIZE;
+        final int blockEndRow = (endRow + BLOCK_SIZE) / BLOCK_SIZE;
+        final int blockStartColumn = column / BLOCK_SIZE;
+        final int blockEndColumn = (endColumn + BLOCK_SIZE) / BLOCK_SIZE;
+
+        // perform copy block-wise, to ensure good cache behavior
+        for (int iBlock = blockStartRow; iBlock < blockEndRow; ++iBlock) {
+            final int iHeight = blockHeight(iBlock);
+            final int firstRow = iBlock * BLOCK_SIZE;
+            final int iStart = FastMath.max(row, firstRow);
+            final int iEnd = FastMath.min(endRow + 1, firstRow + iHeight);
+
+            for (int jBlock = blockStartColumn; jBlock < blockEndColumn; ++jBlock) {
+                final int jWidth = blockWidth(jBlock);
+                final int firstColumn = jBlock * BLOCK_SIZE;
+                final int jStart = FastMath.max(column, firstColumn);
+                final int jEnd = FastMath.min(endColumn + 1, firstColumn + jWidth);
+                final int jLength = jEnd - jStart;
+
+                // handle one block, row by row
+                final T[] block = blocks[iBlock * blockColumns + jBlock];
+                for (int i = iStart; i < iEnd; ++i) {
+                    System.arraycopy(
+                            subMatrix[i - row],
+                            jStart - column,
+                            block,
+                            (i - firstRow) * jWidth + (jStart - firstColumn),
+                            jLength);
+                }
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public FieldMatrix<T> getRowMatrix(final int row) throws OutOfRangeException {
+        checkRowIndex(row);
+        final BlockFieldMatrix<T> out = new BlockFieldMatrix<T>(getField(), 1, columns);
+
+        // perform copy block-wise, to ensure good cache behavior
+        final int iBlock = row / BLOCK_SIZE;
+        final int iRow = row - iBlock * BLOCK_SIZE;
+        int outBlockIndex = 0;
+        int outIndex = 0;
+        T[] outBlock = out.blocks[outBlockIndex];
+        for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
+            final int jWidth = blockWidth(jBlock);
+            final T[] block = blocks[iBlock * blockColumns + jBlock];
+            final int available = outBlock.length - outIndex;
+            if (jWidth > available) {
+                System.arraycopy(block, iRow * jWidth, outBlock, outIndex, available);
+                outBlock = out.blocks[++outBlockIndex];
+                System.arraycopy(block, iRow * jWidth, outBlock, 0, jWidth - available);
+                outIndex = jWidth - available;
+            } else {
+                System.arraycopy(block, iRow * jWidth, outBlock, outIndex, jWidth);
+                outIndex += jWidth;
+            }
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setRowMatrix(final int row, final FieldMatrix<T> matrix)
+            throws MatrixDimensionMismatchException, OutOfRangeException {
+        try {
+            setRowMatrix(row, (BlockFieldMatrix<T>) matrix);
+        } catch (ClassCastException cce) {
+            super.setRowMatrix(row, matrix);
+        }
+    }
+
+    /**
+     * Sets the entries in row number <code>row</code> as a row matrix. Row indices start at 0.
+     *
+     * @param row the row to be set
+     * @param matrix row matrix (must have one row and the same number of columns as the instance)
+     * @throws MatrixDimensionMismatchException if the matrix dimensions do not match one instance
+     *     row.
+     * @throws OutOfRangeException if the specified row index is invalid.
+     */
+    public void setRowMatrix(final int row, final BlockFieldMatrix<T> matrix)
+            throws MatrixDimensionMismatchException, OutOfRangeException {
+        checkRowIndex(row);
+        final int nCols = getColumnDimension();
+        if ((matrix.getRowDimension() != 1) || (matrix.getColumnDimension() != nCols)) {
+            throw new MatrixDimensionMismatchException(
+                    matrix.getRowDimension(), matrix.getColumnDimension(), 1, nCols);
+        }
+
+        // perform copy block-wise, to ensure good cache behavior
+        final int iBlock = row / BLOCK_SIZE;
+        final int iRow = row - iBlock * BLOCK_SIZE;
+        int mBlockIndex = 0;
+        int mIndex = 0;
+        T[] mBlock = matrix.blocks[mBlockIndex];
+        for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
+            final int jWidth = blockWidth(jBlock);
+            final T[] block = blocks[iBlock * blockColumns + jBlock];
+            final int available = mBlock.length - mIndex;
+            if (jWidth > available) {
+                System.arraycopy(mBlock, mIndex, block, iRow * jWidth, available);
+                mBlock = matrix.blocks[++mBlockIndex];
+                System.arraycopy(mBlock, 0, block, iRow * jWidth, jWidth - available);
+                mIndex = jWidth - available;
+            } else {
+                System.arraycopy(mBlock, mIndex, block, iRow * jWidth, jWidth);
+                mIndex += jWidth;
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public FieldMatrix<T> getColumnMatrix(final int column) throws OutOfRangeException {
+        checkColumnIndex(column);
+        final BlockFieldMatrix<T> out = new BlockFieldMatrix<T>(getField(), rows, 1);
+
+        // perform copy block-wise, to ensure good cache behavior
+        final int jBlock = column / BLOCK_SIZE;
+        final int jColumn = column - jBlock * BLOCK_SIZE;
+        final int jWidth = blockWidth(jBlock);
+        int outBlockIndex = 0;
+        int outIndex = 0;
+        T[] outBlock = out.blocks[outBlockIndex];
+        for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
+            final int iHeight = blockHeight(iBlock);
+            final T[] block = blocks[iBlock * blockColumns + jBlock];
+            for (int i = 0; i < iHeight; ++i) {
+                if (outIndex >= outBlock.length) {
+                    outBlock = out.blocks[++outBlockIndex];
+                    outIndex = 0;
+                }
+                outBlock[outIndex++] = block[i * jWidth + jColumn];
+            }
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setColumnMatrix(final int column, final FieldMatrix<T> matrix)
+            throws MatrixDimensionMismatchException, OutOfRangeException {
+        try {
+            setColumnMatrix(column, (BlockFieldMatrix<T>) matrix);
+        } catch (ClassCastException cce) {
+            super.setColumnMatrix(column, matrix);
+        }
+    }
+
+    /**
+     * Sets the entries in column number {@code column} as a column matrix. Column indices start at
+     * 0.
+     *
+     * @param column Column to be set.
+     * @param matrix Column matrix (must have one column and the same number of rows as the
+     *     instance).
+     * @throws MatrixDimensionMismatchException if the matrix dimensions do not match one instance
+     *     column.
+     * @throws OutOfRangeException if the specified column index is invalid.
+     */
+    void setColumnMatrix(final int column, final BlockFieldMatrix<T> matrix)
+            throws MatrixDimensionMismatchException, OutOfRangeException {
+        checkColumnIndex(column);
+        final int nRows = getRowDimension();
+        if ((matrix.getRowDimension() != nRows) || (matrix.getColumnDimension() != 1)) {
+            throw new MatrixDimensionMismatchException(
+                    matrix.getRowDimension(), matrix.getColumnDimension(), nRows, 1);
+        }
+
+        // perform copy block-wise, to ensure good cache behavior
+        final int jBlock = column / BLOCK_SIZE;
+        final int jColumn = column - jBlock * BLOCK_SIZE;
+        final int jWidth = blockWidth(jBlock);
+        int mBlockIndex = 0;
+        int mIndex = 0;
+        T[] mBlock = matrix.blocks[mBlockIndex];
+        for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
+            final int iHeight = blockHeight(iBlock);
+            final T[] block = blocks[iBlock * blockColumns + jBlock];
+            for (int i = 0; i < iHeight; ++i) {
+                if (mIndex >= mBlock.length) {
+                    mBlock = matrix.blocks[++mBlockIndex];
+                    mIndex = 0;
+                }
+                block[i * jWidth + jColumn] = mBlock[mIndex++];
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public FieldVector<T> getRowVector(final int row) throws OutOfRangeException {
+        checkRowIndex(row);
+        final T[] outData = MathArrays.buildArray(getField(), columns);
+
+        // perform copy block-wise, to ensure good cache behavior
+        final int iBlock = row / BLOCK_SIZE;
+        final int iRow = row - iBlock * BLOCK_SIZE;
+        int outIndex = 0;
+        for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
+            final int jWidth = blockWidth(jBlock);
+            final T[] block = blocks[iBlock * blockColumns + jBlock];
+            System.arraycopy(block, iRow * jWidth, outData, outIndex, jWidth);
+            outIndex += jWidth;
+        }
+
+        return new ArrayFieldVector<T>(getField(), outData, false);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setRowVector(final int row, final FieldVector<T> vector)
+            throws MatrixDimensionMismatchException, OutOfRangeException {
+        try {
+            setRow(row, ((ArrayFieldVector<T>) vector).getDataRef());
+        } catch (ClassCastException cce) {
+            super.setRowVector(row, vector);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public FieldVector<T> getColumnVector(final int column) throws OutOfRangeException {
+        checkColumnIndex(column);
+        final T[] outData = MathArrays.buildArray(getField(), rows);
+
+        // perform copy block-wise, to ensure good cache behavior
+        final int jBlock = column / BLOCK_SIZE;
+        final int jColumn = column - jBlock * BLOCK_SIZE;
+        final int jWidth = blockWidth(jBlock);
+        int outIndex = 0;
+        for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
+            final int iHeight = blockHeight(iBlock);
+            final T[] block = blocks[iBlock * blockColumns + jBlock];
+            for (int i = 0; i < iHeight; ++i) {
+                outData[outIndex++] = block[i * jWidth + jColumn];
+            }
+        }
+
+        return new ArrayFieldVector<T>(getField(), outData, false);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setColumnVector(final int column, final FieldVector<T> vector)
+            throws OutOfRangeException, MatrixDimensionMismatchException {
+        try {
+            setColumn(column, ((ArrayFieldVector<T>) vector).getDataRef());
+        } catch (ClassCastException cce) {
+            super.setColumnVector(column, vector);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public T[] getRow(final int row) throws OutOfRangeException {
+        checkRowIndex(row);
+        final T[] out = MathArrays.buildArray(getField(), columns);
+
+        // perform copy block-wise, to ensure good cache behavior
+        final int iBlock = row / BLOCK_SIZE;
+        final int iRow = row - iBlock * BLOCK_SIZE;
+        int outIndex = 0;
+        for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
+            final int jWidth = blockWidth(jBlock);
+            final T[] block = blocks[iBlock * blockColumns + jBlock];
+            System.arraycopy(block, iRow * jWidth, out, outIndex, jWidth);
+            outIndex += jWidth;
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setRow(final int row, final T[] array)
+            throws OutOfRangeException, MatrixDimensionMismatchException {
+        checkRowIndex(row);
+        final int nCols = getColumnDimension();
+        if (array.length != nCols) {
+            throw new MatrixDimensionMismatchException(1, array.length, 1, nCols);
+        }
+
+        // perform copy block-wise, to ensure good cache behavior
+        final int iBlock = row / BLOCK_SIZE;
+        final int iRow = row - iBlock * BLOCK_SIZE;
+        int outIndex = 0;
+        for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
+            final int jWidth = blockWidth(jBlock);
+            final T[] block = blocks[iBlock * blockColumns + jBlock];
+            System.arraycopy(array, outIndex, block, iRow * jWidth, jWidth);
+            outIndex += jWidth;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public T[] getColumn(final int column) throws OutOfRangeException {
+        checkColumnIndex(column);
+        final T[] out = MathArrays.buildArray(getField(), rows);
+
+        // perform copy block-wise, to ensure good cache behavior
+        final int jBlock = column / BLOCK_SIZE;
+        final int jColumn = column - jBlock * BLOCK_SIZE;
+        final int jWidth = blockWidth(jBlock);
+        int outIndex = 0;
+        for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
+            final int iHeight = blockHeight(iBlock);
+            final T[] block = blocks[iBlock * blockColumns + jBlock];
+            for (int i = 0; i < iHeight; ++i) {
+                out[outIndex++] = block[i * jWidth + jColumn];
+            }
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setColumn(final int column, final T[] array)
+            throws MatrixDimensionMismatchException, OutOfRangeException {
+        checkColumnIndex(column);
+        final int nRows = getRowDimension();
+        if (array.length != nRows) {
+            throw new MatrixDimensionMismatchException(array.length, 1, nRows, 1);
+        }
+
+        // perform copy block-wise, to ensure good cache behavior
+        final int jBlock = column / BLOCK_SIZE;
+        final int jColumn = column - jBlock * BLOCK_SIZE;
+        final int jWidth = blockWidth(jBlock);
+        int outIndex = 0;
+        for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
+            final int iHeight = blockHeight(iBlock);
+            final T[] block = blocks[iBlock * blockColumns + jBlock];
+            for (int i = 0; i < iHeight; ++i) {
+                block[i * jWidth + jColumn] = array[outIndex++];
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public T getEntry(final int row, final int column) throws OutOfRangeException {
+        checkRowIndex(row);
+        checkColumnIndex(column);
+
+        final int iBlock = row / BLOCK_SIZE;
+        final int jBlock = column / BLOCK_SIZE;
+        final int k =
+                (row - iBlock * BLOCK_SIZE) * blockWidth(jBlock) + (column - jBlock * BLOCK_SIZE);
+
+        return blocks[iBlock * blockColumns + jBlock][k];
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setEntry(final int row, final int column, final T value)
+            throws OutOfRangeException {
+        checkRowIndex(row);
+        checkColumnIndex(column);
+
+        final int iBlock = row / BLOCK_SIZE;
+        final int jBlock = column / BLOCK_SIZE;
+        final int k =
+                (row - iBlock * BLOCK_SIZE) * blockWidth(jBlock) + (column - jBlock * BLOCK_SIZE);
+
+        blocks[iBlock * blockColumns + jBlock][k] = value;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addToEntry(final int row, final int column, final T increment)
+            throws OutOfRangeException {
+        checkRowIndex(row);
+        checkColumnIndex(column);
+
+        final int iBlock = row / BLOCK_SIZE;
+        final int jBlock = column / BLOCK_SIZE;
+        final int k =
+                (row - iBlock * BLOCK_SIZE) * blockWidth(jBlock) + (column - jBlock * BLOCK_SIZE);
+        final T[] blockIJ = blocks[iBlock * blockColumns + jBlock];
+
+        blockIJ[k] = blockIJ[k].add(increment);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void multiplyEntry(final int row, final int column, final T factor)
+            throws OutOfRangeException {
+        checkRowIndex(row);
+        checkColumnIndex(column);
+
+        final int iBlock = row / BLOCK_SIZE;
+        final int jBlock = column / BLOCK_SIZE;
+        final int k =
+                (row - iBlock * BLOCK_SIZE) * blockWidth(jBlock) + (column - jBlock * BLOCK_SIZE);
+        final T[] blockIJ = blocks[iBlock * blockColumns + jBlock];
+
+        blockIJ[k] = blockIJ[k].multiply(factor);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public FieldMatrix<T> transpose() {
+        final int nRows = getRowDimension();
+        final int nCols = getColumnDimension();
+        final BlockFieldMatrix<T> out = new BlockFieldMatrix<T>(getField(), nCols, nRows);
+
+        // perform transpose block-wise, to ensure good cache behavior
+        int blockIndex = 0;
+        for (int iBlock = 0; iBlock < blockColumns; ++iBlock) {
+            for (int jBlock = 0; jBlock < blockRows; ++jBlock) {
+
+                // transpose current block
+                final T[] outBlock = out.blocks[blockIndex];
+                final T[] tBlock = blocks[jBlock * blockColumns + iBlock];
+                final int pStart = iBlock * BLOCK_SIZE;
+                final int pEnd = FastMath.min(pStart + BLOCK_SIZE, columns);
+                final int qStart = jBlock * BLOCK_SIZE;
+                final int qEnd = FastMath.min(qStart + BLOCK_SIZE, rows);
+                int k = 0;
+                for (int p = pStart; p < pEnd; ++p) {
+                    final int lInc = pEnd - pStart;
+                    int l = p - pStart;
+                    for (int q = qStart; q < qEnd; ++q) {
+                        outBlock[k] = tBlock[l];
+                        ++k;
+                        l += lInc;
+                    }
+                }
+
+                // go to next block
+                ++blockIndex;
+            }
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getRowDimension() {
+        return rows;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getColumnDimension() {
+        return columns;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public T[] operate(final T[] v) throws DimensionMismatchException {
+        if (v.length != columns) {
+            throw new DimensionMismatchException(v.length, columns);
+        }
+        final T[] out = MathArrays.buildArray(getField(), rows);
+        final T zero = getField().getZero();
+
+        // perform multiplication block-wise, to ensure good cache behavior
+        for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
+            final int pStart = iBlock * BLOCK_SIZE;
+            final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows);
+            for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
+                final T[] block = blocks[iBlock * blockColumns + jBlock];
+                final int qStart = jBlock * BLOCK_SIZE;
+                final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns);
+                int k = 0;
+                for (int p = pStart; p < pEnd; ++p) {
+                    T sum = zero;
+                    int q = qStart;
+                    while (q < qEnd - 3) {
+                        sum =
+                                sum.add(block[k].multiply(v[q]))
+                                        .add(block[k + 1].multiply(v[q + 1]))
+                                        .add(block[k + 2].multiply(v[q + 2]))
+                                        .add(block[k + 3].multiply(v[q + 3]));
+                        k += 4;
+                        q += 4;
+                    }
+                    while (q < qEnd) {
+                        sum = sum.add(block[k++].multiply(v[q++]));
+                    }
+                    out[p] = out[p].add(sum);
+                }
+            }
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public T[] preMultiply(final T[] v) throws DimensionMismatchException {
+
+        if (v.length != rows) {
+            throw new DimensionMismatchException(v.length, rows);
+        }
+        final T[] out = MathArrays.buildArray(getField(), columns);
+        final T zero = getField().getZero();
+
+        // perform multiplication block-wise, to ensure good cache behavior
+        for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
+            final int jWidth = blockWidth(jBlock);
+            final int jWidth2 = jWidth + jWidth;
+            final int jWidth3 = jWidth2 + jWidth;
+            final int jWidth4 = jWidth3 + jWidth;
+            final int qStart = jBlock * BLOCK_SIZE;
+            final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns);
+            for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
+                final T[] block = blocks[iBlock * blockColumns + jBlock];
+                final int pStart = iBlock * BLOCK_SIZE;
+                final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows);
+                for (int q = qStart; q < qEnd; ++q) {
+                    int k = q - qStart;
+                    T sum = zero;
+                    int p = pStart;
+                    while (p < pEnd - 3) {
+                        sum =
+                                sum.add(block[k].multiply(v[p]))
+                                        .add(block[k + jWidth].multiply(v[p + 1]))
+                                        .add(block[k + jWidth2].multiply(v[p + 2]))
+                                        .add(block[k + jWidth3].multiply(v[p + 3]));
+                        k += jWidth4;
+                        p += 4;
+                    }
+                    while (p < pEnd) {
+                        sum = sum.add(block[k].multiply(v[p++]));
+                        k += jWidth;
+                    }
+                    out[q] = out[q].add(sum);
+                }
+            }
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public T walkInRowOrder(final FieldMatrixChangingVisitor<T> visitor) {
+        visitor.start(rows, columns, 0, rows - 1, 0, columns - 1);
+        for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
+            final int pStart = iBlock * BLOCK_SIZE;
+            final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows);
+            for (int p = pStart; p < pEnd; ++p) {
+                for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
+                    final int jWidth = blockWidth(jBlock);
+                    final int qStart = jBlock * BLOCK_SIZE;
+                    final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns);
+                    final T[] block = blocks[iBlock * blockColumns + jBlock];
+                    int k = (p - pStart) * jWidth;
+                    for (int q = qStart; q < qEnd; ++q) {
+                        block[k] = visitor.visit(p, q, block[k]);
+                        ++k;
+                    }
+                }
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public T walkInRowOrder(final FieldMatrixPreservingVisitor<T> visitor) {
+        visitor.start(rows, columns, 0, rows - 1, 0, columns - 1);
+        for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
+            final int pStart = iBlock * BLOCK_SIZE;
+            final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows);
+            for (int p = pStart; p < pEnd; ++p) {
+                for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
+                    final int jWidth = blockWidth(jBlock);
+                    final int qStart = jBlock * BLOCK_SIZE;
+                    final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns);
+                    final T[] block = blocks[iBlock * blockColumns + jBlock];
+                    int k = (p - pStart) * jWidth;
+                    for (int q = qStart; q < qEnd; ++q) {
+                        visitor.visit(p, q, block[k]);
+                        ++k;
+                    }
+                }
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public T walkInRowOrder(
+            final FieldMatrixChangingVisitor<T> visitor,
+            final int startRow,
+            final int endRow,
+            final int startColumn,
+            final int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException {
+        checkSubMatrixIndex(startRow, endRow, startColumn, endColumn);
+        visitor.start(rows, columns, startRow, endRow, startColumn, endColumn);
+        for (int iBlock = startRow / BLOCK_SIZE; iBlock < 1 + endRow / BLOCK_SIZE; ++iBlock) {
+            final int p0 = iBlock * BLOCK_SIZE;
+            final int pStart = FastMath.max(startRow, p0);
+            final int pEnd = FastMath.min((iBlock + 1) * BLOCK_SIZE, 1 + endRow);
+            for (int p = pStart; p < pEnd; ++p) {
+                for (int jBlock = startColumn / BLOCK_SIZE;
+                        jBlock < 1 + endColumn / BLOCK_SIZE;
+                        ++jBlock) {
+                    final int jWidth = blockWidth(jBlock);
+                    final int q0 = jBlock * BLOCK_SIZE;
+                    final int qStart = FastMath.max(startColumn, q0);
+                    final int qEnd = FastMath.min((jBlock + 1) * BLOCK_SIZE, 1 + endColumn);
+                    final T[] block = blocks[iBlock * blockColumns + jBlock];
+                    int k = (p - p0) * jWidth + qStart - q0;
+                    for (int q = qStart; q < qEnd; ++q) {
+                        block[k] = visitor.visit(p, q, block[k]);
+                        ++k;
+                    }
+                }
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public T walkInRowOrder(
+            final FieldMatrixPreservingVisitor<T> visitor,
+            final int startRow,
+            final int endRow,
+            final int startColumn,
+            final int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException {
+        checkSubMatrixIndex(startRow, endRow, startColumn, endColumn);
+        visitor.start(rows, columns, startRow, endRow, startColumn, endColumn);
+        for (int iBlock = startRow / BLOCK_SIZE; iBlock < 1 + endRow / BLOCK_SIZE; ++iBlock) {
+            final int p0 = iBlock * BLOCK_SIZE;
+            final int pStart = FastMath.max(startRow, p0);
+            final int pEnd = FastMath.min((iBlock + 1) * BLOCK_SIZE, 1 + endRow);
+            for (int p = pStart; p < pEnd; ++p) {
+                for (int jBlock = startColumn / BLOCK_SIZE;
+                        jBlock < 1 + endColumn / BLOCK_SIZE;
+                        ++jBlock) {
+                    final int jWidth = blockWidth(jBlock);
+                    final int q0 = jBlock * BLOCK_SIZE;
+                    final int qStart = FastMath.max(startColumn, q0);
+                    final int qEnd = FastMath.min((jBlock + 1) * BLOCK_SIZE, 1 + endColumn);
+                    final T[] block = blocks[iBlock * blockColumns + jBlock];
+                    int k = (p - p0) * jWidth + qStart - q0;
+                    for (int q = qStart; q < qEnd; ++q) {
+                        visitor.visit(p, q, block[k]);
+                        ++k;
+                    }
+                }
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public T walkInOptimizedOrder(final FieldMatrixChangingVisitor<T> visitor) {
+        visitor.start(rows, columns, 0, rows - 1, 0, columns - 1);
+        int blockIndex = 0;
+        for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
+            final int pStart = iBlock * BLOCK_SIZE;
+            final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows);
+            for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
+                final int qStart = jBlock * BLOCK_SIZE;
+                final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns);
+                final T[] block = blocks[blockIndex];
+                int k = 0;
+                for (int p = pStart; p < pEnd; ++p) {
+                    for (int q = qStart; q < qEnd; ++q) {
+                        block[k] = visitor.visit(p, q, block[k]);
+                        ++k;
+                    }
+                }
+                ++blockIndex;
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public T walkInOptimizedOrder(final FieldMatrixPreservingVisitor<T> visitor) {
+        visitor.start(rows, columns, 0, rows - 1, 0, columns - 1);
+        int blockIndex = 0;
+        for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
+            final int pStart = iBlock * BLOCK_SIZE;
+            final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows);
+            for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
+                final int qStart = jBlock * BLOCK_SIZE;
+                final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns);
+                final T[] block = blocks[blockIndex];
+                int k = 0;
+                for (int p = pStart; p < pEnd; ++p) {
+                    for (int q = qStart; q < qEnd; ++q) {
+                        visitor.visit(p, q, block[k]);
+                        ++k;
+                    }
+                }
+                ++blockIndex;
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public T walkInOptimizedOrder(
+            final FieldMatrixChangingVisitor<T> visitor,
+            final int startRow,
+            final int endRow,
+            final int startColumn,
+            final int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException {
+        checkSubMatrixIndex(startRow, endRow, startColumn, endColumn);
+        visitor.start(rows, columns, startRow, endRow, startColumn, endColumn);
+        for (int iBlock = startRow / BLOCK_SIZE; iBlock < 1 + endRow / BLOCK_SIZE; ++iBlock) {
+            final int p0 = iBlock * BLOCK_SIZE;
+            final int pStart = FastMath.max(startRow, p0);
+            final int pEnd = FastMath.min((iBlock + 1) * BLOCK_SIZE, 1 + endRow);
+            for (int jBlock = startColumn / BLOCK_SIZE;
+                    jBlock < 1 + endColumn / BLOCK_SIZE;
+                    ++jBlock) {
+                final int jWidth = blockWidth(jBlock);
+                final int q0 = jBlock * BLOCK_SIZE;
+                final int qStart = FastMath.max(startColumn, q0);
+                final int qEnd = FastMath.min((jBlock + 1) * BLOCK_SIZE, 1 + endColumn);
+                final T[] block = blocks[iBlock * blockColumns + jBlock];
+                for (int p = pStart; p < pEnd; ++p) {
+                    int k = (p - p0) * jWidth + qStart - q0;
+                    for (int q = qStart; q < qEnd; ++q) {
+                        block[k] = visitor.visit(p, q, block[k]);
+                        ++k;
+                    }
+                }
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public T walkInOptimizedOrder(
+            final FieldMatrixPreservingVisitor<T> visitor,
+            final int startRow,
+            final int endRow,
+            final int startColumn,
+            final int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException {
+        checkSubMatrixIndex(startRow, endRow, startColumn, endColumn);
+        visitor.start(rows, columns, startRow, endRow, startColumn, endColumn);
+        for (int iBlock = startRow / BLOCK_SIZE; iBlock < 1 + endRow / BLOCK_SIZE; ++iBlock) {
+            final int p0 = iBlock * BLOCK_SIZE;
+            final int pStart = FastMath.max(startRow, p0);
+            final int pEnd = FastMath.min((iBlock + 1) * BLOCK_SIZE, 1 + endRow);
+            for (int jBlock = startColumn / BLOCK_SIZE;
+                    jBlock < 1 + endColumn / BLOCK_SIZE;
+                    ++jBlock) {
+                final int jWidth = blockWidth(jBlock);
+                final int q0 = jBlock * BLOCK_SIZE;
+                final int qStart = FastMath.max(startColumn, q0);
+                final int qEnd = FastMath.min((jBlock + 1) * BLOCK_SIZE, 1 + endColumn);
+                final T[] block = blocks[iBlock * blockColumns + jBlock];
+                for (int p = pStart; p < pEnd; ++p) {
+                    int k = (p - p0) * jWidth + qStart - q0;
+                    for (int q = qStart; q < qEnd; ++q) {
+                        visitor.visit(p, q, block[k]);
+                        ++k;
+                    }
+                }
+            }
+        }
+        return visitor.end();
+    }
+
+    /**
+     * Get the height of a block.
+     *
+     * @param blockRow row index (in block sense) of the block
+     * @return height (number of rows) of the block
+     */
+    private int blockHeight(final int blockRow) {
+        return (blockRow == blockRows - 1) ? rows - blockRow * BLOCK_SIZE : BLOCK_SIZE;
+    }
+
+    /**
+     * Get the width of a block.
+     *
+     * @param blockColumn column index (in block sense) of the block
+     * @return width (number of columns) of the block
+     */
+    private int blockWidth(final int blockColumn) {
+        return (blockColumn == blockColumns - 1) ? columns - blockColumn * BLOCK_SIZE : BLOCK_SIZE;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/BlockRealMatrix.java b/src/main/java/org/apache/commons/math3/linear/BlockRealMatrix.java
new file mode 100644
index 0000000..a1346be
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/BlockRealMatrix.java
@@ -0,0 +1,1653 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+/**
+ * Cache-friendly implementation of RealMatrix using a flat arrays to store square blocks of the
+ * matrix.
+ *
+ * <p>This implementation is specially designed to be cache-friendly. Square blocks are stored as
+ * small arrays and allow efficient traversal of data both in row major direction and columns major
+ * direction, one block at a time. This greatly increases performances for algorithms that use
+ * crossed directions loops like multiplication or transposition.
+ *
+ * <p>The size of square blocks is a static parameter. It may be tuned according to the cache size
+ * of the target computer processor. As a rule of thumbs, it should be the largest value that allows
+ * three blocks to be simultaneously cached (this is necessary for example for matrix
+ * multiplication). The default value is to use 52x52 blocks which is well suited for processors
+ * with 64k L1 cache (one block holds 2704 values or 21632 bytes). This value could be lowered to
+ * 36x36 for processors with 32k L1 cache.
+ *
+ * <p>The regular blocks represent {@link #BLOCK_SIZE} x {@link #BLOCK_SIZE} squares. Blocks at
+ * right hand side and bottom side which may be smaller to fit matrix dimensions. The square blocks
+ * are flattened in row major order in single dimension arrays which are therefore {@link
+ * #BLOCK_SIZE}<sup>2</sup> elements long for regular blocks. The blocks are themselves organized in
+ * row major order.
+ *
+ * <p>As an example, for a block size of 52x52, a 100x60 matrix would be stored in 4 blocks. Block 0
+ * would be a double[2704] array holding the upper left 52x52 square, block 1 would be a double[416]
+ * array holding the upper right 52x8 rectangle, block 2 would be a double[2496] array holding the
+ * lower left 48x52 rectangle and block 3 would be a double[384] array holding the lower right 48x8
+ * rectangle.
+ *
+ * <p>The layout complexity overhead versus simple mapping of matrices to java arrays is negligible
+ * for small matrices (about 1%). The gain from cache efficiency leads to up to 3-fold improvements
+ * for matrices of moderate to large size.
+ *
+ * @since 2.0
+ */
+public class BlockRealMatrix extends AbstractRealMatrix implements Serializable {
+    /** Block size. */
+    public static final int BLOCK_SIZE = 52;
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 4991895511313664478L;
+
+    /** Blocks of matrix entries. */
+    private final double blocks[][];
+
+    /** Number of rows of the matrix. */
+    private final int rows;
+
+    /** Number of columns of the matrix. */
+    private final int columns;
+
+    /** Number of block rows of the matrix. */
+    private final int blockRows;
+
+    /** Number of block columns of the matrix. */
+    private final int blockColumns;
+
+    /**
+     * Create a new matrix with the supplied row and column dimensions.
+     *
+     * @param rows the number of rows in the new matrix
+     * @param columns the number of columns in the new matrix
+     * @throws NotStrictlyPositiveException if row or column dimension is not positive.
+     */
+    public BlockRealMatrix(final int rows, final int columns) throws NotStrictlyPositiveException {
+        super(rows, columns);
+        this.rows = rows;
+        this.columns = columns;
+
+        // number of blocks
+        blockRows = (rows + BLOCK_SIZE - 1) / BLOCK_SIZE;
+        blockColumns = (columns + BLOCK_SIZE - 1) / BLOCK_SIZE;
+
+        // allocate storage blocks, taking care of smaller ones at right and bottom
+        blocks = createBlocksLayout(rows, columns);
+    }
+
+    /**
+     * Create a new dense matrix copying entries from raw layout data.
+     *
+     * <p>The input array <em>must</em> already be in raw layout.
+     *
+     * <p>Calling this constructor is equivalent to call:
+     *
+     * <pre>matrix = new BlockRealMatrix(rawData.length, rawData[0].length,
+     *                                   toBlocksLayout(rawData), false);</pre>
+     *
+     * @param rawData data for new matrix, in raw layout
+     * @throws DimensionMismatchException if the shape of {@code blockData} is inconsistent with
+     *     block layout.
+     * @throws NotStrictlyPositiveException if row or column dimension is not positive.
+     * @see #BlockRealMatrix(int, int, double[][], boolean)
+     */
+    public BlockRealMatrix(final double[][] rawData)
+            throws DimensionMismatchException, NotStrictlyPositiveException {
+        this(rawData.length, rawData[0].length, toBlocksLayout(rawData), false);
+    }
+
+    /**
+     * Create a new dense matrix copying entries from block layout data.
+     *
+     * <p>The input array <em>must</em> already be in blocks layout.
+     *
+     * @param rows Number of rows in the new matrix.
+     * @param columns Number of columns in the new matrix.
+     * @param blockData data for new matrix
+     * @param copyArray Whether the input array will be copied or referenced.
+     * @throws DimensionMismatchException if the shape of {@code blockData} is inconsistent with
+     *     block layout.
+     * @throws NotStrictlyPositiveException if row or column dimension is not positive.
+     * @see #createBlocksLayout(int, int)
+     * @see #toBlocksLayout(double[][])
+     * @see #BlockRealMatrix(double[][])
+     */
+    public BlockRealMatrix(
+            final int rows, final int columns, final double[][] blockData, final boolean copyArray)
+            throws DimensionMismatchException, NotStrictlyPositiveException {
+        super(rows, columns);
+        this.rows = rows;
+        this.columns = columns;
+
+        // number of blocks
+        blockRows = (rows + BLOCK_SIZE - 1) / BLOCK_SIZE;
+        blockColumns = (columns + BLOCK_SIZE - 1) / BLOCK_SIZE;
+
+        if (copyArray) {
+            // allocate storage blocks, taking care of smaller ones at right and bottom
+            blocks = new double[blockRows * blockColumns][];
+        } else {
+            // reference existing array
+            blocks = blockData;
+        }
+
+        int index = 0;
+        for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
+            final int iHeight = blockHeight(iBlock);
+            for (int jBlock = 0; jBlock < blockColumns; ++jBlock, ++index) {
+                if (blockData[index].length != iHeight * blockWidth(jBlock)) {
+                    throw new DimensionMismatchException(
+                            blockData[index].length, iHeight * blockWidth(jBlock));
+                }
+                if (copyArray) {
+                    blocks[index] = blockData[index].clone();
+                }
+            }
+        }
+    }
+
+    /**
+     * Convert a data array from raw layout to blocks layout.
+     *
+     * <p>Raw layout is the straightforward layout where element at row i and column j is in array
+     * element <code>rawData[i][j]</code>. Blocks layout is the layout used in {@link
+     * BlockRealMatrix} instances, where the matrix is split in square blocks (except at right and
+     * bottom side where blocks may be rectangular to fit matrix size) and each block is stored in a
+     * flattened one-dimensional array.
+     *
+     * <p>This method creates an array in blocks layout from an input array in raw layout. It can be
+     * used to provide the array argument of the {@link #BlockRealMatrix(int, int, double[][],
+     * boolean)} constructor.
+     *
+     * @param rawData Data array in raw layout.
+     * @return a new data array containing the same entries but in blocks layout.
+     * @throws DimensionMismatchException if {@code rawData} is not rectangular.
+     * @see #createBlocksLayout(int, int)
+     * @see #BlockRealMatrix(int, int, double[][], boolean)
+     */
+    public static double[][] toBlocksLayout(final double[][] rawData)
+            throws DimensionMismatchException {
+        final int rows = rawData.length;
+        final int columns = rawData[0].length;
+        final int blockRows = (rows + BLOCK_SIZE - 1) / BLOCK_SIZE;
+        final int blockColumns = (columns + BLOCK_SIZE - 1) / BLOCK_SIZE;
+
+        // safety checks
+        for (int i = 0; i < rawData.length; ++i) {
+            final int length = rawData[i].length;
+            if (length != columns) {
+                throw new DimensionMismatchException(columns, length);
+            }
+        }
+
+        // convert array
+        final double[][] blocks = new double[blockRows * blockColumns][];
+        int blockIndex = 0;
+        for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
+            final int pStart = iBlock * BLOCK_SIZE;
+            final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows);
+            final int iHeight = pEnd - pStart;
+            for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
+                final int qStart = jBlock * BLOCK_SIZE;
+                final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns);
+                final int jWidth = qEnd - qStart;
+
+                // allocate new block
+                final double[] block = new double[iHeight * jWidth];
+                blocks[blockIndex] = block;
+
+                // copy data
+                int index = 0;
+                for (int p = pStart; p < pEnd; ++p) {
+                    System.arraycopy(rawData[p], qStart, block, index, jWidth);
+                    index += jWidth;
+                }
+                ++blockIndex;
+            }
+        }
+
+        return blocks;
+    }
+
+    /**
+     * Create a data array in blocks layout.
+     *
+     * <p>This method can be used to create the array argument of the {@link #BlockRealMatrix(int,
+     * int, double[][], boolean)} constructor.
+     *
+     * @param rows Number of rows in the new matrix.
+     * @param columns Number of columns in the new matrix.
+     * @return a new data array in blocks layout.
+     * @see #toBlocksLayout(double[][])
+     * @see #BlockRealMatrix(int, int, double[][], boolean)
+     */
+    public static double[][] createBlocksLayout(final int rows, final int columns) {
+        final int blockRows = (rows + BLOCK_SIZE - 1) / BLOCK_SIZE;
+        final int blockColumns = (columns + BLOCK_SIZE - 1) / BLOCK_SIZE;
+
+        final double[][] blocks = new double[blockRows * blockColumns][];
+        int blockIndex = 0;
+        for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
+            final int pStart = iBlock * BLOCK_SIZE;
+            final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows);
+            final int iHeight = pEnd - pStart;
+            for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
+                final int qStart = jBlock * BLOCK_SIZE;
+                final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns);
+                final int jWidth = qEnd - qStart;
+                blocks[blockIndex] = new double[iHeight * jWidth];
+                ++blockIndex;
+            }
+        }
+
+        return blocks;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public BlockRealMatrix createMatrix(final int rowDimension, final int columnDimension)
+            throws NotStrictlyPositiveException {
+        return new BlockRealMatrix(rowDimension, columnDimension);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public BlockRealMatrix copy() {
+        // create an empty matrix
+        BlockRealMatrix copied = new BlockRealMatrix(rows, columns);
+
+        // copy the blocks
+        for (int i = 0; i < blocks.length; ++i) {
+            System.arraycopy(blocks[i], 0, copied.blocks[i], 0, blocks[i].length);
+        }
+
+        return copied;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public BlockRealMatrix add(final RealMatrix m) throws MatrixDimensionMismatchException {
+        try {
+            return add((BlockRealMatrix) m);
+        } catch (ClassCastException cce) {
+            // safety check
+            MatrixUtils.checkAdditionCompatible(this, m);
+
+            final BlockRealMatrix out = new BlockRealMatrix(rows, columns);
+
+            // perform addition block-wise, to ensure good cache behavior
+            int blockIndex = 0;
+            for (int iBlock = 0; iBlock < out.blockRows; ++iBlock) {
+                for (int jBlock = 0; jBlock < out.blockColumns; ++jBlock) {
+
+                    // perform addition on the current block
+                    final double[] outBlock = out.blocks[blockIndex];
+                    final double[] tBlock = blocks[blockIndex];
+                    final int pStart = iBlock * BLOCK_SIZE;
+                    final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows);
+                    final int qStart = jBlock * BLOCK_SIZE;
+                    final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns);
+                    int k = 0;
+                    for (int p = pStart; p < pEnd; ++p) {
+                        for (int q = qStart; q < qEnd; ++q) {
+                            outBlock[k] = tBlock[k] + m.getEntry(p, q);
+                            ++k;
+                        }
+                    }
+                    // go to next block
+                    ++blockIndex;
+                }
+            }
+
+            return out;
+        }
+    }
+
+    /**
+     * Compute the sum of this matrix and {@code m}.
+     *
+     * @param m Matrix to be added.
+     * @return {@code this} + m.
+     * @throws MatrixDimensionMismatchException if {@code m} is not the same size as this matrix.
+     */
+    public BlockRealMatrix add(final BlockRealMatrix m) throws MatrixDimensionMismatchException {
+        // safety check
+        MatrixUtils.checkAdditionCompatible(this, m);
+
+        final BlockRealMatrix out = new BlockRealMatrix(rows, columns);
+
+        // perform addition block-wise, to ensure good cache behavior
+        for (int blockIndex = 0; blockIndex < out.blocks.length; ++blockIndex) {
+            final double[] outBlock = out.blocks[blockIndex];
+            final double[] tBlock = blocks[blockIndex];
+            final double[] mBlock = m.blocks[blockIndex];
+            for (int k = 0; k < outBlock.length; ++k) {
+                outBlock[k] = tBlock[k] + mBlock[k];
+            }
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public BlockRealMatrix subtract(final RealMatrix m) throws MatrixDimensionMismatchException {
+        try {
+            return subtract((BlockRealMatrix) m);
+        } catch (ClassCastException cce) {
+            // safety check
+            MatrixUtils.checkSubtractionCompatible(this, m);
+
+            final BlockRealMatrix out = new BlockRealMatrix(rows, columns);
+
+            // perform subtraction block-wise, to ensure good cache behavior
+            int blockIndex = 0;
+            for (int iBlock = 0; iBlock < out.blockRows; ++iBlock) {
+                for (int jBlock = 0; jBlock < out.blockColumns; ++jBlock) {
+
+                    // perform subtraction on the current block
+                    final double[] outBlock = out.blocks[blockIndex];
+                    final double[] tBlock = blocks[blockIndex];
+                    final int pStart = iBlock * BLOCK_SIZE;
+                    final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows);
+                    final int qStart = jBlock * BLOCK_SIZE;
+                    final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns);
+                    int k = 0;
+                    for (int p = pStart; p < pEnd; ++p) {
+                        for (int q = qStart; q < qEnd; ++q) {
+                            outBlock[k] = tBlock[k] - m.getEntry(p, q);
+                            ++k;
+                        }
+                    }
+                    // go to next block
+                    ++blockIndex;
+                }
+            }
+
+            return out;
+        }
+    }
+
+    /**
+     * Subtract {@code m} from this matrix.
+     *
+     * @param m Matrix to be subtracted.
+     * @return {@code this} - m.
+     * @throws MatrixDimensionMismatchException if {@code m} is not the same size as this matrix.
+     */
+    public BlockRealMatrix subtract(final BlockRealMatrix m)
+            throws MatrixDimensionMismatchException {
+        // safety check
+        MatrixUtils.checkSubtractionCompatible(this, m);
+
+        final BlockRealMatrix out = new BlockRealMatrix(rows, columns);
+
+        // perform subtraction block-wise, to ensure good cache behavior
+        for (int blockIndex = 0; blockIndex < out.blocks.length; ++blockIndex) {
+            final double[] outBlock = out.blocks[blockIndex];
+            final double[] tBlock = blocks[blockIndex];
+            final double[] mBlock = m.blocks[blockIndex];
+            for (int k = 0; k < outBlock.length; ++k) {
+                outBlock[k] = tBlock[k] - mBlock[k];
+            }
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public BlockRealMatrix scalarAdd(final double d) {
+
+        final BlockRealMatrix out = new BlockRealMatrix(rows, columns);
+
+        // perform subtraction block-wise, to ensure good cache behavior
+        for (int blockIndex = 0; blockIndex < out.blocks.length; ++blockIndex) {
+            final double[] outBlock = out.blocks[blockIndex];
+            final double[] tBlock = blocks[blockIndex];
+            for (int k = 0; k < outBlock.length; ++k) {
+                outBlock[k] = tBlock[k] + d;
+            }
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealMatrix scalarMultiply(final double d) {
+        final BlockRealMatrix out = new BlockRealMatrix(rows, columns);
+
+        // perform subtraction block-wise, to ensure good cache behavior
+        for (int blockIndex = 0; blockIndex < out.blocks.length; ++blockIndex) {
+            final double[] outBlock = out.blocks[blockIndex];
+            final double[] tBlock = blocks[blockIndex];
+            for (int k = 0; k < outBlock.length; ++k) {
+                outBlock[k] = tBlock[k] * d;
+            }
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public BlockRealMatrix multiply(final RealMatrix m) throws DimensionMismatchException {
+        try {
+            return multiply((BlockRealMatrix) m);
+        } catch (ClassCastException cce) {
+            // safety check
+            MatrixUtils.checkMultiplicationCompatible(this, m);
+
+            final BlockRealMatrix out = new BlockRealMatrix(rows, m.getColumnDimension());
+
+            // perform multiplication block-wise, to ensure good cache behavior
+            int blockIndex = 0;
+            for (int iBlock = 0; iBlock < out.blockRows; ++iBlock) {
+                final int pStart = iBlock * BLOCK_SIZE;
+                final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows);
+
+                for (int jBlock = 0; jBlock < out.blockColumns; ++jBlock) {
+                    final int qStart = jBlock * BLOCK_SIZE;
+                    final int qEnd = FastMath.min(qStart + BLOCK_SIZE, m.getColumnDimension());
+
+                    // select current block
+                    final double[] outBlock = out.blocks[blockIndex];
+
+                    // perform multiplication on current block
+                    for (int kBlock = 0; kBlock < blockColumns; ++kBlock) {
+                        final int kWidth = blockWidth(kBlock);
+                        final double[] tBlock = blocks[iBlock * blockColumns + kBlock];
+                        final int rStart = kBlock * BLOCK_SIZE;
+                        int k = 0;
+                        for (int p = pStart; p < pEnd; ++p) {
+                            final int lStart = (p - pStart) * kWidth;
+                            final int lEnd = lStart + kWidth;
+                            for (int q = qStart; q < qEnd; ++q) {
+                                double sum = 0;
+                                int r = rStart;
+                                for (int l = lStart; l < lEnd; ++l) {
+                                    sum += tBlock[l] * m.getEntry(r, q);
+                                    ++r;
+                                }
+                                outBlock[k] += sum;
+                                ++k;
+                            }
+                        }
+                    }
+                    // go to next block
+                    ++blockIndex;
+                }
+            }
+
+            return out;
+        }
+    }
+
+    /**
+     * Returns the result of postmultiplying this by {@code m}.
+     *
+     * @param m Matrix to postmultiply by.
+     * @return {@code this} * m.
+     * @throws DimensionMismatchException if the matrices are not compatible.
+     */
+    public BlockRealMatrix multiply(BlockRealMatrix m) throws DimensionMismatchException {
+        // safety check
+        MatrixUtils.checkMultiplicationCompatible(this, m);
+
+        final BlockRealMatrix out = new BlockRealMatrix(rows, m.columns);
+
+        // perform multiplication block-wise, to ensure good cache behavior
+        int blockIndex = 0;
+        for (int iBlock = 0; iBlock < out.blockRows; ++iBlock) {
+
+            final int pStart = iBlock * BLOCK_SIZE;
+            final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows);
+
+            for (int jBlock = 0; jBlock < out.blockColumns; ++jBlock) {
+                final int jWidth = out.blockWidth(jBlock);
+                final int jWidth2 = jWidth + jWidth;
+                final int jWidth3 = jWidth2 + jWidth;
+                final int jWidth4 = jWidth3 + jWidth;
+
+                // select current block
+                final double[] outBlock = out.blocks[blockIndex];
+
+                // perform multiplication on current block
+                for (int kBlock = 0; kBlock < blockColumns; ++kBlock) {
+                    final int kWidth = blockWidth(kBlock);
+                    final double[] tBlock = blocks[iBlock * blockColumns + kBlock];
+                    final double[] mBlock = m.blocks[kBlock * m.blockColumns + jBlock];
+                    int k = 0;
+                    for (int p = pStart; p < pEnd; ++p) {
+                        final int lStart = (p - pStart) * kWidth;
+                        final int lEnd = lStart + kWidth;
+                        for (int nStart = 0; nStart < jWidth; ++nStart) {
+                            double sum = 0;
+                            int l = lStart;
+                            int n = nStart;
+                            while (l < lEnd - 3) {
+                                sum +=
+                                        tBlock[l] * mBlock[n]
+                                                + tBlock[l + 1] * mBlock[n + jWidth]
+                                                + tBlock[l + 2] * mBlock[n + jWidth2]
+                                                + tBlock[l + 3] * mBlock[n + jWidth3];
+                                l += 4;
+                                n += jWidth4;
+                            }
+                            while (l < lEnd) {
+                                sum += tBlock[l++] * mBlock[n];
+                                n += jWidth;
+                            }
+                            outBlock[k] += sum;
+                            ++k;
+                        }
+                    }
+                }
+                // go to next block
+                ++blockIndex;
+            }
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double[][] getData() {
+        final double[][] data = new double[getRowDimension()][getColumnDimension()];
+        final int lastColumns = columns - (blockColumns - 1) * BLOCK_SIZE;
+
+        for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
+            final int pStart = iBlock * BLOCK_SIZE;
+            final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows);
+            int regularPos = 0;
+            int lastPos = 0;
+            for (int p = pStart; p < pEnd; ++p) {
+                final double[] dataP = data[p];
+                int blockIndex = iBlock * blockColumns;
+                int dataPos = 0;
+                for (int jBlock = 0; jBlock < blockColumns - 1; ++jBlock) {
+                    System.arraycopy(blocks[blockIndex++], regularPos, dataP, dataPos, BLOCK_SIZE);
+                    dataPos += BLOCK_SIZE;
+                }
+                System.arraycopy(blocks[blockIndex], lastPos, dataP, dataPos, lastColumns);
+                regularPos += BLOCK_SIZE;
+                lastPos += lastColumns;
+            }
+        }
+
+        return data;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getNorm() {
+        final double[] colSums = new double[BLOCK_SIZE];
+        double maxColSum = 0;
+        for (int jBlock = 0; jBlock < blockColumns; jBlock++) {
+            final int jWidth = blockWidth(jBlock);
+            Arrays.fill(colSums, 0, jWidth, 0.0);
+            for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
+                final int iHeight = blockHeight(iBlock);
+                final double[] block = blocks[iBlock * blockColumns + jBlock];
+                for (int j = 0; j < jWidth; ++j) {
+                    double sum = 0;
+                    for (int i = 0; i < iHeight; ++i) {
+                        sum += FastMath.abs(block[i * jWidth + j]);
+                    }
+                    colSums[j] += sum;
+                }
+            }
+            for (int j = 0; j < jWidth; ++j) {
+                maxColSum = FastMath.max(maxColSum, colSums[j]);
+            }
+        }
+        return maxColSum;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getFrobeniusNorm() {
+        double sum2 = 0;
+        for (int blockIndex = 0; blockIndex < blocks.length; ++blockIndex) {
+            for (final double entry : blocks[blockIndex]) {
+                sum2 += entry * entry;
+            }
+        }
+        return FastMath.sqrt(sum2);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public BlockRealMatrix getSubMatrix(
+            final int startRow, final int endRow, final int startColumn, final int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException {
+        // safety checks
+        MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn);
+
+        // create the output matrix
+        final BlockRealMatrix out =
+                new BlockRealMatrix(endRow - startRow + 1, endColumn - startColumn + 1);
+
+        // compute blocks shifts
+        final int blockStartRow = startRow / BLOCK_SIZE;
+        final int rowsShift = startRow % BLOCK_SIZE;
+        final int blockStartColumn = startColumn / BLOCK_SIZE;
+        final int columnsShift = startColumn % BLOCK_SIZE;
+
+        // perform extraction block-wise, to ensure good cache behavior
+        int pBlock = blockStartRow;
+        for (int iBlock = 0; iBlock < out.blockRows; ++iBlock) {
+            final int iHeight = out.blockHeight(iBlock);
+            int qBlock = blockStartColumn;
+            for (int jBlock = 0; jBlock < out.blockColumns; ++jBlock) {
+                final int jWidth = out.blockWidth(jBlock);
+
+                // handle one block of the output matrix
+                final int outIndex = iBlock * out.blockColumns + jBlock;
+                final double[] outBlock = out.blocks[outIndex];
+                final int index = pBlock * blockColumns + qBlock;
+                final int width = blockWidth(qBlock);
+
+                final int heightExcess = iHeight + rowsShift - BLOCK_SIZE;
+                final int widthExcess = jWidth + columnsShift - BLOCK_SIZE;
+                if (heightExcess > 0) {
+                    // the submatrix block spans on two blocks rows from the original matrix
+                    if (widthExcess > 0) {
+                        // the submatrix block spans on two blocks columns from the original matrix
+                        final int width2 = blockWidth(qBlock + 1);
+                        copyBlockPart(
+                                blocks[index],
+                                width,
+                                rowsShift,
+                                BLOCK_SIZE,
+                                columnsShift,
+                                BLOCK_SIZE,
+                                outBlock,
+                                jWidth,
+                                0,
+                                0);
+                        copyBlockPart(
+                                blocks[index + 1],
+                                width2,
+                                rowsShift,
+                                BLOCK_SIZE,
+                                0,
+                                widthExcess,
+                                outBlock,
+                                jWidth,
+                                0,
+                                jWidth - widthExcess);
+                        copyBlockPart(
+                                blocks[index + blockColumns],
+                                width,
+                                0,
+                                heightExcess,
+                                columnsShift,
+                                BLOCK_SIZE,
+                                outBlock,
+                                jWidth,
+                                iHeight - heightExcess,
+                                0);
+                        copyBlockPart(
+                                blocks[index + blockColumns + 1],
+                                width2,
+                                0,
+                                heightExcess,
+                                0,
+                                widthExcess,
+                                outBlock,
+                                jWidth,
+                                iHeight - heightExcess,
+                                jWidth - widthExcess);
+                    } else {
+                        // the submatrix block spans on one block column from the original matrix
+                        copyBlockPart(
+                                blocks[index],
+                                width,
+                                rowsShift,
+                                BLOCK_SIZE,
+                                columnsShift,
+                                jWidth + columnsShift,
+                                outBlock,
+                                jWidth,
+                                0,
+                                0);
+                        copyBlockPart(
+                                blocks[index + blockColumns],
+                                width,
+                                0,
+                                heightExcess,
+                                columnsShift,
+                                jWidth + columnsShift,
+                                outBlock,
+                                jWidth,
+                                iHeight - heightExcess,
+                                0);
+                    }
+                } else {
+                    // the submatrix block spans on one block row from the original matrix
+                    if (widthExcess > 0) {
+                        // the submatrix block spans on two blocks columns from the original matrix
+                        final int width2 = blockWidth(qBlock + 1);
+                        copyBlockPart(
+                                blocks[index],
+                                width,
+                                rowsShift,
+                                iHeight + rowsShift,
+                                columnsShift,
+                                BLOCK_SIZE,
+                                outBlock,
+                                jWidth,
+                                0,
+                                0);
+                        copyBlockPart(
+                                blocks[index + 1],
+                                width2,
+                                rowsShift,
+                                iHeight + rowsShift,
+                                0,
+                                widthExcess,
+                                outBlock,
+                                jWidth,
+                                0,
+                                jWidth - widthExcess);
+                    } else {
+                        // the submatrix block spans on one block column from the original matrix
+                        copyBlockPart(
+                                blocks[index],
+                                width,
+                                rowsShift,
+                                iHeight + rowsShift,
+                                columnsShift,
+                                jWidth + columnsShift,
+                                outBlock,
+                                jWidth,
+                                0,
+                                0);
+                    }
+                }
+                ++qBlock;
+            }
+            ++pBlock;
+        }
+
+        return out;
+    }
+
+    /**
+     * Copy a part of a block into another one
+     *
+     * <p>This method can be called only when the specified part fits in both blocks, no
+     * verification is done here.
+     *
+     * @param srcBlock source block
+     * @param srcWidth source block width ({@link #BLOCK_SIZE} or smaller)
+     * @param srcStartRow start row in the source block
+     * @param srcEndRow end row (exclusive) in the source block
+     * @param srcStartColumn start column in the source block
+     * @param srcEndColumn end column (exclusive) in the source block
+     * @param dstBlock destination block
+     * @param dstWidth destination block width ({@link #BLOCK_SIZE} or smaller)
+     * @param dstStartRow start row in the destination block
+     * @param dstStartColumn start column in the destination block
+     */
+    private void copyBlockPart(
+            final double[] srcBlock,
+            final int srcWidth,
+            final int srcStartRow,
+            final int srcEndRow,
+            final int srcStartColumn,
+            final int srcEndColumn,
+            final double[] dstBlock,
+            final int dstWidth,
+            final int dstStartRow,
+            final int dstStartColumn) {
+        final int length = srcEndColumn - srcStartColumn;
+        int srcPos = srcStartRow * srcWidth + srcStartColumn;
+        int dstPos = dstStartRow * dstWidth + dstStartColumn;
+        for (int srcRow = srcStartRow; srcRow < srcEndRow; ++srcRow) {
+            System.arraycopy(srcBlock, srcPos, dstBlock, dstPos, length);
+            srcPos += srcWidth;
+            dstPos += dstWidth;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setSubMatrix(final double[][] subMatrix, final int row, final int column)
+            throws OutOfRangeException,
+                    NoDataException,
+                    NullArgumentException,
+                    DimensionMismatchException {
+        // safety checks
+        MathUtils.checkNotNull(subMatrix);
+        final int refLength = subMatrix[0].length;
+        if (refLength == 0) {
+            throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_COLUMN);
+        }
+        final int endRow = row + subMatrix.length - 1;
+        final int endColumn = column + refLength - 1;
+        MatrixUtils.checkSubMatrixIndex(this, row, endRow, column, endColumn);
+        for (final double[] subRow : subMatrix) {
+            if (subRow.length != refLength) {
+                throw new DimensionMismatchException(refLength, subRow.length);
+            }
+        }
+
+        // compute blocks bounds
+        final int blockStartRow = row / BLOCK_SIZE;
+        final int blockEndRow = (endRow + BLOCK_SIZE) / BLOCK_SIZE;
+        final int blockStartColumn = column / BLOCK_SIZE;
+        final int blockEndColumn = (endColumn + BLOCK_SIZE) / BLOCK_SIZE;
+
+        // perform copy block-wise, to ensure good cache behavior
+        for (int iBlock = blockStartRow; iBlock < blockEndRow; ++iBlock) {
+            final int iHeight = blockHeight(iBlock);
+            final int firstRow = iBlock * BLOCK_SIZE;
+            final int iStart = FastMath.max(row, firstRow);
+            final int iEnd = FastMath.min(endRow + 1, firstRow + iHeight);
+
+            for (int jBlock = blockStartColumn; jBlock < blockEndColumn; ++jBlock) {
+                final int jWidth = blockWidth(jBlock);
+                final int firstColumn = jBlock * BLOCK_SIZE;
+                final int jStart = FastMath.max(column, firstColumn);
+                final int jEnd = FastMath.min(endColumn + 1, firstColumn + jWidth);
+                final int jLength = jEnd - jStart;
+
+                // handle one block, row by row
+                final double[] block = blocks[iBlock * blockColumns + jBlock];
+                for (int i = iStart; i < iEnd; ++i) {
+                    System.arraycopy(
+                            subMatrix[i - row],
+                            jStart - column,
+                            block,
+                            (i - firstRow) * jWidth + (jStart - firstColumn),
+                            jLength);
+                }
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public BlockRealMatrix getRowMatrix(final int row) throws OutOfRangeException {
+        MatrixUtils.checkRowIndex(this, row);
+        final BlockRealMatrix out = new BlockRealMatrix(1, columns);
+
+        // perform copy block-wise, to ensure good cache behavior
+        final int iBlock = row / BLOCK_SIZE;
+        final int iRow = row - iBlock * BLOCK_SIZE;
+        int outBlockIndex = 0;
+        int outIndex = 0;
+        double[] outBlock = out.blocks[outBlockIndex];
+        for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
+            final int jWidth = blockWidth(jBlock);
+            final double[] block = blocks[iBlock * blockColumns + jBlock];
+            final int available = outBlock.length - outIndex;
+            if (jWidth > available) {
+                System.arraycopy(block, iRow * jWidth, outBlock, outIndex, available);
+                outBlock = out.blocks[++outBlockIndex];
+                System.arraycopy(block, iRow * jWidth, outBlock, 0, jWidth - available);
+                outIndex = jWidth - available;
+            } else {
+                System.arraycopy(block, iRow * jWidth, outBlock, outIndex, jWidth);
+                outIndex += jWidth;
+            }
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setRowMatrix(final int row, final RealMatrix matrix)
+            throws OutOfRangeException, MatrixDimensionMismatchException {
+        try {
+            setRowMatrix(row, (BlockRealMatrix) matrix);
+        } catch (ClassCastException cce) {
+            super.setRowMatrix(row, matrix);
+        }
+    }
+
+    /**
+     * Sets the entries in row number <code>row</code> as a row matrix. Row indices start at 0.
+     *
+     * @param row the row to be set
+     * @param matrix row matrix (must have one row and the same number of columns as the instance)
+     * @throws OutOfRangeException if the specified row index is invalid.
+     * @throws MatrixDimensionMismatchException if the matrix dimensions do not match one instance
+     *     row.
+     */
+    public void setRowMatrix(final int row, final BlockRealMatrix matrix)
+            throws OutOfRangeException, MatrixDimensionMismatchException {
+        MatrixUtils.checkRowIndex(this, row);
+        final int nCols = getColumnDimension();
+        if ((matrix.getRowDimension() != 1) || (matrix.getColumnDimension() != nCols)) {
+            throw new MatrixDimensionMismatchException(
+                    matrix.getRowDimension(), matrix.getColumnDimension(), 1, nCols);
+        }
+
+        // perform copy block-wise, to ensure good cache behavior
+        final int iBlock = row / BLOCK_SIZE;
+        final int iRow = row - iBlock * BLOCK_SIZE;
+        int mBlockIndex = 0;
+        int mIndex = 0;
+        double[] mBlock = matrix.blocks[mBlockIndex];
+        for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
+            final int jWidth = blockWidth(jBlock);
+            final double[] block = blocks[iBlock * blockColumns + jBlock];
+            final int available = mBlock.length - mIndex;
+            if (jWidth > available) {
+                System.arraycopy(mBlock, mIndex, block, iRow * jWidth, available);
+                mBlock = matrix.blocks[++mBlockIndex];
+                System.arraycopy(mBlock, 0, block, iRow * jWidth, jWidth - available);
+                mIndex = jWidth - available;
+            } else {
+                System.arraycopy(mBlock, mIndex, block, iRow * jWidth, jWidth);
+                mIndex += jWidth;
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public BlockRealMatrix getColumnMatrix(final int column) throws OutOfRangeException {
+        MatrixUtils.checkColumnIndex(this, column);
+        final BlockRealMatrix out = new BlockRealMatrix(rows, 1);
+
+        // perform copy block-wise, to ensure good cache behavior
+        final int jBlock = column / BLOCK_SIZE;
+        final int jColumn = column - jBlock * BLOCK_SIZE;
+        final int jWidth = blockWidth(jBlock);
+        int outBlockIndex = 0;
+        int outIndex = 0;
+        double[] outBlock = out.blocks[outBlockIndex];
+        for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
+            final int iHeight = blockHeight(iBlock);
+            final double[] block = blocks[iBlock * blockColumns + jBlock];
+            for (int i = 0; i < iHeight; ++i) {
+                if (outIndex >= outBlock.length) {
+                    outBlock = out.blocks[++outBlockIndex];
+                    outIndex = 0;
+                }
+                outBlock[outIndex++] = block[i * jWidth + jColumn];
+            }
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setColumnMatrix(final int column, final RealMatrix matrix)
+            throws OutOfRangeException, MatrixDimensionMismatchException {
+        try {
+            setColumnMatrix(column, (BlockRealMatrix) matrix);
+        } catch (ClassCastException cce) {
+            super.setColumnMatrix(column, matrix);
+        }
+    }
+
+    /**
+     * Sets the entries in column number <code>column</code> as a column matrix. Column indices
+     * start at 0.
+     *
+     * @param column the column to be set
+     * @param matrix column matrix (must have one column and the same number of rows as the
+     *     instance)
+     * @throws OutOfRangeException if the specified column index is invalid.
+     * @throws MatrixDimensionMismatchException if the matrix dimensions do not match one instance
+     *     column.
+     */
+    void setColumnMatrix(final int column, final BlockRealMatrix matrix)
+            throws OutOfRangeException, MatrixDimensionMismatchException {
+        MatrixUtils.checkColumnIndex(this, column);
+        final int nRows = getRowDimension();
+        if ((matrix.getRowDimension() != nRows) || (matrix.getColumnDimension() != 1)) {
+            throw new MatrixDimensionMismatchException(
+                    matrix.getRowDimension(), matrix.getColumnDimension(), nRows, 1);
+        }
+
+        // perform copy block-wise, to ensure good cache behavior
+        final int jBlock = column / BLOCK_SIZE;
+        final int jColumn = column - jBlock * BLOCK_SIZE;
+        final int jWidth = blockWidth(jBlock);
+        int mBlockIndex = 0;
+        int mIndex = 0;
+        double[] mBlock = matrix.blocks[mBlockIndex];
+        for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
+            final int iHeight = blockHeight(iBlock);
+            final double[] block = blocks[iBlock * blockColumns + jBlock];
+            for (int i = 0; i < iHeight; ++i) {
+                if (mIndex >= mBlock.length) {
+                    mBlock = matrix.blocks[++mBlockIndex];
+                    mIndex = 0;
+                }
+                block[i * jWidth + jColumn] = mBlock[mIndex++];
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector getRowVector(final int row) throws OutOfRangeException {
+        MatrixUtils.checkRowIndex(this, row);
+        final double[] outData = new double[columns];
+
+        // perform copy block-wise, to ensure good cache behavior
+        final int iBlock = row / BLOCK_SIZE;
+        final int iRow = row - iBlock * BLOCK_SIZE;
+        int outIndex = 0;
+        for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
+            final int jWidth = blockWidth(jBlock);
+            final double[] block = blocks[iBlock * blockColumns + jBlock];
+            System.arraycopy(block, iRow * jWidth, outData, outIndex, jWidth);
+            outIndex += jWidth;
+        }
+
+        return new ArrayRealVector(outData, false);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setRowVector(final int row, final RealVector vector)
+            throws OutOfRangeException, MatrixDimensionMismatchException {
+        try {
+            setRow(row, ((ArrayRealVector) vector).getDataRef());
+        } catch (ClassCastException cce) {
+            super.setRowVector(row, vector);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector getColumnVector(final int column) throws OutOfRangeException {
+        MatrixUtils.checkColumnIndex(this, column);
+        final double[] outData = new double[rows];
+
+        // perform copy block-wise, to ensure good cache behavior
+        final int jBlock = column / BLOCK_SIZE;
+        final int jColumn = column - jBlock * BLOCK_SIZE;
+        final int jWidth = blockWidth(jBlock);
+        int outIndex = 0;
+        for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
+            final int iHeight = blockHeight(iBlock);
+            final double[] block = blocks[iBlock * blockColumns + jBlock];
+            for (int i = 0; i < iHeight; ++i) {
+                outData[outIndex++] = block[i * jWidth + jColumn];
+            }
+        }
+
+        return new ArrayRealVector(outData, false);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setColumnVector(final int column, final RealVector vector)
+            throws OutOfRangeException, MatrixDimensionMismatchException {
+        try {
+            setColumn(column, ((ArrayRealVector) vector).getDataRef());
+        } catch (ClassCastException cce) {
+            super.setColumnVector(column, vector);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double[] getRow(final int row) throws OutOfRangeException {
+        MatrixUtils.checkRowIndex(this, row);
+        final double[] out = new double[columns];
+
+        // perform copy block-wise, to ensure good cache behavior
+        final int iBlock = row / BLOCK_SIZE;
+        final int iRow = row - iBlock * BLOCK_SIZE;
+        int outIndex = 0;
+        for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
+            final int jWidth = blockWidth(jBlock);
+            final double[] block = blocks[iBlock * blockColumns + jBlock];
+            System.arraycopy(block, iRow * jWidth, out, outIndex, jWidth);
+            outIndex += jWidth;
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setRow(final int row, final double[] array)
+            throws OutOfRangeException, MatrixDimensionMismatchException {
+        MatrixUtils.checkRowIndex(this, row);
+        final int nCols = getColumnDimension();
+        if (array.length != nCols) {
+            throw new MatrixDimensionMismatchException(1, array.length, 1, nCols);
+        }
+
+        // perform copy block-wise, to ensure good cache behavior
+        final int iBlock = row / BLOCK_SIZE;
+        final int iRow = row - iBlock * BLOCK_SIZE;
+        int outIndex = 0;
+        for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
+            final int jWidth = blockWidth(jBlock);
+            final double[] block = blocks[iBlock * blockColumns + jBlock];
+            System.arraycopy(array, outIndex, block, iRow * jWidth, jWidth);
+            outIndex += jWidth;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double[] getColumn(final int column) throws OutOfRangeException {
+        MatrixUtils.checkColumnIndex(this, column);
+        final double[] out = new double[rows];
+
+        // perform copy block-wise, to ensure good cache behavior
+        final int jBlock = column / BLOCK_SIZE;
+        final int jColumn = column - jBlock * BLOCK_SIZE;
+        final int jWidth = blockWidth(jBlock);
+        int outIndex = 0;
+        for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
+            final int iHeight = blockHeight(iBlock);
+            final double[] block = blocks[iBlock * blockColumns + jBlock];
+            for (int i = 0; i < iHeight; ++i) {
+                out[outIndex++] = block[i * jWidth + jColumn];
+            }
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setColumn(final int column, final double[] array)
+            throws OutOfRangeException, MatrixDimensionMismatchException {
+        MatrixUtils.checkColumnIndex(this, column);
+        final int nRows = getRowDimension();
+        if (array.length != nRows) {
+            throw new MatrixDimensionMismatchException(array.length, 1, nRows, 1);
+        }
+
+        // perform copy block-wise, to ensure good cache behavior
+        final int jBlock = column / BLOCK_SIZE;
+        final int jColumn = column - jBlock * BLOCK_SIZE;
+        final int jWidth = blockWidth(jBlock);
+        int outIndex = 0;
+        for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
+            final int iHeight = blockHeight(iBlock);
+            final double[] block = blocks[iBlock * blockColumns + jBlock];
+            for (int i = 0; i < iHeight; ++i) {
+                block[i * jWidth + jColumn] = array[outIndex++];
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getEntry(final int row, final int column) throws OutOfRangeException {
+        MatrixUtils.checkMatrixIndex(this, row, column);
+        final int iBlock = row / BLOCK_SIZE;
+        final int jBlock = column / BLOCK_SIZE;
+        final int k =
+                (row - iBlock * BLOCK_SIZE) * blockWidth(jBlock) + (column - jBlock * BLOCK_SIZE);
+        return blocks[iBlock * blockColumns + jBlock][k];
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setEntry(final int row, final int column, final double value)
+            throws OutOfRangeException {
+        MatrixUtils.checkMatrixIndex(this, row, column);
+        final int iBlock = row / BLOCK_SIZE;
+        final int jBlock = column / BLOCK_SIZE;
+        final int k =
+                (row - iBlock * BLOCK_SIZE) * blockWidth(jBlock) + (column - jBlock * BLOCK_SIZE);
+        blocks[iBlock * blockColumns + jBlock][k] = value;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addToEntry(final int row, final int column, final double increment)
+            throws OutOfRangeException {
+        MatrixUtils.checkMatrixIndex(this, row, column);
+        final int iBlock = row / BLOCK_SIZE;
+        final int jBlock = column / BLOCK_SIZE;
+        final int k =
+                (row - iBlock * BLOCK_SIZE) * blockWidth(jBlock) + (column - jBlock * BLOCK_SIZE);
+        blocks[iBlock * blockColumns + jBlock][k] += increment;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void multiplyEntry(final int row, final int column, final double factor)
+            throws OutOfRangeException {
+        MatrixUtils.checkMatrixIndex(this, row, column);
+        final int iBlock = row / BLOCK_SIZE;
+        final int jBlock = column / BLOCK_SIZE;
+        final int k =
+                (row - iBlock * BLOCK_SIZE) * blockWidth(jBlock) + (column - jBlock * BLOCK_SIZE);
+        blocks[iBlock * blockColumns + jBlock][k] *= factor;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public BlockRealMatrix transpose() {
+        final int nRows = getRowDimension();
+        final int nCols = getColumnDimension();
+        final BlockRealMatrix out = new BlockRealMatrix(nCols, nRows);
+
+        // perform transpose block-wise, to ensure good cache behavior
+        int blockIndex = 0;
+        for (int iBlock = 0; iBlock < blockColumns; ++iBlock) {
+            for (int jBlock = 0; jBlock < blockRows; ++jBlock) {
+                // transpose current block
+                final double[] outBlock = out.blocks[blockIndex];
+                final double[] tBlock = blocks[jBlock * blockColumns + iBlock];
+                final int pStart = iBlock * BLOCK_SIZE;
+                final int pEnd = FastMath.min(pStart + BLOCK_SIZE, columns);
+                final int qStart = jBlock * BLOCK_SIZE;
+                final int qEnd = FastMath.min(qStart + BLOCK_SIZE, rows);
+                int k = 0;
+                for (int p = pStart; p < pEnd; ++p) {
+                    final int lInc = pEnd - pStart;
+                    int l = p - pStart;
+                    for (int q = qStart; q < qEnd; ++q) {
+                        outBlock[k] = tBlock[l];
+                        ++k;
+                        l += lInc;
+                    }
+                }
+                // go to next block
+                ++blockIndex;
+            }
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getRowDimension() {
+        return rows;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getColumnDimension() {
+        return columns;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double[] operate(final double[] v) throws DimensionMismatchException {
+        if (v.length != columns) {
+            throw new DimensionMismatchException(v.length, columns);
+        }
+        final double[] out = new double[rows];
+
+        // perform multiplication block-wise, to ensure good cache behavior
+        for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
+            final int pStart = iBlock * BLOCK_SIZE;
+            final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows);
+            for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
+                final double[] block = blocks[iBlock * blockColumns + jBlock];
+                final int qStart = jBlock * BLOCK_SIZE;
+                final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns);
+                int k = 0;
+                for (int p = pStart; p < pEnd; ++p) {
+                    double sum = 0;
+                    int q = qStart;
+                    while (q < qEnd - 3) {
+                        sum +=
+                                block[k] * v[q]
+                                        + block[k + 1] * v[q + 1]
+                                        + block[k + 2] * v[q + 2]
+                                        + block[k + 3] * v[q + 3];
+                        k += 4;
+                        q += 4;
+                    }
+                    while (q < qEnd) {
+                        sum += block[k++] * v[q++];
+                    }
+                    out[p] += sum;
+                }
+            }
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double[] preMultiply(final double[] v) throws DimensionMismatchException {
+        if (v.length != rows) {
+            throw new DimensionMismatchException(v.length, rows);
+        }
+        final double[] out = new double[columns];
+
+        // perform multiplication block-wise, to ensure good cache behavior
+        for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
+            final int jWidth = blockWidth(jBlock);
+            final int jWidth2 = jWidth + jWidth;
+            final int jWidth3 = jWidth2 + jWidth;
+            final int jWidth4 = jWidth3 + jWidth;
+            final int qStart = jBlock * BLOCK_SIZE;
+            final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns);
+            for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
+                final double[] block = blocks[iBlock * blockColumns + jBlock];
+                final int pStart = iBlock * BLOCK_SIZE;
+                final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows);
+                for (int q = qStart; q < qEnd; ++q) {
+                    int k = q - qStart;
+                    double sum = 0;
+                    int p = pStart;
+                    while (p < pEnd - 3) {
+                        sum +=
+                                block[k] * v[p]
+                                        + block[k + jWidth] * v[p + 1]
+                                        + block[k + jWidth2] * v[p + 2]
+                                        + block[k + jWidth3] * v[p + 3];
+                        k += jWidth4;
+                        p += 4;
+                    }
+                    while (p < pEnd) {
+                        sum += block[k] * v[p++];
+                        k += jWidth;
+                    }
+                    out[q] += sum;
+                }
+            }
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double walkInRowOrder(final RealMatrixChangingVisitor visitor) {
+        visitor.start(rows, columns, 0, rows - 1, 0, columns - 1);
+        for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
+            final int pStart = iBlock * BLOCK_SIZE;
+            final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows);
+            for (int p = pStart; p < pEnd; ++p) {
+                for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
+                    final int jWidth = blockWidth(jBlock);
+                    final int qStart = jBlock * BLOCK_SIZE;
+                    final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns);
+                    final double[] block = blocks[iBlock * blockColumns + jBlock];
+                    int k = (p - pStart) * jWidth;
+                    for (int q = qStart; q < qEnd; ++q) {
+                        block[k] = visitor.visit(p, q, block[k]);
+                        ++k;
+                    }
+                }
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double walkInRowOrder(final RealMatrixPreservingVisitor visitor) {
+        visitor.start(rows, columns, 0, rows - 1, 0, columns - 1);
+        for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
+            final int pStart = iBlock * BLOCK_SIZE;
+            final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows);
+            for (int p = pStart; p < pEnd; ++p) {
+                for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
+                    final int jWidth = blockWidth(jBlock);
+                    final int qStart = jBlock * BLOCK_SIZE;
+                    final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns);
+                    final double[] block = blocks[iBlock * blockColumns + jBlock];
+                    int k = (p - pStart) * jWidth;
+                    for (int q = qStart; q < qEnd; ++q) {
+                        visitor.visit(p, q, block[k]);
+                        ++k;
+                    }
+                }
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double walkInRowOrder(
+            final RealMatrixChangingVisitor visitor,
+            final int startRow,
+            final int endRow,
+            final int startColumn,
+            final int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException {
+        MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn);
+        visitor.start(rows, columns, startRow, endRow, startColumn, endColumn);
+        for (int iBlock = startRow / BLOCK_SIZE; iBlock < 1 + endRow / BLOCK_SIZE; ++iBlock) {
+            final int p0 = iBlock * BLOCK_SIZE;
+            final int pStart = FastMath.max(startRow, p0);
+            final int pEnd = FastMath.min((iBlock + 1) * BLOCK_SIZE, 1 + endRow);
+            for (int p = pStart; p < pEnd; ++p) {
+                for (int jBlock = startColumn / BLOCK_SIZE;
+                        jBlock < 1 + endColumn / BLOCK_SIZE;
+                        ++jBlock) {
+                    final int jWidth = blockWidth(jBlock);
+                    final int q0 = jBlock * BLOCK_SIZE;
+                    final int qStart = FastMath.max(startColumn, q0);
+                    final int qEnd = FastMath.min((jBlock + 1) * BLOCK_SIZE, 1 + endColumn);
+                    final double[] block = blocks[iBlock * blockColumns + jBlock];
+                    int k = (p - p0) * jWidth + qStart - q0;
+                    for (int q = qStart; q < qEnd; ++q) {
+                        block[k] = visitor.visit(p, q, block[k]);
+                        ++k;
+                    }
+                }
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double walkInRowOrder(
+            final RealMatrixPreservingVisitor visitor,
+            final int startRow,
+            final int endRow,
+            final int startColumn,
+            final int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException {
+        MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn);
+        visitor.start(rows, columns, startRow, endRow, startColumn, endColumn);
+        for (int iBlock = startRow / BLOCK_SIZE; iBlock < 1 + endRow / BLOCK_SIZE; ++iBlock) {
+            final int p0 = iBlock * BLOCK_SIZE;
+            final int pStart = FastMath.max(startRow, p0);
+            final int pEnd = FastMath.min((iBlock + 1) * BLOCK_SIZE, 1 + endRow);
+            for (int p = pStart; p < pEnd; ++p) {
+                for (int jBlock = startColumn / BLOCK_SIZE;
+                        jBlock < 1 + endColumn / BLOCK_SIZE;
+                        ++jBlock) {
+                    final int jWidth = blockWidth(jBlock);
+                    final int q0 = jBlock * BLOCK_SIZE;
+                    final int qStart = FastMath.max(startColumn, q0);
+                    final int qEnd = FastMath.min((jBlock + 1) * BLOCK_SIZE, 1 + endColumn);
+                    final double[] block = blocks[iBlock * blockColumns + jBlock];
+                    int k = (p - p0) * jWidth + qStart - q0;
+                    for (int q = qStart; q < qEnd; ++q) {
+                        visitor.visit(p, q, block[k]);
+                        ++k;
+                    }
+                }
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double walkInOptimizedOrder(final RealMatrixChangingVisitor visitor) {
+        visitor.start(rows, columns, 0, rows - 1, 0, columns - 1);
+        int blockIndex = 0;
+        for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
+            final int pStart = iBlock * BLOCK_SIZE;
+            final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows);
+            for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
+                final int qStart = jBlock * BLOCK_SIZE;
+                final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns);
+                final double[] block = blocks[blockIndex];
+                int k = 0;
+                for (int p = pStart; p < pEnd; ++p) {
+                    for (int q = qStart; q < qEnd; ++q) {
+                        block[k] = visitor.visit(p, q, block[k]);
+                        ++k;
+                    }
+                }
+                ++blockIndex;
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double walkInOptimizedOrder(final RealMatrixPreservingVisitor visitor) {
+        visitor.start(rows, columns, 0, rows - 1, 0, columns - 1);
+        int blockIndex = 0;
+        for (int iBlock = 0; iBlock < blockRows; ++iBlock) {
+            final int pStart = iBlock * BLOCK_SIZE;
+            final int pEnd = FastMath.min(pStart + BLOCK_SIZE, rows);
+            for (int jBlock = 0; jBlock < blockColumns; ++jBlock) {
+                final int qStart = jBlock * BLOCK_SIZE;
+                final int qEnd = FastMath.min(qStart + BLOCK_SIZE, columns);
+                final double[] block = blocks[blockIndex];
+                int k = 0;
+                for (int p = pStart; p < pEnd; ++p) {
+                    for (int q = qStart; q < qEnd; ++q) {
+                        visitor.visit(p, q, block[k]);
+                        ++k;
+                    }
+                }
+                ++blockIndex;
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double walkInOptimizedOrder(
+            final RealMatrixChangingVisitor visitor,
+            final int startRow,
+            final int endRow,
+            final int startColumn,
+            final int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException {
+        MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn);
+        visitor.start(rows, columns, startRow, endRow, startColumn, endColumn);
+        for (int iBlock = startRow / BLOCK_SIZE; iBlock < 1 + endRow / BLOCK_SIZE; ++iBlock) {
+            final int p0 = iBlock * BLOCK_SIZE;
+            final int pStart = FastMath.max(startRow, p0);
+            final int pEnd = FastMath.min((iBlock + 1) * BLOCK_SIZE, 1 + endRow);
+            for (int jBlock = startColumn / BLOCK_SIZE;
+                    jBlock < 1 + endColumn / BLOCK_SIZE;
+                    ++jBlock) {
+                final int jWidth = blockWidth(jBlock);
+                final int q0 = jBlock * BLOCK_SIZE;
+                final int qStart = FastMath.max(startColumn, q0);
+                final int qEnd = FastMath.min((jBlock + 1) * BLOCK_SIZE, 1 + endColumn);
+                final double[] block = blocks[iBlock * blockColumns + jBlock];
+                for (int p = pStart; p < pEnd; ++p) {
+                    int k = (p - p0) * jWidth + qStart - q0;
+                    for (int q = qStart; q < qEnd; ++q) {
+                        block[k] = visitor.visit(p, q, block[k]);
+                        ++k;
+                    }
+                }
+            }
+        }
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double walkInOptimizedOrder(
+            final RealMatrixPreservingVisitor visitor,
+            final int startRow,
+            final int endRow,
+            final int startColumn,
+            final int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException {
+        MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn);
+        visitor.start(rows, columns, startRow, endRow, startColumn, endColumn);
+        for (int iBlock = startRow / BLOCK_SIZE; iBlock < 1 + endRow / BLOCK_SIZE; ++iBlock) {
+            final int p0 = iBlock * BLOCK_SIZE;
+            final int pStart = FastMath.max(startRow, p0);
+            final int pEnd = FastMath.min((iBlock + 1) * BLOCK_SIZE, 1 + endRow);
+            for (int jBlock = startColumn / BLOCK_SIZE;
+                    jBlock < 1 + endColumn / BLOCK_SIZE;
+                    ++jBlock) {
+                final int jWidth = blockWidth(jBlock);
+                final int q0 = jBlock * BLOCK_SIZE;
+                final int qStart = FastMath.max(startColumn, q0);
+                final int qEnd = FastMath.min((jBlock + 1) * BLOCK_SIZE, 1 + endColumn);
+                final double[] block = blocks[iBlock * blockColumns + jBlock];
+                for (int p = pStart; p < pEnd; ++p) {
+                    int k = (p - p0) * jWidth + qStart - q0;
+                    for (int q = qStart; q < qEnd; ++q) {
+                        visitor.visit(p, q, block[k]);
+                        ++k;
+                    }
+                }
+            }
+        }
+        return visitor.end();
+    }
+
+    /**
+     * Get the height of a block.
+     *
+     * @param blockRow row index (in block sense) of the block
+     * @return height (number of rows) of the block
+     */
+    private int blockHeight(final int blockRow) {
+        return (blockRow == blockRows - 1) ? rows - blockRow * BLOCK_SIZE : BLOCK_SIZE;
+    }
+
+    /**
+     * Get the width of a block.
+     *
+     * @param blockColumn column index (in block sense) of the block
+     * @return width (number of columns) of the block
+     */
+    private int blockWidth(final int blockColumn) {
+        return (blockColumn == blockColumns - 1) ? columns - blockColumn * BLOCK_SIZE : BLOCK_SIZE;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/CholeskyDecomposition.java b/src/main/java/org/apache/commons/math3/linear/CholeskyDecomposition.java
new file mode 100644
index 0000000..907ace9
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/CholeskyDecomposition.java
@@ -0,0 +1,319 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Calculates the Cholesky decomposition of a matrix.
+ *
+ * <p>The Cholesky decomposition of a real symmetric positive-definite matrix A consists of a lower
+ * triangular matrix L with same size such that: A = LL<sup>T</sup>. In a sense, this is the square
+ * root of A.
+ *
+ * <p>This class is based on the class with similar name from the <a
+ * href="http://math.nist.gov/javanumerics/jama/">JAMA</a> library, with the following changes:
+ *
+ * <ul>
+ *   <li>a {@link #getLT() getLT} method has been added,
+ *   <li>the {@code isspd} method has been removed, since the constructor of this class throws a
+ *       {@link NonPositiveDefiniteMatrixException} when a matrix cannot be decomposed,
+ *   <li>a {@link #getDeterminant() getDeterminant} method has been added,
+ *   <li>the {@code solve} method has been replaced by a {@link #getSolver() getSolver} method and
+ *       the equivalent method provided by the returned {@link DecompositionSolver}.
+ * </ul>
+ *
+ * @see <a href="http://mathworld.wolfram.com/CholeskyDecomposition.html">MathWorld</a>
+ * @see <a href="http://en.wikipedia.org/wiki/Cholesky_decomposition">Wikipedia</a>
+ * @since 2.0 (changed to concrete class in 3.0)
+ */
+public class CholeskyDecomposition {
+    /**
+     * Default threshold above which off-diagonal elements are considered too different and matrix
+     * not symmetric.
+     */
+    public static final double DEFAULT_RELATIVE_SYMMETRY_THRESHOLD = 1.0e-15;
+
+    /**
+     * Default threshold below which diagonal elements are considered null and matrix not positive
+     * definite.
+     */
+    public static final double DEFAULT_ABSOLUTE_POSITIVITY_THRESHOLD = 1.0e-10;
+
+    /** Row-oriented storage for L<sup>T</sup> matrix data. */
+    private double[][] lTData;
+
+    /** Cached value of L. */
+    private RealMatrix cachedL;
+
+    /** Cached value of LT. */
+    private RealMatrix cachedLT;
+
+    /**
+     * Calculates the Cholesky decomposition of the given matrix.
+     *
+     * <p>Calling this constructor is equivalent to call {@link #CholeskyDecomposition(RealMatrix,
+     * double, double)} with the thresholds set to the default values {@link
+     * #DEFAULT_RELATIVE_SYMMETRY_THRESHOLD} and {@link #DEFAULT_ABSOLUTE_POSITIVITY_THRESHOLD}
+     *
+     * @param matrix the matrix to decompose
+     * @throws NonSquareMatrixException if the matrix is not square.
+     * @throws NonSymmetricMatrixException if the matrix is not symmetric.
+     * @throws NonPositiveDefiniteMatrixException if the matrix is not strictly positive definite.
+     * @see #CholeskyDecomposition(RealMatrix, double, double)
+     * @see #DEFAULT_RELATIVE_SYMMETRY_THRESHOLD
+     * @see #DEFAULT_ABSOLUTE_POSITIVITY_THRESHOLD
+     */
+    public CholeskyDecomposition(final RealMatrix matrix) {
+        this(matrix, DEFAULT_RELATIVE_SYMMETRY_THRESHOLD, DEFAULT_ABSOLUTE_POSITIVITY_THRESHOLD);
+    }
+
+    /**
+     * Calculates the Cholesky decomposition of the given matrix.
+     *
+     * @param matrix the matrix to decompose
+     * @param relativeSymmetryThreshold threshold above which off-diagonal elements are considered
+     *     too different and matrix not symmetric
+     * @param absolutePositivityThreshold threshold below which diagonal elements are considered
+     *     null and matrix not positive definite
+     * @throws NonSquareMatrixException if the matrix is not square.
+     * @throws NonSymmetricMatrixException if the matrix is not symmetric.
+     * @throws NonPositiveDefiniteMatrixException if the matrix is not strictly positive definite.
+     * @see #CholeskyDecomposition(RealMatrix)
+     * @see #DEFAULT_RELATIVE_SYMMETRY_THRESHOLD
+     * @see #DEFAULT_ABSOLUTE_POSITIVITY_THRESHOLD
+     */
+    public CholeskyDecomposition(
+            final RealMatrix matrix,
+            final double relativeSymmetryThreshold,
+            final double absolutePositivityThreshold) {
+        if (!matrix.isSquare()) {
+            throw new NonSquareMatrixException(
+                    matrix.getRowDimension(), matrix.getColumnDimension());
+        }
+
+        final int order = matrix.getRowDimension();
+        lTData = matrix.getData();
+        cachedL = null;
+        cachedLT = null;
+
+        // check the matrix before transformation
+        for (int i = 0; i < order; ++i) {
+            final double[] lI = lTData[i];
+
+            // check off-diagonal elements (and reset them to 0)
+            for (int j = i + 1; j < order; ++j) {
+                final double[] lJ = lTData[j];
+                final double lIJ = lI[j];
+                final double lJI = lJ[i];
+                final double maxDelta =
+                        relativeSymmetryThreshold
+                                * FastMath.max(FastMath.abs(lIJ), FastMath.abs(lJI));
+                if (FastMath.abs(lIJ - lJI) > maxDelta) {
+                    throw new NonSymmetricMatrixException(i, j, relativeSymmetryThreshold);
+                }
+                lJ[i] = 0;
+            }
+        }
+
+        // transform the matrix
+        for (int i = 0; i < order; ++i) {
+
+            final double[] ltI = lTData[i];
+
+            // check diagonal element
+            if (ltI[i] <= absolutePositivityThreshold) {
+                throw new NonPositiveDefiniteMatrixException(
+                        ltI[i], i, absolutePositivityThreshold);
+            }
+
+            ltI[i] = FastMath.sqrt(ltI[i]);
+            final double inverse = 1.0 / ltI[i];
+
+            for (int q = order - 1; q > i; --q) {
+                ltI[q] *= inverse;
+                final double[] ltQ = lTData[q];
+                for (int p = q; p < order; ++p) {
+                    ltQ[p] -= ltI[q] * ltI[p];
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the matrix L of the decomposition.
+     *
+     * <p>L is an lower-triangular matrix
+     *
+     * @return the L matrix
+     */
+    public RealMatrix getL() {
+        if (cachedL == null) {
+            cachedL = getLT().transpose();
+        }
+        return cachedL;
+    }
+
+    /**
+     * Returns the transpose of the matrix L of the decomposition.
+     *
+     * <p>L<sup>T</sup> is an upper-triangular matrix
+     *
+     * @return the transpose of the matrix L of the decomposition
+     */
+    public RealMatrix getLT() {
+
+        if (cachedLT == null) {
+            cachedLT = MatrixUtils.createRealMatrix(lTData);
+        }
+
+        // return the cached matrix
+        return cachedLT;
+    }
+
+    /**
+     * Return the determinant of the matrix
+     *
+     * @return determinant of the matrix
+     */
+    public double getDeterminant() {
+        double determinant = 1.0;
+        for (int i = 0; i < lTData.length; ++i) {
+            double lTii = lTData[i][i];
+            determinant *= lTii * lTii;
+        }
+        return determinant;
+    }
+
+    /**
+     * Get a solver for finding the A &times; X = B solution in least square sense.
+     *
+     * @return a solver
+     */
+    public DecompositionSolver getSolver() {
+        return new Solver(lTData);
+    }
+
+    /** Specialized solver. */
+    private static class Solver implements DecompositionSolver {
+        /** Row-oriented storage for L<sup>T</sup> matrix data. */
+        private final double[][] lTData;
+
+        /**
+         * Build a solver from decomposed matrix.
+         *
+         * @param lTData row-oriented storage for L<sup>T</sup> matrix data
+         */
+        private Solver(final double[][] lTData) {
+            this.lTData = lTData;
+        }
+
+        /** {@inheritDoc} */
+        public boolean isNonSingular() {
+            // if we get this far, the matrix was positive definite, hence non-singular
+            return true;
+        }
+
+        /** {@inheritDoc} */
+        public RealVector solve(final RealVector b) {
+            final int m = lTData.length;
+            if (b.getDimension() != m) {
+                throw new DimensionMismatchException(b.getDimension(), m);
+            }
+
+            final double[] x = b.toArray();
+
+            // Solve LY = b
+            for (int j = 0; j < m; j++) {
+                final double[] lJ = lTData[j];
+                x[j] /= lJ[j];
+                final double xJ = x[j];
+                for (int i = j + 1; i < m; i++) {
+                    x[i] -= xJ * lJ[i];
+                }
+            }
+
+            // Solve LTX = Y
+            for (int j = m - 1; j >= 0; j--) {
+                x[j] /= lTData[j][j];
+                final double xJ = x[j];
+                for (int i = 0; i < j; i++) {
+                    x[i] -= xJ * lTData[i][j];
+                }
+            }
+
+            return new ArrayRealVector(x, false);
+        }
+
+        /** {@inheritDoc} */
+        public RealMatrix solve(RealMatrix b) {
+            final int m = lTData.length;
+            if (b.getRowDimension() != m) {
+                throw new DimensionMismatchException(b.getRowDimension(), m);
+            }
+
+            final int nColB = b.getColumnDimension();
+            final double[][] x = b.getData();
+
+            // Solve LY = b
+            for (int j = 0; j < m; j++) {
+                final double[] lJ = lTData[j];
+                final double lJJ = lJ[j];
+                final double[] xJ = x[j];
+                for (int k = 0; k < nColB; ++k) {
+                    xJ[k] /= lJJ;
+                }
+                for (int i = j + 1; i < m; i++) {
+                    final double[] xI = x[i];
+                    final double lJI = lJ[i];
+                    for (int k = 0; k < nColB; ++k) {
+                        xI[k] -= xJ[k] * lJI;
+                    }
+                }
+            }
+
+            // Solve LTX = Y
+            for (int j = m - 1; j >= 0; j--) {
+                final double lJJ = lTData[j][j];
+                final double[] xJ = x[j];
+                for (int k = 0; k < nColB; ++k) {
+                    xJ[k] /= lJJ;
+                }
+                for (int i = 0; i < j; i++) {
+                    final double[] xI = x[i];
+                    final double lIJ = lTData[i][j];
+                    for (int k = 0; k < nColB; ++k) {
+                        xI[k] -= xJ[k] * lIJ;
+                    }
+                }
+            }
+
+            return new Array2DRowRealMatrix(x);
+        }
+
+        /**
+         * Get the inverse of the decomposed matrix.
+         *
+         * @return the inverse matrix.
+         */
+        public RealMatrix getInverse() {
+            return solve(MatrixUtils.createRealIdentityMatrix(lTData.length));
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/ConjugateGradient.java b/src/main/java/org/apache/commons/math3/linear/ConjugateGradient.java
new file mode 100644
index 0000000..61dab67
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/ConjugateGradient.java
@@ -0,0 +1,231 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.util.ExceptionContext;
+import org.apache.commons.math3.util.IterationManager;
+
+/**
+ * This is an implementation of the conjugate gradient method for {@link RealLinearOperator}. It
+ * follows closely the template by <a href="#BARR1994">Barrett et al. (1994)</a> (figure 2.5). The
+ * linear system at hand is A &middot; x = b, and the residual is r = b - A &middot; x.
+ *
+ * <h3><a id="stopcrit">Default stopping criterion</a></h3>
+ *
+ * <p>A default stopping criterion is implemented. The iterations stop when || r || &le; &delta; ||
+ * b ||, where b is the right-hand side vector, r the current estimate of the residual, and &delta;
+ * a user-specified tolerance. It should be noted that r is the so-called <em>updated</em> residual,
+ * which might differ from the true residual due to rounding-off errors (see e.g. <a
+ * href="#STRA2002">Strakos and Tichy, 2002</a>).
+ *
+ * <h3>Iteration count</h3>
+ *
+ * <p>In the present context, an iteration should be understood as one evaluation of the
+ * matrix-vector product A &middot; x. The initialization phase therefore counts as one iteration.
+ *
+ * <h3><a id="context">Exception context</a></h3>
+ *
+ * <p>Besides standard {@link DimensionMismatchException}, this class might throw {@link
+ * NonPositiveDefiniteOperatorException} if the linear operator or the preconditioner are not
+ * positive definite. In this case, the {@link ExceptionContext} provides some more information
+ *
+ * <ul>
+ *   <li>key {@code "operator"} points to the offending linear operator, say L,
+ *   <li>key {@code "vector"} points to the offending vector, say x, such that x<sup>T</sup>
+ *       &middot; L &middot; x < 0.
+ * </ul>
+ *
+ * <h3>References</h3>
+ *
+ * <dl>
+ *   <dt><a id="BARR1994">Barret et al. (1994)</a>
+ *   <dd>R. Barrett, M. Berry, T. F. Chan, J. Demmel, J. M. Donato, J. Dongarra, V. Eijkhout, R.
+ *       Pozo, C. Romine and H. Van der Vorst, <a
+ *       href="http://www.netlib.org/linalg/html_templates/Templates.html"><em> Templates for the
+ *       Solution of Linear Systems: Building Blocks for Iterative Methods</em></a>, SIAM
+ *   <dt><a id="STRA2002">Strakos and Tichy (2002)
+ *   <dt>
+ *   <dd>Z. Strakos and P. Tichy, <a
+ *       href="http://etna.mcs.kent.edu/vol.13.2002/pp56-80.dir/pp56-80.pdf"><em>On error estimation
+ *       in the conjugate gradient method and why it works in finite precision
+ *       computations</em></a>, Electronic Transactions on Numerical Analysis 13: 56-80, 2002
+ * </dl>
+ *
+ * @since 3.0
+ */
+public class ConjugateGradient extends PreconditionedIterativeLinearSolver {
+
+    /** Key for the <a href="#context">exception context</a>. */
+    public static final String OPERATOR = "operator";
+
+    /** Key for the <a href="#context">exception context</a>. */
+    public static final String VECTOR = "vector";
+
+    /** {@code true} if positive-definiteness of matrix and preconditioner should be checked. */
+    private boolean check;
+
+    /** The value of &delta;, for the default stopping criterion. */
+    private final double delta;
+
+    /**
+     * Creates a new instance of this class, with <a href="#stopcrit">default stopping
+     * criterion</a>.
+     *
+     * @param maxIterations the maximum number of iterations
+     * @param delta the &delta; parameter for the default stopping criterion
+     * @param check {@code true} if positive definiteness of both matrix and preconditioner should
+     *     be checked
+     */
+    public ConjugateGradient(final int maxIterations, final double delta, final boolean check) {
+        super(maxIterations);
+        this.delta = delta;
+        this.check = check;
+    }
+
+    /**
+     * Creates a new instance of this class, with <a href="#stopcrit">default stopping criterion</a>
+     * and custom iteration manager.
+     *
+     * @param manager the custom iteration manager
+     * @param delta the &delta; parameter for the default stopping criterion
+     * @param check {@code true} if positive definiteness of both matrix and preconditioner should
+     *     be checked
+     * @throws NullArgumentException if {@code manager} is {@code null}
+     */
+    public ConjugateGradient(
+            final IterationManager manager, final double delta, final boolean check)
+            throws NullArgumentException {
+        super(manager);
+        this.delta = delta;
+        this.check = check;
+    }
+
+    /**
+     * Returns {@code true} if positive-definiteness should be checked for both matrix and
+     * preconditioner.
+     *
+     * @return {@code true} if the tests are to be performed
+     */
+    public final boolean getCheck() {
+        return check;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws NonPositiveDefiniteOperatorException if {@code a} or {@code m} is not positive
+     *     definite
+     */
+    @Override
+    public RealVector solveInPlace(
+            final RealLinearOperator a,
+            final RealLinearOperator m,
+            final RealVector b,
+            final RealVector x0)
+            throws NullArgumentException,
+                    NonPositiveDefiniteOperatorException,
+                    NonSquareOperatorException,
+                    DimensionMismatchException,
+                    MaxCountExceededException {
+        checkParameters(a, m, b, x0);
+        final IterationManager manager = getIterationManager();
+        // Initialization of default stopping criterion
+        manager.resetIterationCount();
+        final double rmax = delta * b.getNorm();
+        final RealVector bro = RealVector.unmodifiableRealVector(b);
+
+        // Initialization phase counts as one iteration.
+        manager.incrementIterationCount();
+        // p and x are constructed as copies of x0, since presumably, the type
+        // of x is optimized for the calculation of the matrix-vector product
+        // A.x.
+        final RealVector x = x0;
+        final RealVector xro = RealVector.unmodifiableRealVector(x);
+        final RealVector p = x.copy();
+        RealVector q = a.operate(p);
+
+        final RealVector r = b.combine(1, -1, q);
+        final RealVector rro = RealVector.unmodifiableRealVector(r);
+        double rnorm = r.getNorm();
+        RealVector z;
+        if (m == null) {
+            z = r;
+        } else {
+            z = null;
+        }
+        IterativeLinearSolverEvent evt;
+        evt =
+                new DefaultIterativeLinearSolverEvent(
+                        this, manager.getIterations(), xro, bro, rro, rnorm);
+        manager.fireInitializationEvent(evt);
+        if (rnorm <= rmax) {
+            manager.fireTerminationEvent(evt);
+            return x;
+        }
+        double rhoPrev = 0.;
+        while (true) {
+            manager.incrementIterationCount();
+            evt =
+                    new DefaultIterativeLinearSolverEvent(
+                            this, manager.getIterations(), xro, bro, rro, rnorm);
+            manager.fireIterationStartedEvent(evt);
+            if (m != null) {
+                z = m.operate(r);
+            }
+            final double rhoNext = r.dotProduct(z);
+            if (check && (rhoNext <= 0.)) {
+                final NonPositiveDefiniteOperatorException e;
+                e = new NonPositiveDefiniteOperatorException();
+                final ExceptionContext context = e.getContext();
+                context.setValue(OPERATOR, m);
+                context.setValue(VECTOR, r);
+                throw e;
+            }
+            if (manager.getIterations() == 2) {
+                p.setSubVector(0, z);
+            } else {
+                p.combineToSelf(rhoNext / rhoPrev, 1., z);
+            }
+            q = a.operate(p);
+            final double pq = p.dotProduct(q);
+            if (check && (pq <= 0.)) {
+                final NonPositiveDefiniteOperatorException e;
+                e = new NonPositiveDefiniteOperatorException();
+                final ExceptionContext context = e.getContext();
+                context.setValue(OPERATOR, a);
+                context.setValue(VECTOR, p);
+                throw e;
+            }
+            final double alpha = rhoNext / pq;
+            x.combineToSelf(1., alpha, p);
+            r.combineToSelf(1., -alpha, q);
+            rhoPrev = rhoNext;
+            rnorm = r.getNorm();
+            evt =
+                    new DefaultIterativeLinearSolverEvent(
+                            this, manager.getIterations(), xro, bro, rro, rnorm);
+            manager.fireIterationPerformedEvent(evt);
+            if (rnorm <= rmax) {
+                manager.fireTerminationEvent(evt);
+                return x;
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/DecompositionSolver.java b/src/main/java/org/apache/commons/math3/linear/DecompositionSolver.java
new file mode 100644
index 0000000..a7e1777
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/DecompositionSolver.java
@@ -0,0 +1,97 @@
+/*
+ * 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.math3.linear;
+
+/**
+ * Interface handling decomposition algorithms that can solve A &times; X = B.
+ *
+ * <p>Decomposition algorithms decompose an A matrix has a product of several specific matrices from
+ * which they can solve A &times; X = B in least squares sense: they find X such that ||A &times; X
+ * - B|| is minimal.
+ *
+ * <p>Some solvers like {@link LUDecomposition} can only find the solution for square matrices and
+ * when the solution is an exact linear solution, i.e. when ||A &times; X - B|| is exactly 0. Other
+ * solvers can also find solutions with non-square matrix A and with non-null minimal norm. If an
+ * exact linear solution exists it is also the minimal norm solution.
+ *
+ * @since 2.0
+ */
+public interface DecompositionSolver {
+
+    /**
+     * Solve the linear equation A &times; X = B for matrices A.
+     *
+     * <p>The A matrix is implicit, it is provided by the underlying decomposition algorithm.
+     *
+     * @param b right-hand side of the equation A &times; X = B
+     * @return a vector X that minimizes the two norm of A &times; X - B
+     * @throws org.apache.commons.math3.exception.DimensionMismatchException if the matrices
+     *     dimensions do not match.
+     * @throws SingularMatrixException if the decomposed matrix is singular.
+     */
+    RealVector solve(final RealVector b) throws SingularMatrixException;
+
+    /**
+     * Solve the linear equation A &times; X = B for matrices A.
+     *
+     * <p>The A matrix is implicit, it is provided by the underlying decomposition algorithm.
+     *
+     * @param b right-hand side of the equation A &times; X = B
+     * @return a matrix X that minimizes the two norm of A &times; X - B
+     * @throws org.apache.commons.math3.exception.DimensionMismatchException if the matrices
+     *     dimensions do not match.
+     * @throws SingularMatrixException if the decomposed matrix is singular.
+     */
+    RealMatrix solve(final RealMatrix b) throws SingularMatrixException;
+
+    /**
+     * Check if the decomposed matrix is non-singular.
+     *
+     * @return true if the decomposed matrix is non-singular.
+     */
+    boolean isNonSingular();
+
+    /**
+     * Get the <a
+     * href="http://en.wikipedia.org/wiki/Moore%E2%80%93Penrose_pseudoinverse">pseudo-inverse</a> of
+     * the decomposed matrix.
+     *
+     * <p><em>This is equal to the inverse of the decomposed matrix, if such an inverse exists.</em>
+     *
+     * <p>If no such inverse exists, then the result has properties that resemble that of an
+     * inverse.
+     *
+     * <p>In particular, in this case, if the decomposed matrix is A, then the system of equations
+     * \( A x = b \) may have no solutions, or many. If it has no solutions, then the pseudo-inverse
+     * \( A^+ \) gives the "closest" solution \( z = A^+ b \), meaning \( \left \| A z - b \right
+     * \|_2 \) is minimized. If there are many solutions, then \( z = A^+ b \) is the smallest
+     * solution, meaning \( \left \| z \right \|_2 \) is minimized.
+     *
+     * <p>Note however that some decompositions cannot compute a pseudo-inverse for all matrices.
+     * For example, the {@link LUDecomposition} is not defined for non-square matrices to begin
+     * with. The {@link QRDecomposition} can operate on non-square matrices, but will throw {@link
+     * SingularMatrixException} if the decomposed matrix is singular. Refer to the javadoc of
+     * specific decomposition implementations for more details.
+     *
+     * @return pseudo-inverse matrix (which is the inverse, if it exists), if the decomposition can
+     *     pseudo-invert the decomposed matrix
+     * @throws SingularMatrixException if the decomposed matrix is singular and the decomposition
+     *     can not compute a pseudo-inverse
+     */
+    RealMatrix getInverse() throws SingularMatrixException;
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/DefaultFieldMatrixChangingVisitor.java b/src/main/java/org/apache/commons/math3/linear/DefaultFieldMatrixChangingVisitor.java
new file mode 100644
index 0000000..dbf309e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/DefaultFieldMatrixChangingVisitor.java
@@ -0,0 +1,58 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.FieldElement;
+
+/**
+ * Default implementation of the {@link FieldMatrixChangingVisitor} interface.
+ *
+ * <p>This class is a convenience to create custom visitors without defining all methods. This class
+ * provides default implementations that do nothing.
+ *
+ * @param <T> the type of the field elements
+ * @since 2.0
+ */
+public class DefaultFieldMatrixChangingVisitor<T extends FieldElement<T>>
+        implements FieldMatrixChangingVisitor<T> {
+    /** Zero element of the field. */
+    private final T zero;
+
+    /**
+     * Build a new instance.
+     *
+     * @param zero additive identity of the field
+     */
+    public DefaultFieldMatrixChangingVisitor(final T zero) {
+        this.zero = zero;
+    }
+
+    /** {@inheritDoc} */
+    public void start(
+            int rows, int columns, int startRow, int endRow, int startColumn, int endColumn) {}
+
+    /** {@inheritDoc} */
+    public T visit(int row, int column, T value) {
+        return value;
+    }
+
+    /** {@inheritDoc} */
+    public T end() {
+        return zero;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/DefaultFieldMatrixPreservingVisitor.java b/src/main/java/org/apache/commons/math3/linear/DefaultFieldMatrixPreservingVisitor.java
new file mode 100644
index 0000000..ed11851
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/DefaultFieldMatrixPreservingVisitor.java
@@ -0,0 +1,56 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.FieldElement;
+
+/**
+ * Default implementation of the {@link FieldMatrixPreservingVisitor} interface.
+ *
+ * <p>This class is a convenience to create custom visitors without defining all methods. This class
+ * provides default implementations that do nothing.
+ *
+ * @param <T> the type of the field elements
+ * @since 2.0
+ */
+public class DefaultFieldMatrixPreservingVisitor<T extends FieldElement<T>>
+        implements FieldMatrixPreservingVisitor<T> {
+    /** Zero element of the field. */
+    private final T zero;
+
+    /**
+     * Build a new instance.
+     *
+     * @param zero additive identity of the field
+     */
+    public DefaultFieldMatrixPreservingVisitor(final T zero) {
+        this.zero = zero;
+    }
+
+    /** {@inheritDoc} */
+    public void start(
+            int rows, int columns, int startRow, int endRow, int startColumn, int endColumn) {}
+
+    /** {@inheritDoc} */
+    public void visit(int row, int column, T value) {}
+
+    /** {@inheritDoc} */
+    public T end() {
+        return zero;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/DefaultIterativeLinearSolverEvent.java b/src/main/java/org/apache/commons/math3/linear/DefaultIterativeLinearSolverEvent.java
new file mode 100644
index 0000000..fee346e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/DefaultIterativeLinearSolverEvent.java
@@ -0,0 +1,142 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.exception.MathUnsupportedOperationException;
+
+/** A default concrete implementation of the abstract class {@link IterativeLinearSolverEvent}. */
+public class DefaultIterativeLinearSolverEvent extends IterativeLinearSolverEvent {
+
+    /** */
+    private static final long serialVersionUID = 20120129L;
+
+    /** The right-hand side vector. */
+    private final RealVector b;
+
+    /** The current estimate of the residual. */
+    private final RealVector r;
+
+    /** The current estimate of the norm of the residual. */
+    private final double rnorm;
+
+    /** The current estimate of the solution. */
+    private final RealVector x;
+
+    /**
+     * Creates a new instance of this class. This implementation does <em>not</em> deep copy the
+     * specified vectors {@code x}, {@code b}, {@code r}. Therefore the user must make sure that
+     * these vectors are either unmodifiable views or deep copies of the same vectors actually used
+     * by the {@code source}. Failure to do so may compromise subsequent iterations of the {@code
+     * source}. If the residual vector {@code r} is {@code null}, then {@link #getResidual()} throws
+     * a {@link MathUnsupportedOperationException}, and {@link #providesResidual()} returns {@code
+     * false}.
+     *
+     * @param source the iterative solver which fired this event
+     * @param iterations the number of iterations performed at the time {@code this} event is
+     *     created
+     * @param x the current estimate of the solution
+     * @param b the right-hand side vector
+     * @param r the current estimate of the residual (can be {@code null})
+     * @param rnorm the norm of the current estimate of the residual
+     */
+    public DefaultIterativeLinearSolverEvent(
+            final Object source,
+            final int iterations,
+            final RealVector x,
+            final RealVector b,
+            final RealVector r,
+            final double rnorm) {
+        super(source, iterations);
+        this.x = x;
+        this.b = b;
+        this.r = r;
+        this.rnorm = rnorm;
+    }
+
+    /**
+     * Creates a new instance of this class. This implementation does <em>not</em> deep copy the
+     * specified vectors {@code x}, {@code b}. Therefore the user must make sure that these vectors
+     * are either unmodifiable views or deep copies of the same vectors actually used by the {@code
+     * source}. Failure to do so may compromise subsequent iterations of the {@code source}.
+     * Callling {@link #getResidual()} on instances returned by this constructor throws a {@link
+     * MathUnsupportedOperationException}, while {@link #providesResidual()} returns {@code false}.
+     *
+     * @param source the iterative solver which fired this event
+     * @param iterations the number of iterations performed at the time {@code this} event is
+     *     created
+     * @param x the current estimate of the solution
+     * @param b the right-hand side vector
+     * @param rnorm the norm of the current estimate of the residual
+     */
+    public DefaultIterativeLinearSolverEvent(
+            final Object source,
+            final int iterations,
+            final RealVector x,
+            final RealVector b,
+            final double rnorm) {
+        super(source, iterations);
+        this.x = x;
+        this.b = b;
+        this.r = null;
+        this.rnorm = rnorm;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getNormOfResidual() {
+        return rnorm;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>This implementation throws an {@link MathUnsupportedOperationException} if no residual
+     * vector {@code r} was provided at construction time.
+     */
+    @Override
+    public RealVector getResidual() {
+        if (r != null) {
+            return r;
+        }
+        throw new MathUnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector getRightHandSideVector() {
+        return b;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector getSolution() {
+        return x;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>This implementation returns {@code true} if a non-{@code null} value was specified for the
+     * residual vector {@code r} at construction time.
+     *
+     * @return {@code true} if {@code r != null}
+     */
+    @Override
+    public boolean providesResidual() {
+        return r != null;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/DefaultRealMatrixChangingVisitor.java b/src/main/java/org/apache/commons/math3/linear/DefaultRealMatrixChangingVisitor.java
new file mode 100644
index 0000000..ce561cb
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/DefaultRealMatrixChangingVisitor.java
@@ -0,0 +1,42 @@
+/*
+ * 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.math3.linear;
+
+/**
+ * Default implementation of the {@link RealMatrixChangingVisitor} interface.
+ *
+ * <p>This class is a convenience to create custom visitors without defining all methods. This class
+ * provides default implementations that do nothing.
+ *
+ * @since 2.0
+ */
+public class DefaultRealMatrixChangingVisitor implements RealMatrixChangingVisitor {
+    /** {@inheritDoc} */
+    public void start(
+            int rows, int columns, int startRow, int endRow, int startColumn, int endColumn) {}
+
+    /** {@inheritDoc} */
+    public double visit(int row, int column, double value) {
+        return value;
+    }
+
+    /** {@inheritDoc} */
+    public double end() {
+        return 0;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/DefaultRealMatrixPreservingVisitor.java b/src/main/java/org/apache/commons/math3/linear/DefaultRealMatrixPreservingVisitor.java
new file mode 100644
index 0000000..bd16ad3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/DefaultRealMatrixPreservingVisitor.java
@@ -0,0 +1,40 @@
+/*
+ * 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.math3.linear;
+
+/**
+ * Default implementation of the {@link RealMatrixPreservingVisitor} interface.
+ *
+ * <p>This class is a convenience to create custom visitors without defining all methods. This class
+ * provides default implementations that do nothing.
+ *
+ * @since 2.0
+ */
+public class DefaultRealMatrixPreservingVisitor implements RealMatrixPreservingVisitor {
+    /** {@inheritDoc} */
+    public void start(
+            int rows, int columns, int startRow, int endRow, int startColumn, int endColumn) {}
+
+    /** {@inheritDoc} */
+    public void visit(int row, int column, double value) {}
+
+    /** {@inheritDoc} */
+    public double end() {
+        return 0;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/DiagonalMatrix.java b/src/main/java/org/apache/commons/math3/linear/DiagonalMatrix.java
new file mode 100644
index 0000000..6246700
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/DiagonalMatrix.java
@@ -0,0 +1,353 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+import org.apache.commons.math3.util.Precision;
+
+import java.io.Serializable;
+
+/**
+ * Implementation of a diagonal matrix.
+ *
+ * @since 3.1.1
+ */
+public class DiagonalMatrix extends AbstractRealMatrix implements Serializable {
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 20121229L;
+
+    /** Entries of the diagonal. */
+    private final double[] data;
+
+    /**
+     * Creates a matrix with the supplied dimension.
+     *
+     * @param dimension Number of rows and columns in the new matrix.
+     * @throws NotStrictlyPositiveException if the dimension is not positive.
+     */
+    public DiagonalMatrix(final int dimension) throws NotStrictlyPositiveException {
+        super(dimension, dimension);
+        data = new double[dimension];
+    }
+
+    /**
+     * Creates a matrix using the input array as the underlying data. <br>
+     * The input array is copied, not referenced.
+     *
+     * @param d Data for the new matrix.
+     */
+    public DiagonalMatrix(final double[] d) {
+        this(d, true);
+    }
+
+    /**
+     * Creates a matrix using the input array as the underlying data. <br>
+     * If an array is created specially in order to be embedded in a this instance and not used
+     * directly, the {@code copyArray} may be set to {@code false}. This will prevent the copying
+     * and improve performance as no new array will be built and no data will be copied.
+     *
+     * @param d Data for new matrix.
+     * @param copyArray if {@code true}, the input array will be copied, otherwise it will be
+     *     referenced.
+     * @exception NullArgumentException if d is null
+     */
+    public DiagonalMatrix(final double[] d, final boolean copyArray) throws NullArgumentException {
+        MathUtils.checkNotNull(d);
+        data = copyArray ? d.clone() : d;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws DimensionMismatchException if the requested dimensions are not equal.
+     */
+    @Override
+    public RealMatrix createMatrix(final int rowDimension, final int columnDimension)
+            throws NotStrictlyPositiveException, DimensionMismatchException {
+        if (rowDimension != columnDimension) {
+            throw new DimensionMismatchException(rowDimension, columnDimension);
+        }
+
+        return new DiagonalMatrix(rowDimension);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealMatrix copy() {
+        return new DiagonalMatrix(data);
+    }
+
+    /**
+     * Compute the sum of {@code this} and {@code m}.
+     *
+     * @param m Matrix to be added.
+     * @return {@code this + m}.
+     * @throws MatrixDimensionMismatchException if {@code m} is not the same size as {@code this}.
+     */
+    public DiagonalMatrix add(final DiagonalMatrix m) throws MatrixDimensionMismatchException {
+        // Safety check.
+        MatrixUtils.checkAdditionCompatible(this, m);
+
+        final int dim = getRowDimension();
+        final double[] outData = new double[dim];
+        for (int i = 0; i < dim; i++) {
+            outData[i] = data[i] + m.data[i];
+        }
+
+        return new DiagonalMatrix(outData, false);
+    }
+
+    /**
+     * Returns {@code this} minus {@code m}.
+     *
+     * @param m Matrix to be subtracted.
+     * @return {@code this - m}
+     * @throws MatrixDimensionMismatchException if {@code m} is not the same size as {@code this}.
+     */
+    public DiagonalMatrix subtract(final DiagonalMatrix m) throws MatrixDimensionMismatchException {
+        MatrixUtils.checkSubtractionCompatible(this, m);
+
+        final int dim = getRowDimension();
+        final double[] outData = new double[dim];
+        for (int i = 0; i < dim; i++) {
+            outData[i] = data[i] - m.data[i];
+        }
+
+        return new DiagonalMatrix(outData, false);
+    }
+
+    /**
+     * Returns the result of postmultiplying {@code this} by {@code m}.
+     *
+     * @param m matrix to postmultiply by
+     * @return {@code this * m}
+     * @throws DimensionMismatchException if {@code columnDimension(this) != rowDimension(m)}
+     */
+    public DiagonalMatrix multiply(final DiagonalMatrix m) throws DimensionMismatchException {
+        MatrixUtils.checkMultiplicationCompatible(this, m);
+
+        final int dim = getRowDimension();
+        final double[] outData = new double[dim];
+        for (int i = 0; i < dim; i++) {
+            outData[i] = data[i] * m.data[i];
+        }
+
+        return new DiagonalMatrix(outData, false);
+    }
+
+    /**
+     * Returns the result of postmultiplying {@code this} by {@code m}.
+     *
+     * @param m matrix to postmultiply by
+     * @return {@code this * m}
+     * @throws DimensionMismatchException if {@code columnDimension(this) != rowDimension(m)}
+     */
+    @Override
+    public RealMatrix multiply(final RealMatrix m) throws DimensionMismatchException {
+        if (m instanceof DiagonalMatrix) {
+            return multiply((DiagonalMatrix) m);
+        } else {
+            MatrixUtils.checkMultiplicationCompatible(this, m);
+            final int nRows = m.getRowDimension();
+            final int nCols = m.getColumnDimension();
+            final double[][] product = new double[nRows][nCols];
+            for (int r = 0; r < nRows; r++) {
+                for (int c = 0; c < nCols; c++) {
+                    product[r][c] = data[r] * m.getEntry(r, c);
+                }
+            }
+            return new Array2DRowRealMatrix(product, false);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double[][] getData() {
+        final int dim = getRowDimension();
+        final double[][] out = new double[dim][dim];
+
+        for (int i = 0; i < dim; i++) {
+            out[i][i] = data[i];
+        }
+
+        return out;
+    }
+
+    /**
+     * Gets a reference to the underlying data array.
+     *
+     * @return 1-dimensional array of entries.
+     */
+    public double[] getDataRef() {
+        return data;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getEntry(final int row, final int column) throws OutOfRangeException {
+        MatrixUtils.checkMatrixIndex(this, row, column);
+        return row == column ? data[row] : 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws NumberIsTooLargeException if {@code row != column} and value is non-zero.
+     */
+    @Override
+    public void setEntry(final int row, final int column, final double value)
+            throws OutOfRangeException, NumberIsTooLargeException {
+        if (row == column) {
+            MatrixUtils.checkRowIndex(this, row);
+            data[row] = value;
+        } else {
+            ensureZero(value);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws NumberIsTooLargeException if {@code row != column} and increment is non-zero.
+     */
+    @Override
+    public void addToEntry(final int row, final int column, final double increment)
+            throws OutOfRangeException, NumberIsTooLargeException {
+        if (row == column) {
+            MatrixUtils.checkRowIndex(this, row);
+            data[row] += increment;
+        } else {
+            ensureZero(increment);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void multiplyEntry(final int row, final int column, final double factor)
+            throws OutOfRangeException {
+        // we don't care about non-diagonal elements for multiplication
+        if (row == column) {
+            MatrixUtils.checkRowIndex(this, row);
+            data[row] *= factor;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getRowDimension() {
+        return data.length;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getColumnDimension() {
+        return data.length;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double[] operate(final double[] v) throws DimensionMismatchException {
+        return multiply(new DiagonalMatrix(v, false)).getDataRef();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double[] preMultiply(final double[] v) throws DimensionMismatchException {
+        return operate(v);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector preMultiply(final RealVector v) throws DimensionMismatchException {
+        final double[] vectorData;
+        if (v instanceof ArrayRealVector) {
+            vectorData = ((ArrayRealVector) v).getDataRef();
+        } else {
+            vectorData = v.toArray();
+        }
+        return MatrixUtils.createRealVector(preMultiply(vectorData));
+    }
+
+    /**
+     * Ensure a value is zero.
+     *
+     * @param value value to check
+     * @exception NumberIsTooLargeException if value is not zero
+     */
+    private void ensureZero(final double value) throws NumberIsTooLargeException {
+        if (!Precision.equals(0.0, value, 1)) {
+            throw new NumberIsTooLargeException(FastMath.abs(value), 0, true);
+        }
+    }
+
+    /**
+     * Computes the inverse of this diagonal matrix.
+     *
+     * <p>Note: this method will use a singularity threshold of 0, use {@link #inverse(double)} if a
+     * different threshold is needed.
+     *
+     * @return the inverse of {@code m}
+     * @throws SingularMatrixException if the matrix is singular
+     * @since 3.3
+     */
+    public DiagonalMatrix inverse() throws SingularMatrixException {
+        return inverse(0);
+    }
+
+    /**
+     * Computes the inverse of this diagonal matrix.
+     *
+     * @param threshold Singularity threshold.
+     * @return the inverse of {@code m}
+     * @throws SingularMatrixException if the matrix is singular
+     * @since 3.3
+     */
+    public DiagonalMatrix inverse(double threshold) throws SingularMatrixException {
+        if (isSingular(threshold)) {
+            throw new SingularMatrixException();
+        }
+
+        final double[] result = new double[data.length];
+        for (int i = 0; i < data.length; i++) {
+            result[i] = 1.0 / data[i];
+        }
+        return new DiagonalMatrix(result, false);
+    }
+
+    /**
+     * Returns whether this diagonal matrix is singular, i.e. any diagonal entry is equal to {@code
+     * 0} within the given threshold.
+     *
+     * @param threshold Singularity threshold.
+     * @return {@code true} if the matrix is singular, {@code false} otherwise
+     * @since 3.3
+     */
+    public boolean isSingular(double threshold) {
+        for (int i = 0; i < data.length; i++) {
+            if (Precision.equals(data[i], 0.0, threshold)) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/EigenDecomposition.java b/src/main/java/org/apache/commons/math3/linear/EigenDecomposition.java
new file mode 100644
index 0000000..505897f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/EigenDecomposition.java
@@ -0,0 +1,968 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.complex.Complex;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.MathUnsupportedOperationException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.Precision;
+
+/**
+ * Calculates the eigen decomposition of a real matrix.
+ *
+ * <p>The eigen decomposition of matrix A is a set of two matrices: V and D such that A = V &times;
+ * D &times; V<sup>T</sup>. A, V and D are all m &times; m matrices.
+ *
+ * <p>This class is similar in spirit to the <code>EigenvalueDecomposition</code> class from the <a
+ * href="http://math.nist.gov/javanumerics/jama/">JAMA</a> library, with the following changes:
+ *
+ * <ul>
+ *   <li>a {@link #getVT() getVt} method has been added,
+ *   <li>two {@link #getRealEigenvalue(int) getRealEigenvalue} and {@link #getImagEigenvalue(int)
+ *       getImagEigenvalue} methods to pick up a single eigenvalue have been added,
+ *   <li>a {@link #getEigenvector(int) getEigenvector} method to pick up a single eigenvector has
+ *       been added,
+ *   <li>a {@link #getDeterminant() getDeterminant} method has been added.
+ *   <li>a {@link #getSolver() getSolver} method has been added.
+ * </ul>
+ *
+ * <p>As of 3.1, this class supports general real matrices (both symmetric and non-symmetric):
+ *
+ * <p>If A is symmetric, then A = V*D*V' where the eigenvalue matrix D is diagonal and the
+ * eigenvector matrix V is orthogonal, i.e. A = V.multiply(D.multiply(V.transpose())) and
+ * V.multiply(V.transpose()) equals the identity matrix.
+ *
+ * <p>If A is not symmetric, then the eigenvalue matrix D is block diagonal with the real
+ * eigenvalues in 1-by-1 blocks and any complex eigenvalues, lambda + i*mu, in 2-by-2 blocks:
+ *
+ * <pre>
+ *    [lambda, mu    ]
+ *    [   -mu, lambda]
+ * </pre>
+ *
+ * The columns of V represent the eigenvectors in the sense that A*V = V*D, i.e. A.multiply(V)
+ * equals V.multiply(D). The matrix V may be badly conditioned, or even singular, so the validity of
+ * the equation A = V*D*inverse(V) depends upon the condition of V.
+ *
+ * <p>This implementation is based on the paper by A. Drubrulle, R.S. Martin and J.H. Wilkinson "The
+ * Implicit QL Algorithm" in Wilksinson and Reinsch (1971) Handbook for automatic computation, vol.
+ * 2, Linear algebra, Springer-Verlag, New-York
+ *
+ * @see <a href="http://mathworld.wolfram.com/EigenDecomposition.html">MathWorld</a>
+ * @see <a href="http://en.wikipedia.org/wiki/Eigendecomposition_of_a_matrix">Wikipedia</a>
+ * @since 2.0 (changed to concrete class in 3.0)
+ */
+public class EigenDecomposition {
+    /** Internally used epsilon criteria. */
+    private static final double EPSILON = 1e-12;
+
+    /** Maximum number of iterations accepted in the implicit QL transformation */
+    private byte maxIter = 30;
+
+    /** Main diagonal of the tridiagonal matrix. */
+    private double[] main;
+
+    /** Secondary diagonal of the tridiagonal matrix. */
+    private double[] secondary;
+
+    /** Transformer to tridiagonal (may be null if matrix is already tridiagonal). */
+    private TriDiagonalTransformer transformer;
+
+    /** Real part of the realEigenvalues. */
+    private double[] realEigenvalues;
+
+    /** Imaginary part of the realEigenvalues. */
+    private double[] imagEigenvalues;
+
+    /** Eigenvectors. */
+    private ArrayRealVector[] eigenvectors;
+
+    /** Cached value of V. */
+    private RealMatrix cachedV;
+
+    /** Cached value of D. */
+    private RealMatrix cachedD;
+
+    /** Cached value of Vt. */
+    private RealMatrix cachedVt;
+
+    /** Whether the matrix is symmetric. */
+    private final boolean isSymmetric;
+
+    /**
+     * Calculates the eigen decomposition of the given real matrix.
+     *
+     * <p>Supports decomposition of a general matrix since 3.1.
+     *
+     * @param matrix Matrix to decompose.
+     * @throws MaxCountExceededException if the algorithm fails to converge.
+     * @throws MathArithmeticException if the decomposition of a general matrix results in a matrix
+     *     with zero norm
+     * @since 3.1
+     */
+    public EigenDecomposition(final RealMatrix matrix) throws MathArithmeticException {
+        final double symTol =
+                10 * matrix.getRowDimension() * matrix.getColumnDimension() * Precision.EPSILON;
+        isSymmetric = MatrixUtils.isSymmetric(matrix, symTol);
+        if (isSymmetric) {
+            transformToTridiagonal(matrix);
+            findEigenVectors(transformer.getQ().getData());
+        } else {
+            final SchurTransformer t = transformToSchur(matrix);
+            findEigenVectorsFromSchur(t);
+        }
+    }
+
+    /**
+     * Calculates the eigen decomposition of the given real matrix.
+     *
+     * @param matrix Matrix to decompose.
+     * @param splitTolerance Dummy parameter (present for backward compatibility only).
+     * @throws MathArithmeticException if the decomposition of a general matrix results in a matrix
+     *     with zero norm
+     * @throws MaxCountExceededException if the algorithm fails to converge.
+     * @deprecated in 3.1 (to be removed in 4.0) due to unused parameter
+     */
+    @Deprecated
+    public EigenDecomposition(final RealMatrix matrix, final double splitTolerance)
+            throws MathArithmeticException {
+        this(matrix);
+    }
+
+    /**
+     * Calculates the eigen decomposition of the symmetric tridiagonal matrix. The Householder
+     * matrix is assumed to be the identity matrix.
+     *
+     * @param main Main diagonal of the symmetric tridiagonal form.
+     * @param secondary Secondary of the tridiagonal form.
+     * @throws MaxCountExceededException if the algorithm fails to converge.
+     * @since 3.1
+     */
+    public EigenDecomposition(final double[] main, final double[] secondary) {
+        isSymmetric = true;
+        this.main = main.clone();
+        this.secondary = secondary.clone();
+        transformer = null;
+        final int size = main.length;
+        final double[][] z = new double[size][size];
+        for (int i = 0; i < size; i++) {
+            z[i][i] = 1.0;
+        }
+        findEigenVectors(z);
+    }
+
+    /**
+     * Calculates the eigen decomposition of the symmetric tridiagonal matrix. The Householder
+     * matrix is assumed to be the identity matrix.
+     *
+     * @param main Main diagonal of the symmetric tridiagonal form.
+     * @param secondary Secondary of the tridiagonal form.
+     * @param splitTolerance Dummy parameter (present for backward compatibility only).
+     * @throws MaxCountExceededException if the algorithm fails to converge.
+     * @deprecated in 3.1 (to be removed in 4.0) due to unused parameter
+     */
+    @Deprecated
+    public EigenDecomposition(
+            final double[] main, final double[] secondary, final double splitTolerance) {
+        this(main, secondary);
+    }
+
+    /**
+     * Gets the matrix V of the decomposition. V is an orthogonal matrix, i.e. its transpose is also
+     * its inverse. The columns of V are the eigenvectors of the original matrix. No assumption is
+     * made about the orientation of the system axes formed by the columns of V (e.g. in a
+     * 3-dimension space, V can form a left- or right-handed system).
+     *
+     * @return the V matrix.
+     */
+    public RealMatrix getV() {
+
+        if (cachedV == null) {
+            final int m = eigenvectors.length;
+            cachedV = MatrixUtils.createRealMatrix(m, m);
+            for (int k = 0; k < m; ++k) {
+                cachedV.setColumnVector(k, eigenvectors[k]);
+            }
+        }
+        // return the cached matrix
+        return cachedV;
+    }
+
+    /**
+     * Gets the block diagonal matrix D of the decomposition. D is a block diagonal matrix. Real
+     * eigenvalues are on the diagonal while complex values are on 2x2 blocks { {real +imaginary},
+     * {-imaginary, real} }.
+     *
+     * @return the D matrix.
+     * @see #getRealEigenvalues()
+     * @see #getImagEigenvalues()
+     */
+    public RealMatrix getD() {
+
+        if (cachedD == null) {
+            // cache the matrix for subsequent calls
+            cachedD = MatrixUtils.createRealDiagonalMatrix(realEigenvalues);
+
+            for (int i = 0; i < imagEigenvalues.length; i++) {
+                if (Precision.compareTo(imagEigenvalues[i], 0.0, EPSILON) > 0) {
+                    cachedD.setEntry(i, i + 1, imagEigenvalues[i]);
+                } else if (Precision.compareTo(imagEigenvalues[i], 0.0, EPSILON) < 0) {
+                    cachedD.setEntry(i, i - 1, imagEigenvalues[i]);
+                }
+            }
+        }
+        return cachedD;
+    }
+
+    /**
+     * Gets the transpose of the matrix V of the decomposition. V is an orthogonal matrix, i.e. its
+     * transpose is also its inverse. The columns of V are the eigenvectors of the original matrix.
+     * No assumption is made about the orientation of the system axes formed by the columns of V
+     * (e.g. in a 3-dimension space, V can form a left- or right-handed system).
+     *
+     * @return the transpose of the V matrix.
+     */
+    public RealMatrix getVT() {
+
+        if (cachedVt == null) {
+            final int m = eigenvectors.length;
+            cachedVt = MatrixUtils.createRealMatrix(m, m);
+            for (int k = 0; k < m; ++k) {
+                cachedVt.setRowVector(k, eigenvectors[k]);
+            }
+        }
+
+        // return the cached matrix
+        return cachedVt;
+    }
+
+    /**
+     * Returns whether the calculated eigen values are complex or real.
+     *
+     * <p>The method performs a zero check for each element of the {@link #getImagEigenvalues()}
+     * array and returns {@code true} if any element is not equal to zero.
+     *
+     * @return {@code true} if the eigen values are complex, {@code false} otherwise
+     * @since 3.1
+     */
+    public boolean hasComplexEigenvalues() {
+        for (int i = 0; i < imagEigenvalues.length; i++) {
+            if (!Precision.equals(imagEigenvalues[i], 0.0, EPSILON)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Gets a copy of the real parts of the eigenvalues of the original matrix.
+     *
+     * @return a copy of the real parts of the eigenvalues of the original matrix.
+     * @see #getD()
+     * @see #getRealEigenvalue(int)
+     * @see #getImagEigenvalues()
+     */
+    public double[] getRealEigenvalues() {
+        return realEigenvalues.clone();
+    }
+
+    /**
+     * Returns the real part of the i<sup>th</sup> eigenvalue of the original matrix.
+     *
+     * @param i index of the eigenvalue (counting from 0)
+     * @return real part of the i<sup>th</sup> eigenvalue of the original matrix.
+     * @see #getD()
+     * @see #getRealEigenvalues()
+     * @see #getImagEigenvalue(int)
+     */
+    public double getRealEigenvalue(final int i) {
+        return realEigenvalues[i];
+    }
+
+    /**
+     * Gets a copy of the imaginary parts of the eigenvalues of the original matrix.
+     *
+     * @return a copy of the imaginary parts of the eigenvalues of the original matrix.
+     * @see #getD()
+     * @see #getImagEigenvalue(int)
+     * @see #getRealEigenvalues()
+     */
+    public double[] getImagEigenvalues() {
+        return imagEigenvalues.clone();
+    }
+
+    /**
+     * Gets the imaginary part of the i<sup>th</sup> eigenvalue of the original matrix.
+     *
+     * @param i Index of the eigenvalue (counting from 0).
+     * @return the imaginary part of the i<sup>th</sup> eigenvalue of the original matrix.
+     * @see #getD()
+     * @see #getImagEigenvalues()
+     * @see #getRealEigenvalue(int)
+     */
+    public double getImagEigenvalue(final int i) {
+        return imagEigenvalues[i];
+    }
+
+    /**
+     * Gets a copy of the i<sup>th</sup> eigenvector of the original matrix.
+     *
+     * @param i Index of the eigenvector (counting from 0).
+     * @return a copy of the i<sup>th</sup> eigenvector of the original matrix.
+     * @see #getD()
+     */
+    public RealVector getEigenvector(final int i) {
+        return eigenvectors[i].copy();
+    }
+
+    /**
+     * Computes the determinant of the matrix.
+     *
+     * @return the determinant of the matrix.
+     */
+    public double getDeterminant() {
+        double determinant = 1;
+        for (double lambda : realEigenvalues) {
+            determinant *= lambda;
+        }
+        return determinant;
+    }
+
+    /**
+     * Computes the square-root of the matrix. This implementation assumes that the matrix is
+     * symmetric and positive definite.
+     *
+     * @return the square-root of the matrix.
+     * @throws MathUnsupportedOperationException if the matrix is not symmetric or not positive
+     *     definite.
+     * @since 3.1
+     */
+    public RealMatrix getSquareRoot() {
+        if (!isSymmetric) {
+            throw new MathUnsupportedOperationException();
+        }
+
+        final double[] sqrtEigenValues = new double[realEigenvalues.length];
+        for (int i = 0; i < realEigenvalues.length; i++) {
+            final double eigen = realEigenvalues[i];
+            if (eigen <= 0) {
+                throw new MathUnsupportedOperationException();
+            }
+            sqrtEigenValues[i] = FastMath.sqrt(eigen);
+        }
+        final RealMatrix sqrtEigen = MatrixUtils.createRealDiagonalMatrix(sqrtEigenValues);
+        final RealMatrix v = getV();
+        final RealMatrix vT = getVT();
+
+        return v.multiply(sqrtEigen).multiply(vT);
+    }
+
+    /**
+     * Gets a solver for finding the A &times; X = B solution in exact linear sense.
+     *
+     * <p>Since 3.1, eigen decomposition of a general matrix is supported, but the {@link
+     * DecompositionSolver} only supports real eigenvalues.
+     *
+     * @return a solver
+     * @throws MathUnsupportedOperationException if the decomposition resulted in complex
+     *     eigenvalues
+     */
+    public DecompositionSolver getSolver() {
+        if (hasComplexEigenvalues()) {
+            throw new MathUnsupportedOperationException();
+        }
+        return new Solver(realEigenvalues, imagEigenvalues, eigenvectors);
+    }
+
+    /** Specialized solver. */
+    private static class Solver implements DecompositionSolver {
+        /** Real part of the realEigenvalues. */
+        private double[] realEigenvalues;
+
+        /** Imaginary part of the realEigenvalues. */
+        private double[] imagEigenvalues;
+
+        /** Eigenvectors. */
+        private final ArrayRealVector[] eigenvectors;
+
+        /**
+         * Builds a solver from decomposed matrix.
+         *
+         * @param realEigenvalues Real parts of the eigenvalues.
+         * @param imagEigenvalues Imaginary parts of the eigenvalues.
+         * @param eigenvectors Eigenvectors.
+         */
+        private Solver(
+                final double[] realEigenvalues,
+                final double[] imagEigenvalues,
+                final ArrayRealVector[] eigenvectors) {
+            this.realEigenvalues = realEigenvalues;
+            this.imagEigenvalues = imagEigenvalues;
+            this.eigenvectors = eigenvectors;
+        }
+
+        /**
+         * Solves the linear equation A &times; X = B for symmetric matrices A.
+         *
+         * <p>This method only finds exact linear solutions, i.e. solutions for which ||A &times; X
+         * - B|| is exactly 0.
+         *
+         * @param b Right-hand side of the equation A &times; X = B.
+         * @return a Vector X that minimizes the two norm of A &times; X - B.
+         * @throws DimensionMismatchException if the matrices dimensions do not match.
+         * @throws SingularMatrixException if the decomposed matrix is singular.
+         */
+        public RealVector solve(final RealVector b) {
+            if (!isNonSingular()) {
+                throw new SingularMatrixException();
+            }
+
+            final int m = realEigenvalues.length;
+            if (b.getDimension() != m) {
+                throw new DimensionMismatchException(b.getDimension(), m);
+            }
+
+            final double[] bp = new double[m];
+            for (int i = 0; i < m; ++i) {
+                final ArrayRealVector v = eigenvectors[i];
+                final double[] vData = v.getDataRef();
+                final double s = v.dotProduct(b) / realEigenvalues[i];
+                for (int j = 0; j < m; ++j) {
+                    bp[j] += s * vData[j];
+                }
+            }
+
+            return new ArrayRealVector(bp, false);
+        }
+
+        /** {@inheritDoc} */
+        public RealMatrix solve(RealMatrix b) {
+
+            if (!isNonSingular()) {
+                throw new SingularMatrixException();
+            }
+
+            final int m = realEigenvalues.length;
+            if (b.getRowDimension() != m) {
+                throw new DimensionMismatchException(b.getRowDimension(), m);
+            }
+
+            final int nColB = b.getColumnDimension();
+            final double[][] bp = new double[m][nColB];
+            final double[] tmpCol = new double[m];
+            for (int k = 0; k < nColB; ++k) {
+                for (int i = 0; i < m; ++i) {
+                    tmpCol[i] = b.getEntry(i, k);
+                    bp[i][k] = 0;
+                }
+                for (int i = 0; i < m; ++i) {
+                    final ArrayRealVector v = eigenvectors[i];
+                    final double[] vData = v.getDataRef();
+                    double s = 0;
+                    for (int j = 0; j < m; ++j) {
+                        s += v.getEntry(j) * tmpCol[j];
+                    }
+                    s /= realEigenvalues[i];
+                    for (int j = 0; j < m; ++j) {
+                        bp[j][k] += s * vData[j];
+                    }
+                }
+            }
+
+            return new Array2DRowRealMatrix(bp, false);
+        }
+
+        /**
+         * Checks whether the decomposed matrix is non-singular.
+         *
+         * @return true if the decomposed matrix is non-singular.
+         */
+        public boolean isNonSingular() {
+            double largestEigenvalueNorm = 0.0;
+            // Looping over all values (in case they are not sorted in decreasing
+            // order of their norm).
+            for (int i = 0; i < realEigenvalues.length; ++i) {
+                largestEigenvalueNorm = FastMath.max(largestEigenvalueNorm, eigenvalueNorm(i));
+            }
+            // Corner case: zero matrix, all exactly 0 eigenvalues
+            if (largestEigenvalueNorm == 0.0) {
+                return false;
+            }
+            for (int i = 0; i < realEigenvalues.length; ++i) {
+                // Looking for eigenvalues that are 0, where we consider anything much much smaller
+                // than the largest eigenvalue to be effectively 0.
+                if (Precision.equals(eigenvalueNorm(i) / largestEigenvalueNorm, 0, EPSILON)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        /**
+         * @param i which eigenvalue to find the norm of
+         * @return the norm of ith (complex) eigenvalue.
+         */
+        private double eigenvalueNorm(int i) {
+            final double re = realEigenvalues[i];
+            final double im = imagEigenvalues[i];
+            return FastMath.sqrt(re * re + im * im);
+        }
+
+        /**
+         * Get the inverse of the decomposed matrix.
+         *
+         * @return the inverse matrix.
+         * @throws SingularMatrixException if the decomposed matrix is singular.
+         */
+        public RealMatrix getInverse() {
+            if (!isNonSingular()) {
+                throw new SingularMatrixException();
+            }
+
+            final int m = realEigenvalues.length;
+            final double[][] invData = new double[m][m];
+
+            for (int i = 0; i < m; ++i) {
+                final double[] invI = invData[i];
+                for (int j = 0; j < m; ++j) {
+                    double invIJ = 0;
+                    for (int k = 0; k < m; ++k) {
+                        final double[] vK = eigenvectors[k].getDataRef();
+                        invIJ += vK[i] * vK[j] / realEigenvalues[k];
+                    }
+                    invI[j] = invIJ;
+                }
+            }
+            return MatrixUtils.createRealMatrix(invData);
+        }
+    }
+
+    /**
+     * Transforms the matrix to tridiagonal form.
+     *
+     * @param matrix Matrix to transform.
+     */
+    private void transformToTridiagonal(final RealMatrix matrix) {
+        // transform the matrix to tridiagonal
+        transformer = new TriDiagonalTransformer(matrix);
+        main = transformer.getMainDiagonalRef();
+        secondary = transformer.getSecondaryDiagonalRef();
+    }
+
+    /**
+     * Find eigenvalues and eigenvectors (Dubrulle et al., 1971)
+     *
+     * @param householderMatrix Householder matrix of the transformation to tridiagonal form.
+     */
+    private void findEigenVectors(final double[][] householderMatrix) {
+        final double[][] z = householderMatrix.clone();
+        final int n = main.length;
+        realEigenvalues = new double[n];
+        imagEigenvalues = new double[n];
+        final double[] e = new double[n];
+        for (int i = 0; i < n - 1; i++) {
+            realEigenvalues[i] = main[i];
+            e[i] = secondary[i];
+        }
+        realEigenvalues[n - 1] = main[n - 1];
+        e[n - 1] = 0;
+
+        // Determine the largest main and secondary value in absolute term.
+        double maxAbsoluteValue = 0;
+        for (int i = 0; i < n; i++) {
+            if (FastMath.abs(realEigenvalues[i]) > maxAbsoluteValue) {
+                maxAbsoluteValue = FastMath.abs(realEigenvalues[i]);
+            }
+            if (FastMath.abs(e[i]) > maxAbsoluteValue) {
+                maxAbsoluteValue = FastMath.abs(e[i]);
+            }
+        }
+        // Make null any main and secondary value too small to be significant
+        if (maxAbsoluteValue != 0) {
+            for (int i = 0; i < n; i++) {
+                if (FastMath.abs(realEigenvalues[i]) <= Precision.EPSILON * maxAbsoluteValue) {
+                    realEigenvalues[i] = 0;
+                }
+                if (FastMath.abs(e[i]) <= Precision.EPSILON * maxAbsoluteValue) {
+                    e[i] = 0;
+                }
+            }
+        }
+
+        for (int j = 0; j < n; j++) {
+            int its = 0;
+            int m;
+            do {
+                for (m = j; m < n - 1; m++) {
+                    double delta =
+                            FastMath.abs(realEigenvalues[m]) + FastMath.abs(realEigenvalues[m + 1]);
+                    if (FastMath.abs(e[m]) + delta == delta) {
+                        break;
+                    }
+                }
+                if (m != j) {
+                    if (its == maxIter) {
+                        throw new MaxCountExceededException(
+                                LocalizedFormats.CONVERGENCE_FAILED, maxIter);
+                    }
+                    its++;
+                    double q = (realEigenvalues[j + 1] - realEigenvalues[j]) / (2 * e[j]);
+                    double t = FastMath.sqrt(1 + q * q);
+                    if (q < 0.0) {
+                        q = realEigenvalues[m] - realEigenvalues[j] + e[j] / (q - t);
+                    } else {
+                        q = realEigenvalues[m] - realEigenvalues[j] + e[j] / (q + t);
+                    }
+                    double u = 0.0;
+                    double s = 1.0;
+                    double c = 1.0;
+                    int i;
+                    for (i = m - 1; i >= j; i--) {
+                        double p = s * e[i];
+                        double h = c * e[i];
+                        if (FastMath.abs(p) >= FastMath.abs(q)) {
+                            c = q / p;
+                            t = FastMath.sqrt(c * c + 1.0);
+                            e[i + 1] = p * t;
+                            s = 1.0 / t;
+                            c *= s;
+                        } else {
+                            s = p / q;
+                            t = FastMath.sqrt(s * s + 1.0);
+                            e[i + 1] = q * t;
+                            c = 1.0 / t;
+                            s *= c;
+                        }
+                        if (e[i + 1] == 0.0) {
+                            realEigenvalues[i + 1] -= u;
+                            e[m] = 0.0;
+                            break;
+                        }
+                        q = realEigenvalues[i + 1] - u;
+                        t = (realEigenvalues[i] - q) * s + 2.0 * c * h;
+                        u = s * t;
+                        realEigenvalues[i + 1] = q + u;
+                        q = c * t - h;
+                        for (int ia = 0; ia < n; ia++) {
+                            p = z[ia][i + 1];
+                            z[ia][i + 1] = s * z[ia][i] + c * p;
+                            z[ia][i] = c * z[ia][i] - s * p;
+                        }
+                    }
+                    if (t == 0.0 && i >= j) {
+                        continue;
+                    }
+                    realEigenvalues[j] -= u;
+                    e[j] = q;
+                    e[m] = 0.0;
+                }
+            } while (m != j);
+        }
+
+        // Sort the eigen values (and vectors) in increase order
+        for (int i = 0; i < n; i++) {
+            int k = i;
+            double p = realEigenvalues[i];
+            for (int j = i + 1; j < n; j++) {
+                if (realEigenvalues[j] > p) {
+                    k = j;
+                    p = realEigenvalues[j];
+                }
+            }
+            if (k != i) {
+                realEigenvalues[k] = realEigenvalues[i];
+                realEigenvalues[i] = p;
+                for (int j = 0; j < n; j++) {
+                    p = z[j][i];
+                    z[j][i] = z[j][k];
+                    z[j][k] = p;
+                }
+            }
+        }
+
+        // Determine the largest eigen value in absolute term.
+        maxAbsoluteValue = 0;
+        for (int i = 0; i < n; i++) {
+            if (FastMath.abs(realEigenvalues[i]) > maxAbsoluteValue) {
+                maxAbsoluteValue = FastMath.abs(realEigenvalues[i]);
+            }
+        }
+        // Make null any eigen value too small to be significant
+        if (maxAbsoluteValue != 0.0) {
+            for (int i = 0; i < n; i++) {
+                if (FastMath.abs(realEigenvalues[i]) < Precision.EPSILON * maxAbsoluteValue) {
+                    realEigenvalues[i] = 0;
+                }
+            }
+        }
+        eigenvectors = new ArrayRealVector[n];
+        final double[] tmp = new double[n];
+        for (int i = 0; i < n; i++) {
+            for (int j = 0; j < n; j++) {
+                tmp[j] = z[j][i];
+            }
+            eigenvectors[i] = new ArrayRealVector(tmp);
+        }
+    }
+
+    /**
+     * Transforms the matrix to Schur form and calculates the eigenvalues.
+     *
+     * @param matrix Matrix to transform.
+     * @return the {@link SchurTransformer Shur transform} for this matrix
+     */
+    private SchurTransformer transformToSchur(final RealMatrix matrix) {
+        final SchurTransformer schurTransform = new SchurTransformer(matrix);
+        final double[][] matT = schurTransform.getT().getData();
+
+        realEigenvalues = new double[matT.length];
+        imagEigenvalues = new double[matT.length];
+
+        for (int i = 0; i < realEigenvalues.length; i++) {
+            if (i == (realEigenvalues.length - 1)
+                    || Precision.equals(matT[i + 1][i], 0.0, EPSILON)) {
+                realEigenvalues[i] = matT[i][i];
+            } else {
+                final double x = matT[i + 1][i + 1];
+                final double p = 0.5 * (matT[i][i] - x);
+                final double z =
+                        FastMath.sqrt(FastMath.abs(p * p + matT[i + 1][i] * matT[i][i + 1]));
+                realEigenvalues[i] = x + p;
+                imagEigenvalues[i] = z;
+                realEigenvalues[i + 1] = x + p;
+                imagEigenvalues[i + 1] = -z;
+                i++;
+            }
+        }
+        return schurTransform;
+    }
+
+    /**
+     * Performs a division of two complex numbers.
+     *
+     * @param xr real part of the first number
+     * @param xi imaginary part of the first number
+     * @param yr real part of the second number
+     * @param yi imaginary part of the second number
+     * @return result of the complex division
+     */
+    private Complex cdiv(final double xr, final double xi, final double yr, final double yi) {
+        return new Complex(xr, xi).divide(new Complex(yr, yi));
+    }
+
+    /**
+     * Find eigenvectors from a matrix transformed to Schur form.
+     *
+     * @param schur the schur transformation of the matrix
+     * @throws MathArithmeticException if the Schur form has a norm of zero
+     */
+    private void findEigenVectorsFromSchur(final SchurTransformer schur)
+            throws MathArithmeticException {
+        final double[][] matrixT = schur.getT().getData();
+        final double[][] matrixP = schur.getP().getData();
+
+        final int n = matrixT.length;
+
+        // compute matrix norm
+        double norm = 0.0;
+        for (int i = 0; i < n; i++) {
+            for (int j = FastMath.max(i - 1, 0); j < n; j++) {
+                norm += FastMath.abs(matrixT[i][j]);
+            }
+        }
+
+        // we can not handle a matrix with zero norm
+        if (Precision.equals(norm, 0.0, EPSILON)) {
+            throw new MathArithmeticException(LocalizedFormats.ZERO_NORM);
+        }
+
+        // Backsubstitute to find vectors of upper triangular form
+
+        double r = 0.0;
+        double s = 0.0;
+        double z = 0.0;
+
+        for (int idx = n - 1; idx >= 0; idx--) {
+            double p = realEigenvalues[idx];
+            double q = imagEigenvalues[idx];
+
+            if (Precision.equals(q, 0.0)) {
+                // Real vector
+                int l = idx;
+                matrixT[idx][idx] = 1.0;
+                for (int i = idx - 1; i >= 0; i--) {
+                    double w = matrixT[i][i] - p;
+                    r = 0.0;
+                    for (int j = l; j <= idx; j++) {
+                        r += matrixT[i][j] * matrixT[j][idx];
+                    }
+                    if (Precision.compareTo(imagEigenvalues[i], 0.0, EPSILON) < 0) {
+                        z = w;
+                        s = r;
+                    } else {
+                        l = i;
+                        if (Precision.equals(imagEigenvalues[i], 0.0)) {
+                            if (w != 0.0) {
+                                matrixT[i][idx] = -r / w;
+                            } else {
+                                matrixT[i][idx] = -r / (Precision.EPSILON * norm);
+                            }
+                        } else {
+                            // Solve real equations
+                            double x = matrixT[i][i + 1];
+                            double y = matrixT[i + 1][i];
+                            q =
+                                    (realEigenvalues[i] - p) * (realEigenvalues[i] - p)
+                                            + imagEigenvalues[i] * imagEigenvalues[i];
+                            double t = (x * s - z * r) / q;
+                            matrixT[i][idx] = t;
+                            if (FastMath.abs(x) > FastMath.abs(z)) {
+                                matrixT[i + 1][idx] = (-r - w * t) / x;
+                            } else {
+                                matrixT[i + 1][idx] = (-s - y * t) / z;
+                            }
+                        }
+
+                        // Overflow control
+                        double t = FastMath.abs(matrixT[i][idx]);
+                        if ((Precision.EPSILON * t) * t > 1) {
+                            for (int j = i; j <= idx; j++) {
+                                matrixT[j][idx] /= t;
+                            }
+                        }
+                    }
+                }
+            } else if (q < 0.0) {
+                // Complex vector
+                int l = idx - 1;
+
+                // Last vector component imaginary so matrix is triangular
+                if (FastMath.abs(matrixT[idx][idx - 1]) > FastMath.abs(matrixT[idx - 1][idx])) {
+                    matrixT[idx - 1][idx - 1] = q / matrixT[idx][idx - 1];
+                    matrixT[idx - 1][idx] = -(matrixT[idx][idx] - p) / matrixT[idx][idx - 1];
+                } else {
+                    final Complex result =
+                            cdiv(0.0, -matrixT[idx - 1][idx], matrixT[idx - 1][idx - 1] - p, q);
+                    matrixT[idx - 1][idx - 1] = result.getReal();
+                    matrixT[idx - 1][idx] = result.getImaginary();
+                }
+
+                matrixT[idx][idx - 1] = 0.0;
+                matrixT[idx][idx] = 1.0;
+
+                for (int i = idx - 2; i >= 0; i--) {
+                    double ra = 0.0;
+                    double sa = 0.0;
+                    for (int j = l; j <= idx; j++) {
+                        ra += matrixT[i][j] * matrixT[j][idx - 1];
+                        sa += matrixT[i][j] * matrixT[j][idx];
+                    }
+                    double w = matrixT[i][i] - p;
+
+                    if (Precision.compareTo(imagEigenvalues[i], 0.0, EPSILON) < 0) {
+                        z = w;
+                        r = ra;
+                        s = sa;
+                    } else {
+                        l = i;
+                        if (Precision.equals(imagEigenvalues[i], 0.0)) {
+                            final Complex c = cdiv(-ra, -sa, w, q);
+                            matrixT[i][idx - 1] = c.getReal();
+                            matrixT[i][idx] = c.getImaginary();
+                        } else {
+                            // Solve complex equations
+                            double x = matrixT[i][i + 1];
+                            double y = matrixT[i + 1][i];
+                            double vr =
+                                    (realEigenvalues[i] - p) * (realEigenvalues[i] - p)
+                                            + imagEigenvalues[i] * imagEigenvalues[i]
+                                            - q * q;
+                            final double vi = (realEigenvalues[i] - p) * 2.0 * q;
+                            if (Precision.equals(vr, 0.0) && Precision.equals(vi, 0.0)) {
+                                vr =
+                                        Precision.EPSILON
+                                                * norm
+                                                * (FastMath.abs(w)
+                                                        + FastMath.abs(q)
+                                                        + FastMath.abs(x)
+                                                        + FastMath.abs(y)
+                                                        + FastMath.abs(z));
+                            }
+                            final Complex c =
+                                    cdiv(x * r - z * ra + q * sa, x * s - z * sa - q * ra, vr, vi);
+                            matrixT[i][idx - 1] = c.getReal();
+                            matrixT[i][idx] = c.getImaginary();
+
+                            if (FastMath.abs(x) > (FastMath.abs(z) + FastMath.abs(q))) {
+                                matrixT[i + 1][idx - 1] =
+                                        (-ra - w * matrixT[i][idx - 1] + q * matrixT[i][idx]) / x;
+                                matrixT[i + 1][idx] =
+                                        (-sa - w * matrixT[i][idx] - q * matrixT[i][idx - 1]) / x;
+                            } else {
+                                final Complex c2 =
+                                        cdiv(
+                                                -r - y * matrixT[i][idx - 1],
+                                                -s - y * matrixT[i][idx],
+                                                z,
+                                                q);
+                                matrixT[i + 1][idx - 1] = c2.getReal();
+                                matrixT[i + 1][idx] = c2.getImaginary();
+                            }
+                        }
+
+                        // Overflow control
+                        double t =
+                                FastMath.max(
+                                        FastMath.abs(matrixT[i][idx - 1]),
+                                        FastMath.abs(matrixT[i][idx]));
+                        if ((Precision.EPSILON * t) * t > 1) {
+                            for (int j = i; j <= idx; j++) {
+                                matrixT[j][idx - 1] /= t;
+                                matrixT[j][idx] /= t;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        // Back transformation to get eigenvectors of original matrix
+        for (int j = n - 1; j >= 0; j--) {
+            for (int i = 0; i <= n - 1; i++) {
+                z = 0.0;
+                for (int k = 0; k <= FastMath.min(j, n - 1); k++) {
+                    z += matrixP[i][k] * matrixT[k][j];
+                }
+                matrixP[i][j] = z;
+            }
+        }
+
+        eigenvectors = new ArrayRealVector[n];
+        final double[] tmp = new double[n];
+        for (int i = 0; i < n; i++) {
+            for (int j = 0; j < n; j++) {
+                tmp[j] = matrixP[j][i];
+            }
+            eigenvectors[i] = new ArrayRealVector(tmp);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/FieldDecompositionSolver.java b/src/main/java/org/apache/commons/math3/linear/FieldDecompositionSolver.java
new file mode 100644
index 0000000..ed5e863
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/FieldDecompositionSolver.java
@@ -0,0 +1,79 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.FieldElement;
+
+/**
+ * Interface handling decomposition algorithms that can solve A &times; X = B.
+ *
+ * <p>Decomposition algorithms decompose an A matrix has a product of several specific matrices from
+ * which they can solve A &times; X = B in least squares sense: they find X such that ||A &times; X
+ * - B|| is minimal.
+ *
+ * <p>Some solvers like {@link FieldLUDecomposition} can only find the solution for square matrices
+ * and when the solution is an exact linear solution, i.e. when ||A &times; X - B|| is exactly 0.
+ * Other solvers can also find solutions with non-square matrix A and with non-null minimal norm. If
+ * an exact linear solution exists it is also the minimal norm solution.
+ *
+ * @param <T> the type of the field elements
+ * @since 2.0
+ */
+public interface FieldDecompositionSolver<T extends FieldElement<T>> {
+
+    /**
+     * Solve the linear equation A &times; X = B for matrices A.
+     *
+     * <p>The A matrix is implicit, it is provided by the underlying decomposition algorithm.
+     *
+     * @param b right-hand side of the equation A &times; X = B
+     * @return a vector X that minimizes the two norm of A &times; X - B
+     * @throws org.apache.commons.math3.exception.DimensionMismatchException if the matrices
+     *     dimensions do not match.
+     * @throws SingularMatrixException if the decomposed matrix is singular.
+     */
+    FieldVector<T> solve(final FieldVector<T> b);
+
+    /**
+     * Solve the linear equation A &times; X = B for matrices A.
+     *
+     * <p>The A matrix is implicit, it is provided by the underlying decomposition algorithm.
+     *
+     * @param b right-hand side of the equation A &times; X = B
+     * @return a matrix X that minimizes the two norm of A &times; X - B
+     * @throws org.apache.commons.math3.exception.DimensionMismatchException if the matrices
+     *     dimensions do not match.
+     * @throws SingularMatrixException if the decomposed matrix is singular.
+     */
+    FieldMatrix<T> solve(final FieldMatrix<T> b);
+
+    /**
+     * Check if the decomposed matrix is non-singular.
+     *
+     * @return true if the decomposed matrix is non-singular
+     */
+    boolean isNonSingular();
+
+    /**
+     * Get the inverse (or pseudo-inverse) of the decomposed matrix.
+     *
+     * @return inverse matrix
+     * @throws SingularMatrixException if the decomposed matrix is singular.
+     */
+    FieldMatrix<T> getInverse();
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/FieldLUDecomposition.java b/src/main/java/org/apache/commons/math3/linear/FieldLUDecomposition.java
new file mode 100644
index 0000000..4976651
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/FieldLUDecomposition.java
@@ -0,0 +1,461 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.FieldElement;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * Calculates the LUP-decomposition of a square matrix.
+ *
+ * <p>The LUP-decomposition of a matrix A consists of three matrices L, U and P that satisfy: PA =
+ * LU, L is lower triangular, and U is upper triangular and P is a permutation matrix. All matrices
+ * are m&times;m.
+ *
+ * <p>Since {@link FieldElement field elements} do not provide an ordering operator, the permutation
+ * matrix is computed here only in order to avoid a zero pivot element, no attempt is done to get
+ * the largest pivot element.
+ *
+ * <p>This class is based on the class with similar name from the <a
+ * href="http://math.nist.gov/javanumerics/jama/">JAMA</a> library.
+ *
+ * <ul>
+ *   <li>a {@link #getP() getP} method has been added,
+ *   <li>the {@code det} method has been renamed as {@link #getDeterminant() getDeterminant},
+ *   <li>the {@code getDoublePivot} method has been removed (but the int based {@link #getPivot()
+ *       getPivot} method has been kept),
+ *   <li>the {@code solve} and {@code isNonSingular} methods have been replaced by a {@link
+ *       #getSolver() getSolver} method and the equivalent methods provided by the returned {@link
+ *       DecompositionSolver}.
+ * </ul>
+ *
+ * @param <T> the type of the field elements
+ * @see <a href="http://mathworld.wolfram.com/LUDecomposition.html">MathWorld</a>
+ * @see <a href="http://en.wikipedia.org/wiki/LU_decomposition">Wikipedia</a>
+ * @since 2.0 (changed to concrete class in 3.0)
+ */
+public class FieldLUDecomposition<T extends FieldElement<T>> {
+
+    /** Field to which the elements belong. */
+    private final Field<T> field;
+
+    /** Entries of LU decomposition. */
+    private T[][] lu;
+
+    /** Pivot permutation associated with LU decomposition. */
+    private int[] pivot;
+
+    /** Parity of the permutation associated with the LU decomposition. */
+    private boolean even;
+
+    /** Singularity indicator. */
+    private boolean singular;
+
+    /** Cached value of L. */
+    private FieldMatrix<T> cachedL;
+
+    /** Cached value of U. */
+    private FieldMatrix<T> cachedU;
+
+    /** Cached value of P. */
+    private FieldMatrix<T> cachedP;
+
+    /**
+     * Calculates the LU-decomposition of the given matrix.
+     *
+     * @param matrix The matrix to decompose.
+     * @throws NonSquareMatrixException if matrix is not square
+     */
+    public FieldLUDecomposition(FieldMatrix<T> matrix) {
+        if (!matrix.isSquare()) {
+            throw new NonSquareMatrixException(
+                    matrix.getRowDimension(), matrix.getColumnDimension());
+        }
+
+        final int m = matrix.getColumnDimension();
+        field = matrix.getField();
+        lu = matrix.getData();
+        pivot = new int[m];
+        cachedL = null;
+        cachedU = null;
+        cachedP = null;
+
+        // Initialize permutation array and parity
+        for (int row = 0; row < m; row++) {
+            pivot[row] = row;
+        }
+        even = true;
+        singular = false;
+
+        // Loop over columns
+        for (int col = 0; col < m; col++) {
+
+            T sum = field.getZero();
+
+            // upper
+            for (int row = 0; row < col; row++) {
+                final T[] luRow = lu[row];
+                sum = luRow[col];
+                for (int i = 0; i < row; i++) {
+                    sum = sum.subtract(luRow[i].multiply(lu[i][col]));
+                }
+                luRow[col] = sum;
+            }
+
+            // lower
+            int nonZero = col; // permutation row
+            for (int row = col; row < m; row++) {
+                final T[] luRow = lu[row];
+                sum = luRow[col];
+                for (int i = 0; i < col; i++) {
+                    sum = sum.subtract(luRow[i].multiply(lu[i][col]));
+                }
+                luRow[col] = sum;
+
+                if (lu[nonZero][col].equals(field.getZero())) {
+                    // try to select a better permutation choice
+                    ++nonZero;
+                }
+            }
+
+            // Singularity check
+            if (nonZero >= m) {
+                singular = true;
+                return;
+            }
+
+            // Pivot if necessary
+            if (nonZero != col) {
+                T tmp = field.getZero();
+                for (int i = 0; i < m; i++) {
+                    tmp = lu[nonZero][i];
+                    lu[nonZero][i] = lu[col][i];
+                    lu[col][i] = tmp;
+                }
+                int temp = pivot[nonZero];
+                pivot[nonZero] = pivot[col];
+                pivot[col] = temp;
+                even = !even;
+            }
+
+            // Divide the lower elements by the "winning" diagonal elt.
+            final T luDiag = lu[col][col];
+            for (int row = col + 1; row < m; row++) {
+                final T[] luRow = lu[row];
+                luRow[col] = luRow[col].divide(luDiag);
+            }
+        }
+    }
+
+    /**
+     * Returns the matrix L of the decomposition.
+     *
+     * <p>L is a lower-triangular matrix
+     *
+     * @return the L matrix (or null if decomposed matrix is singular)
+     */
+    public FieldMatrix<T> getL() {
+        if ((cachedL == null) && !singular) {
+            final int m = pivot.length;
+            cachedL = new Array2DRowFieldMatrix<T>(field, m, m);
+            for (int i = 0; i < m; ++i) {
+                final T[] luI = lu[i];
+                for (int j = 0; j < i; ++j) {
+                    cachedL.setEntry(i, j, luI[j]);
+                }
+                cachedL.setEntry(i, i, field.getOne());
+            }
+        }
+        return cachedL;
+    }
+
+    /**
+     * Returns the matrix U of the decomposition.
+     *
+     * <p>U is an upper-triangular matrix
+     *
+     * @return the U matrix (or null if decomposed matrix is singular)
+     */
+    public FieldMatrix<T> getU() {
+        if ((cachedU == null) && !singular) {
+            final int m = pivot.length;
+            cachedU = new Array2DRowFieldMatrix<T>(field, m, m);
+            for (int i = 0; i < m; ++i) {
+                final T[] luI = lu[i];
+                for (int j = i; j < m; ++j) {
+                    cachedU.setEntry(i, j, luI[j]);
+                }
+            }
+        }
+        return cachedU;
+    }
+
+    /**
+     * Returns the P rows permutation matrix.
+     *
+     * <p>P is a sparse matrix with exactly one element set to 1.0 in each row and each column, all
+     * other elements being set to 0.0.
+     *
+     * <p>The positions of the 1 elements are given by the {@link #getPivot() pivot permutation
+     * vector}.
+     *
+     * @return the P rows permutation matrix (or null if decomposed matrix is singular)
+     * @see #getPivot()
+     */
+    public FieldMatrix<T> getP() {
+        if ((cachedP == null) && !singular) {
+            final int m = pivot.length;
+            cachedP = new Array2DRowFieldMatrix<T>(field, m, m);
+            for (int i = 0; i < m; ++i) {
+                cachedP.setEntry(i, pivot[i], field.getOne());
+            }
+        }
+        return cachedP;
+    }
+
+    /**
+     * Returns the pivot permutation vector.
+     *
+     * @return the pivot permutation vector
+     * @see #getP()
+     */
+    public int[] getPivot() {
+        return pivot.clone();
+    }
+
+    /**
+     * Return the determinant of the matrix.
+     *
+     * @return determinant of the matrix
+     */
+    public T getDeterminant() {
+        if (singular) {
+            return field.getZero();
+        } else {
+            final int m = pivot.length;
+            T determinant = even ? field.getOne() : field.getZero().subtract(field.getOne());
+            for (int i = 0; i < m; i++) {
+                determinant = determinant.multiply(lu[i][i]);
+            }
+            return determinant;
+        }
+    }
+
+    /**
+     * Get a solver for finding the A &times; X = B solution in exact linear sense.
+     *
+     * @return a solver
+     */
+    public FieldDecompositionSolver<T> getSolver() {
+        return new Solver<T>(field, lu, pivot, singular);
+    }
+
+    /**
+     * Specialized solver.
+     *
+     * @param <T> the type of the field elements
+     */
+    private static class Solver<T extends FieldElement<T>> implements FieldDecompositionSolver<T> {
+
+        /** Field to which the elements belong. */
+        private final Field<T> field;
+
+        /** Entries of LU decomposition. */
+        private final T[][] lu;
+
+        /** Pivot permutation associated with LU decomposition. */
+        private final int[] pivot;
+
+        /** Singularity indicator. */
+        private final boolean singular;
+
+        /**
+         * Build a solver from decomposed matrix.
+         *
+         * @param field field to which the matrix elements belong
+         * @param lu entries of LU decomposition
+         * @param pivot pivot permutation associated with LU decomposition
+         * @param singular singularity indicator
+         */
+        private Solver(
+                final Field<T> field, final T[][] lu, final int[] pivot, final boolean singular) {
+            this.field = field;
+            this.lu = lu;
+            this.pivot = pivot;
+            this.singular = singular;
+        }
+
+        /** {@inheritDoc} */
+        public boolean isNonSingular() {
+            return !singular;
+        }
+
+        /** {@inheritDoc} */
+        public FieldVector<T> solve(FieldVector<T> b) {
+            try {
+                return solve((ArrayFieldVector<T>) b);
+            } catch (ClassCastException cce) {
+
+                final int m = pivot.length;
+                if (b.getDimension() != m) {
+                    throw new DimensionMismatchException(b.getDimension(), m);
+                }
+                if (singular) {
+                    throw new SingularMatrixException();
+                }
+
+                // Apply permutations to b
+                final T[] bp = MathArrays.buildArray(field, m);
+                for (int row = 0; row < m; row++) {
+                    bp[row] = b.getEntry(pivot[row]);
+                }
+
+                // Solve LY = b
+                for (int col = 0; col < m; col++) {
+                    final T bpCol = bp[col];
+                    for (int i = col + 1; i < m; i++) {
+                        bp[i] = bp[i].subtract(bpCol.multiply(lu[i][col]));
+                    }
+                }
+
+                // Solve UX = Y
+                for (int col = m - 1; col >= 0; col--) {
+                    bp[col] = bp[col].divide(lu[col][col]);
+                    final T bpCol = bp[col];
+                    for (int i = 0; i < col; i++) {
+                        bp[i] = bp[i].subtract(bpCol.multiply(lu[i][col]));
+                    }
+                }
+
+                return new ArrayFieldVector<T>(field, bp, false);
+            }
+        }
+
+        /**
+         * Solve the linear equation A &times; X = B.
+         *
+         * <p>The A matrix is implicit here. It is
+         *
+         * @param b right-hand side of the equation A &times; X = B
+         * @return a vector X such that A &times; X = B
+         * @throws DimensionMismatchException if the matrices dimensions do not match.
+         * @throws SingularMatrixException if the decomposed matrix is singular.
+         */
+        public ArrayFieldVector<T> solve(ArrayFieldVector<T> b) {
+            final int m = pivot.length;
+            final int length = b.getDimension();
+            if (length != m) {
+                throw new DimensionMismatchException(length, m);
+            }
+            if (singular) {
+                throw new SingularMatrixException();
+            }
+
+            // Apply permutations to b
+            final T[] bp = MathArrays.buildArray(field, m);
+            for (int row = 0; row < m; row++) {
+                bp[row] = b.getEntry(pivot[row]);
+            }
+
+            // Solve LY = b
+            for (int col = 0; col < m; col++) {
+                final T bpCol = bp[col];
+                for (int i = col + 1; i < m; i++) {
+                    bp[i] = bp[i].subtract(bpCol.multiply(lu[i][col]));
+                }
+            }
+
+            // Solve UX = Y
+            for (int col = m - 1; col >= 0; col--) {
+                bp[col] = bp[col].divide(lu[col][col]);
+                final T bpCol = bp[col];
+                for (int i = 0; i < col; i++) {
+                    bp[i] = bp[i].subtract(bpCol.multiply(lu[i][col]));
+                }
+            }
+
+            return new ArrayFieldVector<T>(bp, false);
+        }
+
+        /** {@inheritDoc} */
+        public FieldMatrix<T> solve(FieldMatrix<T> b) {
+            final int m = pivot.length;
+            if (b.getRowDimension() != m) {
+                throw new DimensionMismatchException(b.getRowDimension(), m);
+            }
+            if (singular) {
+                throw new SingularMatrixException();
+            }
+
+            final int nColB = b.getColumnDimension();
+
+            // Apply permutations to b
+            final T[][] bp = MathArrays.buildArray(field, m, nColB);
+            for (int row = 0; row < m; row++) {
+                final T[] bpRow = bp[row];
+                final int pRow = pivot[row];
+                for (int col = 0; col < nColB; col++) {
+                    bpRow[col] = b.getEntry(pRow, col);
+                }
+            }
+
+            // Solve LY = b
+            for (int col = 0; col < m; col++) {
+                final T[] bpCol = bp[col];
+                for (int i = col + 1; i < m; i++) {
+                    final T[] bpI = bp[i];
+                    final T luICol = lu[i][col];
+                    for (int j = 0; j < nColB; j++) {
+                        bpI[j] = bpI[j].subtract(bpCol[j].multiply(luICol));
+                    }
+                }
+            }
+
+            // Solve UX = Y
+            for (int col = m - 1; col >= 0; col--) {
+                final T[] bpCol = bp[col];
+                final T luDiag = lu[col][col];
+                for (int j = 0; j < nColB; j++) {
+                    bpCol[j] = bpCol[j].divide(luDiag);
+                }
+                for (int i = 0; i < col; i++) {
+                    final T[] bpI = bp[i];
+                    final T luICol = lu[i][col];
+                    for (int j = 0; j < nColB; j++) {
+                        bpI[j] = bpI[j].subtract(bpCol[j].multiply(luICol));
+                    }
+                }
+            }
+
+            return new Array2DRowFieldMatrix<T>(field, bp, false);
+        }
+
+        /** {@inheritDoc} */
+        public FieldMatrix<T> getInverse() {
+            final int m = pivot.length;
+            final T one = field.getOne();
+            FieldMatrix<T> identity = new Array2DRowFieldMatrix<T>(field, m, m);
+            for (int i = 0; i < m; ++i) {
+                identity.setEntry(i, i, one);
+            }
+            return solve(identity);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/FieldMatrix.java b/src/main/java/org/apache/commons/math3/linear/FieldMatrix.java
new file mode 100644
index 0000000..b738dd8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/FieldMatrix.java
@@ -0,0 +1,818 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.FieldElement;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+
+/**
+ * Interface defining field-valued matrix with basic algebraic operations.
+ *
+ * <p>Matrix element indexing is 0-based -- e.g., <code>getEntry(0, 0)</code> returns the element in
+ * the first row, first column of the matrix.
+ *
+ * @param <T> the type of the field elements
+ */
+public interface FieldMatrix<T extends FieldElement<T>> extends AnyMatrix {
+    /**
+     * Get the type of field elements of the matrix.
+     *
+     * @return the type of field elements of the matrix.
+     */
+    Field<T> getField();
+
+    /**
+     * Create a new FieldMatrix<T> of the same type as the instance with the supplied row and column
+     * dimensions.
+     *
+     * @param rowDimension the number of rows in the new matrix
+     * @param columnDimension the number of columns in the new matrix
+     * @return a new matrix of the same type as the instance
+     * @throws NotStrictlyPositiveException if row or column dimension is not positive.
+     * @since 2.0
+     */
+    FieldMatrix<T> createMatrix(final int rowDimension, final int columnDimension)
+            throws NotStrictlyPositiveException;
+
+    /**
+     * Make a (deep) copy of this.
+     *
+     * @return a copy of this matrix.
+     */
+    FieldMatrix<T> copy();
+
+    /**
+     * Compute the sum of this and m.
+     *
+     * @param m Matrix to be added.
+     * @return {@code this} + {@code m}.
+     * @throws MatrixDimensionMismatchException if {@code m} is not the same size as {@code this}
+     *     matrix.
+     */
+    FieldMatrix<T> add(FieldMatrix<T> m) throws MatrixDimensionMismatchException;
+
+    /**
+     * Subtract {@code m} from this matrix.
+     *
+     * @param m Matrix to be subtracted.
+     * @return {@code this} - {@code m}.
+     * @throws MatrixDimensionMismatchException if {@code m} is not the same size as {@code this}
+     *     matrix.
+     */
+    FieldMatrix<T> subtract(FieldMatrix<T> m) throws MatrixDimensionMismatchException;
+
+    /**
+     * Increment each entry of this matrix.
+     *
+     * @param d Value to be added to each entry.
+     * @return {@code d} + {@code this}.
+     */
+    FieldMatrix<T> scalarAdd(T d);
+
+    /**
+     * Multiply each entry by {@code d}.
+     *
+     * @param d Value to multiply all entries by.
+     * @return {@code d} * {@code this}.
+     */
+    FieldMatrix<T> scalarMultiply(T d);
+
+    /**
+     * Postmultiply this matrix by {@code m}.
+     *
+     * @param m Matrix to postmultiply by.
+     * @return {@code this} * {@code m}.
+     * @throws DimensionMismatchException if the number of columns of {@code this} matrix is not
+     *     equal to the number of rows of matrix {@code m}.
+     */
+    FieldMatrix<T> multiply(FieldMatrix<T> m) throws DimensionMismatchException;
+
+    /**
+     * Premultiply this matrix by {@code m}.
+     *
+     * @param m Matrix to premultiply by.
+     * @return {@code m} * {@code this}.
+     * @throws DimensionMismatchException if the number of columns of {@code m} differs from the
+     *     number of rows of {@code this} matrix.
+     */
+    FieldMatrix<T> preMultiply(FieldMatrix<T> m) throws DimensionMismatchException;
+
+    /**
+     * Returns the result multiplying this with itself <code>p</code> times. Depending on the type
+     * of the field elements, T, instability for high powers might occur.
+     *
+     * @param p raise this to power p
+     * @return this^p
+     * @throws NotPositiveException if {@code p < 0}
+     * @throws NonSquareMatrixException if {@code this matrix} is not square
+     */
+    FieldMatrix<T> power(final int p) throws NonSquareMatrixException, NotPositiveException;
+
+    /**
+     * Returns matrix entries as a two-dimensional array.
+     *
+     * @return a 2-dimensional array of entries.
+     */
+    T[][] getData();
+
+    /**
+     * Get a submatrix. Rows and columns are indicated counting from 0 to n - 1.
+     *
+     * @param startRow Initial row index
+     * @param endRow Final row index (inclusive)
+     * @param startColumn Initial column index
+     * @param endColumn Final column index (inclusive)
+     * @return the matrix containing the data of the specified rows and columns.
+     * @throws NumberIsTooSmallException is {@code endRow < startRow} of {@code endColumn <
+     *     startColumn}.
+     * @throws OutOfRangeException if the indices are not valid.
+     */
+    FieldMatrix<T> getSubMatrix(int startRow, int endRow, int startColumn, int endColumn)
+            throws NumberIsTooSmallException, OutOfRangeException;
+
+    /**
+     * Get a submatrix. Rows and columns are indicated counting from 0 to n - 1.
+     *
+     * @param selectedRows Array of row indices.
+     * @param selectedColumns Array of column indices.
+     * @return the matrix containing the data in the specified rows and columns.
+     * @throws NoDataException if {@code selectedRows} or {@code selectedColumns} is empty
+     * @throws NullArgumentException if {@code selectedRows} or {@code selectedColumns} is {@code
+     *     null}.
+     * @throws OutOfRangeException if row or column selections are not valid.
+     */
+    FieldMatrix<T> getSubMatrix(int[] selectedRows, int[] selectedColumns)
+            throws NoDataException, NullArgumentException, OutOfRangeException;
+
+    /**
+     * Copy a submatrix. Rows and columns are 0-based. The designated submatrix is copied into the
+     * top left portion of the destination array.
+     *
+     * @param startRow Initial row index.
+     * @param endRow Final row index (inclusive).
+     * @param startColumn Initial column index.
+     * @param endColumn Final column index (inclusive).
+     * @param destination The array where the submatrix data should be copied (if larger than
+     *     rows/columns counts, only the upper-left part will be modified).
+     * @throws MatrixDimensionMismatchException if the dimensions of {@code destination} are not
+     *     large enough to hold the submatrix.
+     * @throws NumberIsTooSmallException if {@code endRow < startRow} or {@code endColumn <
+     *     startColumn}.
+     * @throws OutOfRangeException if the indices are not valid.
+     */
+    void copySubMatrix(int startRow, int endRow, int startColumn, int endColumn, T[][] destination)
+            throws MatrixDimensionMismatchException, NumberIsTooSmallException, OutOfRangeException;
+
+    /**
+     * Copy a submatrix. Rows and columns are indicated counting from 0 to n - 1.
+     *
+     * @param selectedRows Array of row indices.
+     * @param selectedColumns Array of column indices.
+     * @param destination Arrays where the submatrix data should be copied (if larger than
+     *     rows/columns counts, only the upper-left part will be used)
+     * @throws MatrixDimensionMismatchException if the dimensions of {@code destination} do not
+     *     match those of {@code this}.
+     * @throws NoDataException if {@code selectedRows} or {@code selectedColumns} is empty
+     * @throws NullArgumentException if {@code selectedRows} or {@code selectedColumns} is {@code
+     *     null}.
+     * @throws OutOfRangeException if the indices are not valid.
+     */
+    void copySubMatrix(int[] selectedRows, int[] selectedColumns, T[][] destination)
+            throws MatrixDimensionMismatchException,
+                    NoDataException,
+                    NullArgumentException,
+                    OutOfRangeException;
+
+    /**
+     * Replace the submatrix starting at {@code (row, column)} using data in the input {@code
+     * subMatrix} array. Indexes are 0-based.
+     *
+     * <p>Example:<br>
+     * Starting with
+     *
+     * <pre>
+     * 1  2  3  4
+     * 5  6  7  8
+     * 9  0  1  2
+     * </pre>
+     *
+     * and <code>subMatrix = {{3, 4} {5,6}}</code>, invoking <code>setSubMatrix(subMatrix,1,1))
+     * </code> will result in
+     *
+     * <pre>
+     * 1  2  3  4
+     * 5  3  4  8
+     * 9  5  6  2
+     * </pre>
+     *
+     * @param subMatrix Array containing the submatrix replacement data.
+     * @param row Row coordinate of the top-left element to be replaced.
+     * @param column Column coordinate of the top-left element to be replaced.
+     * @throws OutOfRangeException if {@code subMatrix} does not fit into this matrix from element
+     *     in {@code (row, column)}.
+     * @throws NoDataException if a row or column of {@code subMatrix} is empty.
+     * @throws DimensionMismatchException if {@code subMatrix} is not rectangular (not all rows have
+     *     the same length).
+     * @throws NullArgumentException if {@code subMatrix} is {@code null}.
+     * @since 2.0
+     */
+    void setSubMatrix(T[][] subMatrix, int row, int column)
+            throws DimensionMismatchException,
+                    OutOfRangeException,
+                    NoDataException,
+                    NullArgumentException;
+
+    /**
+     * Get the entries in row number {@code row} as a row matrix.
+     *
+     * @param row Row to be fetched.
+     * @return a row matrix.
+     * @throws OutOfRangeException if the specified row index is invalid.
+     */
+    FieldMatrix<T> getRowMatrix(int row) throws OutOfRangeException;
+
+    /**
+     * Set the entries in row number {@code row} as a row matrix.
+     *
+     * @param row Row to be set.
+     * @param matrix Row matrix (must have one row and the same number of columns as the instance).
+     * @throws OutOfRangeException if the specified row index is invalid.
+     * @throws MatrixDimensionMismatchException if the matrix dimensions do not match one instance
+     *     row.
+     */
+    void setRowMatrix(int row, FieldMatrix<T> matrix)
+            throws MatrixDimensionMismatchException, OutOfRangeException;
+
+    /**
+     * Get the entries in column number {@code column} as a column matrix.
+     *
+     * @param column Column to be fetched.
+     * @return a column matrix.
+     * @throws OutOfRangeException if the specified column index is invalid.
+     */
+    FieldMatrix<T> getColumnMatrix(int column) throws OutOfRangeException;
+
+    /**
+     * Set the entries in column number {@code column} as a column matrix.
+     *
+     * @param column Column to be set.
+     * @param matrix column matrix (must have one column and the same number of rows as the
+     *     instance).
+     * @throws OutOfRangeException if the specified column index is invalid.
+     * @throws MatrixDimensionMismatchException if the matrix dimensions do not match one instance
+     *     column.
+     */
+    void setColumnMatrix(int column, FieldMatrix<T> matrix)
+            throws MatrixDimensionMismatchException, OutOfRangeException;
+
+    /**
+     * Get the entries in row number {@code row} as a vector.
+     *
+     * @param row Row to be fetched
+     * @return a row vector.
+     * @throws OutOfRangeException if the specified row index is invalid.
+     */
+    FieldVector<T> getRowVector(int row) throws OutOfRangeException;
+
+    /**
+     * Set the entries in row number {@code row} as a vector.
+     *
+     * @param row Row to be set.
+     * @param vector row vector (must have the same number of columns as the instance).
+     * @throws OutOfRangeException if the specified row index is invalid.
+     * @throws MatrixDimensionMismatchException if the vector dimension does not match one instance
+     *     row.
+     */
+    void setRowVector(int row, FieldVector<T> vector)
+            throws MatrixDimensionMismatchException, OutOfRangeException;
+
+    /**
+     * Returns the entries in column number {@code column} as a vector.
+     *
+     * @param column Column to be fetched.
+     * @return a column vector.
+     * @throws OutOfRangeException if the specified column index is invalid.
+     */
+    FieldVector<T> getColumnVector(int column) throws OutOfRangeException;
+
+    /**
+     * Set the entries in column number {@code column} as a vector.
+     *
+     * @param column Column to be set.
+     * @param vector Column vector (must have the same number of rows as the instance).
+     * @throws OutOfRangeException if the specified column index is invalid.
+     * @throws MatrixDimensionMismatchException if the vector dimension does not match one instance
+     *     column.
+     */
+    void setColumnVector(int column, FieldVector<T> vector)
+            throws MatrixDimensionMismatchException, OutOfRangeException;
+
+    /**
+     * Get the entries in row number {@code row} as an array.
+     *
+     * @param row Row to be fetched.
+     * @return array of entries in the row.
+     * @throws OutOfRangeException if the specified row index is not valid.
+     */
+    T[] getRow(int row) throws OutOfRangeException;
+
+    /**
+     * Set the entries in row number {@code row} as a row matrix.
+     *
+     * @param row Row to be set.
+     * @param array Row matrix (must have the same number of columns as the instance).
+     * @throws OutOfRangeException if the specified row index is invalid.
+     * @throws MatrixDimensionMismatchException if the array size does not match one instance row.
+     */
+    void setRow(int row, T[] array) throws MatrixDimensionMismatchException, OutOfRangeException;
+
+    /**
+     * Get the entries in column number {@code col} as an array.
+     *
+     * @param column the column to be fetched
+     * @return array of entries in the column
+     * @throws OutOfRangeException if the specified column index is not valid.
+     */
+    T[] getColumn(int column) throws OutOfRangeException;
+
+    /**
+     * Set the entries in column number {@code column} as a column matrix.
+     *
+     * @param column the column to be set
+     * @param array column array (must have the same number of rows as the instance)
+     * @throws OutOfRangeException if the specified column index is invalid.
+     * @throws MatrixDimensionMismatchException if the array size does not match one instance
+     *     column.
+     */
+    void setColumn(int column, T[] array)
+            throws MatrixDimensionMismatchException, OutOfRangeException;
+
+    /**
+     * Returns the entry in the specified row and column.
+     *
+     * @param row row location of entry to be fetched
+     * @param column column location of entry to be fetched
+     * @return matrix entry in row,column
+     * @throws OutOfRangeException if the row or column index is not valid.
+     */
+    T getEntry(int row, int column) throws OutOfRangeException;
+
+    /**
+     * Set the entry in the specified row and column.
+     *
+     * @param row row location of entry to be set
+     * @param column column location of entry to be set
+     * @param value matrix entry to be set in row,column
+     * @throws OutOfRangeException if the row or column index is not valid.
+     * @since 2.0
+     */
+    void setEntry(int row, int column, T value) throws OutOfRangeException;
+
+    /**
+     * Change an entry in the specified row and column.
+     *
+     * @param row Row location of entry to be set.
+     * @param column Column location of entry to be set.
+     * @param increment Value to add to the current matrix entry in {@code (row, column)}.
+     * @throws OutOfRangeException if the row or column index is not valid.
+     * @since 2.0
+     */
+    void addToEntry(int row, int column, T increment) throws OutOfRangeException;
+
+    /**
+     * Change an entry in the specified row and column.
+     *
+     * @param row Row location of entry to be set.
+     * @param column Column location of entry to be set.
+     * @param factor Multiplication factor for the current matrix entry in {@code (row,column)}
+     * @throws OutOfRangeException if the row or column index is not valid.
+     * @since 2.0
+     */
+    void multiplyEntry(int row, int column, T factor) throws OutOfRangeException;
+
+    /**
+     * Returns the transpose of this matrix.
+     *
+     * @return transpose matrix
+     */
+    FieldMatrix<T> transpose();
+
+    /**
+     * Returns the <a href="http://mathworld.wolfram.com/MatrixTrace.html">trace</a> of the matrix
+     * (the sum of the elements on the main diagonal).
+     *
+     * @return trace
+     * @throws NonSquareMatrixException if the matrix is not square.
+     */
+    T getTrace() throws NonSquareMatrixException;
+
+    /**
+     * Returns the result of multiplying this by the vector {@code v}.
+     *
+     * @param v the vector to operate on
+     * @return {@code this * v}
+     * @throws DimensionMismatchException if the number of columns of {@code this} matrix is not
+     *     equal to the size of the vector {@code v}.
+     */
+    T[] operate(T[] v) throws DimensionMismatchException;
+
+    /**
+     * Returns the result of multiplying this by the vector {@code v}.
+     *
+     * @param v the vector to operate on
+     * @return {@code this * v}
+     * @throws DimensionMismatchException if the number of columns of {@code this} matrix is not
+     *     equal to the size of the vector {@code v}.
+     */
+    FieldVector<T> operate(FieldVector<T> v) throws DimensionMismatchException;
+
+    /**
+     * Returns the (row) vector result of premultiplying this by the vector {@code v}.
+     *
+     * @param v the row vector to premultiply by
+     * @return {@code v * this}
+     * @throws DimensionMismatchException if the number of rows of {@code this} matrix is not equal
+     *     to the size of the vector {@code v}
+     */
+    T[] preMultiply(T[] v) throws DimensionMismatchException;
+
+    /**
+     * Returns the (row) vector result of premultiplying this by the vector {@code v}.
+     *
+     * @param v the row vector to premultiply by
+     * @return {@code v * this}
+     * @throws DimensionMismatchException if the number of rows of {@code this} matrix is not equal
+     *     to the size of the vector {@code v}
+     */
+    FieldVector<T> preMultiply(FieldVector<T> v) throws DimensionMismatchException;
+
+    /**
+     * Visit (and possibly change) all matrix entries in row order.
+     *
+     * <p>Row order starts at upper left and iterating through all elements of a row from left to
+     * right before going to the leftmost element of the next row.
+     *
+     * @param visitor visitor used to process all matrix entries
+     * @see #walkInRowOrder(FieldMatrixPreservingVisitor)
+     * @see #walkInRowOrder(FieldMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInRowOrder(FieldMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(FieldMatrixChangingVisitor)
+     * @see #walkInColumnOrder(FieldMatrixPreservingVisitor)
+     * @see #walkInColumnOrder(FieldMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(FieldMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor)
+     * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor)
+     * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor, int, int, int, int)
+     * @return the value returned by {@link FieldMatrixChangingVisitor#end()} at the end of the walk
+     */
+    T walkInRowOrder(FieldMatrixChangingVisitor<T> visitor);
+
+    /**
+     * Visit (but don't change) all matrix entries in row order.
+     *
+     * <p>Row order starts at upper left and iterating through all elements of a row from left to
+     * right before going to the leftmost element of the next row.
+     *
+     * @param visitor visitor used to process all matrix entries
+     * @see #walkInRowOrder(FieldMatrixChangingVisitor)
+     * @see #walkInRowOrder(FieldMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInRowOrder(FieldMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(FieldMatrixChangingVisitor)
+     * @see #walkInColumnOrder(FieldMatrixPreservingVisitor)
+     * @see #walkInColumnOrder(FieldMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(FieldMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor)
+     * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor)
+     * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor, int, int, int, int)
+     * @return the value returned by {@link FieldMatrixPreservingVisitor#end()} at the end of the
+     *     walk
+     */
+    T walkInRowOrder(FieldMatrixPreservingVisitor<T> visitor);
+
+    /**
+     * Visit (and possibly change) some matrix entries in row order.
+     *
+     * <p>Row order starts at upper left and iterating through all elements of a row from left to
+     * right before going to the leftmost element of the next row.
+     *
+     * @param visitor visitor used to process all matrix entries
+     * @param startRow Initial row index
+     * @param endRow Final row index (inclusive)
+     * @param startColumn Initial column index
+     * @param endColumn Final column index
+     * @throws OutOfRangeException if the indices are not valid.
+     * @throws NumberIsTooSmallException if {@code endRow < startRow} or {@code endColumn <
+     *     startColumn}.
+     * @see #walkInRowOrder(FieldMatrixChangingVisitor)
+     * @see #walkInRowOrder(FieldMatrixPreservingVisitor)
+     * @see #walkInRowOrder(FieldMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(FieldMatrixChangingVisitor)
+     * @see #walkInColumnOrder(FieldMatrixPreservingVisitor)
+     * @see #walkInColumnOrder(FieldMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(FieldMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor)
+     * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor)
+     * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor, int, int, int, int)
+     * @return the value returned by {@link FieldMatrixChangingVisitor#end()} at the end of the walk
+     */
+    T walkInRowOrder(
+            FieldMatrixChangingVisitor<T> visitor,
+            int startRow,
+            int endRow,
+            int startColumn,
+            int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException;
+
+    /**
+     * Visit (but don't change) some matrix entries in row order.
+     *
+     * <p>Row order starts at upper left and iterating through all elements of a row from left to
+     * right before going to the leftmost element of the next row.
+     *
+     * @param visitor visitor used to process all matrix entries
+     * @param startRow Initial row index
+     * @param endRow Final row index (inclusive)
+     * @param startColumn Initial column index
+     * @param endColumn Final column index
+     * @throws OutOfRangeException if the indices are not valid.
+     * @throws NumberIsTooSmallException if {@code endRow < startRow} or {@code endColumn <
+     *     startColumn}.
+     * @see #walkInRowOrder(FieldMatrixChangingVisitor)
+     * @see #walkInRowOrder(FieldMatrixPreservingVisitor)
+     * @see #walkInRowOrder(FieldMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(FieldMatrixChangingVisitor)
+     * @see #walkInColumnOrder(FieldMatrixPreservingVisitor)
+     * @see #walkInColumnOrder(FieldMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(FieldMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor)
+     * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor)
+     * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor, int, int, int, int)
+     * @return the value returned by {@link FieldMatrixPreservingVisitor#end()} at the end of the
+     *     walk
+     */
+    T walkInRowOrder(
+            FieldMatrixPreservingVisitor<T> visitor,
+            int startRow,
+            int endRow,
+            int startColumn,
+            int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException;
+
+    /**
+     * Visit (and possibly change) all matrix entries in column order.
+     *
+     * <p>Column order starts at upper left and iterating through all elements of a column from top
+     * to bottom before going to the topmost element of the next column.
+     *
+     * @param visitor visitor used to process all matrix entries
+     * @see #walkInRowOrder(FieldMatrixChangingVisitor)
+     * @see #walkInRowOrder(FieldMatrixPreservingVisitor)
+     * @see #walkInRowOrder(FieldMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInRowOrder(FieldMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(FieldMatrixPreservingVisitor)
+     * @see #walkInColumnOrder(FieldMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(FieldMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor)
+     * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor)
+     * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor, int, int, int, int)
+     * @return the value returned by {@link FieldMatrixChangingVisitor#end()} at the end of the walk
+     */
+    T walkInColumnOrder(FieldMatrixChangingVisitor<T> visitor);
+
+    /**
+     * Visit (but don't change) all matrix entries in column order.
+     *
+     * <p>Column order starts at upper left and iterating through all elements of a column from top
+     * to bottom before going to the topmost element of the next column.
+     *
+     * @param visitor visitor used to process all matrix entries
+     * @see #walkInRowOrder(FieldMatrixChangingVisitor)
+     * @see #walkInRowOrder(FieldMatrixPreservingVisitor)
+     * @see #walkInRowOrder(FieldMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInRowOrder(FieldMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(FieldMatrixChangingVisitor)
+     * @see #walkInColumnOrder(FieldMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(FieldMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor)
+     * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor)
+     * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor, int, int, int, int)
+     * @return the value returned by {@link FieldMatrixPreservingVisitor#end()} at the end of the
+     *     walk
+     */
+    T walkInColumnOrder(FieldMatrixPreservingVisitor<T> visitor);
+
+    /**
+     * Visit (and possibly change) some matrix entries in column order.
+     *
+     * <p>Column order starts at upper left and iterating through all elements of a column from top
+     * to bottom before going to the topmost element of the next column.
+     *
+     * @param visitor visitor used to process all matrix entries
+     * @param startRow Initial row index
+     * @param endRow Final row index (inclusive)
+     * @param startColumn Initial column index
+     * @param endColumn Final column index
+     * @throws NumberIsTooSmallException if {@code endRow < startRow} or {@code endColumn <
+     *     startColumn}.
+     * @throws OutOfRangeException if the indices are not valid.
+     * @see #walkInRowOrder(FieldMatrixChangingVisitor)
+     * @see #walkInRowOrder(FieldMatrixPreservingVisitor)
+     * @see #walkInRowOrder(FieldMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInRowOrder(FieldMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(FieldMatrixChangingVisitor)
+     * @see #walkInColumnOrder(FieldMatrixPreservingVisitor)
+     * @see #walkInColumnOrder(FieldMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor)
+     * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor)
+     * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor, int, int, int, int)
+     * @return the value returned by {@link FieldMatrixChangingVisitor#end()} at the end of the walk
+     */
+    T walkInColumnOrder(
+            FieldMatrixChangingVisitor<T> visitor,
+            int startRow,
+            int endRow,
+            int startColumn,
+            int endColumn)
+            throws NumberIsTooSmallException, OutOfRangeException;
+
+    /**
+     * Visit (but don't change) some matrix entries in column order.
+     *
+     * <p>Column order starts at upper left and iterating through all elements of a column from top
+     * to bottom before going to the topmost element of the next column.
+     *
+     * @param visitor visitor used to process all matrix entries
+     * @param startRow Initial row index
+     * @param endRow Final row index (inclusive)
+     * @param startColumn Initial column index
+     * @param endColumn Final column index
+     * @throws NumberIsTooSmallException if {@code endRow < startRow} or {@code endColumn <
+     *     startColumn}.
+     * @throws OutOfRangeException if the indices are not valid.
+     * @see #walkInRowOrder(FieldMatrixChangingVisitor)
+     * @see #walkInRowOrder(FieldMatrixPreservingVisitor)
+     * @see #walkInRowOrder(FieldMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInRowOrder(FieldMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(FieldMatrixChangingVisitor)
+     * @see #walkInColumnOrder(FieldMatrixPreservingVisitor)
+     * @see #walkInColumnOrder(FieldMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor)
+     * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor)
+     * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor, int, int, int, int)
+     * @return the value returned by {@link FieldMatrixPreservingVisitor#end()} at the end of the
+     *     walk
+     */
+    T walkInColumnOrder(
+            FieldMatrixPreservingVisitor<T> visitor,
+            int startRow,
+            int endRow,
+            int startColumn,
+            int endColumn)
+            throws NumberIsTooSmallException, OutOfRangeException;
+
+    /**
+     * Visit (and possibly change) all matrix entries using the fastest possible order.
+     *
+     * <p>The fastest walking order depends on the exact matrix class. It may be different from
+     * traditional row or column orders.
+     *
+     * @param visitor visitor used to process all matrix entries
+     * @see #walkInRowOrder(FieldMatrixChangingVisitor)
+     * @see #walkInRowOrder(FieldMatrixPreservingVisitor)
+     * @see #walkInRowOrder(FieldMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInRowOrder(FieldMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(FieldMatrixChangingVisitor)
+     * @see #walkInColumnOrder(FieldMatrixPreservingVisitor)
+     * @see #walkInColumnOrder(FieldMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(FieldMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor)
+     * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor, int, int, int, int)
+     * @return the value returned by {@link FieldMatrixChangingVisitor#end()} at the end of the walk
+     */
+    T walkInOptimizedOrder(FieldMatrixChangingVisitor<T> visitor);
+
+    /**
+     * Visit (but don't change) all matrix entries using the fastest possible order.
+     *
+     * <p>The fastest walking order depends on the exact matrix class. It may be different from
+     * traditional row or column orders.
+     *
+     * @param visitor visitor used to process all matrix entries
+     * @see #walkInRowOrder(FieldMatrixChangingVisitor)
+     * @see #walkInRowOrder(FieldMatrixPreservingVisitor)
+     * @see #walkInRowOrder(FieldMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInRowOrder(FieldMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(FieldMatrixChangingVisitor)
+     * @see #walkInColumnOrder(FieldMatrixPreservingVisitor)
+     * @see #walkInColumnOrder(FieldMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(FieldMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor)
+     * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor, int, int, int, int)
+     * @return the value returned by {@link FieldMatrixPreservingVisitor#end()} at the end of the
+     *     walk
+     */
+    T walkInOptimizedOrder(FieldMatrixPreservingVisitor<T> visitor);
+
+    /**
+     * Visit (and possibly change) some matrix entries using the fastest possible order.
+     *
+     * <p>The fastest walking order depends on the exact matrix class. It may be different from
+     * traditional row or column orders.
+     *
+     * @param visitor visitor used to process all matrix entries
+     * @param startRow Initial row index
+     * @param endRow Final row index (inclusive)
+     * @param startColumn Initial column index
+     * @param endColumn Final column index (inclusive)
+     * @throws NumberIsTooSmallException if {@code endRow < startRow} or {@code endColumn <
+     *     startColumn}.
+     * @throws OutOfRangeException if the indices are not valid.
+     * @see #walkInRowOrder(FieldMatrixChangingVisitor)
+     * @see #walkInRowOrder(FieldMatrixPreservingVisitor)
+     * @see #walkInRowOrder(FieldMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInRowOrder(FieldMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(FieldMatrixChangingVisitor)
+     * @see #walkInColumnOrder(FieldMatrixPreservingVisitor)
+     * @see #walkInColumnOrder(FieldMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(FieldMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor)
+     * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor)
+     * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor, int, int, int, int)
+     * @return the value returned by {@link FieldMatrixChangingVisitor#end()} at the end of the walk
+     */
+    T walkInOptimizedOrder(
+            FieldMatrixChangingVisitor<T> visitor,
+            int startRow,
+            int endRow,
+            int startColumn,
+            int endColumn)
+            throws NumberIsTooSmallException, OutOfRangeException;
+
+    /**
+     * Visit (but don't change) some matrix entries using the fastest possible order.
+     *
+     * <p>The fastest walking order depends on the exact matrix class. It may be different from
+     * traditional row or column orders.
+     *
+     * @param visitor visitor used to process all matrix entries
+     * @param startRow Initial row index
+     * @param endRow Final row index (inclusive)
+     * @param startColumn Initial column index
+     * @param endColumn Final column index (inclusive)
+     * @throws NumberIsTooSmallException if {@code endRow < startRow} or {@code endColumn <
+     *     startColumn}.
+     * @throws OutOfRangeException if the indices are not valid.
+     * @see #walkInRowOrder(FieldMatrixChangingVisitor)
+     * @see #walkInRowOrder(FieldMatrixPreservingVisitor)
+     * @see #walkInRowOrder(FieldMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInRowOrder(FieldMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(FieldMatrixChangingVisitor)
+     * @see #walkInColumnOrder(FieldMatrixPreservingVisitor)
+     * @see #walkInColumnOrder(FieldMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(FieldMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor)
+     * @see #walkInOptimizedOrder(FieldMatrixPreservingVisitor)
+     * @see #walkInOptimizedOrder(FieldMatrixChangingVisitor, int, int, int, int)
+     * @return the value returned by {@link FieldMatrixPreservingVisitor#end()} at the end of the
+     *     walk
+     */
+    T walkInOptimizedOrder(
+            FieldMatrixPreservingVisitor<T> visitor,
+            int startRow,
+            int endRow,
+            int startColumn,
+            int endColumn)
+            throws NumberIsTooSmallException, OutOfRangeException;
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/FieldMatrixChangingVisitor.java b/src/main/java/org/apache/commons/math3/linear/FieldMatrixChangingVisitor.java
new file mode 100644
index 0000000..0a0c391
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/FieldMatrixChangingVisitor.java
@@ -0,0 +1,61 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.FieldElement;
+
+/**
+ * Interface defining a visitor for matrix entries.
+ *
+ * @param <T> the type of the field elements
+ * @since 2.0
+ */
+public interface FieldMatrixChangingVisitor<T extends FieldElement<?>> {
+    /**
+     * Start visiting a matrix.
+     *
+     * <p>This method is called once before any entry of the matrix is visited.
+     *
+     * @param rows number of rows of the matrix
+     * @param columns number of columns of the matrix
+     * @param startRow Initial row index
+     * @param endRow Final row index (inclusive)
+     * @param startColumn Initial column index
+     * @param endColumn Final column index (inclusive)
+     */
+    void start(int rows, int columns, int startRow, int endRow, int startColumn, int endColumn);
+
+    /**
+     * Visit one matrix entry.
+     *
+     * @param row row index of the entry
+     * @param column column index of the entry
+     * @param value current value of the entry
+     * @return the new value to be set for the entry
+     */
+    T visit(int row, int column, T value);
+
+    /**
+     * End visiting a matrix.
+     *
+     * <p>This method is called once after all entries of the matrix have been visited.
+     *
+     * @return the value that the <code>walkInXxxOrder</code> must return
+     */
+    T end();
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/FieldMatrixPreservingVisitor.java b/src/main/java/org/apache/commons/math3/linear/FieldMatrixPreservingVisitor.java
new file mode 100644
index 0000000..7774fcd
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/FieldMatrixPreservingVisitor.java
@@ -0,0 +1,60 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.FieldElement;
+
+/**
+ * Interface defining a visitor for matrix entries.
+ *
+ * @param <T> the type of the field elements
+ * @since 2.0
+ */
+public interface FieldMatrixPreservingVisitor<T extends FieldElement<?>> {
+    /**
+     * Start visiting a matrix.
+     *
+     * <p>This method is called once before any entry of the matrix is visited.
+     *
+     * @param rows number of rows of the matrix
+     * @param columns number of columns of the matrix
+     * @param startRow Initial row index
+     * @param endRow Final row index (inclusive)
+     * @param startColumn Initial column index
+     * @param endColumn Final column index (inclusive)
+     */
+    void start(int rows, int columns, int startRow, int endRow, int startColumn, int endColumn);
+
+    /**
+     * Visit one matrix entry.
+     *
+     * @param row row index of the entry
+     * @param column column index of the entry
+     * @param value current value of the entry
+     */
+    void visit(int row, int column, T value);
+
+    /**
+     * End visiting a matrix.
+     *
+     * <p>This method is called once after all entries of the matrix have been visited.
+     *
+     * @return the value that the <code>walkInXxxOrder</code> must return
+     */
+    T end();
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/FieldVector.java b/src/main/java/org/apache/commons/math3/linear/FieldVector.java
new file mode 100644
index 0000000..58e9d38
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/FieldVector.java
@@ -0,0 +1,324 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.FieldElement;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+
+/**
+ * Interface defining a field-valued vector with basic algebraic operations.
+ *
+ * <p>vector element indexing is 0-based -- e.g., <code>getEntry(0)</code> returns the first element
+ * of the vector.
+ *
+ * <p>The various <code>mapXxx</code> and <code>mapXxxToSelf</code> methods operate on vectors
+ * element-wise, i.e. they perform the same operation (adding a scalar, applying a function ...) on
+ * each element in turn. The <code>mapXxx</code> versions create a new vector to hold the result and
+ * do not change the instance. The <code>mapXxxToSelf</code> versions use the instance itself to
+ * store the results, so the instance is changed by these methods. In both cases, the result vector
+ * is returned by the methods, this allows to use the <i>fluent API</i> style, like this:
+ *
+ * <pre>
+ *   RealVector result = v.mapAddToSelf(3.0).mapTanToSelf().mapSquareToSelf();
+ * </pre>
+ *
+ * <p>Note that as almost all operations on {@link FieldElement} throw {@link NullArgumentException}
+ * when operating on a null element, it is the responsibility of <code>FieldVector</code>
+ * implementations to make sure no null elements are inserted into the vector. This must be done in
+ * all constructors and all setters.
+ *
+ * <p>
+ *
+ * @param <T> the type of the field elements
+ * @since 2.0
+ */
+public interface FieldVector<T extends FieldElement<T>> {
+
+    /**
+     * Get the type of field elements of the vector.
+     *
+     * @return type of field elements of the vector
+     */
+    Field<T> getField();
+
+    /**
+     * Returns a (deep) copy of this.
+     *
+     * @return vector copy
+     */
+    FieldVector<T> copy();
+
+    /**
+     * Compute the sum of {@code this} and {@code v}.
+     *
+     * @param v vector to be added
+     * @return {@code this + v}
+     * @throws DimensionMismatchException if {@code v} is not the same size as {@code this}
+     */
+    FieldVector<T> add(FieldVector<T> v) throws DimensionMismatchException;
+
+    /**
+     * Compute {@code this} minus {@code v}.
+     *
+     * @param v vector to be subtracted
+     * @return {@code this - v}
+     * @throws DimensionMismatchException if {@code v} is not the same size as {@code this}
+     */
+    FieldVector<T> subtract(FieldVector<T> v) throws DimensionMismatchException;
+
+    /**
+     * Map an addition operation to each entry.
+     *
+     * @param d value to be added to each entry
+     * @return {@code this + d}
+     * @throws NullArgumentException if {@code d} is {@code null}.
+     */
+    FieldVector<T> mapAdd(T d) throws NullArgumentException;
+
+    /**
+     * Map an addition operation to each entry.
+     *
+     * <p>The instance <strong>is</strong> changed by this method.
+     *
+     * @param d value to be added to each entry
+     * @return for convenience, return {@code this}
+     * @throws NullArgumentException if {@code d} is {@code null}.
+     */
+    FieldVector<T> mapAddToSelf(T d) throws NullArgumentException;
+
+    /**
+     * Map a subtraction operation to each entry.
+     *
+     * @param d value to be subtracted to each entry
+     * @return {@code this - d}
+     * @throws NullArgumentException if {@code d} is {@code null}
+     */
+    FieldVector<T> mapSubtract(T d) throws NullArgumentException;
+
+    /**
+     * Map a subtraction operation to each entry.
+     *
+     * <p>The instance <strong>is</strong> changed by this method.
+     *
+     * @param d value to be subtracted to each entry
+     * @return for convenience, return {@code this}
+     * @throws NullArgumentException if {@code d} is {@code null}
+     */
+    FieldVector<T> mapSubtractToSelf(T d) throws NullArgumentException;
+
+    /**
+     * Map a multiplication operation to each entry.
+     *
+     * @param d value to multiply all entries by
+     * @return {@code this * d}
+     * @throws NullArgumentException if {@code d} is {@code null}.
+     */
+    FieldVector<T> mapMultiply(T d) throws NullArgumentException;
+
+    /**
+     * Map a multiplication operation to each entry.
+     *
+     * <p>The instance <strong>is</strong> changed by this method.
+     *
+     * @param d value to multiply all entries by
+     * @return for convenience, return {@code this}
+     * @throws NullArgumentException if {@code d} is {@code null}.
+     */
+    FieldVector<T> mapMultiplyToSelf(T d) throws NullArgumentException;
+
+    /**
+     * Map a division operation to each entry.
+     *
+     * @param d value to divide all entries by
+     * @return {@code this / d}
+     * @throws NullArgumentException if {@code d} is {@code null}.
+     * @throws MathArithmeticException if {@code d} is zero.
+     */
+    FieldVector<T> mapDivide(T d) throws NullArgumentException, MathArithmeticException;
+
+    /**
+     * Map a division operation to each entry.
+     *
+     * <p>The instance <strong>is</strong> changed by this method.
+     *
+     * @param d value to divide all entries by
+     * @return for convenience, return {@code this}
+     * @throws NullArgumentException if {@code d} is {@code null}.
+     * @throws MathArithmeticException if {@code d} is zero.
+     */
+    FieldVector<T> mapDivideToSelf(T d) throws NullArgumentException, MathArithmeticException;
+
+    /**
+     * Map the 1/x function to each entry.
+     *
+     * @return a vector containing the result of applying the function to each entry.
+     * @throws MathArithmeticException if one of the entries is zero.
+     */
+    FieldVector<T> mapInv() throws MathArithmeticException;
+
+    /**
+     * Map the 1/x function to each entry.
+     *
+     * <p>The instance <strong>is</strong> changed by this method.
+     *
+     * @return for convenience, return {@code this}
+     * @throws MathArithmeticException if one of the entries is zero.
+     */
+    FieldVector<T> mapInvToSelf() throws MathArithmeticException;
+
+    /**
+     * Element-by-element multiplication.
+     *
+     * @param v vector by which instance elements must be multiplied
+     * @return a vector containing {@code this[i] * v[i]} for all {@code i}
+     * @throws DimensionMismatchException if {@code v} is not the same size as {@code this}
+     */
+    FieldVector<T> ebeMultiply(FieldVector<T> v) throws DimensionMismatchException;
+
+    /**
+     * Element-by-element division.
+     *
+     * @param v vector by which instance elements must be divided
+     * @return a vector containing {@code this[i] / v[i]} for all {@code i}
+     * @throws DimensionMismatchException if {@code v} is not the same size as {@code this}
+     * @throws MathArithmeticException if one entry of {@code v} is zero.
+     */
+    FieldVector<T> ebeDivide(FieldVector<T> v)
+            throws DimensionMismatchException, MathArithmeticException;
+
+    /**
+     * Returns vector entries as a T array.
+     *
+     * @return T array of entries
+     * @deprecated as of 3.1, to be removed in 4.0. Please use the {@link #toArray()} method
+     *     instead.
+     */
+    @Deprecated
+    T[] getData();
+
+    /**
+     * Compute the dot product.
+     *
+     * @param v vector with which dot product should be computed
+     * @return the scalar dot product of {@code this} and {@code v}
+     * @throws DimensionMismatchException if {@code v} is not the same size as {@code this}
+     */
+    T dotProduct(FieldVector<T> v) throws DimensionMismatchException;
+
+    /**
+     * Find the orthogonal projection of this vector onto another vector.
+     *
+     * @param v vector onto which {@code this} must be projected
+     * @return projection of {@code this} onto {@code v}
+     * @throws DimensionMismatchException if {@code v} is not the same size as {@code this}
+     * @throws MathArithmeticException if {@code v} is the null vector.
+     */
+    FieldVector<T> projection(FieldVector<T> v)
+            throws DimensionMismatchException, MathArithmeticException;
+
+    /**
+     * Compute the outer product.
+     *
+     * @param v vector with which outer product should be computed
+     * @return the matrix outer product between instance and v
+     */
+    FieldMatrix<T> outerProduct(FieldVector<T> v);
+
+    /**
+     * Returns the entry in the specified index.
+     *
+     * @param index Index location of entry to be fetched.
+     * @return the vector entry at {@code index}.
+     * @throws OutOfRangeException if the index is not valid.
+     * @see #setEntry(int, FieldElement)
+     */
+    T getEntry(int index) throws OutOfRangeException;
+
+    /**
+     * Set a single element.
+     *
+     * @param index element index.
+     * @param value new value for the element.
+     * @throws OutOfRangeException if the index is not valid.
+     * @see #getEntry(int)
+     */
+    void setEntry(int index, T value) throws OutOfRangeException;
+
+    /**
+     * Returns the size of the vector.
+     *
+     * @return size
+     */
+    int getDimension();
+
+    /**
+     * Construct a vector by appending a vector to this vector.
+     *
+     * @param v vector to append to this one.
+     * @return a new vector
+     */
+    FieldVector<T> append(FieldVector<T> v);
+
+    /**
+     * Construct a vector by appending a T to this vector.
+     *
+     * @param d T to append.
+     * @return a new vector
+     */
+    FieldVector<T> append(T d);
+
+    /**
+     * Get a subvector from consecutive elements.
+     *
+     * @param index index of first element.
+     * @param n number of elements to be retrieved.
+     * @return a vector containing n elements.
+     * @throws OutOfRangeException if the index is not valid.
+     * @throws NotPositiveException if the number of elements if not positive.
+     */
+    FieldVector<T> getSubVector(int index, int n) throws OutOfRangeException, NotPositiveException;
+
+    /**
+     * Set a set of consecutive elements.
+     *
+     * @param index index of first element to be set.
+     * @param v vector containing the values to set.
+     * @throws OutOfRangeException if the index is not valid.
+     */
+    void setSubVector(int index, FieldVector<T> v) throws OutOfRangeException;
+
+    /**
+     * Set all elements to a single value.
+     *
+     * @param value single value to set for all elements
+     */
+    void set(T value);
+
+    /**
+     * Convert the vector to a T array.
+     *
+     * <p>The array is independent from vector data, it's elements are copied.
+     *
+     * @return array containing a copy of vector elements
+     */
+    T[] toArray();
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/FieldVectorChangingVisitor.java b/src/main/java/org/apache/commons/math3/linear/FieldVectorChangingVisitor.java
new file mode 100644
index 0000000..2637a1d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/FieldVectorChangingVisitor.java
@@ -0,0 +1,55 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.FieldElement;
+
+/**
+ * This interface defines a visitor for the entries of a vector. Visitors implementing this
+ * interface may alter the entries of the vector being visited.
+ *
+ * @param <T> the type of the field elements
+ * @since 3.3
+ */
+public interface FieldVectorChangingVisitor<T extends FieldElement<?>> {
+    /**
+     * Start visiting a vector. This method is called once, before any entry of the vector is
+     * visited.
+     *
+     * @param dimension the size of the vector
+     * @param start the index of the first entry to be visited
+     * @param end the index of the last entry to be visited (inclusive)
+     */
+    void start(int dimension, int start, int end);
+
+    /**
+     * Visit one entry of the vector.
+     *
+     * @param index the index of the entry being visited
+     * @param value the value of the entry being visited
+     * @return the new value of the entry being visited
+     */
+    T visit(int index, T value);
+
+    /**
+     * End visiting a vector. This method is called once, after all entries of the vector have been
+     * visited.
+     *
+     * @return the value returned after visiting all entries
+     */
+    T end();
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/FieldVectorPreservingVisitor.java b/src/main/java/org/apache/commons/math3/linear/FieldVectorPreservingVisitor.java
new file mode 100644
index 0000000..f6b8751
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/FieldVectorPreservingVisitor.java
@@ -0,0 +1,54 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.FieldElement;
+
+/**
+ * This interface defines a visitor for the entries of a vector. Visitors implementing this
+ * interface do not alter the entries of the vector being visited.
+ *
+ * @param <T> the type of the field elements
+ * @since 3.3
+ */
+public interface FieldVectorPreservingVisitor<T extends FieldElement<?>> {
+    /**
+     * Start visiting a vector. This method is called once, before any entry of the vector is
+     * visited.
+     *
+     * @param dimension the size of the vector
+     * @param start the index of the first entry to be visited
+     * @param end the index of the last entry to be visited (inclusive)
+     */
+    void start(int dimension, int start, int end);
+
+    /**
+     * Visit one entry of the vector.
+     *
+     * @param index the index of the entry being visited
+     * @param value the value of the entry being visited
+     */
+    void visit(int index, T value);
+
+    /**
+     * End visiting a vector. This method is called once, after all entries of the vector have been
+     * visited.
+     *
+     * @return the value returned after visiting all entries
+     */
+    T end();
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/HessenbergTransformer.java b/src/main/java/org/apache/commons/math3/linear/HessenbergTransformer.java
new file mode 100644
index 0000000..541eb1e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/HessenbergTransformer.java
@@ -0,0 +1,243 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.Precision;
+
+/**
+ * Class transforming a general real matrix to Hessenberg form.
+ *
+ * <p>A m &times; m matrix A can be written as the product of three matrices: A = P &times; H
+ * &times; P<sup>T</sup> with P an orthogonal matrix and H a Hessenberg matrix. Both P and H are m
+ * &times; m matrices.
+ *
+ * <p>Transformation to Hessenberg form is often not a goal by itself, but it is an intermediate
+ * step in more general decomposition algorithms like {@link EigenDecomposition eigen
+ * decomposition}. This class is therefore intended for internal use by the library and is not
+ * public. As a consequence of this explicitly limited scope, many methods directly returns
+ * references to internal arrays, not copies.
+ *
+ * <p>This class is based on the method orthes in class EigenvalueDecomposition from the <a
+ * href="http://math.nist.gov/javanumerics/jama/">JAMA</a> library.
+ *
+ * @see <a href="http://mathworld.wolfram.com/HessenbergDecomposition.html">MathWorld</a>
+ * @see <a href="http://en.wikipedia.org/wiki/Householder_transformation">Householder
+ *     Transformations</a>
+ * @since 3.1
+ */
+class HessenbergTransformer {
+    /** Householder vectors. */
+    private final double householderVectors[][];
+
+    /** Temporary storage vector. */
+    private final double ort[];
+
+    /** Cached value of P. */
+    private RealMatrix cachedP;
+
+    /** Cached value of Pt. */
+    private RealMatrix cachedPt;
+
+    /** Cached value of H. */
+    private RealMatrix cachedH;
+
+    /**
+     * Build the transformation to Hessenberg form of a general matrix.
+     *
+     * @param matrix matrix to transform
+     * @throws NonSquareMatrixException if the matrix is not square
+     */
+    HessenbergTransformer(final RealMatrix matrix) {
+        if (!matrix.isSquare()) {
+            throw new NonSquareMatrixException(
+                    matrix.getRowDimension(), matrix.getColumnDimension());
+        }
+
+        final int m = matrix.getRowDimension();
+        householderVectors = matrix.getData();
+        ort = new double[m];
+        cachedP = null;
+        cachedPt = null;
+        cachedH = null;
+
+        // transform matrix
+        transform();
+    }
+
+    /**
+     * Returns the matrix P of the transform.
+     *
+     * <p>P is an orthogonal matrix, i.e. its inverse is also its transpose.
+     *
+     * @return the P matrix
+     */
+    public RealMatrix getP() {
+        if (cachedP == null) {
+            final int n = householderVectors.length;
+            final int high = n - 1;
+            final double[][] pa = new double[n][n];
+
+            for (int i = 0; i < n; i++) {
+                for (int j = 0; j < n; j++) {
+                    pa[i][j] = (i == j) ? 1 : 0;
+                }
+            }
+
+            for (int m = high - 1; m >= 1; m--) {
+                if (householderVectors[m][m - 1] != 0.0) {
+                    for (int i = m + 1; i <= high; i++) {
+                        ort[i] = householderVectors[i][m - 1];
+                    }
+
+                    for (int j = m; j <= high; j++) {
+                        double g = 0.0;
+
+                        for (int i = m; i <= high; i++) {
+                            g += ort[i] * pa[i][j];
+                        }
+
+                        // Double division avoids possible underflow
+                        g = (g / ort[m]) / householderVectors[m][m - 1];
+
+                        for (int i = m; i <= high; i++) {
+                            pa[i][j] += g * ort[i];
+                        }
+                    }
+                }
+            }
+
+            cachedP = MatrixUtils.createRealMatrix(pa);
+        }
+        return cachedP;
+    }
+
+    /**
+     * Returns the transpose of the matrix P of the transform.
+     *
+     * <p>P is an orthogonal matrix, i.e. its inverse is also its transpose.
+     *
+     * @return the transpose of the P matrix
+     */
+    public RealMatrix getPT() {
+        if (cachedPt == null) {
+            cachedPt = getP().transpose();
+        }
+
+        // return the cached matrix
+        return cachedPt;
+    }
+
+    /**
+     * Returns the Hessenberg matrix H of the transform.
+     *
+     * @return the H matrix
+     */
+    public RealMatrix getH() {
+        if (cachedH == null) {
+            final int m = householderVectors.length;
+            final double[][] h = new double[m][m];
+            for (int i = 0; i < m; ++i) {
+                if (i > 0) {
+                    // copy the entry of the lower sub-diagonal
+                    h[i][i - 1] = householderVectors[i][i - 1];
+                }
+
+                // copy upper triangular part of the matrix
+                for (int j = i; j < m; ++j) {
+                    h[i][j] = householderVectors[i][j];
+                }
+            }
+            cachedH = MatrixUtils.createRealMatrix(h);
+        }
+
+        // return the cached matrix
+        return cachedH;
+    }
+
+    /**
+     * Get the Householder vectors of the transform.
+     *
+     * <p>Note that since this class is only intended for internal use, it returns directly a
+     * reference to its internal arrays, not a copy.
+     *
+     * @return the main diagonal elements of the B matrix
+     */
+    double[][] getHouseholderVectorsRef() {
+        return householderVectors;
+    }
+
+    /**
+     * Transform original matrix to Hessenberg form.
+     *
+     * <p>Transformation is done using Householder transforms.
+     */
+    private void transform() {
+        final int n = householderVectors.length;
+        final int high = n - 1;
+
+        for (int m = 1; m <= high - 1; m++) {
+            // Scale column.
+            double scale = 0;
+            for (int i = m; i <= high; i++) {
+                scale += FastMath.abs(householderVectors[i][m - 1]);
+            }
+
+            if (!Precision.equals(scale, 0)) {
+                // Compute Householder transformation.
+                double h = 0;
+                for (int i = high; i >= m; i--) {
+                    ort[i] = householderVectors[i][m - 1] / scale;
+                    h += ort[i] * ort[i];
+                }
+                final double g = (ort[m] > 0) ? -FastMath.sqrt(h) : FastMath.sqrt(h);
+
+                h -= ort[m] * g;
+                ort[m] -= g;
+
+                // Apply Householder similarity transformation
+                // H = (I - u*u' / h) * H * (I - u*u' / h)
+
+                for (int j = m; j < n; j++) {
+                    double f = 0;
+                    for (int i = high; i >= m; i--) {
+                        f += ort[i] * householderVectors[i][j];
+                    }
+                    f /= h;
+                    for (int i = m; i <= high; i++) {
+                        householderVectors[i][j] -= f * ort[i];
+                    }
+                }
+
+                for (int i = 0; i <= high; i++) {
+                    double f = 0;
+                    for (int j = high; j >= m; j--) {
+                        f += ort[j] * householderVectors[i][j];
+                    }
+                    f /= h;
+                    for (int j = m; j <= high; j++) {
+                        householderVectors[i][j] -= f * ort[j];
+                    }
+                }
+
+                ort[m] = scale * ort[m];
+                householderVectors[m][m - 1] = scale * g;
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/IllConditionedOperatorException.java b/src/main/java/org/apache/commons/math3/linear/IllConditionedOperatorException.java
new file mode 100644
index 0000000..7d52ec2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/IllConditionedOperatorException.java
@@ -0,0 +1,39 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * An exception to be thrown when the condition number of a {@link RealLinearOperator} is too high.
+ *
+ * @since 3.0
+ */
+public class IllConditionedOperatorException extends MathIllegalArgumentException {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = -7883263944530490135L;
+
+    /**
+     * Creates a new instance of this class.
+     *
+     * @param cond An estimate of the condition number of the offending linear operator.
+     */
+    public IllConditionedOperatorException(final double cond) {
+        super(LocalizedFormats.ILL_CONDITIONED_OPERATOR, cond);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/IterativeLinearSolver.java b/src/main/java/org/apache/commons/math3/linear/IterativeLinearSolver.java
new file mode 100644
index 0000000..cb7fa90
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/IterativeLinearSolver.java
@@ -0,0 +1,166 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.util.IterationManager;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * This abstract class defines an iterative solver for the linear system A &middot; x = b. In what
+ * follows, the <em>residual</em> r is defined as r = b - A &middot; x, where A is the linear
+ * operator of the linear system, b is the right-hand side vector, and x the current estimate of the
+ * solution.
+ *
+ * @since 3.0
+ */
+public abstract class IterativeLinearSolver {
+
+    /** The object in charge of managing the iterations. */
+    private final IterationManager manager;
+
+    /**
+     * Creates a new instance of this class, with default iteration manager.
+     *
+     * @param maxIterations the maximum number of iterations
+     */
+    public IterativeLinearSolver(final int maxIterations) {
+        this.manager = new IterationManager(maxIterations);
+    }
+
+    /**
+     * Creates a new instance of this class, with custom iteration manager.
+     *
+     * @param manager the custom iteration manager
+     * @throws NullArgumentException if {@code manager} is {@code null}
+     */
+    public IterativeLinearSolver(final IterationManager manager) throws NullArgumentException {
+        MathUtils.checkNotNull(manager);
+        this.manager = manager;
+    }
+
+    /**
+     * Performs all dimension checks on the parameters of {@link #solve(RealLinearOperator,
+     * RealVector, RealVector) solve} and {@link #solveInPlace(RealLinearOperator, RealVector,
+     * RealVector) solveInPlace}, and throws an exception if one of the checks fails.
+     *
+     * @param a the linear operator A of the system
+     * @param b the right-hand side vector
+     * @param x0 the initial guess of the solution
+     * @throws NullArgumentException if one of the parameters is {@code null}
+     * @throws NonSquareOperatorException if {@code a} is not square
+     * @throws DimensionMismatchException if {@code b} or {@code x0} have dimensions inconsistent
+     *     with {@code a}
+     */
+    protected static void checkParameters(
+            final RealLinearOperator a, final RealVector b, final RealVector x0)
+            throws NullArgumentException, NonSquareOperatorException, DimensionMismatchException {
+        MathUtils.checkNotNull(a);
+        MathUtils.checkNotNull(b);
+        MathUtils.checkNotNull(x0);
+        if (a.getRowDimension() != a.getColumnDimension()) {
+            throw new NonSquareOperatorException(a.getRowDimension(), a.getColumnDimension());
+        }
+        if (b.getDimension() != a.getRowDimension()) {
+            throw new DimensionMismatchException(b.getDimension(), a.getRowDimension());
+        }
+        if (x0.getDimension() != a.getColumnDimension()) {
+            throw new DimensionMismatchException(x0.getDimension(), a.getColumnDimension());
+        }
+    }
+
+    /**
+     * Returns the iteration manager attached to this solver.
+     *
+     * @return the manager
+     */
+    public IterationManager getIterationManager() {
+        return manager;
+    }
+
+    /**
+     * Returns an estimate of the solution to the linear system A &middot; x = b.
+     *
+     * @param a the linear operator A of the system
+     * @param b the right-hand side vector
+     * @return a new vector containing the solution
+     * @throws NullArgumentException if one of the parameters is {@code null}
+     * @throws NonSquareOperatorException if {@code a} is not square
+     * @throws DimensionMismatchException if {@code b} has dimensions inconsistent with {@code a}
+     * @throws MaxCountExceededException at exhaustion of the iteration count, unless a custom
+     *     {@link org.apache.commons.math3.util.Incrementor.MaxCountExceededCallback callback} has
+     *     been set at construction of the {@link IterationManager}
+     */
+    public RealVector solve(final RealLinearOperator a, final RealVector b)
+            throws NullArgumentException,
+                    NonSquareOperatorException,
+                    DimensionMismatchException,
+                    MaxCountExceededException {
+        MathUtils.checkNotNull(a);
+        final RealVector x = new ArrayRealVector(a.getColumnDimension());
+        x.set(0.);
+        return solveInPlace(a, b, x);
+    }
+
+    /**
+     * Returns an estimate of the solution to the linear system A &middot; x = b.
+     *
+     * @param a the linear operator A of the system
+     * @param b the right-hand side vector
+     * @param x0 the initial guess of the solution
+     * @return a new vector containing the solution
+     * @throws NullArgumentException if one of the parameters is {@code null}
+     * @throws NonSquareOperatorException if {@code a} is not square
+     * @throws DimensionMismatchException if {@code b} or {@code x0} have dimensions inconsistent
+     *     with {@code a}
+     * @throws MaxCountExceededException at exhaustion of the iteration count, unless a custom
+     *     {@link org.apache.commons.math3.util.Incrementor.MaxCountExceededCallback callback} has
+     *     been set at construction of the {@link IterationManager}
+     */
+    public RealVector solve(RealLinearOperator a, RealVector b, RealVector x0)
+            throws NullArgumentException,
+                    NonSquareOperatorException,
+                    DimensionMismatchException,
+                    MaxCountExceededException {
+        MathUtils.checkNotNull(x0);
+        return solveInPlace(a, b, x0.copy());
+    }
+
+    /**
+     * Returns an estimate of the solution to the linear system A &middot; x = b. The solution is
+     * computed in-place (initial guess is modified).
+     *
+     * @param a the linear operator A of the system
+     * @param b the right-hand side vector
+     * @param x0 initial guess of the solution
+     * @return a reference to {@code x0} (shallow copy) updated with the solution
+     * @throws NullArgumentException if one of the parameters is {@code null}
+     * @throws NonSquareOperatorException if {@code a} is not square
+     * @throws DimensionMismatchException if {@code b} or {@code x0} have dimensions inconsistent
+     *     with {@code a}
+     * @throws MaxCountExceededException at exhaustion of the iteration count, unless a custom
+     *     {@link org.apache.commons.math3.util.Incrementor.MaxCountExceededCallback callback} has
+     *     been set at construction of the {@link IterationManager}
+     */
+    public abstract RealVector solveInPlace(RealLinearOperator a, RealVector b, RealVector x0)
+            throws NullArgumentException,
+                    NonSquareOperatorException,
+                    DimensionMismatchException,
+                    MaxCountExceededException;
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/IterativeLinearSolverEvent.java b/src/main/java/org/apache/commons/math3/linear/IterativeLinearSolverEvent.java
new file mode 100644
index 0000000..ad98ca3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/IterativeLinearSolverEvent.java
@@ -0,0 +1,104 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.exception.MathUnsupportedOperationException;
+import org.apache.commons.math3.util.IterationEvent;
+
+/**
+ * This is the base class for all events occurring during the iterations of a {@link
+ * IterativeLinearSolver}.
+ *
+ * @since 3.0
+ */
+public abstract class IterativeLinearSolverEvent extends IterationEvent {
+    /** Serialization identifier. */
+    private static final long serialVersionUID = 20120129L;
+
+    /**
+     * Creates a new instance of this class.
+     *
+     * @param source the iterative algorithm on which the event initially occurred
+     * @param iterations the number of iterations performed at the time {@code this} event is
+     *     created
+     */
+    public IterativeLinearSolverEvent(final Object source, final int iterations) {
+        super(source, iterations);
+    }
+
+    /**
+     * Returns the current right-hand side of the linear system to be solved. This method should
+     * return an unmodifiable view, or a deep copy of the actual right-hand side vector, in order
+     * not to compromise subsequent iterations of the source {@link IterativeLinearSolver}.
+     *
+     * @return the right-hand side vector, b
+     */
+    public abstract RealVector getRightHandSideVector();
+
+    /**
+     * Returns the norm of the residual. The returned value is not required to be <em>exact</em>.
+     * Instead, the norm of the so-called <em>updated</em> residual (if available) should be
+     * returned. For example, the {@link ConjugateGradient conjugate gradient} method computes a
+     * sequence of residuals, the norm of which is cheap to compute. However, due to accumulation of
+     * round-off errors, this residual might differ from the true residual after some iterations.
+     * See e.g. A. Greenbaum and Z. Strakos, <em>Predicting the Behavior of Finite Precision Lanzos
+     * and Conjugate Gradient Computations</em>, Technical Report 538, Department of Computer
+     * Science, New York University, 1991 (available <a
+     * href="http://www.archive.org/details/predictingbehavi00gree">here</a>).
+     *
+     * @return the norm of the residual, ||r||
+     */
+    public abstract double getNormOfResidual();
+
+    /**
+     * Returns the residual. This is an optional operation, as all iterative linear solvers do not
+     * provide cheap estimate of the updated residual vector, in which case
+     *
+     * <ul>
+     *   <li>this method should throw a {@link MathUnsupportedOperationException},
+     *   <li>{@link #providesResidual()} returns {@code false}.
+     * </ul>
+     *
+     * <p>The default implementation throws a {@link MathUnsupportedOperationException}. If this
+     * method is overriden, then {@link #providesResidual()} should be overriden as well.
+     *
+     * @return the updated residual, r
+     */
+    public RealVector getResidual() {
+        throw new MathUnsupportedOperationException();
+    }
+
+    /**
+     * Returns the current estimate of the solution to the linear system to be solved. This method
+     * should return an unmodifiable view, or a deep copy of the actual current solution, in order
+     * not to compromise subsequent iterations of the source {@link IterativeLinearSolver}.
+     *
+     * @return the solution, x
+     */
+    public abstract RealVector getSolution();
+
+    /**
+     * Returns {@code true} if {@link #getResidual()} is supported. The default implementation
+     * returns {@code false}.
+     *
+     * @return {@code false} if {@link #getResidual()} throws a {@link
+     *     MathUnsupportedOperationException}
+     */
+    public boolean providesResidual() {
+        return false;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/JacobiPreconditioner.java b/src/main/java/org/apache/commons/math3/linear/JacobiPreconditioner.java
new file mode 100644
index 0000000..d6b0d53
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/JacobiPreconditioner.java
@@ -0,0 +1,127 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.analysis.function.Sqrt;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * This class implements the standard Jacobi (diagonal) preconditioner. For a matrix A<sub>ij</sub>,
+ * this preconditioner is M = diag(1 / A<sub>11</sub>, 1 / A<sub>22</sub>, &hellip;).
+ *
+ * @since 3.0
+ */
+public class JacobiPreconditioner extends RealLinearOperator {
+
+    /** The diagonal coefficients of the preconditioner. */
+    private final ArrayRealVector diag;
+
+    /**
+     * Creates a new instance of this class.
+     *
+     * @param diag the diagonal coefficients of the linear operator to be preconditioned
+     * @param deep {@code true} if a deep copy of the above array should be performed
+     */
+    public JacobiPreconditioner(final double[] diag, final boolean deep) {
+        this.diag = new ArrayRealVector(diag, deep);
+    }
+
+    /**
+     * Creates a new instance of this class. This method extracts the diagonal coefficients of the
+     * specified linear operator. If {@code a} does not extend {@link AbstractRealMatrix}, then the
+     * coefficients of the underlying matrix are not accessible, coefficient extraction is made by
+     * matrix-vector products with the basis vectors (and might therefore take some time). With
+     * matrices, direct entry access is carried out.
+     *
+     * @param a the linear operator for which the preconditioner should be built
+     * @return the diagonal preconditioner made of the inverse of the diagonal coefficients of the
+     *     specified linear operator
+     * @throws NonSquareOperatorException if {@code a} is not square
+     */
+    public static JacobiPreconditioner create(final RealLinearOperator a)
+            throws NonSquareOperatorException {
+        final int n = a.getColumnDimension();
+        if (a.getRowDimension() != n) {
+            throw new NonSquareOperatorException(a.getRowDimension(), n);
+        }
+        final double[] diag = new double[n];
+        if (a instanceof AbstractRealMatrix) {
+            final AbstractRealMatrix m = (AbstractRealMatrix) a;
+            for (int i = 0; i < n; i++) {
+                diag[i] = m.getEntry(i, i);
+            }
+        } else {
+            final ArrayRealVector x = new ArrayRealVector(n);
+            for (int i = 0; i < n; i++) {
+                x.set(0.);
+                x.setEntry(i, 1.);
+                diag[i] = a.operate(x).getEntry(i);
+            }
+        }
+        return new JacobiPreconditioner(diag, false);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getColumnDimension() {
+        return diag.getDimension();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getRowDimension() {
+        return diag.getDimension();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector operate(final RealVector x) {
+        // Dimension check is carried out by ebeDivide
+        return new ArrayRealVector(MathArrays.ebeDivide(x.toArray(), diag.toArray()), false);
+    }
+
+    /**
+     * Returns the square root of {@code this} diagonal operator. More precisely, this method
+     * returns P = diag(1 / &radic;A<sub>11</sub>, 1 / &radic;A<sub>22</sub>, &hellip;).
+     *
+     * @return the square root of {@code this} preconditioner
+     * @since 3.1
+     */
+    public RealLinearOperator sqrt() {
+        final RealVector sqrtDiag = diag.map(new Sqrt());
+        return new RealLinearOperator() {
+            /** {@inheritDoc} */
+            @Override
+            public RealVector operate(final RealVector x) {
+                return new ArrayRealVector(
+                        MathArrays.ebeDivide(x.toArray(), sqrtDiag.toArray()), false);
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public int getRowDimension() {
+                return sqrtDiag.getDimension();
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public int getColumnDimension() {
+                return sqrtDiag.getDimension();
+            }
+        };
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/LUDecomposition.java b/src/main/java/org/apache/commons/math3/linear/LUDecomposition.java
new file mode 100644
index 0000000..798bb61
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/LUDecomposition.java
@@ -0,0 +1,409 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Calculates the LUP-decomposition of a square matrix.
+ *
+ * <p>The LUP-decomposition of a matrix A consists of three matrices L, U and P that satisfy:
+ * P&times;A = L&times;U. L is lower triangular (with unit diagonal terms), U is upper triangular
+ * and P is a permutation matrix. All matrices are m&times;m.
+ *
+ * <p>As shown by the presence of the P matrix, this decomposition is implemented using partial
+ * pivoting.
+ *
+ * <p>This class is based on the class with similar name from the <a
+ * href="http://math.nist.gov/javanumerics/jama/">JAMA</a> library.
+ *
+ * <ul>
+ *   <li>a {@link #getP() getP} method has been added,
+ *   <li>the {@code det} method has been renamed as {@link #getDeterminant() getDeterminant},
+ *   <li>the {@code getDoublePivot} method has been removed (but the int based {@link #getPivot()
+ *       getPivot} method has been kept),
+ *   <li>the {@code solve} and {@code isNonSingular} methods have been replaced by a {@link
+ *       #getSolver() getSolver} method and the equivalent methods provided by the returned {@link
+ *       DecompositionSolver}.
+ * </ul>
+ *
+ * @see <a href="http://mathworld.wolfram.com/LUDecomposition.html">MathWorld</a>
+ * @see <a href="http://en.wikipedia.org/wiki/LU_decomposition">Wikipedia</a>
+ * @since 2.0 (changed to concrete class in 3.0)
+ */
+public class LUDecomposition {
+    /** Default bound to determine effective singularity in LU decomposition. */
+    private static final double DEFAULT_TOO_SMALL = 1e-11;
+
+    /** Entries of LU decomposition. */
+    private final double[][] lu;
+
+    /** Pivot permutation associated with LU decomposition. */
+    private final int[] pivot;
+
+    /** Parity of the permutation associated with the LU decomposition. */
+    private boolean even;
+
+    /** Singularity indicator. */
+    private boolean singular;
+
+    /** Cached value of L. */
+    private RealMatrix cachedL;
+
+    /** Cached value of U. */
+    private RealMatrix cachedU;
+
+    /** Cached value of P. */
+    private RealMatrix cachedP;
+
+    /**
+     * Calculates the LU-decomposition of the given matrix. This constructor uses 1e-11 as default
+     * value for the singularity threshold.
+     *
+     * @param matrix Matrix to decompose.
+     * @throws NonSquareMatrixException if matrix is not square.
+     */
+    public LUDecomposition(RealMatrix matrix) {
+        this(matrix, DEFAULT_TOO_SMALL);
+    }
+
+    /**
+     * Calculates the LU-decomposition of the given matrix.
+     *
+     * @param matrix The matrix to decompose.
+     * @param singularityThreshold threshold (based on partial row norm) under which a matrix is
+     *     considered singular
+     * @throws NonSquareMatrixException if matrix is not square
+     */
+    public LUDecomposition(RealMatrix matrix, double singularityThreshold) {
+        if (!matrix.isSquare()) {
+            throw new NonSquareMatrixException(
+                    matrix.getRowDimension(), matrix.getColumnDimension());
+        }
+
+        final int m = matrix.getColumnDimension();
+        lu = matrix.getData();
+        pivot = new int[m];
+        cachedL = null;
+        cachedU = null;
+        cachedP = null;
+
+        // Initialize permutation array and parity
+        for (int row = 0; row < m; row++) {
+            pivot[row] = row;
+        }
+        even = true;
+        singular = false;
+
+        // Loop over columns
+        for (int col = 0; col < m; col++) {
+
+            // upper
+            for (int row = 0; row < col; row++) {
+                final double[] luRow = lu[row];
+                double sum = luRow[col];
+                for (int i = 0; i < row; i++) {
+                    sum -= luRow[i] * lu[i][col];
+                }
+                luRow[col] = sum;
+            }
+
+            // lower
+            int max = col; // permutation row
+            double largest = Double.NEGATIVE_INFINITY;
+            for (int row = col; row < m; row++) {
+                final double[] luRow = lu[row];
+                double sum = luRow[col];
+                for (int i = 0; i < col; i++) {
+                    sum -= luRow[i] * lu[i][col];
+                }
+                luRow[col] = sum;
+
+                // maintain best permutation choice
+                if (FastMath.abs(sum) > largest) {
+                    largest = FastMath.abs(sum);
+                    max = row;
+                }
+            }
+
+            // Singularity check
+            if (FastMath.abs(lu[max][col]) < singularityThreshold) {
+                singular = true;
+                return;
+            }
+
+            // Pivot if necessary
+            if (max != col) {
+                double tmp = 0;
+                final double[] luMax = lu[max];
+                final double[] luCol = lu[col];
+                for (int i = 0; i < m; i++) {
+                    tmp = luMax[i];
+                    luMax[i] = luCol[i];
+                    luCol[i] = tmp;
+                }
+                int temp = pivot[max];
+                pivot[max] = pivot[col];
+                pivot[col] = temp;
+                even = !even;
+            }
+
+            // Divide the lower elements by the "winning" diagonal elt.
+            final double luDiag = lu[col][col];
+            for (int row = col + 1; row < m; row++) {
+                lu[row][col] /= luDiag;
+            }
+        }
+    }
+
+    /**
+     * Returns the matrix L of the decomposition.
+     *
+     * <p>L is a lower-triangular matrix
+     *
+     * @return the L matrix (or null if decomposed matrix is singular)
+     */
+    public RealMatrix getL() {
+        if ((cachedL == null) && !singular) {
+            final int m = pivot.length;
+            cachedL = MatrixUtils.createRealMatrix(m, m);
+            for (int i = 0; i < m; ++i) {
+                final double[] luI = lu[i];
+                for (int j = 0; j < i; ++j) {
+                    cachedL.setEntry(i, j, luI[j]);
+                }
+                cachedL.setEntry(i, i, 1.0);
+            }
+        }
+        return cachedL;
+    }
+
+    /**
+     * Returns the matrix U of the decomposition.
+     *
+     * <p>U is an upper-triangular matrix
+     *
+     * @return the U matrix (or null if decomposed matrix is singular)
+     */
+    public RealMatrix getU() {
+        if ((cachedU == null) && !singular) {
+            final int m = pivot.length;
+            cachedU = MatrixUtils.createRealMatrix(m, m);
+            for (int i = 0; i < m; ++i) {
+                final double[] luI = lu[i];
+                for (int j = i; j < m; ++j) {
+                    cachedU.setEntry(i, j, luI[j]);
+                }
+            }
+        }
+        return cachedU;
+    }
+
+    /**
+     * Returns the P rows permutation matrix.
+     *
+     * <p>P is a sparse matrix with exactly one element set to 1.0 in each row and each column, all
+     * other elements being set to 0.0.
+     *
+     * <p>The positions of the 1 elements are given by the {@link #getPivot() pivot permutation
+     * vector}.
+     *
+     * @return the P rows permutation matrix (or null if decomposed matrix is singular)
+     * @see #getPivot()
+     */
+    public RealMatrix getP() {
+        if ((cachedP == null) && !singular) {
+            final int m = pivot.length;
+            cachedP = MatrixUtils.createRealMatrix(m, m);
+            for (int i = 0; i < m; ++i) {
+                cachedP.setEntry(i, pivot[i], 1.0);
+            }
+        }
+        return cachedP;
+    }
+
+    /**
+     * Returns the pivot permutation vector.
+     *
+     * @return the pivot permutation vector
+     * @see #getP()
+     */
+    public int[] getPivot() {
+        return pivot.clone();
+    }
+
+    /**
+     * Return the determinant of the matrix
+     *
+     * @return determinant of the matrix
+     */
+    public double getDeterminant() {
+        if (singular) {
+            return 0;
+        } else {
+            final int m = pivot.length;
+            double determinant = even ? 1 : -1;
+            for (int i = 0; i < m; i++) {
+                determinant *= lu[i][i];
+            }
+            return determinant;
+        }
+    }
+
+    /**
+     * Get a solver for finding the A &times; X = B solution in exact linear sense.
+     *
+     * @return a solver
+     */
+    public DecompositionSolver getSolver() {
+        return new Solver(lu, pivot, singular);
+    }
+
+    /** Specialized solver. */
+    private static class Solver implements DecompositionSolver {
+
+        /** Entries of LU decomposition. */
+        private final double[][] lu;
+
+        /** Pivot permutation associated with LU decomposition. */
+        private final int[] pivot;
+
+        /** Singularity indicator. */
+        private final boolean singular;
+
+        /**
+         * Build a solver from decomposed matrix.
+         *
+         * @param lu entries of LU decomposition
+         * @param pivot pivot permutation associated with LU decomposition
+         * @param singular singularity indicator
+         */
+        private Solver(final double[][] lu, final int[] pivot, final boolean singular) {
+            this.lu = lu;
+            this.pivot = pivot;
+            this.singular = singular;
+        }
+
+        /** {@inheritDoc} */
+        public boolean isNonSingular() {
+            return !singular;
+        }
+
+        /** {@inheritDoc} */
+        public RealVector solve(RealVector b) {
+            final int m = pivot.length;
+            if (b.getDimension() != m) {
+                throw new DimensionMismatchException(b.getDimension(), m);
+            }
+            if (singular) {
+                throw new SingularMatrixException();
+            }
+
+            final double[] bp = new double[m];
+
+            // Apply permutations to b
+            for (int row = 0; row < m; row++) {
+                bp[row] = b.getEntry(pivot[row]);
+            }
+
+            // Solve LY = b
+            for (int col = 0; col < m; col++) {
+                final double bpCol = bp[col];
+                for (int i = col + 1; i < m; i++) {
+                    bp[i] -= bpCol * lu[i][col];
+                }
+            }
+
+            // Solve UX = Y
+            for (int col = m - 1; col >= 0; col--) {
+                bp[col] /= lu[col][col];
+                final double bpCol = bp[col];
+                for (int i = 0; i < col; i++) {
+                    bp[i] -= bpCol * lu[i][col];
+                }
+            }
+
+            return new ArrayRealVector(bp, false);
+        }
+
+        /** {@inheritDoc} */
+        public RealMatrix solve(RealMatrix b) {
+
+            final int m = pivot.length;
+            if (b.getRowDimension() != m) {
+                throw new DimensionMismatchException(b.getRowDimension(), m);
+            }
+            if (singular) {
+                throw new SingularMatrixException();
+            }
+
+            final int nColB = b.getColumnDimension();
+
+            // Apply permutations to b
+            final double[][] bp = new double[m][nColB];
+            for (int row = 0; row < m; row++) {
+                final double[] bpRow = bp[row];
+                final int pRow = pivot[row];
+                for (int col = 0; col < nColB; col++) {
+                    bpRow[col] = b.getEntry(pRow, col);
+                }
+            }
+
+            // Solve LY = b
+            for (int col = 0; col < m; col++) {
+                final double[] bpCol = bp[col];
+                for (int i = col + 1; i < m; i++) {
+                    final double[] bpI = bp[i];
+                    final double luICol = lu[i][col];
+                    for (int j = 0; j < nColB; j++) {
+                        bpI[j] -= bpCol[j] * luICol;
+                    }
+                }
+            }
+
+            // Solve UX = Y
+            for (int col = m - 1; col >= 0; col--) {
+                final double[] bpCol = bp[col];
+                final double luDiag = lu[col][col];
+                for (int j = 0; j < nColB; j++) {
+                    bpCol[j] /= luDiag;
+                }
+                for (int i = 0; i < col; i++) {
+                    final double[] bpI = bp[i];
+                    final double luICol = lu[i][col];
+                    for (int j = 0; j < nColB; j++) {
+                        bpI[j] -= bpCol[j] * luICol;
+                    }
+                }
+            }
+
+            return new Array2DRowRealMatrix(bp, false);
+        }
+
+        /**
+         * Get the inverse of the decomposed matrix.
+         *
+         * @return the inverse matrix.
+         * @throws SingularMatrixException if the decomposed matrix is singular.
+         */
+        public RealMatrix getInverse() {
+            return solve(MatrixUtils.createRealIdentityMatrix(pivot.length));
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/MatrixDimensionMismatchException.java b/src/main/java/org/apache/commons/math3/linear/MatrixDimensionMismatchException.java
new file mode 100644
index 0000000..20b10b1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/MatrixDimensionMismatchException.java
@@ -0,0 +1,75 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.exception.MultiDimensionMismatchException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Exception to be thrown when either the number of rows or the number of columns of a matrix do not
+ * match the expected values.
+ *
+ * @since 3.0
+ */
+public class MatrixDimensionMismatchException extends MultiDimensionMismatchException {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = -8415396756375798143L;
+
+    /**
+     * Construct an exception from the mismatched dimensions.
+     *
+     * @param wrongRowDim Wrong row dimension.
+     * @param wrongColDim Wrong column dimension.
+     * @param expectedRowDim Expected row dimension.
+     * @param expectedColDim Expected column dimension.
+     */
+    public MatrixDimensionMismatchException(
+            int wrongRowDim, int wrongColDim, int expectedRowDim, int expectedColDim) {
+        super(
+                LocalizedFormats.DIMENSIONS_MISMATCH_2x2,
+                new Integer[] {wrongRowDim, wrongColDim},
+                new Integer[] {expectedRowDim, expectedColDim});
+    }
+
+    /**
+     * @return the expected row dimension.
+     */
+    public int getWrongRowDimension() {
+        return getWrongDimension(0);
+    }
+
+    /**
+     * @return the expected row dimension.
+     */
+    public int getExpectedRowDimension() {
+        return getExpectedDimension(0);
+    }
+
+    /**
+     * @return the wrong column dimension.
+     */
+    public int getWrongColumnDimension() {
+        return getWrongDimension(1);
+    }
+
+    /**
+     * @return the expected column dimension.
+     */
+    public int getExpectedColumnDimension() {
+        return getExpectedDimension(1);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/MatrixUtils.java b/src/main/java/org/apache/commons/math3/linear/MatrixUtils.java
new file mode 100644
index 0000000..57ef402
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/MatrixUtils.java
@@ -0,0 +1,1080 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.FieldElement;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.ZeroException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.fraction.BigFraction;
+import org.apache.commons.math3.fraction.Fraction;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.util.MathUtils;
+import org.apache.commons.math3.util.Precision;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Arrays;
+
+/** A collection of static methods that operate on or return matrices. */
+public class MatrixUtils {
+
+    /**
+     * The default format for {@link RealMatrix} objects.
+     *
+     * @since 3.1
+     */
+    public static final RealMatrixFormat DEFAULT_FORMAT = RealMatrixFormat.getInstance();
+
+    /**
+     * A format for {@link RealMatrix} objects compatible with octave.
+     *
+     * @since 3.1
+     */
+    public static final RealMatrixFormat OCTAVE_FORMAT =
+            new RealMatrixFormat("[", "]", "", "", "; ", ", ");
+
+    /** Private constructor. */
+    private MatrixUtils() {
+        super();
+    }
+
+    /**
+     * Returns a {@link RealMatrix} with specified dimensions.
+     *
+     * <p>The type of matrix returned depends on the dimension. Below 2<sup>12</sup> elements (i.e.
+     * 4096 elements or 64&times;64 for a square matrix) which can be stored in a 32kB array, a
+     * {@link Array2DRowRealMatrix} instance is built. Above this threshold a {@link
+     * BlockRealMatrix} instance is built.
+     *
+     * <p>The matrix elements are all set to 0.0.
+     *
+     * @param rows number of rows of the matrix
+     * @param columns number of columns of the matrix
+     * @return RealMatrix with specified dimensions
+     * @see #createRealMatrix(double[][])
+     */
+    public static RealMatrix createRealMatrix(final int rows, final int columns) {
+        return (rows * columns <= 4096)
+                ? new Array2DRowRealMatrix(rows, columns)
+                : new BlockRealMatrix(rows, columns);
+    }
+
+    /**
+     * Returns a {@link FieldMatrix} with specified dimensions.
+     *
+     * <p>The type of matrix returned depends on the dimension. Below 2<sup>12</sup> elements (i.e.
+     * 4096 elements or 64&times;64 for a square matrix), a {@link FieldMatrix} instance is built.
+     * Above this threshold a {@link BlockFieldMatrix} instance is built.
+     *
+     * <p>The matrix elements are all set to field.getZero().
+     *
+     * @param <T> the type of the field elements
+     * @param field field to which the matrix elements belong
+     * @param rows number of rows of the matrix
+     * @param columns number of columns of the matrix
+     * @return FieldMatrix with specified dimensions
+     * @see #createFieldMatrix(FieldElement[][])
+     * @since 2.0
+     */
+    public static <T extends FieldElement<T>> FieldMatrix<T> createFieldMatrix(
+            final Field<T> field, final int rows, final int columns) {
+        return (rows * columns <= 4096)
+                ? new Array2DRowFieldMatrix<T>(field, rows, columns)
+                : new BlockFieldMatrix<T>(field, rows, columns);
+    }
+
+    /**
+     * Returns a {@link RealMatrix} whose entries are the the values in the the input array.
+     *
+     * <p>The type of matrix returned depends on the dimension. Below 2<sup>12</sup> elements (i.e.
+     * 4096 elements or 64&times;64 for a square matrix) which can be stored in a 32kB array, a
+     * {@link Array2DRowRealMatrix} instance is built. Above this threshold a {@link
+     * BlockRealMatrix} instance is built.
+     *
+     * <p>The input array is copied, not referenced.
+     *
+     * @param data input array
+     * @return RealMatrix containing the values of the array
+     * @throws org.apache.commons.math3.exception.DimensionMismatchException if {@code data} is not
+     *     rectangular (not all rows have the same length).
+     * @throws NoDataException if a row or column is empty.
+     * @throws NullArgumentException if either {@code data} or {@code data[0]} is {@code null}.
+     * @throws DimensionMismatchException if {@code data} is not rectangular.
+     * @see #createRealMatrix(int, int)
+     */
+    public static RealMatrix createRealMatrix(double[][] data)
+            throws NullArgumentException, DimensionMismatchException, NoDataException {
+        if (data == null || data[0] == null) {
+            throw new NullArgumentException();
+        }
+        return (data.length * data[0].length <= 4096)
+                ? new Array2DRowRealMatrix(data)
+                : new BlockRealMatrix(data);
+    }
+
+    /**
+     * Returns a {@link FieldMatrix} whose entries are the the values in the the input array.
+     *
+     * <p>The type of matrix returned depends on the dimension. Below 2<sup>12</sup> elements (i.e.
+     * 4096 elements or 64&times;64 for a square matrix), a {@link FieldMatrix} instance is built.
+     * Above this threshold a {@link BlockFieldMatrix} instance is built.
+     *
+     * <p>The input array is copied, not referenced.
+     *
+     * @param <T> the type of the field elements
+     * @param data input array
+     * @return a matrix containing the values of the array.
+     * @throws org.apache.commons.math3.exception.DimensionMismatchException if {@code data} is not
+     *     rectangular (not all rows have the same length).
+     * @throws NoDataException if a row or column is empty.
+     * @throws NullArgumentException if either {@code data} or {@code data[0]} is {@code null}.
+     * @see #createFieldMatrix(Field, int, int)
+     * @since 2.0
+     */
+    public static <T extends FieldElement<T>> FieldMatrix<T> createFieldMatrix(T[][] data)
+            throws DimensionMismatchException, NoDataException, NullArgumentException {
+        if (data == null || data[0] == null) {
+            throw new NullArgumentException();
+        }
+        return (data.length * data[0].length <= 4096)
+                ? new Array2DRowFieldMatrix<T>(data)
+                : new BlockFieldMatrix<T>(data);
+    }
+
+    /**
+     * Returns <code>dimension x dimension</code> identity matrix.
+     *
+     * @param dimension dimension of identity matrix to generate
+     * @return identity matrix
+     * @throws IllegalArgumentException if dimension is not positive
+     * @since 1.1
+     */
+    public static RealMatrix createRealIdentityMatrix(int dimension) {
+        final RealMatrix m = createRealMatrix(dimension, dimension);
+        for (int i = 0; i < dimension; ++i) {
+            m.setEntry(i, i, 1.0);
+        }
+        return m;
+    }
+
+    /**
+     * Returns <code>dimension x dimension</code> identity matrix.
+     *
+     * @param <T> the type of the field elements
+     * @param field field to which the elements belong
+     * @param dimension dimension of identity matrix to generate
+     * @return identity matrix
+     * @throws IllegalArgumentException if dimension is not positive
+     * @since 2.0
+     */
+    public static <T extends FieldElement<T>> FieldMatrix<T> createFieldIdentityMatrix(
+            final Field<T> field, final int dimension) {
+        final T zero = field.getZero();
+        final T one = field.getOne();
+        final T[][] d = MathArrays.buildArray(field, dimension, dimension);
+        for (int row = 0; row < dimension; row++) {
+            final T[] dRow = d[row];
+            Arrays.fill(dRow, zero);
+            dRow[row] = one;
+        }
+        return new Array2DRowFieldMatrix<T>(field, d, false);
+    }
+
+    /**
+     * Returns a diagonal matrix with specified elements.
+     *
+     * @param diagonal diagonal elements of the matrix (the array elements will be copied)
+     * @return diagonal matrix
+     * @since 2.0
+     */
+    public static RealMatrix createRealDiagonalMatrix(final double[] diagonal) {
+        final RealMatrix m = createRealMatrix(diagonal.length, diagonal.length);
+        for (int i = 0; i < diagonal.length; ++i) {
+            m.setEntry(i, i, diagonal[i]);
+        }
+        return m;
+    }
+
+    /**
+     * Returns a diagonal matrix with specified elements.
+     *
+     * @param <T> the type of the field elements
+     * @param diagonal diagonal elements of the matrix (the array elements will be copied)
+     * @return diagonal matrix
+     * @since 2.0
+     */
+    public static <T extends FieldElement<T>> FieldMatrix<T> createFieldDiagonalMatrix(
+            final T[] diagonal) {
+        final FieldMatrix<T> m =
+                createFieldMatrix(diagonal[0].getField(), diagonal.length, diagonal.length);
+        for (int i = 0; i < diagonal.length; ++i) {
+            m.setEntry(i, i, diagonal[i]);
+        }
+        return m;
+    }
+
+    /**
+     * Creates a {@link RealVector} using the data from the input array.
+     *
+     * @param data the input data
+     * @return a data.length RealVector
+     * @throws NoDataException if {@code data} is empty.
+     * @throws NullArgumentException if {@code data} is {@code null}.
+     */
+    public static RealVector createRealVector(double[] data)
+            throws NoDataException, NullArgumentException {
+        if (data == null) {
+            throw new NullArgumentException();
+        }
+        return new ArrayRealVector(data, true);
+    }
+
+    /**
+     * Creates a {@link FieldVector} using the data from the input array.
+     *
+     * @param <T> the type of the field elements
+     * @param data the input data
+     * @return a data.length FieldVector
+     * @throws NoDataException if {@code data} is empty.
+     * @throws NullArgumentException if {@code data} is {@code null}.
+     * @throws ZeroException if {@code data} has 0 elements
+     */
+    public static <T extends FieldElement<T>> FieldVector<T> createFieldVector(final T[] data)
+            throws NoDataException, NullArgumentException, ZeroException {
+        if (data == null) {
+            throw new NullArgumentException();
+        }
+        if (data.length == 0) {
+            throw new ZeroException(LocalizedFormats.VECTOR_MUST_HAVE_AT_LEAST_ONE_ELEMENT);
+        }
+        return new ArrayFieldVector<T>(data[0].getField(), data, true);
+    }
+
+    /**
+     * Create a row {@link RealMatrix} using the data from the input array.
+     *
+     * @param rowData the input row data
+     * @return a 1 x rowData.length RealMatrix
+     * @throws NoDataException if {@code rowData} is empty.
+     * @throws NullArgumentException if {@code rowData} is {@code null}.
+     */
+    public static RealMatrix createRowRealMatrix(double[] rowData)
+            throws NoDataException, NullArgumentException {
+        if (rowData == null) {
+            throw new NullArgumentException();
+        }
+        final int nCols = rowData.length;
+        final RealMatrix m = createRealMatrix(1, nCols);
+        for (int i = 0; i < nCols; ++i) {
+            m.setEntry(0, i, rowData[i]);
+        }
+        return m;
+    }
+
+    /**
+     * Create a row {@link FieldMatrix} using the data from the input array.
+     *
+     * @param <T> the type of the field elements
+     * @param rowData the input row data
+     * @return a 1 x rowData.length FieldMatrix
+     * @throws NoDataException if {@code rowData} is empty.
+     * @throws NullArgumentException if {@code rowData} is {@code null}.
+     */
+    public static <T extends FieldElement<T>> FieldMatrix<T> createRowFieldMatrix(final T[] rowData)
+            throws NoDataException, NullArgumentException {
+        if (rowData == null) {
+            throw new NullArgumentException();
+        }
+        final int nCols = rowData.length;
+        if (nCols == 0) {
+            throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_COLUMN);
+        }
+        final FieldMatrix<T> m = createFieldMatrix(rowData[0].getField(), 1, nCols);
+        for (int i = 0; i < nCols; ++i) {
+            m.setEntry(0, i, rowData[i]);
+        }
+        return m;
+    }
+
+    /**
+     * Creates a column {@link RealMatrix} using the data from the input array.
+     *
+     * @param columnData the input column data
+     * @return a columnData x 1 RealMatrix
+     * @throws NoDataException if {@code columnData} is empty.
+     * @throws NullArgumentException if {@code columnData} is {@code null}.
+     */
+    public static RealMatrix createColumnRealMatrix(double[] columnData)
+            throws NoDataException, NullArgumentException {
+        if (columnData == null) {
+            throw new NullArgumentException();
+        }
+        final int nRows = columnData.length;
+        final RealMatrix m = createRealMatrix(nRows, 1);
+        for (int i = 0; i < nRows; ++i) {
+            m.setEntry(i, 0, columnData[i]);
+        }
+        return m;
+    }
+
+    /**
+     * Creates a column {@link FieldMatrix} using the data from the input array.
+     *
+     * @param <T> the type of the field elements
+     * @param columnData the input column data
+     * @return a columnData x 1 FieldMatrix
+     * @throws NoDataException if {@code data} is empty.
+     * @throws NullArgumentException if {@code columnData} is {@code null}.
+     */
+    public static <T extends FieldElement<T>> FieldMatrix<T> createColumnFieldMatrix(
+            final T[] columnData) throws NoDataException, NullArgumentException {
+        if (columnData == null) {
+            throw new NullArgumentException();
+        }
+        final int nRows = columnData.length;
+        if (nRows == 0) {
+            throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_ROW);
+        }
+        final FieldMatrix<T> m = createFieldMatrix(columnData[0].getField(), nRows, 1);
+        for (int i = 0; i < nRows; ++i) {
+            m.setEntry(i, 0, columnData[i]);
+        }
+        return m;
+    }
+
+    /**
+     * Checks whether a matrix is symmetric, within a given relative tolerance.
+     *
+     * @param matrix Matrix to check.
+     * @param relativeTolerance Tolerance of the symmetry check.
+     * @param raiseException If {@code true}, an exception will be raised if the matrix is not
+     *     symmetric.
+     * @return {@code true} if {@code matrix} is symmetric.
+     * @throws NonSquareMatrixException if the matrix is not square.
+     * @throws NonSymmetricMatrixException if the matrix is not symmetric.
+     */
+    private static boolean isSymmetricInternal(
+            RealMatrix matrix, double relativeTolerance, boolean raiseException) {
+        final int rows = matrix.getRowDimension();
+        if (rows != matrix.getColumnDimension()) {
+            if (raiseException) {
+                throw new NonSquareMatrixException(rows, matrix.getColumnDimension());
+            } else {
+                return false;
+            }
+        }
+        for (int i = 0; i < rows; i++) {
+            for (int j = i + 1; j < rows; j++) {
+                final double mij = matrix.getEntry(i, j);
+                final double mji = matrix.getEntry(j, i);
+                if (FastMath.abs(mij - mji)
+                        > FastMath.max(FastMath.abs(mij), FastMath.abs(mji)) * relativeTolerance) {
+                    if (raiseException) {
+                        throw new NonSymmetricMatrixException(i, j, relativeTolerance);
+                    } else {
+                        return false;
+                    }
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Checks whether a matrix is symmetric.
+     *
+     * @param matrix Matrix to check.
+     * @param eps Relative tolerance.
+     * @throws NonSquareMatrixException if the matrix is not square.
+     * @throws NonSymmetricMatrixException if the matrix is not symmetric.
+     * @since 3.1
+     */
+    public static void checkSymmetric(RealMatrix matrix, double eps) {
+        isSymmetricInternal(matrix, eps, true);
+    }
+
+    /**
+     * Checks whether a matrix is symmetric.
+     *
+     * @param matrix Matrix to check.
+     * @param eps Relative tolerance.
+     * @return {@code true} if {@code matrix} is symmetric.
+     * @since 3.1
+     */
+    public static boolean isSymmetric(RealMatrix matrix, double eps) {
+        return isSymmetricInternal(matrix, eps, false);
+    }
+
+    /**
+     * Check if matrix indices are valid.
+     *
+     * @param m Matrix.
+     * @param row Row index to check.
+     * @param column Column index to check.
+     * @throws OutOfRangeException if {@code row} or {@code column} is not a valid index.
+     */
+    public static void checkMatrixIndex(final AnyMatrix m, final int row, final int column)
+            throws OutOfRangeException {
+        checkRowIndex(m, row);
+        checkColumnIndex(m, column);
+    }
+
+    /**
+     * Check if a row index is valid.
+     *
+     * @param m Matrix.
+     * @param row Row index to check.
+     * @throws OutOfRangeException if {@code row} is not a valid index.
+     */
+    public static void checkRowIndex(final AnyMatrix m, final int row) throws OutOfRangeException {
+        if (row < 0 || row >= m.getRowDimension()) {
+            throw new OutOfRangeException(
+                    LocalizedFormats.ROW_INDEX, row, 0, m.getRowDimension() - 1);
+        }
+    }
+
+    /**
+     * Check if a column index is valid.
+     *
+     * @param m Matrix.
+     * @param column Column index to check.
+     * @throws OutOfRangeException if {@code column} is not a valid index.
+     */
+    public static void checkColumnIndex(final AnyMatrix m, final int column)
+            throws OutOfRangeException {
+        if (column < 0 || column >= m.getColumnDimension()) {
+            throw new OutOfRangeException(
+                    LocalizedFormats.COLUMN_INDEX, column, 0, m.getColumnDimension() - 1);
+        }
+    }
+
+    /**
+     * Check if submatrix ranges indices are valid. Rows and columns are indicated counting from 0
+     * to {@code n - 1}.
+     *
+     * @param m Matrix.
+     * @param startRow Initial row index.
+     * @param endRow Final row index.
+     * @param startColumn Initial column index.
+     * @param endColumn Final column index.
+     * @throws OutOfRangeException if the indices are invalid.
+     * @throws NumberIsTooSmallException if {@code endRow < startRow} or {@code endColumn <
+     *     startColumn}.
+     */
+    public static void checkSubMatrixIndex(
+            final AnyMatrix m,
+            final int startRow,
+            final int endRow,
+            final int startColumn,
+            final int endColumn)
+            throws NumberIsTooSmallException, OutOfRangeException {
+        checkRowIndex(m, startRow);
+        checkRowIndex(m, endRow);
+        if (endRow < startRow) {
+            throw new NumberIsTooSmallException(
+                    LocalizedFormats.INITIAL_ROW_AFTER_FINAL_ROW, endRow, startRow, false);
+        }
+
+        checkColumnIndex(m, startColumn);
+        checkColumnIndex(m, endColumn);
+        if (endColumn < startColumn) {
+            throw new NumberIsTooSmallException(
+                    LocalizedFormats.INITIAL_COLUMN_AFTER_FINAL_COLUMN,
+                    endColumn,
+                    startColumn,
+                    false);
+        }
+    }
+
+    /**
+     * Check if submatrix ranges indices are valid. Rows and columns are indicated counting from 0
+     * to n-1.
+     *
+     * @param m Matrix.
+     * @param selectedRows Array of row indices.
+     * @param selectedColumns Array of column indices.
+     * @throws NullArgumentException if {@code selectedRows} or {@code selectedColumns} are {@code
+     *     null}.
+     * @throws NoDataException if the row or column selections are empty (zero length).
+     * @throws OutOfRangeException if row or column selections are not valid.
+     */
+    public static void checkSubMatrixIndex(
+            final AnyMatrix m, final int[] selectedRows, final int[] selectedColumns)
+            throws NoDataException, NullArgumentException, OutOfRangeException {
+        if (selectedRows == null) {
+            throw new NullArgumentException();
+        }
+        if (selectedColumns == null) {
+            throw new NullArgumentException();
+        }
+        if (selectedRows.length == 0) {
+            throw new NoDataException(LocalizedFormats.EMPTY_SELECTED_ROW_INDEX_ARRAY);
+        }
+        if (selectedColumns.length == 0) {
+            throw new NoDataException(LocalizedFormats.EMPTY_SELECTED_COLUMN_INDEX_ARRAY);
+        }
+
+        for (final int row : selectedRows) {
+            checkRowIndex(m, row);
+        }
+        for (final int column : selectedColumns) {
+            checkColumnIndex(m, column);
+        }
+    }
+
+    /**
+     * Check if matrices are addition compatible.
+     *
+     * @param left Left hand side matrix.
+     * @param right Right hand side matrix.
+     * @throws MatrixDimensionMismatchException if the matrices are not addition compatible.
+     */
+    public static void checkAdditionCompatible(final AnyMatrix left, final AnyMatrix right)
+            throws MatrixDimensionMismatchException {
+        if ((left.getRowDimension() != right.getRowDimension())
+                || (left.getColumnDimension() != right.getColumnDimension())) {
+            throw new MatrixDimensionMismatchException(
+                    left.getRowDimension(), left.getColumnDimension(),
+                    right.getRowDimension(), right.getColumnDimension());
+        }
+    }
+
+    /**
+     * Check if matrices are subtraction compatible
+     *
+     * @param left Left hand side matrix.
+     * @param right Right hand side matrix.
+     * @throws MatrixDimensionMismatchException if the matrices are not addition compatible.
+     */
+    public static void checkSubtractionCompatible(final AnyMatrix left, final AnyMatrix right)
+            throws MatrixDimensionMismatchException {
+        if ((left.getRowDimension() != right.getRowDimension())
+                || (left.getColumnDimension() != right.getColumnDimension())) {
+            throw new MatrixDimensionMismatchException(
+                    left.getRowDimension(), left.getColumnDimension(),
+                    right.getRowDimension(), right.getColumnDimension());
+        }
+    }
+
+    /**
+     * Check if matrices are multiplication compatible
+     *
+     * @param left Left hand side matrix.
+     * @param right Right hand side matrix.
+     * @throws DimensionMismatchException if matrices are not multiplication compatible.
+     */
+    public static void checkMultiplicationCompatible(final AnyMatrix left, final AnyMatrix right)
+            throws DimensionMismatchException {
+
+        if (left.getColumnDimension() != right.getRowDimension()) {
+            throw new DimensionMismatchException(
+                    left.getColumnDimension(), right.getRowDimension());
+        }
+    }
+
+    /**
+     * Convert a {@link FieldMatrix}/{@link Fraction} matrix to a {@link RealMatrix}.
+     *
+     * @param m Matrix to convert.
+     * @return the converted matrix.
+     */
+    public static Array2DRowRealMatrix fractionMatrixToRealMatrix(final FieldMatrix<Fraction> m) {
+        final FractionMatrixConverter converter = new FractionMatrixConverter();
+        m.walkInOptimizedOrder(converter);
+        return converter.getConvertedMatrix();
+    }
+
+    /** Converter for {@link FieldMatrix}/{@link Fraction}. */
+    private static class FractionMatrixConverter
+            extends DefaultFieldMatrixPreservingVisitor<Fraction> {
+        /** Converted array. */
+        private double[][] data;
+
+        /** Simple constructor. */
+        FractionMatrixConverter() {
+            super(Fraction.ZERO);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void start(
+                int rows, int columns, int startRow, int endRow, int startColumn, int endColumn) {
+            data = new double[rows][columns];
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void visit(int row, int column, Fraction value) {
+            data[row][column] = value.doubleValue();
+        }
+
+        /**
+         * Get the converted matrix.
+         *
+         * @return the converted matrix.
+         */
+        Array2DRowRealMatrix getConvertedMatrix() {
+            return new Array2DRowRealMatrix(data, false);
+        }
+    }
+
+    /**
+     * Convert a {@link FieldMatrix}/{@link BigFraction} matrix to a {@link RealMatrix}.
+     *
+     * @param m Matrix to convert.
+     * @return the converted matrix.
+     */
+    public static Array2DRowRealMatrix bigFractionMatrixToRealMatrix(
+            final FieldMatrix<BigFraction> m) {
+        final BigFractionMatrixConverter converter = new BigFractionMatrixConverter();
+        m.walkInOptimizedOrder(converter);
+        return converter.getConvertedMatrix();
+    }
+
+    /** Converter for {@link FieldMatrix}/{@link BigFraction}. */
+    private static class BigFractionMatrixConverter
+            extends DefaultFieldMatrixPreservingVisitor<BigFraction> {
+        /** Converted array. */
+        private double[][] data;
+
+        /** Simple constructor. */
+        BigFractionMatrixConverter() {
+            super(BigFraction.ZERO);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void start(
+                int rows, int columns, int startRow, int endRow, int startColumn, int endColumn) {
+            data = new double[rows][columns];
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void visit(int row, int column, BigFraction value) {
+            data[row][column] = value.doubleValue();
+        }
+
+        /**
+         * Get the converted matrix.
+         *
+         * @return the converted matrix.
+         */
+        Array2DRowRealMatrix getConvertedMatrix() {
+            return new Array2DRowRealMatrix(data, false);
+        }
+    }
+
+    /**
+     * Serialize a {@link RealVector}.
+     *
+     * <p>This method is intended to be called from within a private <code>writeObject</code> method
+     * (after a call to <code>oos.defaultWriteObject()</code>) in a class that has a {@link
+     * RealVector} field, which should be declared <code>transient</code>. This way, the default
+     * handling does not serialize the vector (the {@link RealVector} interface is not serializable
+     * by default) but this method does serialize it specifically.
+     *
+     * <p>The following example shows how a simple class with a name and a real vector should be
+     * written:
+     *
+     * <pre><code>
+     * public class NamedVector implements Serializable {
+     *
+     *     private final String name;
+     *     private final transient RealVector coefficients;
+     *
+     *     // omitted constructors, getters ...
+     *
+     *     private void writeObject(ObjectOutputStream oos) throws IOException {
+     *         oos.defaultWriteObject();  // takes care of name field
+     *         MatrixUtils.serializeRealVector(coefficients, oos);
+     *     }
+     *
+     *     private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
+     *         ois.defaultReadObject();  // takes care of name field
+     *         MatrixUtils.deserializeRealVector(this, "coefficients", ois);
+     *     }
+     *
+     * }
+     * </code></pre>
+     *
+     * @param vector real vector to serialize
+     * @param oos stream where the real vector should be written
+     * @exception IOException if object cannot be written to stream
+     * @see #deserializeRealVector(Object, String, ObjectInputStream)
+     */
+    public static void serializeRealVector(final RealVector vector, final ObjectOutputStream oos)
+            throws IOException {
+        final int n = vector.getDimension();
+        oos.writeInt(n);
+        for (int i = 0; i < n; ++i) {
+            oos.writeDouble(vector.getEntry(i));
+        }
+    }
+
+    /**
+     * Deserialize a {@link RealVector} field in a class.
+     *
+     * <p>This method is intended to be called from within a private <code>readObject</code> method
+     * (after a call to <code>ois.defaultReadObject()</code>) in a class that has a {@link
+     * RealVector} field, which should be declared <code>transient</code>. This way, the default
+     * handling does not deserialize the vector (the {@link RealVector} interface is not
+     * serializable by default) but this method does deserialize it specifically.
+     *
+     * @param instance instance in which the field must be set up
+     * @param fieldName name of the field within the class (may be private and final)
+     * @param ois stream from which the real vector should be read
+     * @exception ClassNotFoundException if a class in the stream cannot be found
+     * @exception IOException if object cannot be read from the stream
+     * @see #serializeRealVector(RealVector, ObjectOutputStream)
+     */
+    public static void deserializeRealVector(
+            final Object instance, final String fieldName, final ObjectInputStream ois)
+            throws ClassNotFoundException, IOException {
+        try {
+
+            // read the vector data
+            final int n = ois.readInt();
+            final double[] data = new double[n];
+            for (int i = 0; i < n; ++i) {
+                data[i] = ois.readDouble();
+            }
+
+            // create the instance
+            final RealVector vector = new ArrayRealVector(data, false);
+
+            // set up the field
+            final java.lang.reflect.Field f = instance.getClass().getDeclaredField(fieldName);
+            f.setAccessible(true);
+            f.set(instance, vector);
+
+        } catch (NoSuchFieldException nsfe) {
+            IOException ioe = new IOException();
+            ioe.initCause(nsfe);
+            throw ioe;
+        } catch (IllegalAccessException iae) {
+            IOException ioe = new IOException();
+            ioe.initCause(iae);
+            throw ioe;
+        }
+    }
+
+    /**
+     * Serialize a {@link RealMatrix}.
+     *
+     * <p>This method is intended to be called from within a private <code>writeObject</code> method
+     * (after a call to <code>oos.defaultWriteObject()</code>) in a class that has a {@link
+     * RealMatrix} field, which should be declared <code>transient</code>. This way, the default
+     * handling does not serialize the matrix (the {@link RealMatrix} interface is not serializable
+     * by default) but this method does serialize it specifically.
+     *
+     * <p>The following example shows how a simple class with a name and a real matrix should be
+     * written:
+     *
+     * <pre><code>
+     * public class NamedMatrix implements Serializable {
+     *
+     *     private final String name;
+     *     private final transient RealMatrix coefficients;
+     *
+     *     // omitted constructors, getters ...
+     *
+     *     private void writeObject(ObjectOutputStream oos) throws IOException {
+     *         oos.defaultWriteObject();  // takes care of name field
+     *         MatrixUtils.serializeRealMatrix(coefficients, oos);
+     *     }
+     *
+     *     private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
+     *         ois.defaultReadObject();  // takes care of name field
+     *         MatrixUtils.deserializeRealMatrix(this, "coefficients", ois);
+     *     }
+     *
+     * }
+     * </code></pre>
+     *
+     * @param matrix real matrix to serialize
+     * @param oos stream where the real matrix should be written
+     * @exception IOException if object cannot be written to stream
+     * @see #deserializeRealMatrix(Object, String, ObjectInputStream)
+     */
+    public static void serializeRealMatrix(final RealMatrix matrix, final ObjectOutputStream oos)
+            throws IOException {
+        final int n = matrix.getRowDimension();
+        final int m = matrix.getColumnDimension();
+        oos.writeInt(n);
+        oos.writeInt(m);
+        for (int i = 0; i < n; ++i) {
+            for (int j = 0; j < m; ++j) {
+                oos.writeDouble(matrix.getEntry(i, j));
+            }
+        }
+    }
+
+    /**
+     * Deserialize a {@link RealMatrix} field in a class.
+     *
+     * <p>This method is intended to be called from within a private <code>readObject</code> method
+     * (after a call to <code>ois.defaultReadObject()</code>) in a class that has a {@link
+     * RealMatrix} field, which should be declared <code>transient</code>. This way, the default
+     * handling does not deserialize the matrix (the {@link RealMatrix} interface is not
+     * serializable by default) but this method does deserialize it specifically.
+     *
+     * @param instance instance in which the field must be set up
+     * @param fieldName name of the field within the class (may be private and final)
+     * @param ois stream from which the real matrix should be read
+     * @exception ClassNotFoundException if a class in the stream cannot be found
+     * @exception IOException if object cannot be read from the stream
+     * @see #serializeRealMatrix(RealMatrix, ObjectOutputStream)
+     */
+    public static void deserializeRealMatrix(
+            final Object instance, final String fieldName, final ObjectInputStream ois)
+            throws ClassNotFoundException, IOException {
+        try {
+
+            // read the matrix data
+            final int n = ois.readInt();
+            final int m = ois.readInt();
+            final double[][] data = new double[n][m];
+            for (int i = 0; i < n; ++i) {
+                final double[] dataI = data[i];
+                for (int j = 0; j < m; ++j) {
+                    dataI[j] = ois.readDouble();
+                }
+            }
+
+            // create the instance
+            final RealMatrix matrix = new Array2DRowRealMatrix(data, false);
+
+            // set up the field
+            final java.lang.reflect.Field f = instance.getClass().getDeclaredField(fieldName);
+            f.setAccessible(true);
+            f.set(instance, matrix);
+
+        } catch (NoSuchFieldException nsfe) {
+            IOException ioe = new IOException();
+            ioe.initCause(nsfe);
+            throw ioe;
+        } catch (IllegalAccessException iae) {
+            IOException ioe = new IOException();
+            ioe.initCause(iae);
+            throw ioe;
+        }
+    }
+
+    /**
+     * Solve a system of composed of a Lower Triangular Matrix {@link RealMatrix}.
+     *
+     * <p>This method is called to solve systems of equations which are of the lower triangular
+     * form. The matrix {@link RealMatrix} is assumed, though not checked, to be in lower triangular
+     * form. The vector {@link RealVector} is overwritten with the solution. The matrix is checked
+     * that it is square and its dimensions match the length of the vector.
+     *
+     * @param rm RealMatrix which is lower triangular
+     * @param b RealVector this is overwritten
+     * @throws DimensionMismatchException if the matrix and vector are not conformable
+     * @throws NonSquareMatrixException if the matrix {@code rm} is not square
+     * @throws MathArithmeticException if the absolute value of one of the diagonal coefficient of
+     *     {@code rm} is lower than {@link Precision#SAFE_MIN}
+     */
+    public static void solveLowerTriangularSystem(RealMatrix rm, RealVector b)
+            throws DimensionMismatchException, MathArithmeticException, NonSquareMatrixException {
+        if ((rm == null) || (b == null) || (rm.getRowDimension() != b.getDimension())) {
+            throw new DimensionMismatchException(
+                    (rm == null) ? 0 : rm.getRowDimension(), (b == null) ? 0 : b.getDimension());
+        }
+        if (rm.getColumnDimension() != rm.getRowDimension()) {
+            throw new NonSquareMatrixException(rm.getRowDimension(), rm.getColumnDimension());
+        }
+        int rows = rm.getRowDimension();
+        for (int i = 0; i < rows; i++) {
+            double diag = rm.getEntry(i, i);
+            if (FastMath.abs(diag) < Precision.SAFE_MIN) {
+                throw new MathArithmeticException(LocalizedFormats.ZERO_DENOMINATOR);
+            }
+            double bi = b.getEntry(i) / diag;
+            b.setEntry(i, bi);
+            for (int j = i + 1; j < rows; j++) {
+                b.setEntry(j, b.getEntry(j) - bi * rm.getEntry(j, i));
+            }
+        }
+    }
+
+    /**
+     * Solver a system composed of an Upper Triangular Matrix {@link RealMatrix}.
+     *
+     * <p>This method is called to solve systems of equations which are of the lower triangular
+     * form. The matrix {@link RealMatrix} is assumed, though not checked, to be in upper triangular
+     * form. The vector {@link RealVector} is overwritten with the solution. The matrix is checked
+     * that it is square and its dimensions match the length of the vector.
+     *
+     * @param rm RealMatrix which is upper triangular
+     * @param b RealVector this is overwritten
+     * @throws DimensionMismatchException if the matrix and vector are not conformable
+     * @throws NonSquareMatrixException if the matrix {@code rm} is not square
+     * @throws MathArithmeticException if the absolute value of one of the diagonal coefficient of
+     *     {@code rm} is lower than {@link Precision#SAFE_MIN}
+     */
+    public static void solveUpperTriangularSystem(RealMatrix rm, RealVector b)
+            throws DimensionMismatchException, MathArithmeticException, NonSquareMatrixException {
+        if ((rm == null) || (b == null) || (rm.getRowDimension() != b.getDimension())) {
+            throw new DimensionMismatchException(
+                    (rm == null) ? 0 : rm.getRowDimension(), (b == null) ? 0 : b.getDimension());
+        }
+        if (rm.getColumnDimension() != rm.getRowDimension()) {
+            throw new NonSquareMatrixException(rm.getRowDimension(), rm.getColumnDimension());
+        }
+        int rows = rm.getRowDimension();
+        for (int i = rows - 1; i > -1; i--) {
+            double diag = rm.getEntry(i, i);
+            if (FastMath.abs(diag) < Precision.SAFE_MIN) {
+                throw new MathArithmeticException(LocalizedFormats.ZERO_DENOMINATOR);
+            }
+            double bi = b.getEntry(i) / diag;
+            b.setEntry(i, bi);
+            for (int j = i - 1; j > -1; j--) {
+                b.setEntry(j, b.getEntry(j) - bi * rm.getEntry(j, i));
+            }
+        }
+    }
+
+    /**
+     * Computes the inverse of the given matrix by splitting it into 4 sub-matrices.
+     *
+     * @param m Matrix whose inverse must be computed.
+     * @param splitIndex Index that determines the "split" line and column. The element
+     *     corresponding to this index will part of the upper-left sub-matrix.
+     * @return the inverse of {@code m}.
+     * @throws NonSquareMatrixException if {@code m} is not square.
+     */
+    public static RealMatrix blockInverse(RealMatrix m, int splitIndex) {
+        final int n = m.getRowDimension();
+        if (m.getColumnDimension() != n) {
+            throw new NonSquareMatrixException(m.getRowDimension(), m.getColumnDimension());
+        }
+
+        final int splitIndex1 = splitIndex + 1;
+
+        final RealMatrix a = m.getSubMatrix(0, splitIndex, 0, splitIndex);
+        final RealMatrix b = m.getSubMatrix(0, splitIndex, splitIndex1, n - 1);
+        final RealMatrix c = m.getSubMatrix(splitIndex1, n - 1, 0, splitIndex);
+        final RealMatrix d = m.getSubMatrix(splitIndex1, n - 1, splitIndex1, n - 1);
+
+        final SingularValueDecomposition aDec = new SingularValueDecomposition(a);
+        final DecompositionSolver aSolver = aDec.getSolver();
+        if (!aSolver.isNonSingular()) {
+            throw new SingularMatrixException();
+        }
+        final RealMatrix aInv = aSolver.getInverse();
+
+        final SingularValueDecomposition dDec = new SingularValueDecomposition(d);
+        final DecompositionSolver dSolver = dDec.getSolver();
+        if (!dSolver.isNonSingular()) {
+            throw new SingularMatrixException();
+        }
+        final RealMatrix dInv = dSolver.getInverse();
+
+        final RealMatrix tmp1 = a.subtract(b.multiply(dInv).multiply(c));
+        final SingularValueDecomposition tmp1Dec = new SingularValueDecomposition(tmp1);
+        final DecompositionSolver tmp1Solver = tmp1Dec.getSolver();
+        if (!tmp1Solver.isNonSingular()) {
+            throw new SingularMatrixException();
+        }
+        final RealMatrix result00 = tmp1Solver.getInverse();
+
+        final RealMatrix tmp2 = d.subtract(c.multiply(aInv).multiply(b));
+        final SingularValueDecomposition tmp2Dec = new SingularValueDecomposition(tmp2);
+        final DecompositionSolver tmp2Solver = tmp2Dec.getSolver();
+        if (!tmp2Solver.isNonSingular()) {
+            throw new SingularMatrixException();
+        }
+        final RealMatrix result11 = tmp2Solver.getInverse();
+
+        final RealMatrix result01 = aInv.multiply(b).multiply(result11).scalarMultiply(-1);
+        final RealMatrix result10 = dInv.multiply(c).multiply(result00).scalarMultiply(-1);
+
+        final RealMatrix result = new Array2DRowRealMatrix(n, n);
+        result.setSubMatrix(result00.getData(), 0, 0);
+        result.setSubMatrix(result01.getData(), 0, splitIndex1);
+        result.setSubMatrix(result10.getData(), splitIndex1, 0);
+        result.setSubMatrix(result11.getData(), splitIndex1, splitIndex1);
+
+        return result;
+    }
+
+    /**
+     * Computes the inverse of the given matrix.
+     *
+     * <p>By default, the inverse of the matrix is computed using the QR-decomposition, unless a
+     * more efficient method can be determined for the input matrix.
+     *
+     * <p>Note: this method will use a singularity threshold of 0, use {@link #inverse(RealMatrix,
+     * double)} if a different threshold is needed.
+     *
+     * @param matrix Matrix whose inverse shall be computed
+     * @return the inverse of {@code matrix}
+     * @throws NullArgumentException if {@code matrix} is {@code null}
+     * @throws SingularMatrixException if m is singular
+     * @throws NonSquareMatrixException if matrix is not square
+     * @since 3.3
+     */
+    public static RealMatrix inverse(RealMatrix matrix)
+            throws NullArgumentException, SingularMatrixException, NonSquareMatrixException {
+        return inverse(matrix, 0);
+    }
+
+    /**
+     * Computes the inverse of the given matrix.
+     *
+     * <p>By default, the inverse of the matrix is computed using the QR-decomposition, unless a
+     * more efficient method can be determined for the input matrix.
+     *
+     * @param matrix Matrix whose inverse shall be computed
+     * @param threshold Singularity threshold
+     * @return the inverse of {@code m}
+     * @throws NullArgumentException if {@code matrix} is {@code null}
+     * @throws SingularMatrixException if matrix is singular
+     * @throws NonSquareMatrixException if matrix is not square
+     * @since 3.3
+     */
+    public static RealMatrix inverse(RealMatrix matrix, double threshold)
+            throws NullArgumentException, SingularMatrixException, NonSquareMatrixException {
+
+        MathUtils.checkNotNull(matrix);
+
+        if (!matrix.isSquare()) {
+            throw new NonSquareMatrixException(
+                    matrix.getRowDimension(), matrix.getColumnDimension());
+        }
+
+        if (matrix instanceof DiagonalMatrix) {
+            return ((DiagonalMatrix) matrix).inverse(threshold);
+        } else {
+            QRDecomposition decomposition = new QRDecomposition(matrix, threshold);
+            return decomposition.getSolver().getInverse();
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/NonPositiveDefiniteMatrixException.java b/src/main/java/org/apache/commons/math3/linear/NonPositiveDefiniteMatrixException.java
new file mode 100644
index 0000000..94d97d0
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/NonPositiveDefiniteMatrixException.java
@@ -0,0 +1,75 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.util.ExceptionContext;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Exception to be thrown when a positive definite matrix is expected.
+ *
+ * @since 3.0
+ */
+public class NonPositiveDefiniteMatrixException extends NumberIsTooSmallException {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = 1641613838113738061L;
+
+    /** Index (diagonal element). */
+    private final int index;
+
+    /** Threshold. */
+    private final double threshold;
+
+    /**
+     * Construct an exception.
+     *
+     * @param wrong Value that fails the positivity check.
+     * @param index Row (and column) index.
+     * @param threshold Absolute positivity threshold.
+     */
+    public NonPositiveDefiniteMatrixException(double wrong, int index, double threshold) {
+        super(wrong, threshold, false);
+        this.index = index;
+        this.threshold = threshold;
+
+        final ExceptionContext context = getContext();
+        context.addMessage(LocalizedFormats.NOT_POSITIVE_DEFINITE_MATRIX);
+        context.addMessage(LocalizedFormats.ARRAY_ELEMENT, wrong, index);
+    }
+
+    /**
+     * @return the row index.
+     */
+    public int getRow() {
+        return index;
+    }
+
+    /**
+     * @return the column index.
+     */
+    public int getColumn() {
+        return index;
+    }
+
+    /**
+     * @return the absolute positivity threshold.
+     */
+    public double getThreshold() {
+        return threshold;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/NonPositiveDefiniteOperatorException.java b/src/main/java/org/apache/commons/math3/linear/NonPositiveDefiniteOperatorException.java
new file mode 100644
index 0000000..6bfe001
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/NonPositiveDefiniteOperatorException.java
@@ -0,0 +1,40 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Exception to be thrown when a symmetric, definite positive {@link RealLinearOperator} is
+ * expected. Since the coefficients of the matrix are not accessible, the most general definition is
+ * used to check that {@code A} is not positive definite, i.e. there exists {@code x} such that
+ * {@code x' A x <= 0}. In the terminology of this exception, {@code A} is the "offending" linear
+ * operator and {@code x} the "offending" vector.
+ *
+ * @since 3.0
+ */
+public class NonPositiveDefiniteOperatorException extends MathIllegalArgumentException {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = 917034489420549847L;
+
+    /** Creates a new instance of this class. */
+    public NonPositiveDefiniteOperatorException() {
+        super(LocalizedFormats.NON_POSITIVE_DEFINITE_OPERATOR);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/NonSelfAdjointOperatorException.java b/src/main/java/org/apache/commons/math3/linear/NonSelfAdjointOperatorException.java
new file mode 100644
index 0000000..f98a623
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/NonSelfAdjointOperatorException.java
@@ -0,0 +1,41 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Exception to be thrown when a self-adjoint {@link RealLinearOperator} is expected. Since the
+ * coefficients of the matrix are not accessible, the most general definition is used to check that
+ * A is not self-adjoint, i.e. there exist x and y such as {@code | x' A y - y' A x | >= eps}, where
+ * {@code eps} is a user-specified tolerance, and {@code x'} denotes the transpose of {@code x}. In
+ * the terminology of this exception, {@code A} is the "offending" linear operator, {@code x} and
+ * {@code y} are the first and second "offending" vectors, respectively.
+ *
+ * @since 3.0
+ */
+public class NonSelfAdjointOperatorException extends MathIllegalArgumentException {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = 1784999305030258247L;
+
+    /** Creates a new instance of this class. */
+    public NonSelfAdjointOperatorException() {
+        super(LocalizedFormats.NON_SELF_ADJOINT_OPERATOR);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/NonSquareMatrixException.java b/src/main/java/org/apache/commons/math3/linear/NonSquareMatrixException.java
new file mode 100644
index 0000000..ebce448
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/NonSquareMatrixException.java
@@ -0,0 +1,40 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Exception to be thrown when a square matrix is expected.
+ *
+ * @since 3.0
+ */
+public class NonSquareMatrixException extends DimensionMismatchException {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = -660069396594485772L;
+
+    /**
+     * Construct an exception from the mismatched dimensions.
+     *
+     * @param wrong Row dimension.
+     * @param expected Column dimension.
+     */
+    public NonSquareMatrixException(int wrong, int expected) {
+        super(LocalizedFormats.NON_SQUARE_MATRIX, wrong, expected);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/NonSquareOperatorException.java b/src/main/java/org/apache/commons/math3/linear/NonSquareOperatorException.java
new file mode 100644
index 0000000..7915b58
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/NonSquareOperatorException.java
@@ -0,0 +1,40 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Exception to be thrown when a square linear operator is expected.
+ *
+ * @since 3.0
+ */
+public class NonSquareOperatorException extends DimensionMismatchException {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = -4145007524150846242L;
+
+    /**
+     * Construct an exception from the mismatched dimensions.
+     *
+     * @param wrong Row dimension.
+     * @param expected Column dimension.
+     */
+    public NonSquareOperatorException(int wrong, int expected) {
+        super(LocalizedFormats.NON_SQUARE_OPERATOR, wrong, expected);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/NonSymmetricMatrixException.java b/src/main/java/org/apache/commons/math3/linear/NonSymmetricMatrixException.java
new file mode 100644
index 0000000..c8e0718
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/NonSymmetricMatrixException.java
@@ -0,0 +1,74 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Exception to be thrown when a symmetric matrix is expected.
+ *
+ * @since 3.0
+ */
+public class NonSymmetricMatrixException extends MathIllegalArgumentException {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = -7518495577824189882L;
+
+    /** Row. */
+    private final int row;
+
+    /** Column. */
+    private final int column;
+
+    /** Threshold. */
+    private final double threshold;
+
+    /**
+     * Construct an exception.
+     *
+     * @param row Row index.
+     * @param column Column index.
+     * @param threshold Relative symmetry threshold.
+     */
+    public NonSymmetricMatrixException(int row, int column, double threshold) {
+        super(LocalizedFormats.NON_SYMMETRIC_MATRIX, row, column, threshold);
+        this.row = row;
+        this.column = column;
+        this.threshold = threshold;
+    }
+
+    /**
+     * @return the row index of the entry.
+     */
+    public int getRow() {
+        return row;
+    }
+
+    /**
+     * @return the column index of the entry.
+     */
+    public int getColumn() {
+        return column;
+    }
+
+    /**
+     * @return the relative symmetry threshold.
+     */
+    public double getThreshold() {
+        return threshold;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/OpenMapRealMatrix.java b/src/main/java/org/apache/commons/math3/linear/OpenMapRealMatrix.java
new file mode 100644
index 0000000..95bc3cb
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/OpenMapRealMatrix.java
@@ -0,0 +1,301 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.util.OpenIntToDoubleHashMap;
+
+import java.io.Serializable;
+
+/**
+ * Sparse matrix implementation based on an open addressed map.
+ *
+ * <p>Caveat: This implementation assumes that, for any {@code x}, the equality {@code x * 0d == 0d}
+ * holds. But it is is not true for {@code NaN}. Moreover, zero entries will lose their sign. Some
+ * operations (that involve {@code NaN} and/or infinities) may thus give incorrect results.
+ *
+ * @since 2.0
+ */
+public class OpenMapRealMatrix extends AbstractRealMatrix
+        implements SparseRealMatrix, Serializable {
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -5962461716457143437L;
+
+    /** Number of rows of the matrix. */
+    private final int rows;
+
+    /** Number of columns of the matrix. */
+    private final int columns;
+
+    /** Storage for (sparse) matrix elements. */
+    private final OpenIntToDoubleHashMap entries;
+
+    /**
+     * Build a sparse matrix with the supplied row and column dimensions.
+     *
+     * @param rowDimension Number of rows of the matrix.
+     * @param columnDimension Number of columns of the matrix.
+     * @throws NotStrictlyPositiveException if row or column dimension is not positive.
+     * @throws NumberIsTooLargeException if the total number of entries of the matrix is larger than
+     *     {@code Integer.MAX_VALUE}.
+     */
+    public OpenMapRealMatrix(int rowDimension, int columnDimension)
+            throws NotStrictlyPositiveException, NumberIsTooLargeException {
+        super(rowDimension, columnDimension);
+        long lRow = rowDimension;
+        long lCol = columnDimension;
+        if (lRow * lCol >= Integer.MAX_VALUE) {
+            throw new NumberIsTooLargeException(lRow * lCol, Integer.MAX_VALUE, false);
+        }
+        this.rows = rowDimension;
+        this.columns = columnDimension;
+        this.entries = new OpenIntToDoubleHashMap(0.0);
+    }
+
+    /**
+     * Build a matrix by copying another one.
+     *
+     * @param matrix matrix to copy.
+     */
+    public OpenMapRealMatrix(OpenMapRealMatrix matrix) {
+        this.rows = matrix.rows;
+        this.columns = matrix.columns;
+        this.entries = new OpenIntToDoubleHashMap(matrix.entries);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public OpenMapRealMatrix copy() {
+        return new OpenMapRealMatrix(this);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws NumberIsTooLargeException if the total number of entries of the matrix is larger than
+     *     {@code Integer.MAX_VALUE}.
+     */
+    @Override
+    public OpenMapRealMatrix createMatrix(int rowDimension, int columnDimension)
+            throws NotStrictlyPositiveException, NumberIsTooLargeException {
+        return new OpenMapRealMatrix(rowDimension, columnDimension);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getColumnDimension() {
+        return columns;
+    }
+
+    /**
+     * Compute the sum of this matrix and {@code m}.
+     *
+     * @param m Matrix to be added.
+     * @return {@code this} + {@code m}.
+     * @throws MatrixDimensionMismatchException if {@code m} is not the same size as {@code this}.
+     */
+    public OpenMapRealMatrix add(OpenMapRealMatrix m) throws MatrixDimensionMismatchException {
+
+        MatrixUtils.checkAdditionCompatible(this, m);
+
+        final OpenMapRealMatrix out = new OpenMapRealMatrix(this);
+        for (OpenIntToDoubleHashMap.Iterator iterator = m.entries.iterator();
+                iterator.hasNext(); ) {
+            iterator.advance();
+            final int row = iterator.key() / columns;
+            final int col = iterator.key() - row * columns;
+            out.setEntry(row, col, getEntry(row, col) + iterator.value());
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public OpenMapRealMatrix subtract(final RealMatrix m) throws MatrixDimensionMismatchException {
+        try {
+            return subtract((OpenMapRealMatrix) m);
+        } catch (ClassCastException cce) {
+            return (OpenMapRealMatrix) super.subtract(m);
+        }
+    }
+
+    /**
+     * Subtract {@code m} from this matrix.
+     *
+     * @param m Matrix to be subtracted.
+     * @return {@code this} - {@code m}.
+     * @throws MatrixDimensionMismatchException if {@code m} is not the same size as {@code this}.
+     */
+    public OpenMapRealMatrix subtract(OpenMapRealMatrix m) throws MatrixDimensionMismatchException {
+        MatrixUtils.checkAdditionCompatible(this, m);
+
+        final OpenMapRealMatrix out = new OpenMapRealMatrix(this);
+        for (OpenIntToDoubleHashMap.Iterator iterator = m.entries.iterator();
+                iterator.hasNext(); ) {
+            iterator.advance();
+            final int row = iterator.key() / columns;
+            final int col = iterator.key() - row * columns;
+            out.setEntry(row, col, getEntry(row, col) - iterator.value());
+        }
+
+        return out;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws NumberIsTooLargeException if {@code m} is an {@code OpenMapRealMatrix}, and the total
+     *     number of entries of the product is larger than {@code Integer.MAX_VALUE}.
+     */
+    @Override
+    public RealMatrix multiply(final RealMatrix m)
+            throws DimensionMismatchException, NumberIsTooLargeException {
+        try {
+            return multiply((OpenMapRealMatrix) m);
+        } catch (ClassCastException cce) {
+
+            MatrixUtils.checkMultiplicationCompatible(this, m);
+
+            final int outCols = m.getColumnDimension();
+            final BlockRealMatrix out = new BlockRealMatrix(rows, outCols);
+            for (OpenIntToDoubleHashMap.Iterator iterator = entries.iterator();
+                    iterator.hasNext(); ) {
+                iterator.advance();
+                final double value = iterator.value();
+                final int key = iterator.key();
+                final int i = key / columns;
+                final int k = key % columns;
+                for (int j = 0; j < outCols; ++j) {
+                    out.addToEntry(i, j, value * m.getEntry(k, j));
+                }
+            }
+
+            return out;
+        }
+    }
+
+    /**
+     * Postmultiply this matrix by {@code m}.
+     *
+     * @param m Matrix to postmultiply by.
+     * @return {@code this} * {@code m}.
+     * @throws DimensionMismatchException if the number of rows of {@code m} differ from the number
+     *     of columns of {@code this} matrix.
+     * @throws NumberIsTooLargeException if the total number of entries of the product is larger
+     *     than {@code Integer.MAX_VALUE}.
+     */
+    public OpenMapRealMatrix multiply(OpenMapRealMatrix m)
+            throws DimensionMismatchException, NumberIsTooLargeException {
+        // Safety check.
+        MatrixUtils.checkMultiplicationCompatible(this, m);
+
+        final int outCols = m.getColumnDimension();
+        OpenMapRealMatrix out = new OpenMapRealMatrix(rows, outCols);
+        for (OpenIntToDoubleHashMap.Iterator iterator = entries.iterator(); iterator.hasNext(); ) {
+            iterator.advance();
+            final double value = iterator.value();
+            final int key = iterator.key();
+            final int i = key / columns;
+            final int k = key % columns;
+            for (int j = 0; j < outCols; ++j) {
+                final int rightKey = m.computeKey(k, j);
+                if (m.entries.containsKey(rightKey)) {
+                    final int outKey = out.computeKey(i, j);
+                    final double outValue =
+                            out.entries.get(outKey) + value * m.entries.get(rightKey);
+                    if (outValue == 0.0) {
+                        out.entries.remove(outKey);
+                    } else {
+                        out.entries.put(outKey, outValue);
+                    }
+                }
+            }
+        }
+
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getEntry(int row, int column) throws OutOfRangeException {
+        MatrixUtils.checkRowIndex(this, row);
+        MatrixUtils.checkColumnIndex(this, column);
+        return entries.get(computeKey(row, column));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getRowDimension() {
+        return rows;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setEntry(int row, int column, double value) throws OutOfRangeException {
+        MatrixUtils.checkRowIndex(this, row);
+        MatrixUtils.checkColumnIndex(this, column);
+        if (value == 0.0) {
+            entries.remove(computeKey(row, column));
+        } else {
+            entries.put(computeKey(row, column), value);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addToEntry(int row, int column, double increment) throws OutOfRangeException {
+        MatrixUtils.checkRowIndex(this, row);
+        MatrixUtils.checkColumnIndex(this, column);
+        final int key = computeKey(row, column);
+        final double value = entries.get(key) + increment;
+        if (value == 0.0) {
+            entries.remove(key);
+        } else {
+            entries.put(key, value);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void multiplyEntry(int row, int column, double factor) throws OutOfRangeException {
+        MatrixUtils.checkRowIndex(this, row);
+        MatrixUtils.checkColumnIndex(this, column);
+        final int key = computeKey(row, column);
+        final double value = entries.get(key) * factor;
+        if (value == 0.0) {
+            entries.remove(key);
+        } else {
+            entries.put(key, value);
+        }
+    }
+
+    /**
+     * Compute the key to access a matrix element
+     *
+     * @param row row index of the matrix element
+     * @param column column index of the matrix element
+     * @return key within the map to access the matrix element
+     */
+    private int computeKey(int row, int column) {
+        return row * columns + column;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/OpenMapRealVector.java b/src/main/java/org/apache/commons/math3/linear/OpenMapRealVector.java
new file mode 100644
index 0000000..d13e47e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/OpenMapRealVector.java
@@ -0,0 +1,799 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.OpenIntToDoubleHashMap;
+import org.apache.commons.math3.util.OpenIntToDoubleHashMap.Iterator;
+
+import java.io.Serializable;
+
+/**
+ * This class implements the {@link RealVector} interface with a {@link OpenIntToDoubleHashMap}
+ * backing store.
+ *
+ * <p>Caveat: This implementation assumes that, for any {@code x}, the equality {@code x * 0d == 0d}
+ * holds. But it is is not true for {@code NaN}. Moreover, zero entries will lose their sign. Some
+ * operations (that involve {@code NaN} and/or infinities) may thus give incorrect results, like
+ * multiplications, divisions or functions mapping.
+ *
+ * @since 2.0
+ */
+public class OpenMapRealVector extends SparseRealVector implements Serializable {
+    /** Default Tolerance for having a value considered zero. */
+    public static final double DEFAULT_ZERO_TOLERANCE = 1.0e-12;
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 8772222695580707260L;
+
+    /** Entries of the vector. */
+    private final OpenIntToDoubleHashMap entries;
+
+    /** Dimension of the vector. */
+    private final int virtualSize;
+
+    /** Tolerance for having a value considered zero. */
+    private final double epsilon;
+
+    /**
+     * Build a 0-length vector. Zero-length vectors may be used to initialized construction of
+     * vectors by data gathering. We start with zero-length and use either the {@link
+     * #OpenMapRealVector(OpenMapRealVector, int)} constructor or one of the {@code append} method
+     * ({@link #append(double)}, {@link #append(RealVector)}) to gather data into this vector.
+     */
+    public OpenMapRealVector() {
+        this(0, DEFAULT_ZERO_TOLERANCE);
+    }
+
+    /**
+     * Construct a vector of zeroes.
+     *
+     * @param dimension Size of the vector.
+     */
+    public OpenMapRealVector(int dimension) {
+        this(dimension, DEFAULT_ZERO_TOLERANCE);
+    }
+
+    /**
+     * Construct a vector of zeroes, specifying zero tolerance.
+     *
+     * @param dimension Size of the vector.
+     * @param epsilon Tolerance below which a value considered zero.
+     */
+    public OpenMapRealVector(int dimension, double epsilon) {
+        virtualSize = dimension;
+        entries = new OpenIntToDoubleHashMap(0.0);
+        this.epsilon = epsilon;
+    }
+
+    /**
+     * Build a resized vector, for use with append.
+     *
+     * @param v Original vector.
+     * @param resize Amount to add.
+     */
+    protected OpenMapRealVector(OpenMapRealVector v, int resize) {
+        virtualSize = v.getDimension() + resize;
+        entries = new OpenIntToDoubleHashMap(v.entries);
+        epsilon = v.epsilon;
+    }
+
+    /**
+     * Build a vector with known the sparseness (for advanced use only).
+     *
+     * @param dimension Size of the vector.
+     * @param expectedSize The expected number of non-zero entries.
+     */
+    public OpenMapRealVector(int dimension, int expectedSize) {
+        this(dimension, expectedSize, DEFAULT_ZERO_TOLERANCE);
+    }
+
+    /**
+     * Build a vector with known the sparseness and zero tolerance setting (for advanced use only).
+     *
+     * @param dimension Size of the vector.
+     * @param expectedSize Expected number of non-zero entries.
+     * @param epsilon Tolerance below which a value is considered zero.
+     */
+    public OpenMapRealVector(int dimension, int expectedSize, double epsilon) {
+        virtualSize = dimension;
+        entries = new OpenIntToDoubleHashMap(expectedSize, 0.0);
+        this.epsilon = epsilon;
+    }
+
+    /**
+     * Create from an array. Only non-zero entries will be stored.
+     *
+     * @param values Set of values to create from.
+     */
+    public OpenMapRealVector(double[] values) {
+        this(values, DEFAULT_ZERO_TOLERANCE);
+    }
+
+    /**
+     * Create from an array, specifying zero tolerance. Only non-zero entries will be stored.
+     *
+     * @param values Set of values to create from.
+     * @param epsilon Tolerance below which a value is considered zero.
+     */
+    public OpenMapRealVector(double[] values, double epsilon) {
+        virtualSize = values.length;
+        entries = new OpenIntToDoubleHashMap(0.0);
+        this.epsilon = epsilon;
+        for (int key = 0; key < values.length; key++) {
+            double value = values[key];
+            if (!isDefaultValue(value)) {
+                entries.put(key, value);
+            }
+        }
+    }
+
+    /**
+     * Create from an array. Only non-zero entries will be stored.
+     *
+     * @param values The set of values to create from
+     */
+    public OpenMapRealVector(Double[] values) {
+        this(values, DEFAULT_ZERO_TOLERANCE);
+    }
+
+    /**
+     * Create from an array. Only non-zero entries will be stored.
+     *
+     * @param values Set of values to create from.
+     * @param epsilon Tolerance below which a value is considered zero.
+     */
+    public OpenMapRealVector(Double[] values, double epsilon) {
+        virtualSize = values.length;
+        entries = new OpenIntToDoubleHashMap(0.0);
+        this.epsilon = epsilon;
+        for (int key = 0; key < values.length; key++) {
+            double value = values[key].doubleValue();
+            if (!isDefaultValue(value)) {
+                entries.put(key, value);
+            }
+        }
+    }
+
+    /**
+     * Copy constructor.
+     *
+     * @param v Instance to copy from.
+     */
+    public OpenMapRealVector(OpenMapRealVector v) {
+        virtualSize = v.getDimension();
+        entries = new OpenIntToDoubleHashMap(v.getEntries());
+        epsilon = v.epsilon;
+    }
+
+    /**
+     * Generic copy constructor.
+     *
+     * @param v Instance to copy from.
+     */
+    public OpenMapRealVector(RealVector v) {
+        virtualSize = v.getDimension();
+        entries = new OpenIntToDoubleHashMap(0.0);
+        epsilon = DEFAULT_ZERO_TOLERANCE;
+        for (int key = 0; key < virtualSize; key++) {
+            double value = v.getEntry(key);
+            if (!isDefaultValue(value)) {
+                entries.put(key, value);
+            }
+        }
+    }
+
+    /**
+     * Get the entries of this instance.
+     *
+     * @return the entries of this instance.
+     */
+    private OpenIntToDoubleHashMap getEntries() {
+        return entries;
+    }
+
+    /**
+     * Determine if this value is within epsilon of zero.
+     *
+     * @param value Value to test
+     * @return {@code true} if this value is within epsilon to zero, {@code false} otherwise.
+     * @since 2.1
+     */
+    protected boolean isDefaultValue(double value) {
+        return FastMath.abs(value) < epsilon;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector add(RealVector v) throws DimensionMismatchException {
+        checkVectorDimensions(v.getDimension());
+        if (v instanceof OpenMapRealVector) {
+            return add((OpenMapRealVector) v);
+        } else {
+            return super.add(v);
+        }
+    }
+
+    /**
+     * Optimized method to add two OpenMapRealVectors. It copies the larger vector, then iterates
+     * over the smaller.
+     *
+     * @param v Vector to add.
+     * @return the sum of {@code this} and {@code v}.
+     * @throws DimensionMismatchException if the dimensions do not match.
+     */
+    public OpenMapRealVector add(OpenMapRealVector v) throws DimensionMismatchException {
+        checkVectorDimensions(v.getDimension());
+        boolean copyThis = entries.size() > v.entries.size();
+        OpenMapRealVector res = copyThis ? this.copy() : v.copy();
+        Iterator iter = copyThis ? v.entries.iterator() : entries.iterator();
+        OpenIntToDoubleHashMap randomAccess = copyThis ? entries : v.entries;
+        while (iter.hasNext()) {
+            iter.advance();
+            int key = iter.key();
+            if (randomAccess.containsKey(key)) {
+                res.setEntry(key, randomAccess.get(key) + iter.value());
+            } else {
+                res.setEntry(key, iter.value());
+            }
+        }
+        return res;
+    }
+
+    /**
+     * Optimized method to append a OpenMapRealVector.
+     *
+     * @param v vector to append
+     * @return The result of appending {@code v} to self
+     */
+    public OpenMapRealVector append(OpenMapRealVector v) {
+        OpenMapRealVector res = new OpenMapRealVector(this, v.getDimension());
+        Iterator iter = v.entries.iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            res.setEntry(iter.key() + virtualSize, iter.value());
+        }
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public OpenMapRealVector append(RealVector v) {
+        if (v instanceof OpenMapRealVector) {
+            return append((OpenMapRealVector) v);
+        } else {
+            final OpenMapRealVector res = new OpenMapRealVector(this, v.getDimension());
+            for (int i = 0; i < v.getDimension(); i++) {
+                res.setEntry(i + virtualSize, v.getEntry(i));
+            }
+            return res;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public OpenMapRealVector append(double d) {
+        OpenMapRealVector res = new OpenMapRealVector(this, 1);
+        res.setEntry(virtualSize, d);
+        return res;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 2.1
+     */
+    @Override
+    public OpenMapRealVector copy() {
+        return new OpenMapRealVector(this);
+    }
+
+    /**
+     * Computes the dot product. Note that the computation is now performed in the parent class: no
+     * performance improvement is to be expected from this overloaded method. The previous
+     * implementation was buggy and cannot be easily fixed (see MATH-795).
+     *
+     * @param v Vector.
+     * @return the dot product of this vector with {@code v}.
+     * @throws DimensionMismatchException if {@code v} is not the same size as {@code this} vector.
+     * @deprecated as of 3.1 (to be removed in 4.0). The computation is performed by the parent
+     *     class. The method must be kept to maintain backwards compatibility.
+     */
+    @Deprecated
+    public double dotProduct(OpenMapRealVector v) throws DimensionMismatchException {
+        return dotProduct((RealVector) v);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public OpenMapRealVector ebeDivide(RealVector v) throws DimensionMismatchException {
+        checkVectorDimensions(v.getDimension());
+        OpenMapRealVector res = new OpenMapRealVector(this);
+        /*
+         * MATH-803: it is not sufficient to loop through non zero entries of
+         * this only. Indeed, if this[i] = 0d and v[i] = 0d, then
+         * this[i] / v[i] = NaN, and not 0d.
+         */
+        final int n = getDimension();
+        for (int i = 0; i < n; i++) {
+            res.setEntry(i, this.getEntry(i) / v.getEntry(i));
+        }
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public OpenMapRealVector ebeMultiply(RealVector v) throws DimensionMismatchException {
+        checkVectorDimensions(v.getDimension());
+        OpenMapRealVector res = new OpenMapRealVector(this);
+        Iterator iter = entries.iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            res.setEntry(iter.key(), iter.value() * v.getEntry(iter.key()));
+        }
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public OpenMapRealVector getSubVector(int index, int n)
+            throws NotPositiveException, OutOfRangeException {
+        checkIndex(index);
+        if (n < 0) {
+            throw new NotPositiveException(
+                    LocalizedFormats.NUMBER_OF_ELEMENTS_SHOULD_BE_POSITIVE, n);
+        }
+        checkIndex(index + n - 1);
+        OpenMapRealVector res = new OpenMapRealVector(n);
+        int end = index + n;
+        Iterator iter = entries.iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            int key = iter.key();
+            if (key >= index && key < end) {
+                res.setEntry(key - index, iter.value());
+            }
+        }
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getDimension() {
+        return virtualSize;
+    }
+
+    /**
+     * Optimized method to compute distance.
+     *
+     * @param v Vector to compute distance to.
+     * @return the distance from {@code this} and {@code v}.
+     * @throws DimensionMismatchException if the dimensions do not match.
+     */
+    public double getDistance(OpenMapRealVector v) throws DimensionMismatchException {
+        checkVectorDimensions(v.getDimension());
+        Iterator iter = entries.iterator();
+        double res = 0;
+        while (iter.hasNext()) {
+            iter.advance();
+            int key = iter.key();
+            double delta;
+            delta = iter.value() - v.getEntry(key);
+            res += delta * delta;
+        }
+        iter = v.getEntries().iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            int key = iter.key();
+            if (!entries.containsKey(key)) {
+                final double value = iter.value();
+                res += value * value;
+            }
+        }
+        return FastMath.sqrt(res);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getDistance(RealVector v) throws DimensionMismatchException {
+        checkVectorDimensions(v.getDimension());
+        if (v instanceof OpenMapRealVector) {
+            return getDistance((OpenMapRealVector) v);
+        } else {
+            return super.getDistance(v);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getEntry(int index) throws OutOfRangeException {
+        checkIndex(index);
+        return entries.get(index);
+    }
+
+    /**
+     * Distance between two vectors. This method computes the distance consistent with L<sub>1</sub>
+     * norm, i.e. the sum of the absolute values of elements differences.
+     *
+     * @param v Vector to which distance is requested.
+     * @return distance between this vector and {@code v}.
+     * @throws DimensionMismatchException if the dimensions do not match.
+     */
+    public double getL1Distance(OpenMapRealVector v) throws DimensionMismatchException {
+        checkVectorDimensions(v.getDimension());
+        double max = 0;
+        Iterator iter = entries.iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            double delta = FastMath.abs(iter.value() - v.getEntry(iter.key()));
+            max += delta;
+        }
+        iter = v.getEntries().iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            int key = iter.key();
+            if (!entries.containsKey(key)) {
+                double delta = FastMath.abs(iter.value());
+                max += FastMath.abs(delta);
+            }
+        }
+        return max;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getL1Distance(RealVector v) throws DimensionMismatchException {
+        checkVectorDimensions(v.getDimension());
+        if (v instanceof OpenMapRealVector) {
+            return getL1Distance((OpenMapRealVector) v);
+        } else {
+            return super.getL1Distance(v);
+        }
+    }
+
+    /**
+     * Optimized method to compute LInfDistance.
+     *
+     * @param v Vector to compute distance from.
+     * @return the LInfDistance.
+     * @throws DimensionMismatchException if the dimensions do not match.
+     */
+    private double getLInfDistance(OpenMapRealVector v) throws DimensionMismatchException {
+        checkVectorDimensions(v.getDimension());
+        double max = 0;
+        Iterator iter = entries.iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            double delta = FastMath.abs(iter.value() - v.getEntry(iter.key()));
+            if (delta > max) {
+                max = delta;
+            }
+        }
+        iter = v.getEntries().iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            int key = iter.key();
+            if (!entries.containsKey(key) && iter.value() > max) {
+                max = iter.value();
+            }
+        }
+        return max;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getLInfDistance(RealVector v) throws DimensionMismatchException {
+        checkVectorDimensions(v.getDimension());
+        if (v instanceof OpenMapRealVector) {
+            return getLInfDistance((OpenMapRealVector) v);
+        } else {
+            return super.getLInfDistance(v);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isInfinite() {
+        boolean infiniteFound = false;
+        Iterator iter = entries.iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            final double value = iter.value();
+            if (Double.isNaN(value)) {
+                return false;
+            }
+            if (Double.isInfinite(value)) {
+                infiniteFound = true;
+            }
+        }
+        return infiniteFound;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isNaN() {
+        Iterator iter = entries.iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            if (Double.isNaN(iter.value())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public OpenMapRealVector mapAdd(double d) {
+        return copy().mapAddToSelf(d);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public OpenMapRealVector mapAddToSelf(double d) {
+        for (int i = 0; i < virtualSize; i++) {
+            setEntry(i, getEntry(i) + d);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setEntry(int index, double value) throws OutOfRangeException {
+        checkIndex(index);
+        if (!isDefaultValue(value)) {
+            entries.put(index, value);
+        } else if (entries.containsKey(index)) {
+            entries.remove(index);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setSubVector(int index, RealVector v) throws OutOfRangeException {
+        checkIndex(index);
+        checkIndex(index + v.getDimension() - 1);
+        for (int i = 0; i < v.getDimension(); i++) {
+            setEntry(i + index, v.getEntry(i));
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void set(double value) {
+        for (int i = 0; i < virtualSize; i++) {
+            setEntry(i, value);
+        }
+    }
+
+    /**
+     * Optimized method to subtract OpenMapRealVectors.
+     *
+     * @param v Vector to subtract from {@code this}.
+     * @return the difference of {@code this} and {@code v}.
+     * @throws DimensionMismatchException if the dimensions do not match.
+     */
+    public OpenMapRealVector subtract(OpenMapRealVector v) throws DimensionMismatchException {
+        checkVectorDimensions(v.getDimension());
+        OpenMapRealVector res = copy();
+        Iterator iter = v.getEntries().iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            int key = iter.key();
+            if (entries.containsKey(key)) {
+                res.setEntry(key, entries.get(key) - iter.value());
+            } else {
+                res.setEntry(key, -iter.value());
+            }
+        }
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector subtract(RealVector v) throws DimensionMismatchException {
+        checkVectorDimensions(v.getDimension());
+        if (v instanceof OpenMapRealVector) {
+            return subtract((OpenMapRealVector) v);
+        } else {
+            return super.subtract(v);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public OpenMapRealVector unitVector() throws MathArithmeticException {
+        OpenMapRealVector res = copy();
+        res.unitize();
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void unitize() throws MathArithmeticException {
+        double norm = getNorm();
+        if (isDefaultValue(norm)) {
+            throw new MathArithmeticException(LocalizedFormats.ZERO_NORM);
+        }
+        Iterator iter = entries.iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            entries.put(iter.key(), iter.value() / norm);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double[] toArray() {
+        double[] res = new double[virtualSize];
+        Iterator iter = entries.iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            res[iter.key()] = iter.value();
+        }
+        return res;
+    }
+
+    /**
+     * {@inheritDoc} Implementation Note: This works on exact values, and as a result it is possible
+     * for {@code a.subtract(b)} to be the zero vector, while {@code a.hashCode() != b.hashCode()}.
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        long temp;
+        temp = Double.doubleToLongBits(epsilon);
+        result = prime * result + (int) (temp ^ (temp >>> 32));
+        result = prime * result + virtualSize;
+        Iterator iter = entries.iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            temp = Double.doubleToLongBits(iter.value());
+            result = prime * result + (int) (temp ^ (temp >> 32));
+        }
+        return result;
+    }
+
+    /**
+     * {@inheritDoc} Implementation Note: This performs an exact comparison, and as a result it is
+     * possible for {@code a.subtract(b}} to be the zero vector, while {@code a.equals(b) == false}.
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof OpenMapRealVector)) {
+            return false;
+        }
+        OpenMapRealVector other = (OpenMapRealVector) obj;
+        if (virtualSize != other.virtualSize) {
+            return false;
+        }
+        if (Double.doubleToLongBits(epsilon) != Double.doubleToLongBits(other.epsilon)) {
+            return false;
+        }
+        Iterator iter = entries.iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            double test = other.getEntry(iter.key());
+            if (Double.doubleToLongBits(test) != Double.doubleToLongBits(iter.value())) {
+                return false;
+            }
+        }
+        iter = other.getEntries().iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            double test = iter.value();
+            if (Double.doubleToLongBits(test) != Double.doubleToLongBits(getEntry(iter.key()))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * @return the percentage of none zero elements as a decimal percent.
+     * @since 2.2
+     */
+    public double getSparsity() {
+        return (double) entries.size() / (double) getDimension();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public java.util.Iterator<Entry> sparseIterator() {
+        return new OpenMapSparseIterator();
+    }
+
+    /**
+     * Implementation of {@code Entry} optimized for OpenMap. This implementation does not allow
+     * arbitrary calls to {@code setIndex} since the order in which entries are returned is
+     * undefined.
+     */
+    protected class OpenMapEntry extends Entry {
+        /** Iterator pointing to the entry. */
+        private final Iterator iter;
+
+        /**
+         * Build an entry from an iterator point to an element.
+         *
+         * @param iter Iterator pointing to the entry.
+         */
+        protected OpenMapEntry(Iterator iter) {
+            this.iter = iter;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public double getValue() {
+            return iter.value();
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void setValue(double value) {
+            entries.put(iter.key(), value);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public int getIndex() {
+            return iter.key();
+        }
+    }
+
+    /**
+     * Iterator class to do iteration over just the non-zero elements. This implementation is
+     * fail-fast, so cannot be used to modify any zero element.
+     */
+    protected class OpenMapSparseIterator implements java.util.Iterator<Entry> {
+        /** Underlying iterator. */
+        private final Iterator iter;
+
+        /** Current entry. */
+        private final Entry current;
+
+        /** Simple constructor. */
+        protected OpenMapSparseIterator() {
+            iter = entries.iterator();
+            current = new OpenMapEntry(iter);
+        }
+
+        /** {@inheritDoc} */
+        public boolean hasNext() {
+            return iter.hasNext();
+        }
+
+        /** {@inheritDoc} */
+        public Entry next() {
+            iter.advance();
+            return current;
+        }
+
+        /** {@inheritDoc} */
+        public void remove() {
+            throw new UnsupportedOperationException("Not supported");
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/PreconditionedIterativeLinearSolver.java b/src/main/java/org/apache/commons/math3/linear/PreconditionedIterativeLinearSolver.java
new file mode 100644
index 0000000..6808d46
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/PreconditionedIterativeLinearSolver.java
@@ -0,0 +1,205 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.util.IterationManager;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * This abstract class defines preconditioned iterative solvers. When A is ill-conditioned, instead
+ * of solving system A &middot; x = b directly, it is preferable to solve either <center> (M
+ * &middot; A) &middot; x = M &middot; b </center> (left preconditioning), or <center> (A &middot;
+ * M) &middot; y = b, &nbsp;&nbsp;&nbsp;&nbsp;followed by M &middot; y = x </center> (right
+ * preconditioning), where M approximates in some way A<sup>-1</sup>, while matrix-vector products
+ * of the type M &middot; y remain comparatively easy to compute. In this library, M (not
+ * M<sup>-1</sup>!) is called the <em>preconditionner</em>.
+ *
+ * <p>Concrete implementations of this abstract class must be provided with the preconditioner M, as
+ * a {@link RealLinearOperator}.
+ *
+ * @since 3.0
+ */
+public abstract class PreconditionedIterativeLinearSolver extends IterativeLinearSolver {
+
+    /**
+     * Creates a new instance of this class, with default iteration manager.
+     *
+     * @param maxIterations the maximum number of iterations
+     */
+    public PreconditionedIterativeLinearSolver(final int maxIterations) {
+        super(maxIterations);
+    }
+
+    /**
+     * Creates a new instance of this class, with custom iteration manager.
+     *
+     * @param manager the custom iteration manager
+     * @throws NullArgumentException if {@code manager} is {@code null}
+     */
+    public PreconditionedIterativeLinearSolver(final IterationManager manager)
+            throws NullArgumentException {
+        super(manager);
+    }
+
+    /**
+     * Returns an estimate of the solution to the linear system A &middot; x = b.
+     *
+     * @param a the linear operator A of the system
+     * @param m the preconditioner, M (can be {@code null})
+     * @param b the right-hand side vector
+     * @param x0 the initial guess of the solution
+     * @return a new vector containing the solution
+     * @throws NullArgumentException if one of the parameters is {@code null}
+     * @throws NonSquareOperatorException if {@code a} or {@code m} is not square
+     * @throws DimensionMismatchException if {@code m}, {@code b} or {@code x0} have dimensions
+     *     inconsistent with {@code a}
+     * @throws MaxCountExceededException at exhaustion of the iteration count, unless a custom
+     *     {@link org.apache.commons.math3.util.Incrementor.MaxCountExceededCallback callback} has
+     *     been set at construction of the {@link IterationManager}
+     */
+    public RealVector solve(
+            final RealLinearOperator a,
+            final RealLinearOperator m,
+            final RealVector b,
+            final RealVector x0)
+            throws NullArgumentException,
+                    NonSquareOperatorException,
+                    DimensionMismatchException,
+                    MaxCountExceededException {
+        MathUtils.checkNotNull(x0);
+        return solveInPlace(a, m, b, x0.copy());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector solve(final RealLinearOperator a, final RealVector b)
+            throws NullArgumentException,
+                    NonSquareOperatorException,
+                    DimensionMismatchException,
+                    MaxCountExceededException {
+        MathUtils.checkNotNull(a);
+        final RealVector x = new ArrayRealVector(a.getColumnDimension());
+        x.set(0.);
+        return solveInPlace(a, null, b, x);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector solve(final RealLinearOperator a, final RealVector b, final RealVector x0)
+            throws NullArgumentException,
+                    NonSquareOperatorException,
+                    DimensionMismatchException,
+                    MaxCountExceededException {
+        MathUtils.checkNotNull(x0);
+        return solveInPlace(a, null, b, x0.copy());
+    }
+
+    /**
+     * Performs all dimension checks on the parameters of {@link #solve(RealLinearOperator,
+     * RealLinearOperator, RealVector, RealVector) solve} and {@link
+     * #solveInPlace(RealLinearOperator, RealLinearOperator, RealVector, RealVector) solveInPlace},
+     * and throws an exception if one of the checks fails.
+     *
+     * @param a the linear operator A of the system
+     * @param m the preconditioner, M (can be {@code null})
+     * @param b the right-hand side vector
+     * @param x0 the initial guess of the solution
+     * @throws NullArgumentException if one of the parameters is {@code null}
+     * @throws NonSquareOperatorException if {@code a} or {@code m} is not square
+     * @throws DimensionMismatchException if {@code m}, {@code b} or {@code x0} have dimensions
+     *     inconsistent with {@code a}
+     */
+    protected static void checkParameters(
+            final RealLinearOperator a,
+            final RealLinearOperator m,
+            final RealVector b,
+            final RealVector x0)
+            throws NullArgumentException, NonSquareOperatorException, DimensionMismatchException {
+        checkParameters(a, b, x0);
+        if (m != null) {
+            if (m.getColumnDimension() != m.getRowDimension()) {
+                throw new NonSquareOperatorException(m.getColumnDimension(), m.getRowDimension());
+            }
+            if (m.getRowDimension() != a.getRowDimension()) {
+                throw new DimensionMismatchException(m.getRowDimension(), a.getRowDimension());
+            }
+        }
+    }
+
+    /**
+     * Returns an estimate of the solution to the linear system A &middot; x = b.
+     *
+     * @param a the linear operator A of the system
+     * @param m the preconditioner, M (can be {@code null})
+     * @param b the right-hand side vector
+     * @return a new vector containing the solution
+     * @throws NullArgumentException if one of the parameters is {@code null}
+     * @throws NonSquareOperatorException if {@code a} or {@code m} is not square
+     * @throws DimensionMismatchException if {@code m} or {@code b} have dimensions inconsistent
+     *     with {@code a}
+     * @throws MaxCountExceededException at exhaustion of the iteration count, unless a custom
+     *     {@link org.apache.commons.math3.util.Incrementor.MaxCountExceededCallback callback} has
+     *     been set at construction of the {@link IterationManager}
+     */
+    public RealVector solve(RealLinearOperator a, RealLinearOperator m, RealVector b)
+            throws NullArgumentException,
+                    NonSquareOperatorException,
+                    DimensionMismatchException,
+                    MaxCountExceededException {
+        MathUtils.checkNotNull(a);
+        final RealVector x = new ArrayRealVector(a.getColumnDimension());
+        return solveInPlace(a, m, b, x);
+    }
+
+    /**
+     * Returns an estimate of the solution to the linear system A &middot; x = b. The solution is
+     * computed in-place (initial guess is modified).
+     *
+     * @param a the linear operator A of the system
+     * @param m the preconditioner, M (can be {@code null})
+     * @param b the right-hand side vector
+     * @param x0 the initial guess of the solution
+     * @return a reference to {@code x0} (shallow copy) updated with the solution
+     * @throws NullArgumentException if one of the parameters is {@code null}
+     * @throws NonSquareOperatorException if {@code a} or {@code m} is not square
+     * @throws DimensionMismatchException if {@code m}, {@code b} or {@code x0} have dimensions
+     *     inconsistent with {@code a}
+     * @throws MaxCountExceededException at exhaustion of the iteration count, unless a custom
+     *     {@link org.apache.commons.math3.util.Incrementor.MaxCountExceededCallback callback} has
+     *     been set at construction of the {@link IterationManager}
+     */
+    public abstract RealVector solveInPlace(
+            RealLinearOperator a, RealLinearOperator m, RealVector b, RealVector x0)
+            throws NullArgumentException,
+                    NonSquareOperatorException,
+                    DimensionMismatchException,
+                    MaxCountExceededException;
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector solveInPlace(
+            final RealLinearOperator a, final RealVector b, final RealVector x0)
+            throws NullArgumentException,
+                    NonSquareOperatorException,
+                    DimensionMismatchException,
+                    MaxCountExceededException {
+        return solveInPlace(a, null, b, x0);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/QRDecomposition.java b/src/main/java/org/apache/commons/math3/linear/QRDecomposition.java
new file mode 100644
index 0000000..17092c6
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/QRDecomposition.java
@@ -0,0 +1,492 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.util.FastMath;
+
+import java.util.Arrays;
+
+/**
+ * Calculates the QR-decomposition of a matrix.
+ *
+ * <p>The QR-decomposition of a matrix A consists of two matrices Q and R that satisfy: A = QR, Q is
+ * orthogonal (Q<sup>T</sup>Q = I), and R is upper triangular. If A is m&times;n, Q is m&times;m and
+ * R m&times;n.
+ *
+ * <p>This class compute the decomposition using Householder reflectors.
+ *
+ * <p>For efficiency purposes, the decomposition in packed form is transposed. This allows inner
+ * loop to iterate inside rows, which is much more cache-efficient in Java.
+ *
+ * <p>This class is based on the class with similar name from the <a
+ * href="http://math.nist.gov/javanumerics/jama/">JAMA</a> library, with the following changes:
+ *
+ * <ul>
+ *   <li>a {@link #getQT() getQT} method has been added,
+ *   <li>the {@code solve} and {@code isFullRank} methods have been replaced by a {@link
+ *       #getSolver() getSolver} method and the equivalent methods provided by the returned {@link
+ *       DecompositionSolver}.
+ * </ul>
+ *
+ * @see <a href="http://mathworld.wolfram.com/QRDecomposition.html">MathWorld</a>
+ * @see <a href="http://en.wikipedia.org/wiki/QR_decomposition">Wikipedia</a>
+ * @since 1.2 (changed to concrete class in 3.0)
+ */
+public class QRDecomposition {
+    /**
+     * A packed TRANSPOSED representation of the QR decomposition.
+     *
+     * <p>The elements BELOW the diagonal are the elements of the UPPER triangular matrix R, and the
+     * rows ABOVE the diagonal are the Householder reflector vectors from which an explicit form of
+     * Q can be recomputed if desired.
+     */
+    private double[][] qrt;
+
+    /** The diagonal elements of R. */
+    private double[] rDiag;
+
+    /** Cached value of Q. */
+    private RealMatrix cachedQ;
+
+    /** Cached value of QT. */
+    private RealMatrix cachedQT;
+
+    /** Cached value of R. */
+    private RealMatrix cachedR;
+
+    /** Cached value of H. */
+    private RealMatrix cachedH;
+
+    /** Singularity threshold. */
+    private final double threshold;
+
+    /**
+     * Calculates the QR-decomposition of the given matrix. The singularity threshold defaults to
+     * zero.
+     *
+     * @param matrix The matrix to decompose.
+     * @see #QRDecomposition(RealMatrix,double)
+     */
+    public QRDecomposition(RealMatrix matrix) {
+        this(matrix, 0d);
+    }
+
+    /**
+     * Calculates the QR-decomposition of the given matrix.
+     *
+     * @param matrix The matrix to decompose.
+     * @param threshold Singularity threshold.
+     */
+    public QRDecomposition(RealMatrix matrix, double threshold) {
+        this.threshold = threshold;
+
+        final int m = matrix.getRowDimension();
+        final int n = matrix.getColumnDimension();
+        qrt = matrix.transpose().getData();
+        rDiag = new double[FastMath.min(m, n)];
+        cachedQ = null;
+        cachedQT = null;
+        cachedR = null;
+        cachedH = null;
+
+        decompose(qrt);
+    }
+
+    /**
+     * Decompose matrix.
+     *
+     * @param matrix transposed matrix
+     * @since 3.2
+     */
+    protected void decompose(double[][] matrix) {
+        for (int minor = 0; minor < FastMath.min(matrix.length, matrix[0].length); minor++) {
+            performHouseholderReflection(minor, matrix);
+        }
+    }
+
+    /**
+     * Perform Householder reflection for a minor A(minor, minor) of A.
+     *
+     * @param minor minor index
+     * @param matrix transposed matrix
+     * @since 3.2
+     */
+    protected void performHouseholderReflection(int minor, double[][] matrix) {
+
+        final double[] qrtMinor = matrix[minor];
+
+        /*
+         * Let x be the first column of the minor, and a^2 = |x|^2.
+         * x will be in the positions qr[minor][minor] through qr[m][minor].
+         * The first column of the transformed minor will be (a,0,0,..)'
+         * The sign of a is chosen to be opposite to the sign of the first
+         * component of x. Let's find a:
+         */
+        double xNormSqr = 0;
+        for (int row = minor; row < qrtMinor.length; row++) {
+            final double c = qrtMinor[row];
+            xNormSqr += c * c;
+        }
+        final double a = (qrtMinor[minor] > 0) ? -FastMath.sqrt(xNormSqr) : FastMath.sqrt(xNormSqr);
+        rDiag[minor] = a;
+
+        if (a != 0.0) {
+
+            /*
+             * Calculate the normalized reflection vector v and transform
+             * the first column. We know the norm of v beforehand: v = x-ae
+             * so |v|^2 = <x-ae,x-ae> = <x,x>-2a<x,e>+a^2<e,e> =
+             * a^2+a^2-2a<x,e> = 2a*(a - <x,e>).
+             * Here <x, e> is now qr[minor][minor].
+             * v = x-ae is stored in the column at qr:
+             */
+            qrtMinor[minor] -= a; // now |v|^2 = -2a*(qr[minor][minor])
+
+            /*
+             * Transform the rest of the columns of the minor:
+             * They will be transformed by the matrix H = I-2vv'/|v|^2.
+             * If x is a column vector of the minor, then
+             * Hx = (I-2vv'/|v|^2)x = x-2vv'x/|v|^2 = x - 2<x,v>/|v|^2 v.
+             * Therefore the transformation is easily calculated by
+             * subtracting the column vector (2<x,v>/|v|^2)v from x.
+             *
+             * Let 2<x,v>/|v|^2 = alpha. From above we have
+             * |v|^2 = -2a*(qr[minor][minor]), so
+             * alpha = -<x,v>/(a*qr[minor][minor])
+             */
+            for (int col = minor + 1; col < matrix.length; col++) {
+                final double[] qrtCol = matrix[col];
+                double alpha = 0;
+                for (int row = minor; row < qrtCol.length; row++) {
+                    alpha -= qrtCol[row] * qrtMinor[row];
+                }
+                alpha /= a * qrtMinor[minor];
+
+                // Subtract the column vector alpha*v from x.
+                for (int row = minor; row < qrtCol.length; row++) {
+                    qrtCol[row] -= alpha * qrtMinor[row];
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the matrix R of the decomposition.
+     *
+     * <p>R is an upper-triangular matrix
+     *
+     * @return the R matrix
+     */
+    public RealMatrix getR() {
+
+        if (cachedR == null) {
+
+            // R is supposed to be m x n
+            final int n = qrt.length;
+            final int m = qrt[0].length;
+            double[][] ra = new double[m][n];
+            // copy the diagonal from rDiag and the upper triangle of qr
+            for (int row = FastMath.min(m, n) - 1; row >= 0; row--) {
+                ra[row][row] = rDiag[row];
+                for (int col = row + 1; col < n; col++) {
+                    ra[row][col] = qrt[col][row];
+                }
+            }
+            cachedR = MatrixUtils.createRealMatrix(ra);
+        }
+
+        // return the cached matrix
+        return cachedR;
+    }
+
+    /**
+     * Returns the matrix Q of the decomposition.
+     *
+     * <p>Q is an orthogonal matrix
+     *
+     * @return the Q matrix
+     */
+    public RealMatrix getQ() {
+        if (cachedQ == null) {
+            cachedQ = getQT().transpose();
+        }
+        return cachedQ;
+    }
+
+    /**
+     * Returns the transpose of the matrix Q of the decomposition.
+     *
+     * <p>Q is an orthogonal matrix
+     *
+     * @return the transpose of the Q matrix, Q<sup>T</sup>
+     */
+    public RealMatrix getQT() {
+        if (cachedQT == null) {
+
+            // QT is supposed to be m x m
+            final int n = qrt.length;
+            final int m = qrt[0].length;
+            double[][] qta = new double[m][m];
+
+            /*
+             * Q = Q1 Q2 ... Q_m, so Q is formed by first constructing Q_m and then
+             * applying the Householder transformations Q_(m-1),Q_(m-2),...,Q1 in
+             * succession to the result
+             */
+            for (int minor = m - 1; minor >= FastMath.min(m, n); minor--) {
+                qta[minor][minor] = 1.0d;
+            }
+
+            for (int minor = FastMath.min(m, n) - 1; minor >= 0; minor--) {
+                final double[] qrtMinor = qrt[minor];
+                qta[minor][minor] = 1.0d;
+                if (qrtMinor[minor] != 0.0) {
+                    for (int col = minor; col < m; col++) {
+                        double alpha = 0;
+                        for (int row = minor; row < m; row++) {
+                            alpha -= qta[col][row] * qrtMinor[row];
+                        }
+                        alpha /= rDiag[minor] * qrtMinor[minor];
+
+                        for (int row = minor; row < m; row++) {
+                            qta[col][row] += -alpha * qrtMinor[row];
+                        }
+                    }
+                }
+            }
+            cachedQT = MatrixUtils.createRealMatrix(qta);
+        }
+
+        // return the cached matrix
+        return cachedQT;
+    }
+
+    /**
+     * Returns the Householder reflector vectors.
+     *
+     * <p>H is a lower trapezoidal matrix whose columns represent each successive Householder
+     * reflector vector. This matrix is used to compute Q.
+     *
+     * @return a matrix containing the Householder reflector vectors
+     */
+    public RealMatrix getH() {
+        if (cachedH == null) {
+
+            final int n = qrt.length;
+            final int m = qrt[0].length;
+            double[][] ha = new double[m][n];
+            for (int i = 0; i < m; ++i) {
+                for (int j = 0; j < FastMath.min(i + 1, n); ++j) {
+                    ha[i][j] = qrt[j][i] / -rDiag[j];
+                }
+            }
+            cachedH = MatrixUtils.createRealMatrix(ha);
+        }
+
+        // return the cached matrix
+        return cachedH;
+    }
+
+    /**
+     * Get a solver for finding the A &times; X = B solution in least square sense.
+     *
+     * <p>Least Square sense means a solver can be computed for an overdetermined system, (i.e. a
+     * system with more equations than unknowns, which corresponds to a tall A matrix with more rows
+     * than columns). In any case, if the matrix is singular within the tolerance set at {@link
+     * QRDecomposition#QRDecomposition(RealMatrix, double) construction}, an error will be triggered
+     * when the {@link DecompositionSolver#solve(RealVector) solve} method will be called.
+     *
+     * @return a solver
+     */
+    public DecompositionSolver getSolver() {
+        return new Solver(qrt, rDiag, threshold);
+    }
+
+    /** Specialized solver. */
+    private static class Solver implements DecompositionSolver {
+        /**
+         * A packed TRANSPOSED representation of the QR decomposition.
+         *
+         * <p>The elements BELOW the diagonal are the elements of the UPPER triangular matrix R, and
+         * the rows ABOVE the diagonal are the Householder reflector vectors from which an explicit
+         * form of Q can be recomputed if desired.
+         */
+        private final double[][] qrt;
+
+        /** The diagonal elements of R. */
+        private final double[] rDiag;
+
+        /** Singularity threshold. */
+        private final double threshold;
+
+        /**
+         * Build a solver from decomposed matrix.
+         *
+         * @param qrt Packed TRANSPOSED representation of the QR decomposition.
+         * @param rDiag Diagonal elements of R.
+         * @param threshold Singularity threshold.
+         */
+        private Solver(final double[][] qrt, final double[] rDiag, final double threshold) {
+            this.qrt = qrt;
+            this.rDiag = rDiag;
+            this.threshold = threshold;
+        }
+
+        /** {@inheritDoc} */
+        public boolean isNonSingular() {
+            for (double diag : rDiag) {
+                if (FastMath.abs(diag) <= threshold) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        /** {@inheritDoc} */
+        public RealVector solve(RealVector b) {
+            final int n = qrt.length;
+            final int m = qrt[0].length;
+            if (b.getDimension() != m) {
+                throw new DimensionMismatchException(b.getDimension(), m);
+            }
+            if (!isNonSingular()) {
+                throw new SingularMatrixException();
+            }
+
+            final double[] x = new double[n];
+            final double[] y = b.toArray();
+
+            // apply Householder transforms to solve Q.y = b
+            for (int minor = 0; minor < FastMath.min(m, n); minor++) {
+
+                final double[] qrtMinor = qrt[minor];
+                double dotProduct = 0;
+                for (int row = minor; row < m; row++) {
+                    dotProduct += y[row] * qrtMinor[row];
+                }
+                dotProduct /= rDiag[minor] * qrtMinor[minor];
+
+                for (int row = minor; row < m; row++) {
+                    y[row] += dotProduct * qrtMinor[row];
+                }
+            }
+
+            // solve triangular system R.x = y
+            for (int row = rDiag.length - 1; row >= 0; --row) {
+                y[row] /= rDiag[row];
+                final double yRow = y[row];
+                final double[] qrtRow = qrt[row];
+                x[row] = yRow;
+                for (int i = 0; i < row; i++) {
+                    y[i] -= yRow * qrtRow[i];
+                }
+            }
+
+            return new ArrayRealVector(x, false);
+        }
+
+        /** {@inheritDoc} */
+        public RealMatrix solve(RealMatrix b) {
+            final int n = qrt.length;
+            final int m = qrt[0].length;
+            if (b.getRowDimension() != m) {
+                throw new DimensionMismatchException(b.getRowDimension(), m);
+            }
+            if (!isNonSingular()) {
+                throw new SingularMatrixException();
+            }
+
+            final int columns = b.getColumnDimension();
+            final int blockSize = BlockRealMatrix.BLOCK_SIZE;
+            final int cBlocks = (columns + blockSize - 1) / blockSize;
+            final double[][] xBlocks = BlockRealMatrix.createBlocksLayout(n, columns);
+            final double[][] y = new double[b.getRowDimension()][blockSize];
+            final double[] alpha = new double[blockSize];
+
+            for (int kBlock = 0; kBlock < cBlocks; ++kBlock) {
+                final int kStart = kBlock * blockSize;
+                final int kEnd = FastMath.min(kStart + blockSize, columns);
+                final int kWidth = kEnd - kStart;
+
+                // get the right hand side vector
+                b.copySubMatrix(0, m - 1, kStart, kEnd - 1, y);
+
+                // apply Householder transforms to solve Q.y = b
+                for (int minor = 0; minor < FastMath.min(m, n); minor++) {
+                    final double[] qrtMinor = qrt[minor];
+                    final double factor = 1.0 / (rDiag[minor] * qrtMinor[minor]);
+
+                    Arrays.fill(alpha, 0, kWidth, 0.0);
+                    for (int row = minor; row < m; ++row) {
+                        final double d = qrtMinor[row];
+                        final double[] yRow = y[row];
+                        for (int k = 0; k < kWidth; ++k) {
+                            alpha[k] += d * yRow[k];
+                        }
+                    }
+                    for (int k = 0; k < kWidth; ++k) {
+                        alpha[k] *= factor;
+                    }
+
+                    for (int row = minor; row < m; ++row) {
+                        final double d = qrtMinor[row];
+                        final double[] yRow = y[row];
+                        for (int k = 0; k < kWidth; ++k) {
+                            yRow[k] += alpha[k] * d;
+                        }
+                    }
+                }
+
+                // solve triangular system R.x = y
+                for (int j = rDiag.length - 1; j >= 0; --j) {
+                    final int jBlock = j / blockSize;
+                    final int jStart = jBlock * blockSize;
+                    final double factor = 1.0 / rDiag[j];
+                    final double[] yJ = y[j];
+                    final double[] xBlock = xBlocks[jBlock * cBlocks + kBlock];
+                    int index = (j - jStart) * kWidth;
+                    for (int k = 0; k < kWidth; ++k) {
+                        yJ[k] *= factor;
+                        xBlock[index++] = yJ[k];
+                    }
+
+                    final double[] qrtJ = qrt[j];
+                    for (int i = 0; i < j; ++i) {
+                        final double rIJ = qrtJ[i];
+                        final double[] yI = y[i];
+                        for (int k = 0; k < kWidth; ++k) {
+                            yI[k] -= yJ[k] * rIJ;
+                        }
+                    }
+                }
+            }
+
+            return new BlockRealMatrix(n, columns, xBlocks, false);
+        }
+
+        /**
+         * {@inheritDoc}
+         *
+         * @throws SingularMatrixException if the decomposed matrix is singular.
+         */
+        public RealMatrix getInverse() {
+            return solve(MatrixUtils.createRealIdentityMatrix(qrt[0].length));
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/RRQRDecomposition.java b/src/main/java/org/apache/commons/math3/linear/RRQRDecomposition.java
new file mode 100644
index 0000000..56c1836
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/RRQRDecomposition.java
@@ -0,0 +1,244 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Calculates the rank-revealing QR-decomposition of a matrix, with column pivoting.
+ *
+ * <p>The rank-revealing QR-decomposition of a matrix A consists of three matrices Q, R and P such
+ * that AP=QR. Q is orthogonal (Q<sup>T</sup>Q = I), and R is upper triangular. If A is m&times;n, Q
+ * is m&times;m and R is m&times;n and P is n&times;n.
+ *
+ * <p>QR decomposition with column pivoting produces a rank-revealing QR decomposition and the
+ * {@link #getRank(double)} method may be used to return the rank of the input matrix A.
+ *
+ * <p>This class compute the decomposition using Householder reflectors.
+ *
+ * <p>For efficiency purposes, the decomposition in packed form is transposed. This allows inner
+ * loop to iterate inside rows, which is much more cache-efficient in Java.
+ *
+ * <p>This class is based on the class with similar name from the <a
+ * href="http://math.nist.gov/javanumerics/jama/">JAMA</a> library, with the following changes:
+ *
+ * <ul>
+ *   <li>a {@link #getQT() getQT} method has been added,
+ *   <li>the {@code solve} and {@code isFullRank} methods have been replaced by a {@link
+ *       #getSolver() getSolver} method and the equivalent methods provided by the returned {@link
+ *       DecompositionSolver}.
+ * </ul>
+ *
+ * @see <a href="http://mathworld.wolfram.com/QRDecomposition.html">MathWorld</a>
+ * @see <a href="http://en.wikipedia.org/wiki/QR_decomposition">Wikipedia</a>
+ * @since 3.2
+ */
+public class RRQRDecomposition extends QRDecomposition {
+
+    /** An array to record the column pivoting for later creation of P. */
+    private int[] p;
+
+    /** Cached value of P. */
+    private RealMatrix cachedP;
+
+    /**
+     * Calculates the QR-decomposition of the given matrix. The singularity threshold defaults to
+     * zero.
+     *
+     * @param matrix The matrix to decompose.
+     * @see #RRQRDecomposition(RealMatrix, double)
+     */
+    public RRQRDecomposition(RealMatrix matrix) {
+        this(matrix, 0d);
+    }
+
+    /**
+     * Calculates the QR-decomposition of the given matrix.
+     *
+     * @param matrix The matrix to decompose.
+     * @param threshold Singularity threshold.
+     * @see #RRQRDecomposition(RealMatrix)
+     */
+    public RRQRDecomposition(RealMatrix matrix, double threshold) {
+        super(matrix, threshold);
+    }
+
+    /**
+     * Decompose matrix.
+     *
+     * @param qrt transposed matrix
+     */
+    @Override
+    protected void decompose(double[][] qrt) {
+        p = new int[qrt.length];
+        for (int i = 0; i < p.length; i++) {
+            p[i] = i;
+        }
+        super.decompose(qrt);
+    }
+
+    /**
+     * Perform Householder reflection for a minor A(minor, minor) of A.
+     *
+     * @param minor minor index
+     * @param qrt transposed matrix
+     */
+    @Override
+    protected void performHouseholderReflection(int minor, double[][] qrt) {
+
+        double l2NormSquaredMax = 0;
+        // Find the unreduced column with the greatest L2-Norm
+        int l2NormSquaredMaxIndex = minor;
+        for (int i = minor; i < qrt.length; i++) {
+            double l2NormSquared = 0;
+            for (int j = 0; j < qrt[i].length; j++) {
+                l2NormSquared += qrt[i][j] * qrt[i][j];
+            }
+            if (l2NormSquared > l2NormSquaredMax) {
+                l2NormSquaredMax = l2NormSquared;
+                l2NormSquaredMaxIndex = i;
+            }
+        }
+        // swap the current column with that with the greated L2-Norm and record in p
+        if (l2NormSquaredMaxIndex != minor) {
+            double[] tmp1 = qrt[minor];
+            qrt[minor] = qrt[l2NormSquaredMaxIndex];
+            qrt[l2NormSquaredMaxIndex] = tmp1;
+            int tmp2 = p[minor];
+            p[minor] = p[l2NormSquaredMaxIndex];
+            p[l2NormSquaredMaxIndex] = tmp2;
+        }
+
+        super.performHouseholderReflection(minor, qrt);
+    }
+
+    /**
+     * Returns the pivot matrix, P, used in the QR Decomposition of matrix A such that AP = QR.
+     *
+     * <p>If no pivoting is used in this decomposition then P is equal to the identity matrix.
+     *
+     * @return a permutation matrix.
+     */
+    public RealMatrix getP() {
+        if (cachedP == null) {
+            int n = p.length;
+            cachedP = MatrixUtils.createRealMatrix(n, n);
+            for (int i = 0; i < n; i++) {
+                cachedP.setEntry(p[i], i, 1);
+            }
+        }
+        return cachedP;
+    }
+
+    /**
+     * Return the effective numerical matrix rank.
+     *
+     * <p>The effective numerical rank is the number of non-negligible singular values.
+     *
+     * <p>This implementation looks at Frobenius norms of the sequence of bottom right submatrices.
+     * When a large fall in norm is seen, the rank is returned. The drop is computed as:
+     *
+     * <pre>
+     *   (thisNorm/lastNorm) * rNorm < dropThreshold
+     * </pre>
+     *
+     * <p>where thisNorm is the Frobenius norm of the current submatrix, lastNorm is the Frobenius
+     * norm of the previous submatrix, rNorm is is the Frobenius norm of the complete matrix
+     *
+     * @param dropThreshold threshold triggering rank computation
+     * @return effective numerical matrix rank
+     */
+    public int getRank(final double dropThreshold) {
+        RealMatrix r = getR();
+        int rows = r.getRowDimension();
+        int columns = r.getColumnDimension();
+        int rank = 1;
+        double lastNorm = r.getFrobeniusNorm();
+        double rNorm = lastNorm;
+        while (rank < FastMath.min(rows, columns)) {
+            double thisNorm = r.getSubMatrix(rank, rows - 1, rank, columns - 1).getFrobeniusNorm();
+            if (thisNorm == 0 || (thisNorm / lastNorm) * rNorm < dropThreshold) {
+                break;
+            }
+            lastNorm = thisNorm;
+            rank++;
+        }
+        return rank;
+    }
+
+    /**
+     * Get a solver for finding the A &times; X = B solution in least square sense.
+     *
+     * <p>Least Square sense means a solver can be computed for an overdetermined system, (i.e. a
+     * system with more equations than unknowns, which corresponds to a tall A matrix with more rows
+     * than columns). In any case, if the matrix is singular within the tolerance set at {@link
+     * RRQRDecomposition#RRQRDecomposition(RealMatrix, double) construction}, an error will be
+     * triggered when the {@link DecompositionSolver#solve(RealVector) solve} method will be called.
+     *
+     * @return a solver
+     */
+    @Override
+    public DecompositionSolver getSolver() {
+        return new Solver(super.getSolver(), this.getP());
+    }
+
+    /** Specialized solver. */
+    private static class Solver implements DecompositionSolver {
+
+        /** Upper level solver. */
+        private final DecompositionSolver upper;
+
+        /** A permutation matrix for the pivots used in the QR decomposition */
+        private RealMatrix p;
+
+        /**
+         * Build a solver from decomposed matrix.
+         *
+         * @param upper upper level solver.
+         * @param p permutation matrix
+         */
+        private Solver(final DecompositionSolver upper, final RealMatrix p) {
+            this.upper = upper;
+            this.p = p;
+        }
+
+        /** {@inheritDoc} */
+        public boolean isNonSingular() {
+            return upper.isNonSingular();
+        }
+
+        /** {@inheritDoc} */
+        public RealVector solve(RealVector b) {
+            return p.operate(upper.solve(b));
+        }
+
+        /** {@inheritDoc} */
+        public RealMatrix solve(RealMatrix b) {
+            return p.multiply(upper.solve(b));
+        }
+
+        /**
+         * {@inheritDoc}
+         *
+         * @throws SingularMatrixException if the decomposed matrix is singular.
+         */
+        public RealMatrix getInverse() {
+            return solve(MatrixUtils.createRealIdentityMatrix(p.getRowDimension()));
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/RealLinearOperator.java b/src/main/java/org/apache/commons/math3/linear/RealLinearOperator.java
new file mode 100644
index 0000000..0a86568
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/RealLinearOperator.java
@@ -0,0 +1,103 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+
+/**
+ * This class defines a linear operator operating on real ({@code double}) vector spaces. No direct
+ * access to the coefficients of the underlying matrix is provided.
+ *
+ * <p>The motivation for such an interface is well stated by <a href="#BARR1994">Barrett et al.
+ * (1994)</a>:
+ *
+ * <blockquote>
+ *
+ * We restrict ourselves to iterative methods, which work by repeatedly improving an approximate
+ * solution until it is accurate enough. These methods access the coefficient matrix A of the linear
+ * system only via the matrix-vector product y = A &middot; x (and perhaps z = A<sup>T</sup>
+ * &middot; x). Thus the user need only supply a subroutine for computing y (and perhaps z) given x,
+ * which permits full exploitation of the sparsity or other special structure of A.
+ *
+ * </blockquote>
+ *
+ * <br>
+ *
+ * <dl>
+ *   <dt><a name="BARR1994">Barret et al. (1994)</a>
+ *   <dd>R. Barrett, M. Berry, T. F. Chan, J. Demmel, J. M. Donato, J. Dongarra, V. Eijkhout, R.
+ *       Pozo, C. Romine and H. Van der Vorst, <em>Templates for the Solution of Linear Systems:
+ *       Building Blocks for Iterative Methods</em>, SIAM
+ * </dl>
+ *
+ * @since 3.0
+ */
+public abstract class RealLinearOperator {
+    /**
+     * Returns the dimension of the codomain of this operator.
+     *
+     * @return the number of rows of the underlying matrix
+     */
+    public abstract int getRowDimension();
+
+    /**
+     * Returns the dimension of the domain of this operator.
+     *
+     * @return the number of columns of the underlying matrix
+     */
+    public abstract int getColumnDimension();
+
+    /**
+     * Returns the result of multiplying {@code this} by the vector {@code x}.
+     *
+     * @param x the vector to operate on
+     * @return the product of {@code this} instance with {@code x}
+     * @throws DimensionMismatchException if the column dimension does not match the size of {@code
+     *     x}
+     */
+    public abstract RealVector operate(final RealVector x) throws DimensionMismatchException;
+
+    /**
+     * Returns the result of multiplying the transpose of {@code this} operator by the vector {@code
+     * x} (optional operation). The default implementation throws an {@link
+     * UnsupportedOperationException}. Users overriding this method must also override {@link
+     * #isTransposable()}.
+     *
+     * @param x the vector to operate on
+     * @return the product of the transpose of {@code this} instance with {@code x}
+     * @throws org.apache.commons.math3.exception.DimensionMismatchException if the row dimension
+     *     does not match the size of {@code x}
+     * @throws UnsupportedOperationException if this operation is not supported by {@code this}
+     *     operator
+     */
+    public RealVector operateTranspose(final RealVector x)
+            throws DimensionMismatchException, UnsupportedOperationException {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Returns {@code true} if this operator supports {@link #operateTranspose(RealVector)}. If
+     * {@code true} is returned, {@link #operateTranspose(RealVector)} should not throw {@code
+     * UnsupportedOperationException}. The default implementation returns {@code false}.
+     *
+     * @return {@code false}
+     */
+    public boolean isTransposable() {
+        return false;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/RealMatrix.java b/src/main/java/org/apache/commons/math3/linear/RealMatrix.java
new file mode 100644
index 0000000..183a883
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/RealMatrix.java
@@ -0,0 +1,827 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+
+/**
+ * Interface defining a real-valued matrix with basic algebraic operations.
+ *
+ * <p>Matrix element indexing is 0-based -- e.g., <code>getEntry(0, 0)</code> returns the element in
+ * the first row, first column of the matrix.
+ */
+public interface RealMatrix extends AnyMatrix {
+
+    /**
+     * Create a new RealMatrix of the same type as the instance with the supplied row and column
+     * dimensions.
+     *
+     * @param rowDimension the number of rows in the new matrix
+     * @param columnDimension the number of columns in the new matrix
+     * @return a new matrix of the same type as the instance
+     * @throws NotStrictlyPositiveException if row or column dimension is not positive.
+     * @since 2.0
+     */
+    RealMatrix createMatrix(int rowDimension, int columnDimension)
+            throws NotStrictlyPositiveException;
+
+    /**
+     * Returns a (deep) copy of this.
+     *
+     * @return matrix copy
+     */
+    RealMatrix copy();
+
+    /**
+     * Returns the sum of {@code this} and {@code m}.
+     *
+     * @param m matrix to be added
+     * @return {@code this + m}
+     * @throws MatrixDimensionMismatchException if {@code m} is not the same size as {@code this}.
+     */
+    RealMatrix add(RealMatrix m) throws MatrixDimensionMismatchException;
+
+    /**
+     * Returns {@code this} minus {@code m}.
+     *
+     * @param m matrix to be subtracted
+     * @return {@code this - m}
+     * @throws MatrixDimensionMismatchException if {@code m} is not the same size as {@code this}.
+     */
+    RealMatrix subtract(RealMatrix m) throws MatrixDimensionMismatchException;
+
+    /**
+     * Returns the result of adding {@code d} to each entry of {@code this}.
+     *
+     * @param d value to be added to each entry
+     * @return {@code d + this}
+     */
+    RealMatrix scalarAdd(double d);
+
+    /**
+     * Returns the result of multiplying each entry of {@code this} by {@code d}.
+     *
+     * @param d value to multiply all entries by
+     * @return {@code d * this}
+     */
+    RealMatrix scalarMultiply(double d);
+
+    /**
+     * Returns the result of postmultiplying {@code this} by {@code m}.
+     *
+     * @param m matrix to postmultiply by
+     * @return {@code this * m}
+     * @throws DimensionMismatchException if {@code columnDimension(this) != rowDimension(m)}
+     */
+    RealMatrix multiply(RealMatrix m) throws DimensionMismatchException;
+
+    /**
+     * Returns the result of premultiplying {@code this} by {@code m}.
+     *
+     * @param m matrix to premultiply by
+     * @return {@code m * this}
+     * @throws DimensionMismatchException if {@code rowDimension(this) != columnDimension(m)}
+     */
+    RealMatrix preMultiply(RealMatrix m) throws DimensionMismatchException;
+
+    /**
+     * Returns the result of multiplying {@code this} with itself {@code p} times. Depending on the
+     * underlying storage, instability for high powers might occur.
+     *
+     * @param p raise {@code this} to power {@code p}
+     * @return {@code this^p}
+     * @throws NotPositiveException if {@code p < 0}
+     * @throws NonSquareMatrixException if the matrix is not square
+     */
+    RealMatrix power(final int p) throws NotPositiveException, NonSquareMatrixException;
+
+    /**
+     * Returns matrix entries as a two-dimensional array.
+     *
+     * @return 2-dimensional array of entries
+     */
+    double[][] getData();
+
+    /**
+     * Returns the <a href="http://mathworld.wolfram.com/MaximumAbsoluteRowSumNorm.html">maximum
+     * absolute row sum norm</a> of the matrix.
+     *
+     * @return norm
+     */
+    double getNorm();
+
+    /**
+     * Returns the <a href="http://mathworld.wolfram.com/FrobeniusNorm.html">Frobenius norm</a> of
+     * the matrix.
+     *
+     * @return norm
+     */
+    double getFrobeniusNorm();
+
+    /**
+     * Gets a submatrix. Rows and columns are indicated counting from 0 to n-1.
+     *
+     * @param startRow Initial row index
+     * @param endRow Final row index (inclusive)
+     * @param startColumn Initial column index
+     * @param endColumn Final column index (inclusive)
+     * @return The subMatrix containing the data of the specified rows and columns.
+     * @throws OutOfRangeException if the indices are not valid.
+     * @throws NumberIsTooSmallException if {@code endRow < startRow} or {@code endColumn <
+     *     startColumn}.
+     */
+    RealMatrix getSubMatrix(int startRow, int endRow, int startColumn, int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException;
+
+    /**
+     * Gets a submatrix. Rows and columns are indicated counting from 0 to n-1.
+     *
+     * @param selectedRows Array of row indices.
+     * @param selectedColumns Array of column indices.
+     * @return The subMatrix containing the data in the specified rows and columns
+     * @throws NullArgumentException if the row or column selections are {@code null}
+     * @throws NoDataException if the row or column selections are empty (zero length).
+     * @throws OutOfRangeException if the indices are not valid.
+     */
+    RealMatrix getSubMatrix(int[] selectedRows, int[] selectedColumns)
+            throws NullArgumentException, NoDataException, OutOfRangeException;
+
+    /**
+     * Copy a submatrix. Rows and columns are indicated counting from 0 to n-1.
+     *
+     * @param startRow Initial row index
+     * @param endRow Final row index (inclusive)
+     * @param startColumn Initial column index
+     * @param endColumn Final column index (inclusive)
+     * @param destination The arrays where the submatrix data should be copied (if larger than
+     *     rows/columns counts, only the upper-left part will be used)
+     * @throws OutOfRangeException if the indices are not valid.
+     * @throws NumberIsTooSmallException if {@code endRow < startRow} or {@code endColumn <
+     *     startColumn}.
+     * @throws MatrixDimensionMismatchException if the destination array is too small.
+     */
+    void copySubMatrix(
+            int startRow, int endRow, int startColumn, int endColumn, double[][] destination)
+            throws OutOfRangeException, NumberIsTooSmallException, MatrixDimensionMismatchException;
+
+    /**
+     * Copy a submatrix. Rows and columns are indicated counting from 0 to n-1.
+     *
+     * @param selectedRows Array of row indices.
+     * @param selectedColumns Array of column indices.
+     * @param destination The arrays where the submatrix data should be copied (if larger than
+     *     rows/columns counts, only the upper-left part will be used)
+     * @throws NullArgumentException if the row or column selections are {@code null}
+     * @throws NoDataException if the row or column selections are empty (zero length).
+     * @throws OutOfRangeException if the indices are not valid.
+     * @throws MatrixDimensionMismatchException if the destination array is too small.
+     */
+    void copySubMatrix(int[] selectedRows, int[] selectedColumns, double[][] destination)
+            throws OutOfRangeException,
+                    NullArgumentException,
+                    NoDataException,
+                    MatrixDimensionMismatchException;
+
+    /**
+     * Replace the submatrix starting at {@code row, column} using data in the input {@code
+     * subMatrix} array. Indexes are 0-based.
+     *
+     * <p>Example:<br>
+     * Starting with
+     *
+     * <pre>
+     * 1  2  3  4
+     * 5  6  7  8
+     * 9  0  1  2
+     * </pre>
+     *
+     * and <code>subMatrix = {{3, 4} {5,6}}</code>, invoking {@code setSubMatrix(subMatrix,1,1))}
+     * will result in
+     *
+     * <pre>
+     * 1  2  3  4
+     * 5  3  4  8
+     * 9  5  6  2
+     * </pre>
+     *
+     * @param subMatrix array containing the submatrix replacement data
+     * @param row row coordinate of the top, left element to be replaced
+     * @param column column coordinate of the top, left element to be replaced
+     * @throws NoDataException if {@code subMatrix} is empty.
+     * @throws OutOfRangeException if {@code subMatrix} does not fit into this matrix from element
+     *     in {@code (row, column)}.
+     * @throws DimensionMismatchException if {@code subMatrix} is not rectangular (not all rows have
+     *     the same length) or empty.
+     * @throws NullArgumentException if {@code subMatrix} is {@code null}.
+     * @since 2.0
+     */
+    void setSubMatrix(double[][] subMatrix, int row, int column)
+            throws NoDataException,
+                    OutOfRangeException,
+                    DimensionMismatchException,
+                    NullArgumentException;
+
+    /**
+     * Get the entries at the given row index as a row matrix. Row indices start at 0.
+     *
+     * @param row Row to be fetched.
+     * @return row Matrix.
+     * @throws OutOfRangeException if the specified row index is invalid.
+     */
+    RealMatrix getRowMatrix(int row) throws OutOfRangeException;
+
+    /**
+     * Sets the specified {@code row} of {@code this} matrix to the entries of the specified row
+     * {@code matrix}. Row indices start at 0.
+     *
+     * @param row Row to be set.
+     * @param matrix Row matrix to be copied (must have one row and the same number of columns as
+     *     the instance).
+     * @throws OutOfRangeException if the specified row index is invalid.
+     * @throws MatrixDimensionMismatchException if the row dimension of the {@code matrix} is not
+     *     {@code 1}, or the column dimensions of {@code this} and {@code matrix} do not match.
+     */
+    void setRowMatrix(int row, RealMatrix matrix)
+            throws OutOfRangeException, MatrixDimensionMismatchException;
+
+    /**
+     * Get the entries at the given column index as a column matrix. Column indices start at 0.
+     *
+     * @param column Column to be fetched.
+     * @return column Matrix.
+     * @throws OutOfRangeException if the specified column index is invalid.
+     */
+    RealMatrix getColumnMatrix(int column) throws OutOfRangeException;
+
+    /**
+     * Sets the specified {@code column} of {@code this} matrix to the entries of the specified
+     * column {@code matrix}. Column indices start at 0.
+     *
+     * @param column Column to be set.
+     * @param matrix Column matrix to be copied (must have one column and the same number of rows as
+     *     the instance).
+     * @throws OutOfRangeException if the specified column index is invalid.
+     * @throws MatrixDimensionMismatchException if the column dimension of the {@code matrix} is not
+     *     {@code 1}, or the row dimensions of {@code this} and {@code matrix} do not match.
+     */
+    void setColumnMatrix(int column, RealMatrix matrix)
+            throws OutOfRangeException, MatrixDimensionMismatchException;
+
+    /**
+     * Returns the entries in row number {@code row} as a vector. Row indices start at 0.
+     *
+     * @param row Row to be fetched.
+     * @return a row vector.
+     * @throws OutOfRangeException if the specified row index is invalid.
+     */
+    RealVector getRowVector(int row) throws OutOfRangeException;
+
+    /**
+     * Sets the specified {@code row} of {@code this} matrix to the entries of the specified {@code
+     * vector}. Row indices start at 0.
+     *
+     * @param row Row to be set.
+     * @param vector row vector to be copied (must have the same number of column as the instance).
+     * @throws OutOfRangeException if the specified row index is invalid.
+     * @throws MatrixDimensionMismatchException if the {@code vector} dimension does not match the
+     *     column dimension of {@code this} matrix.
+     */
+    void setRowVector(int row, RealVector vector)
+            throws OutOfRangeException, MatrixDimensionMismatchException;
+
+    /**
+     * Get the entries at the given column index as a vector. Column indices start at 0.
+     *
+     * @param column Column to be fetched.
+     * @return a column vector.
+     * @throws OutOfRangeException if the specified column index is invalid
+     */
+    RealVector getColumnVector(int column) throws OutOfRangeException;
+
+    /**
+     * Sets the specified {@code column} of {@code this} matrix to the entries of the specified
+     * {@code vector}. Column indices start at 0.
+     *
+     * @param column Column to be set.
+     * @param vector column vector to be copied (must have the same number of rows as the instance).
+     * @throws OutOfRangeException if the specified column index is invalid.
+     * @throws MatrixDimensionMismatchException if the {@code vector} dimension does not match the
+     *     row dimension of {@code this} matrix.
+     */
+    void setColumnVector(int column, RealVector vector)
+            throws OutOfRangeException, MatrixDimensionMismatchException;
+
+    /**
+     * Get the entries at the given row index. Row indices start at 0.
+     *
+     * @param row Row to be fetched.
+     * @return the array of entries in the row.
+     * @throws OutOfRangeException if the specified row index is not valid.
+     */
+    double[] getRow(int row) throws OutOfRangeException;
+
+    /**
+     * Sets the specified {@code row} of {@code this} matrix to the entries of the specified {@code
+     * array}. Row indices start at 0.
+     *
+     * @param row Row to be set.
+     * @param array Row matrix to be copied (must have the same number of columns as the instance)
+     * @throws OutOfRangeException if the specified row index is invalid.
+     * @throws MatrixDimensionMismatchException if the {@code array} length does not match the
+     *     column dimension of {@code this} matrix.
+     */
+    void setRow(int row, double[] array)
+            throws OutOfRangeException, MatrixDimensionMismatchException;
+
+    /**
+     * Get the entries at the given column index as an array. Column indices start at 0.
+     *
+     * @param column Column to be fetched.
+     * @return the array of entries in the column.
+     * @throws OutOfRangeException if the specified column index is not valid.
+     */
+    double[] getColumn(int column) throws OutOfRangeException;
+
+    /**
+     * Sets the specified {@code column} of {@code this} matrix to the entries of the specified
+     * {@code array}. Column indices start at 0.
+     *
+     * @param column Column to be set.
+     * @param array Column array to be copied (must have the same number of rows as the instance).
+     * @throws OutOfRangeException if the specified column index is invalid.
+     * @throws MatrixDimensionMismatchException if the {@code array} length does not match the row
+     *     dimension of {@code this} matrix.
+     */
+    void setColumn(int column, double[] array)
+            throws OutOfRangeException, MatrixDimensionMismatchException;
+
+    /**
+     * Get the entry in the specified row and column. Row and column indices start at 0.
+     *
+     * @param row Row index of entry to be fetched.
+     * @param column Column index of entry to be fetched.
+     * @return the matrix entry at {@code (row, column)}.
+     * @throws OutOfRangeException if the row or column index is not valid.
+     */
+    double getEntry(int row, int column) throws OutOfRangeException;
+
+    /**
+     * Set the entry in the specified row and column. Row and column indices start at 0.
+     *
+     * @param row Row index of entry to be set.
+     * @param column Column index of entry to be set.
+     * @param value the new value of the entry.
+     * @throws OutOfRangeException if the row or column index is not valid
+     * @since 2.0
+     */
+    void setEntry(int row, int column, double value) throws OutOfRangeException;
+
+    /**
+     * Adds (in place) the specified value to the specified entry of {@code this} matrix. Row and
+     * column indices start at 0.
+     *
+     * @param row Row index of the entry to be modified.
+     * @param column Column index of the entry to be modified.
+     * @param increment value to add to the matrix entry.
+     * @throws OutOfRangeException if the row or column index is not valid.
+     * @since 2.0
+     */
+    void addToEntry(int row, int column, double increment) throws OutOfRangeException;
+
+    /**
+     * Multiplies (in place) the specified entry of {@code this} matrix by the specified value. Row
+     * and column indices start at 0.
+     *
+     * @param row Row index of the entry to be modified.
+     * @param column Column index of the entry to be modified.
+     * @param factor Multiplication factor for the matrix entry.
+     * @throws OutOfRangeException if the row or column index is not valid.
+     * @since 2.0
+     */
+    void multiplyEntry(int row, int column, double factor) throws OutOfRangeException;
+
+    /**
+     * Returns the transpose of this matrix.
+     *
+     * @return transpose matrix
+     */
+    RealMatrix transpose();
+
+    /**
+     * Returns the <a href="http://mathworld.wolfram.com/MatrixTrace.html">trace</a> of the matrix
+     * (the sum of the elements on the main diagonal).
+     *
+     * @return the trace.
+     * @throws NonSquareMatrixException if the matrix is not square.
+     */
+    double getTrace() throws NonSquareMatrixException;
+
+    /**
+     * Returns the result of multiplying this by the vector {@code v}.
+     *
+     * @param v the vector to operate on
+     * @return {@code this * v}
+     * @throws DimensionMismatchException if the length of {@code v} does not match the column
+     *     dimension of {@code this}.
+     */
+    double[] operate(double[] v) throws DimensionMismatchException;
+
+    /**
+     * Returns the result of multiplying this by the vector {@code v}.
+     *
+     * @param v the vector to operate on
+     * @return {@code this * v}
+     * @throws DimensionMismatchException if the dimension of {@code v} does not match the column
+     *     dimension of {@code this}.
+     */
+    RealVector operate(RealVector v) throws DimensionMismatchException;
+
+    /**
+     * Returns the (row) vector result of premultiplying this by the vector {@code v}.
+     *
+     * @param v the row vector to premultiply by
+     * @return {@code v * this}
+     * @throws DimensionMismatchException if the length of {@code v} does not match the row
+     *     dimension of {@code this}.
+     */
+    double[] preMultiply(double[] v) throws DimensionMismatchException;
+
+    /**
+     * Returns the (row) vector result of premultiplying this by the vector {@code v}.
+     *
+     * @param v the row vector to premultiply by
+     * @return {@code v * this}
+     * @throws DimensionMismatchException if the dimension of {@code v} does not match the row
+     *     dimension of {@code this}.
+     */
+    RealVector preMultiply(RealVector v) throws DimensionMismatchException;
+
+    /**
+     * Visit (and possibly change) all matrix entries in row order.
+     *
+     * <p>Row order starts at upper left and iterating through all elements of a row from left to
+     * right before going to the leftmost element of the next row.
+     *
+     * @param visitor visitor used to process all matrix entries
+     * @see #walkInRowOrder(RealMatrixPreservingVisitor)
+     * @see #walkInRowOrder(RealMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInRowOrder(RealMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(RealMatrixChangingVisitor)
+     * @see #walkInColumnOrder(RealMatrixPreservingVisitor)
+     * @see #walkInColumnOrder(RealMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(RealMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(RealMatrixChangingVisitor)
+     * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor)
+     * @see #walkInOptimizedOrder(RealMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor, int, int, int, int)
+     * @return the value returned by {@link RealMatrixChangingVisitor#end()} at the end of the walk
+     */
+    double walkInRowOrder(RealMatrixChangingVisitor visitor);
+
+    /**
+     * Visit (but don't change) all matrix entries in row order.
+     *
+     * <p>Row order starts at upper left and iterating through all elements of a row from left to
+     * right before going to the leftmost element of the next row.
+     *
+     * @param visitor visitor used to process all matrix entries
+     * @see #walkInRowOrder(RealMatrixChangingVisitor)
+     * @see #walkInRowOrder(RealMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInRowOrder(RealMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(RealMatrixChangingVisitor)
+     * @see #walkInColumnOrder(RealMatrixPreservingVisitor)
+     * @see #walkInColumnOrder(RealMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(RealMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(RealMatrixChangingVisitor)
+     * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor)
+     * @see #walkInOptimizedOrder(RealMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor, int, int, int, int)
+     * @return the value returned by {@link RealMatrixPreservingVisitor#end()} at the end of the
+     *     walk
+     */
+    double walkInRowOrder(RealMatrixPreservingVisitor visitor);
+
+    /**
+     * Visit (and possibly change) some matrix entries in row order.
+     *
+     * <p>Row order starts at upper left and iterating through all elements of a row from left to
+     * right before going to the leftmost element of the next row.
+     *
+     * @param visitor visitor used to process all matrix entries
+     * @param startRow Initial row index
+     * @param endRow Final row index (inclusive)
+     * @param startColumn Initial column index
+     * @param endColumn Final column index
+     * @throws OutOfRangeException if the indices are not valid.
+     * @throws NumberIsTooSmallException if {@code endRow < startRow} or {@code endColumn <
+     *     startColumn}.
+     * @see #walkInRowOrder(RealMatrixChangingVisitor)
+     * @see #walkInRowOrder(RealMatrixPreservingVisitor)
+     * @see #walkInRowOrder(RealMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(RealMatrixChangingVisitor)
+     * @see #walkInColumnOrder(RealMatrixPreservingVisitor)
+     * @see #walkInColumnOrder(RealMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(RealMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(RealMatrixChangingVisitor)
+     * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor)
+     * @see #walkInOptimizedOrder(RealMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor, int, int, int, int)
+     * @return the value returned by {@link RealMatrixChangingVisitor#end()} at the end of the walk
+     */
+    double walkInRowOrder(
+            RealMatrixChangingVisitor visitor,
+            int startRow,
+            int endRow,
+            int startColumn,
+            int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException;
+
+    /**
+     * Visit (but don't change) some matrix entries in row order.
+     *
+     * <p>Row order starts at upper left and iterating through all elements of a row from left to
+     * right before going to the leftmost element of the next row.
+     *
+     * @param visitor visitor used to process all matrix entries
+     * @param startRow Initial row index
+     * @param endRow Final row index (inclusive)
+     * @param startColumn Initial column index
+     * @param endColumn Final column index
+     * @throws OutOfRangeException if the indices are not valid.
+     * @throws NumberIsTooSmallException if {@code endRow < startRow} or {@code endColumn <
+     *     startColumn}.
+     * @see #walkInRowOrder(RealMatrixChangingVisitor)
+     * @see #walkInRowOrder(RealMatrixPreservingVisitor)
+     * @see #walkInRowOrder(RealMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(RealMatrixChangingVisitor)
+     * @see #walkInColumnOrder(RealMatrixPreservingVisitor)
+     * @see #walkInColumnOrder(RealMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(RealMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(RealMatrixChangingVisitor)
+     * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor)
+     * @see #walkInOptimizedOrder(RealMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor, int, int, int, int)
+     * @return the value returned by {@link RealMatrixPreservingVisitor#end()} at the end of the
+     *     walk
+     */
+    double walkInRowOrder(
+            RealMatrixPreservingVisitor visitor,
+            int startRow,
+            int endRow,
+            int startColumn,
+            int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException;
+
+    /**
+     * Visit (and possibly change) all matrix entries in column order.
+     *
+     * <p>Column order starts at upper left and iterating through all elements of a column from top
+     * to bottom before going to the topmost element of the next column.
+     *
+     * @param visitor visitor used to process all matrix entries
+     * @see #walkInRowOrder(RealMatrixChangingVisitor)
+     * @see #walkInRowOrder(RealMatrixPreservingVisitor)
+     * @see #walkInRowOrder(RealMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInRowOrder(RealMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(RealMatrixPreservingVisitor)
+     * @see #walkInColumnOrder(RealMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(RealMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(RealMatrixChangingVisitor)
+     * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor)
+     * @see #walkInOptimizedOrder(RealMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor, int, int, int, int)
+     * @return the value returned by {@link RealMatrixChangingVisitor#end()} at the end of the walk
+     */
+    double walkInColumnOrder(RealMatrixChangingVisitor visitor);
+
+    /**
+     * Visit (but don't change) all matrix entries in column order.
+     *
+     * <p>Column order starts at upper left and iterating through all elements of a column from top
+     * to bottom before going to the topmost element of the next column.
+     *
+     * @param visitor visitor used to process all matrix entries
+     * @see #walkInRowOrder(RealMatrixChangingVisitor)
+     * @see #walkInRowOrder(RealMatrixPreservingVisitor)
+     * @see #walkInRowOrder(RealMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInRowOrder(RealMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(RealMatrixChangingVisitor)
+     * @see #walkInColumnOrder(RealMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(RealMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(RealMatrixChangingVisitor)
+     * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor)
+     * @see #walkInOptimizedOrder(RealMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor, int, int, int, int)
+     * @return the value returned by {@link RealMatrixPreservingVisitor#end()} at the end of the
+     *     walk
+     */
+    double walkInColumnOrder(RealMatrixPreservingVisitor visitor);
+
+    /**
+     * Visit (and possibly change) some matrix entries in column order.
+     *
+     * <p>Column order starts at upper left and iterating through all elements of a column from top
+     * to bottom before going to the topmost element of the next column.
+     *
+     * @param visitor visitor used to process all matrix entries
+     * @param startRow Initial row index
+     * @param endRow Final row index (inclusive)
+     * @param startColumn Initial column index
+     * @param endColumn Final column index
+     * @throws OutOfRangeException if the indices are not valid.
+     * @throws NumberIsTooSmallException if {@code endRow < startRow} or {@code endColumn <
+     *     startColumn}.
+     * @see #walkInRowOrder(RealMatrixChangingVisitor)
+     * @see #walkInRowOrder(RealMatrixPreservingVisitor)
+     * @see #walkInRowOrder(RealMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInRowOrder(RealMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(RealMatrixChangingVisitor)
+     * @see #walkInColumnOrder(RealMatrixPreservingVisitor)
+     * @see #walkInColumnOrder(RealMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(RealMatrixChangingVisitor)
+     * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor)
+     * @see #walkInOptimizedOrder(RealMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor, int, int, int, int)
+     * @return the value returned by {@link RealMatrixChangingVisitor#end()} at the end of the walk
+     */
+    double walkInColumnOrder(
+            RealMatrixChangingVisitor visitor,
+            int startRow,
+            int endRow,
+            int startColumn,
+            int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException;
+
+    /**
+     * Visit (but don't change) some matrix entries in column order.
+     *
+     * <p>Column order starts at upper left and iterating through all elements of a column from top
+     * to bottom before going to the topmost element of the next column.
+     *
+     * @param visitor visitor used to process all matrix entries
+     * @param startRow Initial row index
+     * @param endRow Final row index (inclusive)
+     * @param startColumn Initial column index
+     * @param endColumn Final column index
+     * @throws OutOfRangeException if the indices are not valid.
+     * @throws NumberIsTooSmallException if {@code endRow < startRow} or {@code endColumn <
+     *     startColumn}.
+     * @see #walkInRowOrder(RealMatrixChangingVisitor)
+     * @see #walkInRowOrder(RealMatrixPreservingVisitor)
+     * @see #walkInRowOrder(RealMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInRowOrder(RealMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(RealMatrixChangingVisitor)
+     * @see #walkInColumnOrder(RealMatrixPreservingVisitor)
+     * @see #walkInColumnOrder(RealMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(RealMatrixChangingVisitor)
+     * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor)
+     * @see #walkInOptimizedOrder(RealMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor, int, int, int, int)
+     * @return the value returned by {@link RealMatrixPreservingVisitor#end()} at the end of the
+     *     walk
+     */
+    double walkInColumnOrder(
+            RealMatrixPreservingVisitor visitor,
+            int startRow,
+            int endRow,
+            int startColumn,
+            int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException;
+
+    /**
+     * Visit (and possibly change) all matrix entries using the fastest possible order.
+     *
+     * <p>The fastest walking order depends on the exact matrix class. It may be different from
+     * traditional row or column orders.
+     *
+     * @param visitor visitor used to process all matrix entries
+     * @see #walkInRowOrder(RealMatrixChangingVisitor)
+     * @see #walkInRowOrder(RealMatrixPreservingVisitor)
+     * @see #walkInRowOrder(RealMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInRowOrder(RealMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(RealMatrixChangingVisitor)
+     * @see #walkInColumnOrder(RealMatrixPreservingVisitor)
+     * @see #walkInColumnOrder(RealMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(RealMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor)
+     * @see #walkInOptimizedOrder(RealMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor, int, int, int, int)
+     * @return the value returned by {@link RealMatrixChangingVisitor#end()} at the end of the walk
+     */
+    double walkInOptimizedOrder(RealMatrixChangingVisitor visitor);
+
+    /**
+     * Visit (but don't change) all matrix entries using the fastest possible order.
+     *
+     * <p>The fastest walking order depends on the exact matrix class. It may be different from
+     * traditional row or column orders.
+     *
+     * @param visitor visitor used to process all matrix entries
+     * @see #walkInRowOrder(RealMatrixChangingVisitor)
+     * @see #walkInRowOrder(RealMatrixPreservingVisitor)
+     * @see #walkInRowOrder(RealMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInRowOrder(RealMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(RealMatrixChangingVisitor)
+     * @see #walkInColumnOrder(RealMatrixPreservingVisitor)
+     * @see #walkInColumnOrder(RealMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(RealMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(RealMatrixChangingVisitor)
+     * @see #walkInOptimizedOrder(RealMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor, int, int, int, int)
+     * @return the value returned by {@link RealMatrixPreservingVisitor#end()} at the end of the
+     *     walk
+     */
+    double walkInOptimizedOrder(RealMatrixPreservingVisitor visitor);
+
+    /**
+     * Visit (and possibly change) some matrix entries using the fastest possible order.
+     *
+     * <p>The fastest walking order depends on the exact matrix class. It may be different from
+     * traditional row or column orders.
+     *
+     * @param visitor visitor used to process all matrix entries
+     * @param startRow Initial row index
+     * @param endRow Final row index (inclusive)
+     * @param startColumn Initial column index
+     * @param endColumn Final column index (inclusive)
+     * @throws OutOfRangeException if the indices are not valid.
+     * @throws NumberIsTooSmallException if {@code endRow < startRow} or {@code endColumn <
+     *     startColumn}.
+     * @see #walkInRowOrder(RealMatrixChangingVisitor)
+     * @see #walkInRowOrder(RealMatrixPreservingVisitor)
+     * @see #walkInRowOrder(RealMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInRowOrder(RealMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(RealMatrixChangingVisitor)
+     * @see #walkInColumnOrder(RealMatrixPreservingVisitor)
+     * @see #walkInColumnOrder(RealMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(RealMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(RealMatrixChangingVisitor)
+     * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor)
+     * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor, int, int, int, int)
+     * @return the value returned by {@link RealMatrixChangingVisitor#end()} at the end of the walk
+     */
+    double walkInOptimizedOrder(
+            RealMatrixChangingVisitor visitor,
+            int startRow,
+            int endRow,
+            int startColumn,
+            int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException;
+
+    /**
+     * Visit (but don't change) some matrix entries using the fastest possible order.
+     *
+     * <p>The fastest walking order depends on the exact matrix class. It may be different from
+     * traditional row or column orders.
+     *
+     * @param visitor visitor used to process all matrix entries
+     * @param startRow Initial row index
+     * @param endRow Final row index (inclusive)
+     * @param startColumn Initial column index
+     * @param endColumn Final column index (inclusive)
+     * @throws OutOfRangeException if the indices are not valid.
+     * @throws NumberIsTooSmallException if {@code endRow < startRow} or {@code endColumn <
+     *     startColumn}.
+     * @see #walkInRowOrder(RealMatrixChangingVisitor)
+     * @see #walkInRowOrder(RealMatrixPreservingVisitor)
+     * @see #walkInRowOrder(RealMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInRowOrder(RealMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(RealMatrixChangingVisitor)
+     * @see #walkInColumnOrder(RealMatrixPreservingVisitor)
+     * @see #walkInColumnOrder(RealMatrixChangingVisitor, int, int, int, int)
+     * @see #walkInColumnOrder(RealMatrixPreservingVisitor, int, int, int, int)
+     * @see #walkInOptimizedOrder(RealMatrixChangingVisitor)
+     * @see #walkInOptimizedOrder(RealMatrixPreservingVisitor)
+     * @see #walkInOptimizedOrder(RealMatrixChangingVisitor, int, int, int, int)
+     * @return the value returned by {@link RealMatrixPreservingVisitor#end()} at the end of the
+     *     walk
+     */
+    double walkInOptimizedOrder(
+            RealMatrixPreservingVisitor visitor,
+            int startRow,
+            int endRow,
+            int startColumn,
+            int endColumn)
+            throws OutOfRangeException, NumberIsTooSmallException;
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/RealMatrixChangingVisitor.java b/src/main/java/org/apache/commons/math3/linear/RealMatrixChangingVisitor.java
new file mode 100644
index 0000000..6798655
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/RealMatrixChangingVisitor.java
@@ -0,0 +1,59 @@
+/*
+ * 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.math3.linear;
+
+/**
+ * Interface defining a visitor for matrix entries.
+ *
+ * @see DefaultRealMatrixChangingVisitor
+ * @since 2.0
+ */
+public interface RealMatrixChangingVisitor {
+    /**
+     * Start visiting a matrix.
+     *
+     * <p>This method is called once before any entry of the matrix is visited.
+     *
+     * @param rows number of rows of the matrix
+     * @param columns number of columns of the matrix
+     * @param startRow Initial row index
+     * @param endRow Final row index (inclusive)
+     * @param startColumn Initial column index
+     * @param endColumn Final column index (inclusive)
+     */
+    void start(int rows, int columns, int startRow, int endRow, int startColumn, int endColumn);
+
+    /**
+     * Visit one matrix entry.
+     *
+     * @param row row index of the entry
+     * @param column column index of the entry
+     * @param value current value of the entry
+     * @return the new value to be set for the entry
+     */
+    double visit(int row, int column, double value);
+
+    /**
+     * End visiting a matrix.
+     *
+     * <p>This method is called once after all entries of the matrix have been visited.
+     *
+     * @return the value that the <code>walkInXxxOrder</code> must return
+     */
+    double end();
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/RealMatrixFormat.java b/src/main/java/org/apache/commons/math3/linear/RealMatrixFormat.java
new file mode 100644
index 0000000..d4eee6b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/RealMatrixFormat.java
@@ -0,0 +1,442 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.exception.MathParseException;
+import org.apache.commons.math3.util.CompositeFormat;
+
+import java.text.FieldPosition;
+import java.text.NumberFormat;
+import java.text.ParsePosition;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Formats a {@code nxm} matrix in components list format
+ * "{{a<sub>0</sub><sub>0</sub>,a<sub>0</sub><sub>1</sub>, ...,
+ * a<sub>0</sub><sub>m-1</sub>},{a<sub>1</sub><sub>0</sub>, a<sub>1</sub><sub>1</sub>, ...,
+ * a<sub>1</sub><sub>m-1</sub>},{...},{ a<sub>n-1</sub><sub>0</sub>, a<sub>n-1</sub><sub>1</sub>,
+ * ..., a<sub>n-1</sub><sub>m-1</sub>}}".
+ *
+ * <p>The prefix and suffix "{" and "}", the row prefix and suffix "{" and "}", the row separator
+ * "," and the column separator "," can be replaced by any user-defined strings. The number format
+ * for components can be configured.
+ *
+ * <p>White space is ignored at parse time, even if it is in the prefix, suffix or separator
+ * specifications. So even if the default separator does include a space character that is used at
+ * format time, both input string "{{1,1,1}}" and " { { 1 , 1 , 1 } } " will be parsed without error
+ * and the same matrix will be returned. In the second case, however, the parse position after
+ * parsing will be just after the closing curly brace, i.e. just before the trailing space.
+ *
+ * <p><b>Note:</b> the grouping functionality of the used {@link NumberFormat} is disabled to
+ * prevent problems when parsing (e.g. 1,345.34 would be a valid number but conflicts with the
+ * default column separator).
+ *
+ * @since 3.1
+ */
+public class RealMatrixFormat {
+
+    /** The default prefix: "{". */
+    private static final String DEFAULT_PREFIX = "{";
+
+    /** The default suffix: "}". */
+    private static final String DEFAULT_SUFFIX = "}";
+
+    /** The default row prefix: "{". */
+    private static final String DEFAULT_ROW_PREFIX = "{";
+
+    /** The default row suffix: "}". */
+    private static final String DEFAULT_ROW_SUFFIX = "}";
+
+    /** The default row separator: ",". */
+    private static final String DEFAULT_ROW_SEPARATOR = ",";
+
+    /** The default column separator: ",". */
+    private static final String DEFAULT_COLUMN_SEPARATOR = ",";
+
+    /** Prefix. */
+    private final String prefix;
+
+    /** Suffix. */
+    private final String suffix;
+
+    /** Row prefix. */
+    private final String rowPrefix;
+
+    /** Row suffix. */
+    private final String rowSuffix;
+
+    /** Row separator. */
+    private final String rowSeparator;
+
+    /** Column separator. */
+    private final String columnSeparator;
+
+    /** The format used for components. */
+    private final NumberFormat format;
+
+    /**
+     * Create an instance with default settings.
+     *
+     * <p>The instance uses the default prefix, suffix and row/column separator: "[", "]", ";" and
+     * ", " and the default number format for components.
+     */
+    public RealMatrixFormat() {
+        this(
+                DEFAULT_PREFIX,
+                DEFAULT_SUFFIX,
+                DEFAULT_ROW_PREFIX,
+                DEFAULT_ROW_SUFFIX,
+                DEFAULT_ROW_SEPARATOR,
+                DEFAULT_COLUMN_SEPARATOR,
+                CompositeFormat.getDefaultNumberFormat());
+    }
+
+    /**
+     * Create an instance with a custom number format for components.
+     *
+     * @param format the custom format for components.
+     */
+    public RealMatrixFormat(final NumberFormat format) {
+        this(
+                DEFAULT_PREFIX,
+                DEFAULT_SUFFIX,
+                DEFAULT_ROW_PREFIX,
+                DEFAULT_ROW_SUFFIX,
+                DEFAULT_ROW_SEPARATOR,
+                DEFAULT_COLUMN_SEPARATOR,
+                format);
+    }
+
+    /**
+     * Create an instance with custom prefix, suffix and separator.
+     *
+     * @param prefix prefix to use instead of the default "{"
+     * @param suffix suffix to use instead of the default "}"
+     * @param rowPrefix row prefix to use instead of the default "{"
+     * @param rowSuffix row suffix to use instead of the default "}"
+     * @param rowSeparator tow separator to use instead of the default ";"
+     * @param columnSeparator column separator to use instead of the default ", "
+     */
+    public RealMatrixFormat(
+            final String prefix,
+            final String suffix,
+            final String rowPrefix,
+            final String rowSuffix,
+            final String rowSeparator,
+            final String columnSeparator) {
+        this(
+                prefix,
+                suffix,
+                rowPrefix,
+                rowSuffix,
+                rowSeparator,
+                columnSeparator,
+                CompositeFormat.getDefaultNumberFormat());
+    }
+
+    /**
+     * Create an instance with custom prefix, suffix, separator and format for components.
+     *
+     * @param prefix prefix to use instead of the default "{"
+     * @param suffix suffix to use instead of the default "}"
+     * @param rowPrefix row prefix to use instead of the default "{"
+     * @param rowSuffix row suffix to use instead of the default "}"
+     * @param rowSeparator tow separator to use instead of the default ";"
+     * @param columnSeparator column separator to use instead of the default ", "
+     * @param format the custom format for components.
+     */
+    public RealMatrixFormat(
+            final String prefix,
+            final String suffix,
+            final String rowPrefix,
+            final String rowSuffix,
+            final String rowSeparator,
+            final String columnSeparator,
+            final NumberFormat format) {
+        this.prefix = prefix;
+        this.suffix = suffix;
+        this.rowPrefix = rowPrefix;
+        this.rowSuffix = rowSuffix;
+        this.rowSeparator = rowSeparator;
+        this.columnSeparator = columnSeparator;
+        this.format = format;
+        // disable grouping to prevent parsing problems
+        this.format.setGroupingUsed(false);
+    }
+
+    /**
+     * Get the set of locales for which real vectors formats are available.
+     *
+     * <p>This is the same set as the {@link NumberFormat} set.
+     *
+     * @return available real vector format locales.
+     */
+    public static Locale[] getAvailableLocales() {
+        return NumberFormat.getAvailableLocales();
+    }
+
+    /**
+     * Get the format prefix.
+     *
+     * @return format prefix.
+     */
+    public String getPrefix() {
+        return prefix;
+    }
+
+    /**
+     * Get the format suffix.
+     *
+     * @return format suffix.
+     */
+    public String getSuffix() {
+        return suffix;
+    }
+
+    /**
+     * Get the format prefix.
+     *
+     * @return format prefix.
+     */
+    public String getRowPrefix() {
+        return rowPrefix;
+    }
+
+    /**
+     * Get the format suffix.
+     *
+     * @return format suffix.
+     */
+    public String getRowSuffix() {
+        return rowSuffix;
+    }
+
+    /**
+     * Get the format separator between rows of the matrix.
+     *
+     * @return format separator for rows.
+     */
+    public String getRowSeparator() {
+        return rowSeparator;
+    }
+
+    /**
+     * Get the format separator between components.
+     *
+     * @return format separator between components.
+     */
+    public String getColumnSeparator() {
+        return columnSeparator;
+    }
+
+    /**
+     * Get the components format.
+     *
+     * @return components format.
+     */
+    public NumberFormat getFormat() {
+        return format;
+    }
+
+    /**
+     * Returns the default real vector format for the current locale.
+     *
+     * @return the default real vector format.
+     */
+    public static RealMatrixFormat getInstance() {
+        return getInstance(Locale.getDefault());
+    }
+
+    /**
+     * Returns the default real vector format for the given locale.
+     *
+     * @param locale the specific locale used by the format.
+     * @return the real vector format specific to the given locale.
+     */
+    public static RealMatrixFormat getInstance(final Locale locale) {
+        return new RealMatrixFormat(CompositeFormat.getDefaultNumberFormat(locale));
+    }
+
+    /**
+     * This method calls {@link #format(RealMatrix,StringBuffer,FieldPosition)}.
+     *
+     * @param m RealMatrix object to format.
+     * @return a formatted matrix.
+     */
+    public String format(RealMatrix m) {
+        return format(m, new StringBuffer(), new FieldPosition(0)).toString();
+    }
+
+    /**
+     * Formats a {@link RealMatrix} object to produce a string.
+     *
+     * @param matrix the object to format.
+     * @param toAppendTo where the text is to be appended
+     * @param pos On input: an alignment field, if desired. On output: the offsets of the alignment
+     *     field
+     * @return the value passed in as toAppendTo.
+     */
+    public StringBuffer format(RealMatrix matrix, StringBuffer toAppendTo, FieldPosition pos) {
+
+        pos.setBeginIndex(0);
+        pos.setEndIndex(0);
+
+        // format prefix
+        toAppendTo.append(prefix);
+
+        // format rows
+        final int rows = matrix.getRowDimension();
+        for (int i = 0; i < rows; ++i) {
+            toAppendTo.append(rowPrefix);
+            for (int j = 0; j < matrix.getColumnDimension(); ++j) {
+                if (j > 0) {
+                    toAppendTo.append(columnSeparator);
+                }
+                CompositeFormat.formatDouble(matrix.getEntry(i, j), format, toAppendTo, pos);
+            }
+            toAppendTo.append(rowSuffix);
+            if (i < rows - 1) {
+                toAppendTo.append(rowSeparator);
+            }
+        }
+
+        // format suffix
+        toAppendTo.append(suffix);
+
+        return toAppendTo;
+    }
+
+    /**
+     * Parse a string to produce a {@link RealMatrix} object.
+     *
+     * @param source String to parse.
+     * @return the parsed {@link RealMatrix} object.
+     * @throws MathParseException if the beginning of the specified string cannot be parsed.
+     */
+    public RealMatrix parse(String source) {
+        final ParsePosition parsePosition = new ParsePosition(0);
+        final RealMatrix result = parse(source, parsePosition);
+        if (parsePosition.getIndex() == 0) {
+            throw new MathParseException(
+                    source, parsePosition.getErrorIndex(), Array2DRowRealMatrix.class);
+        }
+        return result;
+    }
+
+    /**
+     * Parse a string to produce a {@link RealMatrix} object.
+     *
+     * @param source String to parse.
+     * @param pos input/ouput parsing parameter.
+     * @return the parsed {@link RealMatrix} object.
+     */
+    public RealMatrix parse(String source, ParsePosition pos) {
+        int initialIndex = pos.getIndex();
+
+        final String trimmedPrefix = prefix.trim();
+        final String trimmedSuffix = suffix.trim();
+        final String trimmedRowPrefix = rowPrefix.trim();
+        final String trimmedRowSuffix = rowSuffix.trim();
+        final String trimmedColumnSeparator = columnSeparator.trim();
+        final String trimmedRowSeparator = rowSeparator.trim();
+
+        // parse prefix
+        CompositeFormat.parseAndIgnoreWhitespace(source, pos);
+        if (!CompositeFormat.parseFixedstring(source, trimmedPrefix, pos)) {
+            return null;
+        }
+
+        // parse components
+        List<List<Number>> matrix = new ArrayList<List<Number>>();
+        List<Number> rowComponents = new ArrayList<Number>();
+        for (boolean loop = true; loop; ) {
+
+            if (!rowComponents.isEmpty()) {
+                CompositeFormat.parseAndIgnoreWhitespace(source, pos);
+                if (!CompositeFormat.parseFixedstring(source, trimmedColumnSeparator, pos)) {
+                    if (trimmedRowSuffix.length() != 0
+                            && !CompositeFormat.parseFixedstring(source, trimmedRowSuffix, pos)) {
+                        return null;
+                    } else {
+                        CompositeFormat.parseAndIgnoreWhitespace(source, pos);
+                        if (CompositeFormat.parseFixedstring(source, trimmedRowSeparator, pos)) {
+                            matrix.add(rowComponents);
+                            rowComponents = new ArrayList<Number>();
+                            continue;
+                        } else {
+                            loop = false;
+                        }
+                    }
+                }
+            } else {
+                CompositeFormat.parseAndIgnoreWhitespace(source, pos);
+                if (trimmedRowPrefix.length() != 0
+                        && !CompositeFormat.parseFixedstring(source, trimmedRowPrefix, pos)) {
+                    return null;
+                }
+            }
+
+            if (loop) {
+                CompositeFormat.parseAndIgnoreWhitespace(source, pos);
+                Number component = CompositeFormat.parseNumber(source, format, pos);
+                if (component != null) {
+                    rowComponents.add(component);
+                } else {
+                    if (rowComponents.isEmpty()) {
+                        loop = false;
+                    } else {
+                        // invalid component
+                        // set index back to initial, error index should already be set
+                        pos.setIndex(initialIndex);
+                        return null;
+                    }
+                }
+            }
+        }
+
+        if (!rowComponents.isEmpty()) {
+            matrix.add(rowComponents);
+        }
+
+        // parse suffix
+        CompositeFormat.parseAndIgnoreWhitespace(source, pos);
+        if (!CompositeFormat.parseFixedstring(source, trimmedSuffix, pos)) {
+            return null;
+        }
+
+        // do not allow an empty matrix
+        if (matrix.isEmpty()) {
+            pos.setIndex(initialIndex);
+            return null;
+        }
+
+        // build vector
+        double[][] data = new double[matrix.size()][];
+        int row = 0;
+        for (List<Number> rowList : matrix) {
+            data[row] = new double[rowList.size()];
+            for (int i = 0; i < rowList.size(); i++) {
+                data[row][i] = rowList.get(i).doubleValue();
+            }
+            row++;
+        }
+        return MatrixUtils.createRealMatrix(data);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/RealMatrixPreservingVisitor.java b/src/main/java/org/apache/commons/math3/linear/RealMatrixPreservingVisitor.java
new file mode 100644
index 0000000..094ce07
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/RealMatrixPreservingVisitor.java
@@ -0,0 +1,58 @@
+/*
+ * 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.math3.linear;
+
+/**
+ * Interface defining a visitor for matrix entries.
+ *
+ * @see DefaultRealMatrixPreservingVisitor
+ * @since 2.0
+ */
+public interface RealMatrixPreservingVisitor {
+    /**
+     * Start visiting a matrix.
+     *
+     * <p>This method is called once before any entry of the matrix is visited.
+     *
+     * @param rows number of rows of the matrix
+     * @param columns number of columns of the matrix
+     * @param startRow Initial row index
+     * @param endRow Final row index (inclusive)
+     * @param startColumn Initial column index
+     * @param endColumn Final column index (inclusive)
+     */
+    void start(int rows, int columns, int startRow, int endRow, int startColumn, int endColumn);
+
+    /**
+     * Visit one matrix entry.
+     *
+     * @param row row index of the entry
+     * @param column column index of the entry
+     * @param value current value of the entry
+     */
+    void visit(int row, int column, double value);
+
+    /**
+     * End visiting a matrix.
+     *
+     * <p>This method is called once after all entries of the matrix have been visited.
+     *
+     * @return the value that the <code>walkInXxxOrder</code> must return
+     */
+    double end();
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/RealVector.java b/src/main/java/org/apache/commons/math3/linear/RealVector.java
new file mode 100644
index 0000000..ca25235
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/RealVector.java
@@ -0,0 +1,1509 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.analysis.FunctionUtils;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.function.Add;
+import org.apache.commons.math3.analysis.function.Divide;
+import org.apache.commons.math3.analysis.function.Multiply;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.MathUnsupportedOperationException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.FastMath;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Class defining a real-valued vector with basic algebraic operations.
+ *
+ * <p>vector element indexing is 0-based -- e.g., {@code getEntry(0)} returns the first element of
+ * the vector.
+ *
+ * <p>The {@code code map} and {@code mapToSelf} methods operate on vectors element-wise, i.e. they
+ * perform the same operation (adding a scalar, applying a function ...) on each element in turn.
+ * The {@code map} versions create a new vector to hold the result and do not change the instance.
+ * The {@code mapToSelf} version uses the instance itself to store the results, so the instance is
+ * changed by this method. In all cases, the result vector is returned by the methods, allowing the
+ * <i>fluent API</i> style, like this:
+ *
+ * <pre>
+ *   RealVector result = v.mapAddToSelf(3.4).mapToSelf(new Tan()).mapToSelf(new Power(2.3));
+ * </pre>
+ *
+ * @since 2.1
+ */
+public abstract class RealVector {
+    /**
+     * Returns the size of the vector.
+     *
+     * @return the size of this vector.
+     */
+    public abstract int getDimension();
+
+    /**
+     * Return the entry at the specified index.
+     *
+     * @param index Index location of entry to be fetched.
+     * @return the vector entry at {@code index}.
+     * @throws OutOfRangeException if the index is not valid.
+     * @see #setEntry(int, double)
+     */
+    public abstract double getEntry(int index) throws OutOfRangeException;
+
+    /**
+     * Set a single element.
+     *
+     * @param index element index.
+     * @param value new value for the element.
+     * @throws OutOfRangeException if the index is not valid.
+     * @see #getEntry(int)
+     */
+    public abstract void setEntry(int index, double value) throws OutOfRangeException;
+
+    /**
+     * Change an entry at the specified index.
+     *
+     * @param index Index location of entry to be set.
+     * @param increment Value to add to the vector entry.
+     * @throws OutOfRangeException if the index is not valid.
+     * @since 3.0
+     */
+    public void addToEntry(int index, double increment) throws OutOfRangeException {
+        setEntry(index, getEntry(index) + increment);
+    }
+
+    /**
+     * Construct a new vector by appending a vector to this vector.
+     *
+     * @param v vector to append to this one.
+     * @return a new vector.
+     */
+    public abstract RealVector append(RealVector v);
+
+    /**
+     * Construct a new vector by appending a double to this vector.
+     *
+     * @param d double to append.
+     * @return a new vector.
+     */
+    public abstract RealVector append(double d);
+
+    /**
+     * Get a subvector from consecutive elements.
+     *
+     * @param index index of first element.
+     * @param n number of elements to be retrieved.
+     * @return a vector containing n elements.
+     * @throws OutOfRangeException if the index is not valid.
+     * @throws NotPositiveException if the number of elements is not positive.
+     */
+    public abstract RealVector getSubVector(int index, int n)
+            throws NotPositiveException, OutOfRangeException;
+
+    /**
+     * Set a sequence of consecutive elements.
+     *
+     * @param index index of first element to be set.
+     * @param v vector containing the values to set.
+     * @throws OutOfRangeException if the index is not valid.
+     */
+    public abstract void setSubVector(int index, RealVector v) throws OutOfRangeException;
+
+    /**
+     * Check whether any coordinate of this vector is {@code NaN}.
+     *
+     * @return {@code true} if any coordinate of this vector is {@code NaN}, {@code false}
+     *     otherwise.
+     */
+    public abstract boolean isNaN();
+
+    /**
+     * Check whether any coordinate of this vector is infinite and none are {@code NaN}.
+     *
+     * @return {@code true} if any coordinate of this vector is infinite and none are {@code NaN},
+     *     {@code false} otherwise.
+     */
+    public abstract boolean isInfinite();
+
+    /**
+     * Check if instance and specified vectors have the same dimension.
+     *
+     * @param v Vector to compare instance with.
+     * @throws DimensionMismatchException if the vectors do not have the same dimension.
+     */
+    protected void checkVectorDimensions(RealVector v) throws DimensionMismatchException {
+        checkVectorDimensions(v.getDimension());
+    }
+
+    /**
+     * Check if instance dimension is equal to some expected value.
+     *
+     * @param n Expected dimension.
+     * @throws DimensionMismatchException if the dimension is inconsistent with the vector size.
+     */
+    protected void checkVectorDimensions(int n) throws DimensionMismatchException {
+        int d = getDimension();
+        if (d != n) {
+            throw new DimensionMismatchException(d, n);
+        }
+    }
+
+    /**
+     * Check if an index is valid.
+     *
+     * @param index Index to check.
+     * @exception OutOfRangeException if {@code index} is not valid.
+     */
+    protected void checkIndex(final int index) throws OutOfRangeException {
+        if (index < 0 || index >= getDimension()) {
+            throw new OutOfRangeException(LocalizedFormats.INDEX, index, 0, getDimension() - 1);
+        }
+    }
+
+    /**
+     * Checks that the indices of a subvector are valid.
+     *
+     * @param start the index of the first entry of the subvector
+     * @param end the index of the last entry of the subvector (inclusive)
+     * @throws OutOfRangeException if {@code start} of {@code end} are not valid
+     * @throws NumberIsTooSmallException if {@code end < start}
+     * @since 3.1
+     */
+    protected void checkIndices(final int start, final int end)
+            throws NumberIsTooSmallException, OutOfRangeException {
+        final int dim = getDimension();
+        if ((start < 0) || (start >= dim)) {
+            throw new OutOfRangeException(LocalizedFormats.INDEX, start, 0, dim - 1);
+        }
+        if ((end < 0) || (end >= dim)) {
+            throw new OutOfRangeException(LocalizedFormats.INDEX, end, 0, dim - 1);
+        }
+        if (end < start) {
+            // TODO Use more specific error message
+            throw new NumberIsTooSmallException(
+                    LocalizedFormats.INITIAL_ROW_AFTER_FINAL_ROW, end, start, false);
+        }
+    }
+
+    /**
+     * Compute the sum of this vector and {@code v}. Returns a new vector. Does not change instance
+     * data.
+     *
+     * @param v Vector to be added.
+     * @return {@code this} + {@code v}.
+     * @throws DimensionMismatchException if {@code v} is not the same size as {@code this} vector.
+     */
+    public RealVector add(RealVector v) throws DimensionMismatchException {
+        checkVectorDimensions(v);
+        RealVector result = v.copy();
+        Iterator<Entry> it = iterator();
+        while (it.hasNext()) {
+            final Entry e = it.next();
+            final int index = e.getIndex();
+            result.setEntry(index, e.getValue() + result.getEntry(index));
+        }
+        return result;
+    }
+
+    /**
+     * Subtract {@code v} from this vector. Returns a new vector. Does not change instance data.
+     *
+     * @param v Vector to be subtracted.
+     * @return {@code this} - {@code v}.
+     * @throws DimensionMismatchException if {@code v} is not the same size as {@code this} vector.
+     */
+    public RealVector subtract(RealVector v) throws DimensionMismatchException {
+        checkVectorDimensions(v);
+        RealVector result = v.mapMultiply(-1d);
+        Iterator<Entry> it = iterator();
+        while (it.hasNext()) {
+            final Entry e = it.next();
+            final int index = e.getIndex();
+            result.setEntry(index, e.getValue() + result.getEntry(index));
+        }
+        return result;
+    }
+
+    /**
+     * Add a value to each entry. Returns a new vector. Does not change instance data.
+     *
+     * @param d Value to be added to each entry.
+     * @return {@code this} + {@code d}.
+     */
+    public RealVector mapAdd(double d) {
+        return copy().mapAddToSelf(d);
+    }
+
+    /**
+     * Add a value to each entry. The instance is changed in-place.
+     *
+     * @param d Value to be added to each entry.
+     * @return {@code this}.
+     */
+    public RealVector mapAddToSelf(double d) {
+        if (d != 0) {
+            return mapToSelf(FunctionUtils.fix2ndArgument(new Add(), d));
+        }
+        return this;
+    }
+
+    /**
+     * Returns a (deep) copy of this vector.
+     *
+     * @return a vector copy.
+     */
+    public abstract RealVector copy();
+
+    /**
+     * Compute the dot product of this vector with {@code v}.
+     *
+     * @param v Vector with which dot product should be computed
+     * @return the scalar dot product between this instance and {@code v}.
+     * @throws DimensionMismatchException if {@code v} is not the same size as {@code this} vector.
+     */
+    public double dotProduct(RealVector v) throws DimensionMismatchException {
+        checkVectorDimensions(v);
+        double d = 0;
+        final int n = getDimension();
+        for (int i = 0; i < n; i++) {
+            d += getEntry(i) * v.getEntry(i);
+        }
+        return d;
+    }
+
+    /**
+     * Computes the cosine of the angle between this vector and the argument.
+     *
+     * @param v Vector.
+     * @return the cosine of the angle between this vector and {@code v}.
+     * @throws MathArithmeticException if {@code this} or {@code v} is the null vector
+     * @throws DimensionMismatchException if the dimensions of {@code this} and {@code v} do not
+     *     match
+     */
+    public double cosine(RealVector v) throws DimensionMismatchException, MathArithmeticException {
+        final double norm = getNorm();
+        final double vNorm = v.getNorm();
+
+        if (norm == 0 || vNorm == 0) {
+            throw new MathArithmeticException(LocalizedFormats.ZERO_NORM);
+        }
+        return dotProduct(v) / (norm * vNorm);
+    }
+
+    /**
+     * Element-by-element division.
+     *
+     * @param v Vector by which instance elements must be divided.
+     * @return a vector containing this[i] / v[i] for all i.
+     * @throws DimensionMismatchException if {@code v} is not the same size as {@code this} vector.
+     */
+    public abstract RealVector ebeDivide(RealVector v) throws DimensionMismatchException;
+
+    /**
+     * Element-by-element multiplication.
+     *
+     * @param v Vector by which instance elements must be multiplied
+     * @return a vector containing this[i] * v[i] for all i.
+     * @throws DimensionMismatchException if {@code v} is not the same size as {@code this} vector.
+     */
+    public abstract RealVector ebeMultiply(RealVector v) throws DimensionMismatchException;
+
+    /**
+     * Distance between two vectors.
+     *
+     * <p>This method computes the distance consistent with the L<sub>2</sub> norm, i.e. the square
+     * root of the sum of element differences, or Euclidean distance.
+     *
+     * @param v Vector to which distance is requested.
+     * @return the distance between two vectors.
+     * @throws DimensionMismatchException if {@code v} is not the same size as {@code this} vector.
+     * @see #getL1Distance(RealVector)
+     * @see #getLInfDistance(RealVector)
+     * @see #getNorm()
+     */
+    public double getDistance(RealVector v) throws DimensionMismatchException {
+        checkVectorDimensions(v);
+        double d = 0;
+        Iterator<Entry> it = iterator();
+        while (it.hasNext()) {
+            final Entry e = it.next();
+            final double diff = e.getValue() - v.getEntry(e.getIndex());
+            d += diff * diff;
+        }
+        return FastMath.sqrt(d);
+    }
+
+    /**
+     * Returns the L<sub>2</sub> norm of the vector.
+     *
+     * <p>The L<sub>2</sub> norm is the root of the sum of the squared elements.
+     *
+     * @return the norm.
+     * @see #getL1Norm()
+     * @see #getLInfNorm()
+     * @see #getDistance(RealVector)
+     */
+    public double getNorm() {
+        double sum = 0;
+        Iterator<Entry> it = iterator();
+        while (it.hasNext()) {
+            final Entry e = it.next();
+            final double value = e.getValue();
+            sum += value * value;
+        }
+        return FastMath.sqrt(sum);
+    }
+
+    /**
+     * Returns the L<sub>1</sub> norm of the vector.
+     *
+     * <p>The L<sub>1</sub> norm is the sum of the absolute values of the elements.
+     *
+     * @return the norm.
+     * @see #getNorm()
+     * @see #getLInfNorm()
+     * @see #getL1Distance(RealVector)
+     */
+    public double getL1Norm() {
+        double norm = 0;
+        Iterator<Entry> it = iterator();
+        while (it.hasNext()) {
+            final Entry e = it.next();
+            norm += FastMath.abs(e.getValue());
+        }
+        return norm;
+    }
+
+    /**
+     * Returns the L<sub>&infin;</sub> norm of the vector.
+     *
+     * <p>The L<sub>&infin;</sub> norm is the max of the absolute values of the elements.
+     *
+     * @return the norm.
+     * @see #getNorm()
+     * @see #getL1Norm()
+     * @see #getLInfDistance(RealVector)
+     */
+    public double getLInfNorm() {
+        double norm = 0;
+        Iterator<Entry> it = iterator();
+        while (it.hasNext()) {
+            final Entry e = it.next();
+            norm = FastMath.max(norm, FastMath.abs(e.getValue()));
+        }
+        return norm;
+    }
+
+    /**
+     * Distance between two vectors.
+     *
+     * <p>This method computes the distance consistent with L<sub>1</sub> norm, i.e. the sum of the
+     * absolute values of the elements differences.
+     *
+     * @param v Vector to which distance is requested.
+     * @return the distance between two vectors.
+     * @throws DimensionMismatchException if {@code v} is not the same size as {@code this} vector.
+     */
+    public double getL1Distance(RealVector v) throws DimensionMismatchException {
+        checkVectorDimensions(v);
+        double d = 0;
+        Iterator<Entry> it = iterator();
+        while (it.hasNext()) {
+            final Entry e = it.next();
+            d += FastMath.abs(e.getValue() - v.getEntry(e.getIndex()));
+        }
+        return d;
+    }
+
+    /**
+     * Distance between two vectors.
+     *
+     * <p>This method computes the distance consistent with L<sub>&infin;</sub> norm, i.e. the max
+     * of the absolute values of element differences.
+     *
+     * @param v Vector to which distance is requested.
+     * @return the distance between two vectors.
+     * @throws DimensionMismatchException if {@code v} is not the same size as {@code this} vector.
+     * @see #getDistance(RealVector)
+     * @see #getL1Distance(RealVector)
+     * @see #getLInfNorm()
+     */
+    public double getLInfDistance(RealVector v) throws DimensionMismatchException {
+        checkVectorDimensions(v);
+        double d = 0;
+        Iterator<Entry> it = iterator();
+        while (it.hasNext()) {
+            final Entry e = it.next();
+            d = FastMath.max(FastMath.abs(e.getValue() - v.getEntry(e.getIndex())), d);
+        }
+        return d;
+    }
+
+    /**
+     * Get the index of the minimum entry.
+     *
+     * @return the index of the minimum entry or -1 if vector length is 0 or all entries are {@code
+     *     NaN}.
+     */
+    public int getMinIndex() {
+        int minIndex = -1;
+        double minValue = Double.POSITIVE_INFINITY;
+        Iterator<Entry> iterator = iterator();
+        while (iterator.hasNext()) {
+            final Entry entry = iterator.next();
+            if (entry.getValue() <= minValue) {
+                minIndex = entry.getIndex();
+                minValue = entry.getValue();
+            }
+        }
+        return minIndex;
+    }
+
+    /**
+     * Get the value of the minimum entry.
+     *
+     * @return the value of the minimum entry or {@code NaN} if all entries are {@code NaN}.
+     */
+    public double getMinValue() {
+        final int minIndex = getMinIndex();
+        return minIndex < 0 ? Double.NaN : getEntry(minIndex);
+    }
+
+    /**
+     * Get the index of the maximum entry.
+     *
+     * @return the index of the maximum entry or -1 if vector length is 0 or all entries are {@code
+     *     NaN}
+     */
+    public int getMaxIndex() {
+        int maxIndex = -1;
+        double maxValue = Double.NEGATIVE_INFINITY;
+        Iterator<Entry> iterator = iterator();
+        while (iterator.hasNext()) {
+            final Entry entry = iterator.next();
+            if (entry.getValue() >= maxValue) {
+                maxIndex = entry.getIndex();
+                maxValue = entry.getValue();
+            }
+        }
+        return maxIndex;
+    }
+
+    /**
+     * Get the value of the maximum entry.
+     *
+     * @return the value of the maximum entry or {@code NaN} if all entries are {@code NaN}.
+     */
+    public double getMaxValue() {
+        final int maxIndex = getMaxIndex();
+        return maxIndex < 0 ? Double.NaN : getEntry(maxIndex);
+    }
+
+    /**
+     * Multiply each entry by the argument. Returns a new vector. Does not change instance data.
+     *
+     * @param d Multiplication factor.
+     * @return {@code this} * {@code d}.
+     */
+    public RealVector mapMultiply(double d) {
+        return copy().mapMultiplyToSelf(d);
+    }
+
+    /**
+     * Multiply each entry. The instance is changed in-place.
+     *
+     * @param d Multiplication factor.
+     * @return {@code this}.
+     */
+    public RealVector mapMultiplyToSelf(double d) {
+        return mapToSelf(FunctionUtils.fix2ndArgument(new Multiply(), d));
+    }
+
+    /**
+     * Subtract a value from each entry. Returns a new vector. Does not change instance data.
+     *
+     * @param d Value to be subtracted.
+     * @return {@code this} - {@code d}.
+     */
+    public RealVector mapSubtract(double d) {
+        return copy().mapSubtractToSelf(d);
+    }
+
+    /**
+     * Subtract a value from each entry. The instance is changed in-place.
+     *
+     * @param d Value to be subtracted.
+     * @return {@code this}.
+     */
+    public RealVector mapSubtractToSelf(double d) {
+        return mapAddToSelf(-d);
+    }
+
+    /**
+     * Divide each entry by the argument. Returns a new vector. Does not change instance data.
+     *
+     * @param d Value to divide by.
+     * @return {@code this} / {@code d}.
+     */
+    public RealVector mapDivide(double d) {
+        return copy().mapDivideToSelf(d);
+    }
+
+    /**
+     * Divide each entry by the argument. The instance is changed in-place.
+     *
+     * @param d Value to divide by.
+     * @return {@code this}.
+     */
+    public RealVector mapDivideToSelf(double d) {
+        return mapToSelf(FunctionUtils.fix2ndArgument(new Divide(), d));
+    }
+
+    /**
+     * Compute the outer product.
+     *
+     * @param v Vector with which outer product should be computed.
+     * @return the matrix outer product between this instance and {@code v}.
+     */
+    public RealMatrix outerProduct(RealVector v) {
+        final int m = this.getDimension();
+        final int n = v.getDimension();
+        final RealMatrix product;
+        if (v instanceof SparseRealVector || this instanceof SparseRealVector) {
+            product = new OpenMapRealMatrix(m, n);
+        } else {
+            product = new Array2DRowRealMatrix(m, n);
+        }
+        for (int i = 0; i < m; i++) {
+            for (int j = 0; j < n; j++) {
+                product.setEntry(i, j, this.getEntry(i) * v.getEntry(j));
+            }
+        }
+        return product;
+    }
+
+    /**
+     * Find the orthogonal projection of this vector onto another vector.
+     *
+     * @param v vector onto which instance must be projected.
+     * @return projection of the instance onto {@code v}.
+     * @throws DimensionMismatchException if {@code v} is not the same size as {@code this} vector.
+     * @throws MathArithmeticException if {@code this} or {@code v} is the null vector
+     */
+    public RealVector projection(final RealVector v)
+            throws DimensionMismatchException, MathArithmeticException {
+        final double norm2 = v.dotProduct(v);
+        if (norm2 == 0.0) {
+            throw new MathArithmeticException(LocalizedFormats.ZERO_NORM);
+        }
+        return v.mapMultiply(dotProduct(v) / v.dotProduct(v));
+    }
+
+    /**
+     * Set all elements to a single value.
+     *
+     * @param value Single value to set for all elements.
+     */
+    public void set(double value) {
+        Iterator<Entry> it = iterator();
+        while (it.hasNext()) {
+            final Entry e = it.next();
+            e.setValue(value);
+        }
+    }
+
+    /**
+     * Convert the vector to an array of {@code double}s. The array is independent from this vector
+     * data: the elements are copied.
+     *
+     * @return an array containing a copy of the vector elements.
+     */
+    public double[] toArray() {
+        int dim = getDimension();
+        double[] values = new double[dim];
+        for (int i = 0; i < dim; i++) {
+            values[i] = getEntry(i);
+        }
+        return values;
+    }
+
+    /**
+     * Creates a unit vector pointing in the direction of this vector. The instance is not changed
+     * by this method.
+     *
+     * @return a unit vector pointing in direction of this vector.
+     * @throws MathArithmeticException if the norm is zero.
+     */
+    public RealVector unitVector() throws MathArithmeticException {
+        final double norm = getNorm();
+        if (norm == 0) {
+            throw new MathArithmeticException(LocalizedFormats.ZERO_NORM);
+        }
+        return mapDivide(norm);
+    }
+
+    /**
+     * Converts this vector into a unit vector. The instance itself is changed by this method.
+     *
+     * @throws MathArithmeticException if the norm is zero.
+     */
+    public void unitize() throws MathArithmeticException {
+        final double norm = getNorm();
+        if (norm == 0) {
+            throw new MathArithmeticException(LocalizedFormats.ZERO_NORM);
+        }
+        mapDivideToSelf(getNorm());
+    }
+
+    /**
+     * Create a sparse iterator over the vector, which may omit some entries. The ommitted entries
+     * are either exact zeroes (for dense implementations) or are the entries which are not stored
+     * (for real sparse vectors). No guarantees are made about order of iteration.
+     *
+     * <p>Note: derived classes are required to return an {@link Iterator} that returns non-null
+     * {@link Entry} objects as long as {@link Iterator#hasNext()} returns {@code true}.
+     *
+     * @return a sparse iterator.
+     */
+    public Iterator<Entry> sparseIterator() {
+        return new SparseEntryIterator();
+    }
+
+    /**
+     * Generic dense iterator. Iteration is in increasing order of the vector index.
+     *
+     * <p>Note: derived classes are required to return an {@link Iterator} that returns non-null
+     * {@link Entry} objects as long as {@link Iterator#hasNext()} returns {@code true}.
+     *
+     * @return a dense iterator.
+     */
+    public Iterator<Entry> iterator() {
+        final int dim = getDimension();
+        return new Iterator<Entry>() {
+
+            /** Current index. */
+            private int i = 0;
+
+            /** Current entry. */
+            private Entry e = new Entry();
+
+            /** {@inheritDoc} */
+            public boolean hasNext() {
+                return i < dim;
+            }
+
+            /** {@inheritDoc} */
+            public Entry next() {
+                if (i < dim) {
+                    e.setIndex(i++);
+                    return e;
+                } else {
+                    throw new NoSuchElementException();
+                }
+            }
+
+            /**
+             * {@inheritDoc}
+             *
+             * @throws MathUnsupportedOperationException in all circumstances.
+             */
+            public void remove() throws MathUnsupportedOperationException {
+                throw new MathUnsupportedOperationException();
+            }
+        };
+    }
+
+    /**
+     * Acts as if implemented as:
+     *
+     * <pre>
+     *  return copy().mapToSelf(function);
+     * </pre>
+     *
+     * Returns a new vector. Does not change instance data.
+     *
+     * @param function Function to apply to each entry.
+     * @return a new vector.
+     */
+    public RealVector map(UnivariateFunction function) {
+        return copy().mapToSelf(function);
+    }
+
+    /**
+     * Acts as if it is implemented as:
+     *
+     * <pre>
+     *  Entry e = null;
+     *  for(Iterator<Entry> it = iterator(); it.hasNext(); e = it.next()) {
+     *      e.setValue(function.value(e.getValue()));
+     *  }
+     * </pre>
+     *
+     * Entries of this vector are modified in-place by this method.
+     *
+     * @param function Function to apply to each entry.
+     * @return a reference to this vector.
+     */
+    public RealVector mapToSelf(UnivariateFunction function) {
+        Iterator<Entry> it = iterator();
+        while (it.hasNext()) {
+            final Entry e = it.next();
+            e.setValue(function.value(e.getValue()));
+        }
+        return this;
+    }
+
+    /**
+     * Returns a new vector representing {@code a * this + b * y}, the linear combination of {@code
+     * this} and {@code y}. Returns a new vector. Does not change instance data.
+     *
+     * @param a Coefficient of {@code this}.
+     * @param b Coefficient of {@code y}.
+     * @param y Vector with which {@code this} is linearly combined.
+     * @return a vector containing {@code a * this[i] + b * y[i]} for all {@code i}.
+     * @throws DimensionMismatchException if {@code y} is not the same size as {@code this} vector.
+     */
+    public RealVector combine(double a, double b, RealVector y) throws DimensionMismatchException {
+        return copy().combineToSelf(a, b, y);
+    }
+
+    /**
+     * Updates {@code this} with the linear combination of {@code this} and {@code y}.
+     *
+     * @param a Weight of {@code this}.
+     * @param b Weight of {@code y}.
+     * @param y Vector with which {@code this} is linearly combined.
+     * @return {@code this}, with components equal to {@code a * this[i] + b * y[i]} for all {@code
+     *     i}.
+     * @throws DimensionMismatchException if {@code y} is not the same size as {@code this} vector.
+     */
+    public RealVector combineToSelf(double a, double b, RealVector y)
+            throws DimensionMismatchException {
+        checkVectorDimensions(y);
+        for (int i = 0; i < getDimension(); i++) {
+            final double xi = getEntry(i);
+            final double yi = y.getEntry(i);
+            setEntry(i, a * xi + b * yi);
+        }
+        return this;
+    }
+
+    /**
+     * Visits (but does not alter) all entries of this vector in default order (increasing index).
+     *
+     * @param visitor the visitor to be used to process the entries of this vector
+     * @return the value returned by {@link RealVectorPreservingVisitor#end()} at the end of the
+     *     walk
+     * @since 3.1
+     */
+    public double walkInDefaultOrder(final RealVectorPreservingVisitor visitor) {
+        final int dim = getDimension();
+        visitor.start(dim, 0, dim - 1);
+        for (int i = 0; i < dim; i++) {
+            visitor.visit(i, getEntry(i));
+        }
+        return visitor.end();
+    }
+
+    /**
+     * Visits (but does not alter) some entries of this vector in default order (increasing index).
+     *
+     * @param visitor visitor to be used to process the entries of this vector
+     * @param start the index of the first entry to be visited
+     * @param end the index of the last entry to be visited (inclusive)
+     * @return the value returned by {@link RealVectorPreservingVisitor#end()} at the end of the
+     *     walk
+     * @throws NumberIsTooSmallException if {@code end < start}.
+     * @throws OutOfRangeException if the indices are not valid.
+     * @since 3.1
+     */
+    public double walkInDefaultOrder(
+            final RealVectorPreservingVisitor visitor, final int start, final int end)
+            throws NumberIsTooSmallException, OutOfRangeException {
+        checkIndices(start, end);
+        visitor.start(getDimension(), start, end);
+        for (int i = start; i <= end; i++) {
+            visitor.visit(i, getEntry(i));
+        }
+        return visitor.end();
+    }
+
+    /**
+     * Visits (but does not alter) all entries of this vector in optimized order. The order in which
+     * the entries are visited is selected so as to lead to the most efficient implementation; it
+     * might depend on the concrete implementation of this abstract class.
+     *
+     * @param visitor the visitor to be used to process the entries of this vector
+     * @return the value returned by {@link RealVectorPreservingVisitor#end()} at the end of the
+     *     walk
+     * @since 3.1
+     */
+    public double walkInOptimizedOrder(final RealVectorPreservingVisitor visitor) {
+        return walkInDefaultOrder(visitor);
+    }
+
+    /**
+     * Visits (but does not alter) some entries of this vector in optimized order. The order in
+     * which the entries are visited is selected so as to lead to the most efficient implementation;
+     * it might depend on the concrete implementation of this abstract class.
+     *
+     * @param visitor visitor to be used to process the entries of this vector
+     * @param start the index of the first entry to be visited
+     * @param end the index of the last entry to be visited (inclusive)
+     * @return the value returned by {@link RealVectorPreservingVisitor#end()} at the end of the
+     *     walk
+     * @throws NumberIsTooSmallException if {@code end < start}.
+     * @throws OutOfRangeException if the indices are not valid.
+     * @since 3.1
+     */
+    public double walkInOptimizedOrder(
+            final RealVectorPreservingVisitor visitor, final int start, final int end)
+            throws NumberIsTooSmallException, OutOfRangeException {
+        return walkInDefaultOrder(visitor, start, end);
+    }
+
+    /**
+     * Visits (and possibly alters) all entries of this vector in default order (increasing index).
+     *
+     * @param visitor the visitor to be used to process and modify the entries of this vector
+     * @return the value returned by {@link RealVectorChangingVisitor#end()} at the end of the walk
+     * @since 3.1
+     */
+    public double walkInDefaultOrder(final RealVectorChangingVisitor visitor) {
+        final int dim = getDimension();
+        visitor.start(dim, 0, dim - 1);
+        for (int i = 0; i < dim; i++) {
+            setEntry(i, visitor.visit(i, getEntry(i)));
+        }
+        return visitor.end();
+    }
+
+    /**
+     * Visits (and possibly alters) some entries of this vector in default order (increasing index).
+     *
+     * @param visitor visitor to be used to process the entries of this vector
+     * @param start the index of the first entry to be visited
+     * @param end the index of the last entry to be visited (inclusive)
+     * @return the value returned by {@link RealVectorChangingVisitor#end()} at the end of the walk
+     * @throws NumberIsTooSmallException if {@code end < start}.
+     * @throws OutOfRangeException if the indices are not valid.
+     * @since 3.1
+     */
+    public double walkInDefaultOrder(
+            final RealVectorChangingVisitor visitor, final int start, final int end)
+            throws NumberIsTooSmallException, OutOfRangeException {
+        checkIndices(start, end);
+        visitor.start(getDimension(), start, end);
+        for (int i = start; i <= end; i++) {
+            setEntry(i, visitor.visit(i, getEntry(i)));
+        }
+        return visitor.end();
+    }
+
+    /**
+     * Visits (and possibly alters) all entries of this vector in optimized order. The order in
+     * which the entries are visited is selected so as to lead to the most efficient implementation;
+     * it might depend on the concrete implementation of this abstract class.
+     *
+     * @param visitor the visitor to be used to process the entries of this vector
+     * @return the value returned by {@link RealVectorChangingVisitor#end()} at the end of the walk
+     * @since 3.1
+     */
+    public double walkInOptimizedOrder(final RealVectorChangingVisitor visitor) {
+        return walkInDefaultOrder(visitor);
+    }
+
+    /**
+     * Visits (and possibly change) some entries of this vector in optimized order. The order in
+     * which the entries are visited is selected so as to lead to the most efficient implementation;
+     * it might depend on the concrete implementation of this abstract class.
+     *
+     * @param visitor visitor to be used to process the entries of this vector
+     * @param start the index of the first entry to be visited
+     * @param end the index of the last entry to be visited (inclusive)
+     * @return the value returned by {@link RealVectorChangingVisitor#end()} at the end of the walk
+     * @throws NumberIsTooSmallException if {@code end < start}.
+     * @throws OutOfRangeException if the indices are not valid.
+     * @since 3.1
+     */
+    public double walkInOptimizedOrder(
+            final RealVectorChangingVisitor visitor, final int start, final int end)
+            throws NumberIsTooSmallException, OutOfRangeException {
+        return walkInDefaultOrder(visitor, start, end);
+    }
+
+    /** An entry in the vector. */
+    protected class Entry {
+        /** Index of this entry. */
+        private int index;
+
+        /** Simple constructor. */
+        public Entry() {
+            setIndex(0);
+        }
+
+        /**
+         * Get the value of the entry.
+         *
+         * @return the value of the entry.
+         */
+        public double getValue() {
+            return getEntry(getIndex());
+        }
+
+        /**
+         * Set the value of the entry.
+         *
+         * @param value New value for the entry.
+         */
+        public void setValue(double value) {
+            setEntry(getIndex(), value);
+        }
+
+        /**
+         * Get the index of the entry.
+         *
+         * @return the index of the entry.
+         */
+        public int getIndex() {
+            return index;
+        }
+
+        /**
+         * Set the index of the entry.
+         *
+         * @param index New index for the entry.
+         */
+        public void setIndex(int index) {
+            this.index = index;
+        }
+    }
+
+    /**
+     * Test for the equality of two real vectors. If all coordinates of two real vectors are exactly
+     * the same, and none are {@code NaN}, the two real vectors are considered to be equal. {@code
+     * NaN} coordinates are considered to affect globally the vector and be equals to each other -
+     * i.e, if either (or all) coordinates of the real vector are equal to {@code NaN}, the real
+     * vector is equal to a vector with all {@code NaN} coordinates.
+     *
+     * <p>This method <em>must</em> be overriden by concrete subclasses of {@link RealVector} (the
+     * current implementation throws an exception).
+     *
+     * @param other Object to test for equality.
+     * @return {@code true} if two vector objects are equal, {@code false} if {@code other} is null,
+     *     not an instance of {@code RealVector}, or not equal to this {@code RealVector} instance.
+     * @throws MathUnsupportedOperationException if this method is not overridden.
+     */
+    @Override
+    public boolean equals(Object other) throws MathUnsupportedOperationException {
+        throw new MathUnsupportedOperationException();
+    }
+
+    /**
+     * {@inheritDoc}. This method <em>must</em> be overriden by concrete subclasses of {@link
+     * RealVector} (current implementation throws an exception).
+     *
+     * @throws MathUnsupportedOperationException if this method is not overridden.
+     */
+    @Override
+    public int hashCode() throws MathUnsupportedOperationException {
+        throw new MathUnsupportedOperationException();
+    }
+
+    /**
+     * This class should rarely be used, but is here to provide a default implementation of
+     * sparseIterator(), which is implemented by walking over the entries, skipping those that are
+     * zero.
+     *
+     * <p>Concrete subclasses which are SparseVector implementations should make their own sparse
+     * iterator, rather than using this one.
+     *
+     * <p>This implementation might be useful for ArrayRealVector, when expensive operations which
+     * preserve the default value are to be done on the entries, and the fraction of non-default
+     * values is small (i.e. someone took a SparseVector, and passed it into the copy-constructor of
+     * ArrayRealVector)
+     */
+    protected class SparseEntryIterator implements Iterator<Entry> {
+        /** Dimension of the vector. */
+        private final int dim;
+
+        /** Last entry returned by {@link #next()}. */
+        private Entry current;
+
+        /** Next entry for {@link #next()} to return. */
+        private Entry next;
+
+        /** Simple constructor. */
+        protected SparseEntryIterator() {
+            dim = getDimension();
+            current = new Entry();
+            next = new Entry();
+            if (next.getValue() == 0) {
+                advance(next);
+            }
+        }
+
+        /**
+         * Advance an entry up to the next nonzero one.
+         *
+         * @param e entry to advance.
+         */
+        protected void advance(Entry e) {
+            if (e == null) {
+                return;
+            }
+            do {
+                e.setIndex(e.getIndex() + 1);
+            } while (e.getIndex() < dim && e.getValue() == 0);
+            if (e.getIndex() >= dim) {
+                e.setIndex(-1);
+            }
+        }
+
+        /** {@inheritDoc} */
+        public boolean hasNext() {
+            return next.getIndex() >= 0;
+        }
+
+        /** {@inheritDoc} */
+        public Entry next() {
+            int index = next.getIndex();
+            if (index < 0) {
+                throw new NoSuchElementException();
+            }
+            current.setIndex(index);
+            advance(next);
+            return current;
+        }
+
+        /**
+         * {@inheritDoc}
+         *
+         * @throws MathUnsupportedOperationException in all circumstances.
+         */
+        public void remove() throws MathUnsupportedOperationException {
+            throw new MathUnsupportedOperationException();
+        }
+    }
+
+    /**
+     * Returns an unmodifiable view of the specified vector. The returned vector has read-only
+     * access. An attempt to modify it will result in a {@link MathUnsupportedOperationException}.
+     * However, the returned vector is <em>not</em> immutable, since any modification of {@code v}
+     * will also change the returned view. For example, in the following piece of code
+     *
+     * <pre>
+     *     RealVector v = new ArrayRealVector(2);
+     *     RealVector w = RealVector.unmodifiableRealVector(v);
+     *     v.setEntry(0, 1.2);
+     *     v.setEntry(1, -3.4);
+     * </pre>
+     *
+     * the changes will be seen in the {@code w} view of {@code v}.
+     *
+     * @param v Vector for which an unmodifiable view is to be returned.
+     * @return an unmodifiable view of {@code v}.
+     */
+    public static RealVector unmodifiableRealVector(final RealVector v) {
+        /**
+         * This anonymous class is an implementation of {@link RealVector} with read-only access. It
+         * wraps any {@link RealVector}, and exposes all methods which do not modify it. Invoking
+         * methods which should normally result in the modification of the calling {@link
+         * RealVector} results in a {@link MathUnsupportedOperationException}. It should be noted
+         * that {@link UnmodifiableVector} is <em>not</em> immutable.
+         */
+        return new RealVector() {
+            /**
+             * {@inheritDoc}
+             *
+             * @throws MathUnsupportedOperationException in all circumstances.
+             */
+            @Override
+            public RealVector mapToSelf(UnivariateFunction function)
+                    throws MathUnsupportedOperationException {
+                throw new MathUnsupportedOperationException();
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public RealVector map(UnivariateFunction function) {
+                return v.map(function);
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public Iterator<Entry> iterator() {
+                final Iterator<Entry> i = v.iterator();
+                return new Iterator<Entry>() {
+                    /** The current entry. */
+                    private final UnmodifiableEntry e = new UnmodifiableEntry();
+
+                    /** {@inheritDoc} */
+                    public boolean hasNext() {
+                        return i.hasNext();
+                    }
+
+                    /** {@inheritDoc} */
+                    public Entry next() {
+                        e.setIndex(i.next().getIndex());
+                        return e;
+                    }
+
+                    /**
+                     * {@inheritDoc}
+                     *
+                     * @throws MathUnsupportedOperationException in all circumstances.
+                     */
+                    public void remove() throws MathUnsupportedOperationException {
+                        throw new MathUnsupportedOperationException();
+                    }
+                };
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public Iterator<Entry> sparseIterator() {
+                final Iterator<Entry> i = v.sparseIterator();
+
+                return new Iterator<Entry>() {
+                    /** The current entry. */
+                    private final UnmodifiableEntry e = new UnmodifiableEntry();
+
+                    /** {@inheritDoc} */
+                    public boolean hasNext() {
+                        return i.hasNext();
+                    }
+
+                    /** {@inheritDoc} */
+                    public Entry next() {
+                        e.setIndex(i.next().getIndex());
+                        return e;
+                    }
+
+                    /**
+                     * {@inheritDoc}
+                     *
+                     * @throws MathUnsupportedOperationException in all circumstances.
+                     */
+                    public void remove() throws MathUnsupportedOperationException {
+                        throw new MathUnsupportedOperationException();
+                    }
+                };
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public RealVector copy() {
+                return v.copy();
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public RealVector add(RealVector w) throws DimensionMismatchException {
+                return v.add(w);
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public RealVector subtract(RealVector w) throws DimensionMismatchException {
+                return v.subtract(w);
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public RealVector mapAdd(double d) {
+                return v.mapAdd(d);
+            }
+
+            /**
+             * {@inheritDoc}
+             *
+             * @throws MathUnsupportedOperationException in all circumstances.
+             */
+            @Override
+            public RealVector mapAddToSelf(double d) throws MathUnsupportedOperationException {
+                throw new MathUnsupportedOperationException();
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public RealVector mapSubtract(double d) {
+                return v.mapSubtract(d);
+            }
+
+            /**
+             * {@inheritDoc}
+             *
+             * @throws MathUnsupportedOperationException in all circumstances.
+             */
+            @Override
+            public RealVector mapSubtractToSelf(double d) throws MathUnsupportedOperationException {
+                throw new MathUnsupportedOperationException();
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public RealVector mapMultiply(double d) {
+                return v.mapMultiply(d);
+            }
+
+            /**
+             * {@inheritDoc}
+             *
+             * @throws MathUnsupportedOperationException in all circumstances.
+             */
+            @Override
+            public RealVector mapMultiplyToSelf(double d) throws MathUnsupportedOperationException {
+                throw new MathUnsupportedOperationException();
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public RealVector mapDivide(double d) {
+                return v.mapDivide(d);
+            }
+
+            /**
+             * {@inheritDoc}
+             *
+             * @throws MathUnsupportedOperationException in all circumstances.
+             */
+            @Override
+            public RealVector mapDivideToSelf(double d) throws MathUnsupportedOperationException {
+                throw new MathUnsupportedOperationException();
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public RealVector ebeMultiply(RealVector w) throws DimensionMismatchException {
+                return v.ebeMultiply(w);
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public RealVector ebeDivide(RealVector w) throws DimensionMismatchException {
+                return v.ebeDivide(w);
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public double dotProduct(RealVector w) throws DimensionMismatchException {
+                return v.dotProduct(w);
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public double cosine(RealVector w)
+                    throws DimensionMismatchException, MathArithmeticException {
+                return v.cosine(w);
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public double getNorm() {
+                return v.getNorm();
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public double getL1Norm() {
+                return v.getL1Norm();
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public double getLInfNorm() {
+                return v.getLInfNorm();
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public double getDistance(RealVector w) throws DimensionMismatchException {
+                return v.getDistance(w);
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public double getL1Distance(RealVector w) throws DimensionMismatchException {
+                return v.getL1Distance(w);
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public double getLInfDistance(RealVector w) throws DimensionMismatchException {
+                return v.getLInfDistance(w);
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public RealVector unitVector() throws MathArithmeticException {
+                return v.unitVector();
+            }
+
+            /**
+             * {@inheritDoc}
+             *
+             * @throws MathUnsupportedOperationException in all circumstances.
+             */
+            @Override
+            public void unitize() throws MathUnsupportedOperationException {
+                throw new MathUnsupportedOperationException();
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public RealMatrix outerProduct(RealVector w) {
+                return v.outerProduct(w);
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public double getEntry(int index) throws OutOfRangeException {
+                return v.getEntry(index);
+            }
+
+            /**
+             * {@inheritDoc}
+             *
+             * @throws MathUnsupportedOperationException in all circumstances.
+             */
+            @Override
+            public void setEntry(int index, double value) throws MathUnsupportedOperationException {
+                throw new MathUnsupportedOperationException();
+            }
+
+            /**
+             * {@inheritDoc}
+             *
+             * @throws MathUnsupportedOperationException in all circumstances.
+             */
+            @Override
+            public void addToEntry(int index, double value)
+                    throws MathUnsupportedOperationException {
+                throw new MathUnsupportedOperationException();
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public int getDimension() {
+                return v.getDimension();
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public RealVector append(RealVector w) {
+                return v.append(w);
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public RealVector append(double d) {
+                return v.append(d);
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public RealVector getSubVector(int index, int n)
+                    throws OutOfRangeException, NotPositiveException {
+                return v.getSubVector(index, n);
+            }
+
+            /**
+             * {@inheritDoc}
+             *
+             * @throws MathUnsupportedOperationException in all circumstances.
+             */
+            @Override
+            public void setSubVector(int index, RealVector w)
+                    throws MathUnsupportedOperationException {
+                throw new MathUnsupportedOperationException();
+            }
+
+            /**
+             * {@inheritDoc}
+             *
+             * @throws MathUnsupportedOperationException in all circumstances.
+             */
+            @Override
+            public void set(double value) throws MathUnsupportedOperationException {
+                throw new MathUnsupportedOperationException();
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public double[] toArray() {
+                return v.toArray();
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public boolean isNaN() {
+                return v.isNaN();
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public boolean isInfinite() {
+                return v.isInfinite();
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public RealVector combine(double a, double b, RealVector y)
+                    throws DimensionMismatchException {
+                return v.combine(a, b, y);
+            }
+
+            /**
+             * {@inheritDoc}
+             *
+             * @throws MathUnsupportedOperationException in all circumstances.
+             */
+            @Override
+            public RealVector combineToSelf(double a, double b, RealVector y)
+                    throws MathUnsupportedOperationException {
+                throw new MathUnsupportedOperationException();
+            }
+
+            /** An entry in the vector. */
+            class UnmodifiableEntry extends Entry {
+                /** {@inheritDoc} */
+                @Override
+                public double getValue() {
+                    return v.getEntry(getIndex());
+                }
+
+                /**
+                 * {@inheritDoc}
+                 *
+                 * @throws MathUnsupportedOperationException in all circumstances.
+                 */
+                @Override
+                public void setValue(double value) throws MathUnsupportedOperationException {
+                    throw new MathUnsupportedOperationException();
+                }
+            }
+        };
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/RealVectorChangingVisitor.java b/src/main/java/org/apache/commons/math3/linear/RealVectorChangingVisitor.java
new file mode 100644
index 0000000..8e8597a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/RealVectorChangingVisitor.java
@@ -0,0 +1,56 @@
+/*
+ * 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.math3.linear;
+
+/**
+ * This interface defines a visitor for the entries of a vector. Visitors implementing this
+ * interface may alter the entries of the vector being visited.
+ *
+ * @since 3.1
+ */
+public interface RealVectorChangingVisitor {
+    /**
+     * Start visiting a vector. This method is called once, before any entry of the vector is
+     * visited.
+     *
+     * @param dimension the size of the vector
+     * @param start the index of the first entry to be visited
+     * @param end the index of the last entry to be visited (inclusive)
+     */
+    void start(int dimension, int start, int end);
+
+    /**
+     * Visit one entry of the vector.
+     *
+     * @param index the index of the entry being visited
+     * @param value the value of the entry being visited
+     * @return the new value of the entry being visited
+     */
+    double visit(int index, double value);
+
+    /**
+     * End visiting a vector. This method is called once, after all entries of the vector have been
+     * visited.
+     *
+     * @return the value returned by {@link
+     *     RealVector#walkInDefaultOrder(RealVectorChangingVisitor)}, {@link
+     *     RealVector#walkInDefaultOrder(RealVectorChangingVisitor, int, int)}, {@link
+     *     RealVector#walkInOptimizedOrder(RealVectorChangingVisitor)} or {@link
+     *     RealVector#walkInOptimizedOrder(RealVectorChangingVisitor, int, int)}
+     */
+    double end();
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/RealVectorFormat.java b/src/main/java/org/apache/commons/math3/linear/RealVectorFormat.java
new file mode 100644
index 0000000..addf1ca
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/RealVectorFormat.java
@@ -0,0 +1,310 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.exception.MathParseException;
+import org.apache.commons.math3.util.CompositeFormat;
+
+import java.text.FieldPosition;
+import java.text.NumberFormat;
+import java.text.ParsePosition;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Formats a vector in components list format "{v0; v1; ...; vk-1}".
+ *
+ * <p>The prefix and suffix "{" and "}" and the separator "; " can be replaced by any user-defined
+ * strings. The number format for components can be configured.
+ *
+ * <p>White space is ignored at parse time, even if it is in the prefix, suffix or separator
+ * specifications. So even if the default separator does include a space character that is used at
+ * format time, both input string "{1;1;1}" and " { 1 ; 1 ; 1 } " will be parsed without error and
+ * the same vector will be returned. In the second case, however, the parse position after parsing
+ * will be just after the closing curly brace, i.e. just before the trailing space.
+ *
+ * @since 2.0
+ */
+public class RealVectorFormat {
+
+    /** The default prefix: "{". */
+    private static final String DEFAULT_PREFIX = "{";
+
+    /** The default suffix: "}". */
+    private static final String DEFAULT_SUFFIX = "}";
+
+    /** The default separator: ", ". */
+    private static final String DEFAULT_SEPARATOR = "; ";
+
+    /** Prefix. */
+    private final String prefix;
+
+    /** Suffix. */
+    private final String suffix;
+
+    /** Separator. */
+    private final String separator;
+
+    /** Trimmed prefix. */
+    private final String trimmedPrefix;
+
+    /** Trimmed suffix. */
+    private final String trimmedSuffix;
+
+    /** Trimmed separator. */
+    private final String trimmedSeparator;
+
+    /** The format used for components. */
+    private final NumberFormat format;
+
+    /**
+     * Create an instance with default settings.
+     *
+     * <p>The instance uses the default prefix, suffix and separator: "{", "}", and "; " and the
+     * default number format for components.
+     */
+    public RealVectorFormat() {
+        this(
+                DEFAULT_PREFIX,
+                DEFAULT_SUFFIX,
+                DEFAULT_SEPARATOR,
+                CompositeFormat.getDefaultNumberFormat());
+    }
+
+    /**
+     * Create an instance with a custom number format for components.
+     *
+     * @param format the custom format for components.
+     */
+    public RealVectorFormat(final NumberFormat format) {
+        this(DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_SEPARATOR, format);
+    }
+
+    /**
+     * Create an instance with custom prefix, suffix and separator.
+     *
+     * @param prefix prefix to use instead of the default "{"
+     * @param suffix suffix to use instead of the default "}"
+     * @param separator separator to use instead of the default "; "
+     */
+    public RealVectorFormat(final String prefix, final String suffix, final String separator) {
+        this(prefix, suffix, separator, CompositeFormat.getDefaultNumberFormat());
+    }
+
+    /**
+     * Create an instance with custom prefix, suffix, separator and format for components.
+     *
+     * @param prefix prefix to use instead of the default "{"
+     * @param suffix suffix to use instead of the default "}"
+     * @param separator separator to use instead of the default "; "
+     * @param format the custom format for components.
+     */
+    public RealVectorFormat(
+            final String prefix,
+            final String suffix,
+            final String separator,
+            final NumberFormat format) {
+        this.prefix = prefix;
+        this.suffix = suffix;
+        this.separator = separator;
+        trimmedPrefix = prefix.trim();
+        trimmedSuffix = suffix.trim();
+        trimmedSeparator = separator.trim();
+        this.format = format;
+    }
+
+    /**
+     * Get the set of locales for which real vectors formats are available.
+     *
+     * <p>This is the same set as the {@link NumberFormat} set.
+     *
+     * @return available real vector format locales.
+     */
+    public static Locale[] getAvailableLocales() {
+        return NumberFormat.getAvailableLocales();
+    }
+
+    /**
+     * Get the format prefix.
+     *
+     * @return format prefix.
+     */
+    public String getPrefix() {
+        return prefix;
+    }
+
+    /**
+     * Get the format suffix.
+     *
+     * @return format suffix.
+     */
+    public String getSuffix() {
+        return suffix;
+    }
+
+    /**
+     * Get the format separator between components.
+     *
+     * @return format separator.
+     */
+    public String getSeparator() {
+        return separator;
+    }
+
+    /**
+     * Get the components format.
+     *
+     * @return components format.
+     */
+    public NumberFormat getFormat() {
+        return format;
+    }
+
+    /**
+     * Returns the default real vector format for the current locale.
+     *
+     * @return the default real vector format.
+     */
+    public static RealVectorFormat getInstance() {
+        return getInstance(Locale.getDefault());
+    }
+
+    /**
+     * Returns the default real vector format for the given locale.
+     *
+     * @param locale the specific locale used by the format.
+     * @return the real vector format specific to the given locale.
+     */
+    public static RealVectorFormat getInstance(final Locale locale) {
+        return new RealVectorFormat(CompositeFormat.getDefaultNumberFormat(locale));
+    }
+
+    /**
+     * This method calls {@link #format(RealVector,StringBuffer,FieldPosition)}.
+     *
+     * @param v RealVector object to format.
+     * @return a formatted vector.
+     */
+    public String format(RealVector v) {
+        return format(v, new StringBuffer(), new FieldPosition(0)).toString();
+    }
+
+    /**
+     * Formats a {@link RealVector} object to produce a string.
+     *
+     * @param vector the object to format.
+     * @param toAppendTo where the text is to be appended
+     * @param pos On input: an alignment field, if desired. On output: the offsets of the alignment
+     *     field
+     * @return the value passed in as toAppendTo.
+     */
+    public StringBuffer format(RealVector vector, StringBuffer toAppendTo, FieldPosition pos) {
+
+        pos.setBeginIndex(0);
+        pos.setEndIndex(0);
+
+        // format prefix
+        toAppendTo.append(prefix);
+
+        // format components
+        for (int i = 0; i < vector.getDimension(); ++i) {
+            if (i > 0) {
+                toAppendTo.append(separator);
+            }
+            CompositeFormat.formatDouble(vector.getEntry(i), format, toAppendTo, pos);
+        }
+
+        // format suffix
+        toAppendTo.append(suffix);
+
+        return toAppendTo;
+    }
+
+    /**
+     * Parse a string to produce a {@link RealVector} object.
+     *
+     * @param source String to parse.
+     * @return the parsed {@link RealVector} object.
+     * @throws MathParseException if the beginning of the specified string cannot be parsed.
+     */
+    public ArrayRealVector parse(String source) {
+        final ParsePosition parsePosition = new ParsePosition(0);
+        final ArrayRealVector result = parse(source, parsePosition);
+        if (parsePosition.getIndex() == 0) {
+            throw new MathParseException(
+                    source, parsePosition.getErrorIndex(), ArrayRealVector.class);
+        }
+        return result;
+    }
+
+    /**
+     * Parse a string to produce a {@link RealVector} object.
+     *
+     * @param source String to parse.
+     * @param pos input/ouput parsing parameter.
+     * @return the parsed {@link RealVector} object.
+     */
+    public ArrayRealVector parse(String source, ParsePosition pos) {
+        int initialIndex = pos.getIndex();
+
+        // parse prefix
+        CompositeFormat.parseAndIgnoreWhitespace(source, pos);
+        if (!CompositeFormat.parseFixedstring(source, trimmedPrefix, pos)) {
+            return null;
+        }
+
+        // parse components
+        List<Number> components = new ArrayList<Number>();
+        for (boolean loop = true; loop; ) {
+
+            if (!components.isEmpty()) {
+                CompositeFormat.parseAndIgnoreWhitespace(source, pos);
+                if (!CompositeFormat.parseFixedstring(source, trimmedSeparator, pos)) {
+                    loop = false;
+                }
+            }
+
+            if (loop) {
+                CompositeFormat.parseAndIgnoreWhitespace(source, pos);
+                Number component = CompositeFormat.parseNumber(source, format, pos);
+                if (component != null) {
+                    components.add(component);
+                } else {
+                    // invalid component
+                    // set index back to initial, error index should already be set
+                    pos.setIndex(initialIndex);
+                    return null;
+                }
+            }
+        }
+
+        // parse suffix
+        CompositeFormat.parseAndIgnoreWhitespace(source, pos);
+        if (!CompositeFormat.parseFixedstring(source, trimmedSuffix, pos)) {
+            return null;
+        }
+
+        // build vector
+        double[] data = new double[components.size()];
+        for (int i = 0; i < data.length; ++i) {
+            data[i] = components.get(i).doubleValue();
+        }
+        return new ArrayRealVector(data, false);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/RealVectorPreservingVisitor.java b/src/main/java/org/apache/commons/math3/linear/RealVectorPreservingVisitor.java
new file mode 100644
index 0000000..b1118d6
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/RealVectorPreservingVisitor.java
@@ -0,0 +1,55 @@
+/*
+ * 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.math3.linear;
+
+/**
+ * This interface defines a visitor for the entries of a vector. Visitors implementing this
+ * interface do not alter the entries of the vector being visited.
+ *
+ * @since 3.1
+ */
+public interface RealVectorPreservingVisitor {
+    /**
+     * Start visiting a vector. This method is called once, before any entry of the vector is
+     * visited.
+     *
+     * @param dimension the size of the vector
+     * @param start the index of the first entry to be visited
+     * @param end the index of the last entry to be visited (inclusive)
+     */
+    void start(int dimension, int start, int end);
+
+    /**
+     * Visit one entry of the vector.
+     *
+     * @param index the index of the entry being visited
+     * @param value the value of the entry being visited
+     */
+    void visit(int index, double value);
+
+    /**
+     * End visiting a vector. This method is called once, after all entries of the vector have been
+     * visited.
+     *
+     * @return the value returned by {@link
+     *     RealVector#walkInDefaultOrder(RealVectorPreservingVisitor)}, {@link
+     *     RealVector#walkInDefaultOrder(RealVectorPreservingVisitor, int, int)}, {@link
+     *     RealVector#walkInOptimizedOrder(RealVectorPreservingVisitor)} or {@link
+     *     RealVector#walkInOptimizedOrder(RealVectorPreservingVisitor, int, int)}
+     */
+    double end();
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/RectangularCholeskyDecomposition.java b/src/main/java/org/apache/commons/math3/linear/RectangularCholeskyDecomposition.java
new file mode 100644
index 0000000..20eff16
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/RectangularCholeskyDecomposition.java
@@ -0,0 +1,191 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Calculates the rectangular Cholesky decomposition of a matrix.
+ *
+ * <p>The rectangular Cholesky decomposition of a real symmetric positive semidefinite matrix A
+ * consists of a rectangular matrix B with the same number of rows such that: A is almost equal to
+ * BB<sup>T</sup>, depending on a user-defined tolerance. In a sense, this is the square root of A.
+ *
+ * <p>The difference with respect to the regular {@link CholeskyDecomposition} is that rows/columns
+ * may be permuted (hence the rectangular shape instead of the traditional triangular shape) and
+ * there is a threshold to ignore small diagonal elements. This is used for example to generate
+ * {@link org.apache.commons.math3.random.CorrelatedRandomVectorGenerator correlated random
+ * n-dimensions vectors} in a p-dimension subspace (p < n). In other words, it allows generating
+ * random vectors from a covariance matrix that is only positive semidefinite, and not positive
+ * definite.
+ *
+ * <p>Rectangular Cholesky decomposition is <em>not</em> suited for solving linear systems, so it
+ * does not provide any {@link DecompositionSolver decomposition solver}.
+ *
+ * @see <a href="http://mathworld.wolfram.com/CholeskyDecomposition.html">MathWorld</a>
+ * @see <a href="http://en.wikipedia.org/wiki/Cholesky_decomposition">Wikipedia</a>
+ * @since 2.0 (changed to concrete class in 3.0)
+ */
+public class RectangularCholeskyDecomposition {
+
+    /** Permutated Cholesky root of the symmetric positive semidefinite matrix. */
+    private final RealMatrix root;
+
+    /** Rank of the symmetric positive semidefinite matrix. */
+    private int rank;
+
+    /**
+     * Decompose a symmetric positive semidefinite matrix.
+     *
+     * <p><b>Note:</b> this constructor follows the linpack method to detect dependent columns by
+     * proceeding with the Cholesky algorithm until a nonpositive diagonal element is encountered.
+     *
+     * @see <a href="http://eprints.ma.man.ac.uk/1193/01/covered/MIMS_ep2008_56.pdf">Analysis of the
+     *     Cholesky Decomposition of a Semi-definite Matrix</a>
+     * @param matrix Symmetric positive semidefinite matrix.
+     * @exception NonPositiveDefiniteMatrixException if the matrix is not positive semidefinite.
+     * @since 3.1
+     */
+    public RectangularCholeskyDecomposition(RealMatrix matrix)
+            throws NonPositiveDefiniteMatrixException {
+        this(matrix, 0);
+    }
+
+    /**
+     * Decompose a symmetric positive semidefinite matrix.
+     *
+     * @param matrix Symmetric positive semidefinite matrix.
+     * @param small Diagonal elements threshold under which columns are considered to be dependent
+     *     on previous ones and are discarded.
+     * @exception NonPositiveDefiniteMatrixException if the matrix is not positive semidefinite.
+     */
+    public RectangularCholeskyDecomposition(RealMatrix matrix, double small)
+            throws NonPositiveDefiniteMatrixException {
+
+        final int order = matrix.getRowDimension();
+        final double[][] c = matrix.getData();
+        final double[][] b = new double[order][order];
+
+        int[] index = new int[order];
+        for (int i = 0; i < order; ++i) {
+            index[i] = i;
+        }
+
+        int r = 0;
+        for (boolean loop = true; loop; ) {
+
+            // find maximal diagonal element
+            int swapR = r;
+            for (int i = r + 1; i < order; ++i) {
+                int ii = index[i];
+                int isr = index[swapR];
+                if (c[ii][ii] > c[isr][isr]) {
+                    swapR = i;
+                }
+            }
+
+            // swap elements
+            if (swapR != r) {
+                final int tmpIndex = index[r];
+                index[r] = index[swapR];
+                index[swapR] = tmpIndex;
+                final double[] tmpRow = b[r];
+                b[r] = b[swapR];
+                b[swapR] = tmpRow;
+            }
+
+            // check diagonal element
+            int ir = index[r];
+            if (c[ir][ir] <= small) {
+
+                if (r == 0) {
+                    throw new NonPositiveDefiniteMatrixException(c[ir][ir], ir, small);
+                }
+
+                // check remaining diagonal elements
+                for (int i = r; i < order; ++i) {
+                    if (c[index[i]][index[i]] < -small) {
+                        // there is at least one sufficiently negative diagonal element,
+                        // the symmetric positive semidefinite matrix is wrong
+                        throw new NonPositiveDefiniteMatrixException(
+                                c[index[i]][index[i]], i, small);
+                    }
+                }
+
+                // all remaining diagonal elements are close to zero, we consider we have
+                // found the rank of the symmetric positive semidefinite matrix
+                loop = false;
+
+            } else {
+
+                // transform the matrix
+                final double sqrt = FastMath.sqrt(c[ir][ir]);
+                b[r][r] = sqrt;
+                final double inverse = 1 / sqrt;
+                final double inverse2 = 1 / c[ir][ir];
+                for (int i = r + 1; i < order; ++i) {
+                    final int ii = index[i];
+                    final double e = inverse * c[ii][ir];
+                    b[i][r] = e;
+                    c[ii][ii] -= c[ii][ir] * c[ii][ir] * inverse2;
+                    for (int j = r + 1; j < i; ++j) {
+                        final int ij = index[j];
+                        final double f = c[ii][ij] - e * b[j][r];
+                        c[ii][ij] = f;
+                        c[ij][ii] = f;
+                    }
+                }
+
+                // prepare next iteration
+                loop = ++r < order;
+            }
+        }
+
+        // build the root matrix
+        rank = r;
+        root = MatrixUtils.createRealMatrix(order, r);
+        for (int i = 0; i < order; ++i) {
+            for (int j = 0; j < r; ++j) {
+                root.setEntry(index[i], j, b[i][j]);
+            }
+        }
+    }
+
+    /**
+     * Get the root of the covariance matrix. The root is the rectangular matrix <code>B</code> such
+     * that the covariance matrix is equal to <code>B.B<sup>T</sup></code>
+     *
+     * @return root of the square matrix
+     * @see #getRank()
+     */
+    public RealMatrix getRootMatrix() {
+        return root;
+    }
+
+    /**
+     * Get the rank of the symmetric positive semidefinite matrix. The r is the number of
+     * independent rows in the symmetric positive semidefinite matrix, it is also the number of
+     * columns of the rectangular matrix of the decomposition.
+     *
+     * @return r of the square matrix.
+     * @see #getRootMatrix()
+     */
+    public int getRank() {
+        return rank;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/SchurTransformer.java b/src/main/java/org/apache/commons/math3/linear/SchurTransformer.java
new file mode 100644
index 0000000..bea333f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/SchurTransformer.java
@@ -0,0 +1,474 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.Precision;
+
+/**
+ * Class transforming a general real matrix to Schur form.
+ *
+ * <p>A m &times; m matrix A can be written as the product of three matrices: A = P &times; T
+ * &times; P<sup>T</sup> with P an orthogonal matrix and T an quasi-triangular matrix. Both P and T
+ * are m &times; m matrices.
+ *
+ * <p>Transformation to Schur form is often not a goal by itself, but it is an intermediate step in
+ * more general decomposition algorithms like {@link EigenDecomposition eigen decomposition}. This
+ * class is therefore intended for internal use by the library and is not public. As a consequence
+ * of this explicitly limited scope, many methods directly returns references to internal arrays,
+ * not copies.
+ *
+ * <p>This class is based on the method hqr2 in class EigenvalueDecomposition from the <a
+ * href="http://math.nist.gov/javanumerics/jama/">JAMA</a> library.
+ *
+ * @see <a href="http://mathworld.wolfram.com/SchurDecomposition.html">Schur Decomposition -
+ *     MathWorld</a>
+ * @see <a href="http://en.wikipedia.org/wiki/Schur_decomposition">Schur Decomposition -
+ *     Wikipedia</a>
+ * @see <a href="http://en.wikipedia.org/wiki/Householder_transformation">Householder
+ *     Transformations</a>
+ * @since 3.1
+ */
+class SchurTransformer {
+    /** Maximum allowed iterations for convergence of the transformation. */
+    private static final int MAX_ITERATIONS = 100;
+
+    /** P matrix. */
+    private final double matrixP[][];
+
+    /** T matrix. */
+    private final double matrixT[][];
+
+    /** Cached value of P. */
+    private RealMatrix cachedP;
+
+    /** Cached value of T. */
+    private RealMatrix cachedT;
+
+    /** Cached value of PT. */
+    private RealMatrix cachedPt;
+
+    /** Epsilon criteria taken from JAMA code (originally was 2^-52). */
+    private final double epsilon = Precision.EPSILON;
+
+    /**
+     * Build the transformation to Schur form of a general real matrix.
+     *
+     * @param matrix matrix to transform
+     * @throws NonSquareMatrixException if the matrix is not square
+     */
+    SchurTransformer(final RealMatrix matrix) {
+        if (!matrix.isSquare()) {
+            throw new NonSquareMatrixException(
+                    matrix.getRowDimension(), matrix.getColumnDimension());
+        }
+
+        HessenbergTransformer transformer = new HessenbergTransformer(matrix);
+        matrixT = transformer.getH().getData();
+        matrixP = transformer.getP().getData();
+        cachedT = null;
+        cachedP = null;
+        cachedPt = null;
+
+        // transform matrix
+        transform();
+    }
+
+    /**
+     * Returns the matrix P of the transform.
+     *
+     * <p>P is an orthogonal matrix, i.e. its inverse is also its transpose.
+     *
+     * @return the P matrix
+     */
+    public RealMatrix getP() {
+        if (cachedP == null) {
+            cachedP = MatrixUtils.createRealMatrix(matrixP);
+        }
+        return cachedP;
+    }
+
+    /**
+     * Returns the transpose of the matrix P of the transform.
+     *
+     * <p>P is an orthogonal matrix, i.e. its inverse is also its transpose.
+     *
+     * @return the transpose of the P matrix
+     */
+    public RealMatrix getPT() {
+        if (cachedPt == null) {
+            cachedPt = getP().transpose();
+        }
+
+        // return the cached matrix
+        return cachedPt;
+    }
+
+    /**
+     * Returns the quasi-triangular Schur matrix T of the transform.
+     *
+     * @return the T matrix
+     */
+    public RealMatrix getT() {
+        if (cachedT == null) {
+            cachedT = MatrixUtils.createRealMatrix(matrixT);
+        }
+
+        // return the cached matrix
+        return cachedT;
+    }
+
+    /**
+     * Transform original matrix to Schur form.
+     *
+     * @throws MaxCountExceededException if the transformation does not converge
+     */
+    private void transform() {
+        final int n = matrixT.length;
+
+        // compute matrix norm
+        final double norm = getNorm();
+
+        // shift information
+        final ShiftInfo shift = new ShiftInfo();
+
+        // Outer loop over eigenvalue index
+        int iteration = 0;
+        int iu = n - 1;
+        while (iu >= 0) {
+
+            // Look for single small sub-diagonal element
+            final int il = findSmallSubDiagonalElement(iu, norm);
+
+            // Check for convergence
+            if (il == iu) {
+                // One root found
+                matrixT[iu][iu] += shift.exShift;
+                iu--;
+                iteration = 0;
+            } else if (il == iu - 1) {
+                // Two roots found
+                double p = (matrixT[iu - 1][iu - 1] - matrixT[iu][iu]) / 2.0;
+                double q = p * p + matrixT[iu][iu - 1] * matrixT[iu - 1][iu];
+                matrixT[iu][iu] += shift.exShift;
+                matrixT[iu - 1][iu - 1] += shift.exShift;
+
+                if (q >= 0) {
+                    double z = FastMath.sqrt(FastMath.abs(q));
+                    if (p >= 0) {
+                        z = p + z;
+                    } else {
+                        z = p - z;
+                    }
+                    final double x = matrixT[iu][iu - 1];
+                    final double s = FastMath.abs(x) + FastMath.abs(z);
+                    p = x / s;
+                    q = z / s;
+                    final double r = FastMath.sqrt(p * p + q * q);
+                    p /= r;
+                    q /= r;
+
+                    // Row modification
+                    for (int j = iu - 1; j < n; j++) {
+                        z = matrixT[iu - 1][j];
+                        matrixT[iu - 1][j] = q * z + p * matrixT[iu][j];
+                        matrixT[iu][j] = q * matrixT[iu][j] - p * z;
+                    }
+
+                    // Column modification
+                    for (int i = 0; i <= iu; i++) {
+                        z = matrixT[i][iu - 1];
+                        matrixT[i][iu - 1] = q * z + p * matrixT[i][iu];
+                        matrixT[i][iu] = q * matrixT[i][iu] - p * z;
+                    }
+
+                    // Accumulate transformations
+                    for (int i = 0; i <= n - 1; i++) {
+                        z = matrixP[i][iu - 1];
+                        matrixP[i][iu - 1] = q * z + p * matrixP[i][iu];
+                        matrixP[i][iu] = q * matrixP[i][iu] - p * z;
+                    }
+                }
+                iu -= 2;
+                iteration = 0;
+            } else {
+                // No convergence yet
+                computeShift(il, iu, iteration, shift);
+
+                // stop transformation after too many iterations
+                if (++iteration > MAX_ITERATIONS) {
+                    throw new MaxCountExceededException(
+                            LocalizedFormats.CONVERGENCE_FAILED, MAX_ITERATIONS);
+                }
+
+                // the initial houseHolder vector for the QR step
+                final double[] hVec = new double[3];
+
+                final int im = initQRStep(il, iu, shift, hVec);
+                performDoubleQRStep(il, im, iu, shift, hVec);
+            }
+        }
+    }
+
+    /**
+     * Computes the L1 norm of the (quasi-)triangular matrix T.
+     *
+     * @return the L1 norm of matrix T
+     */
+    private double getNorm() {
+        double norm = 0.0;
+        for (int i = 0; i < matrixT.length; i++) {
+            // as matrix T is (quasi-)triangular, also take the sub-diagonal element into account
+            for (int j = FastMath.max(i - 1, 0); j < matrixT.length; j++) {
+                norm += FastMath.abs(matrixT[i][j]);
+            }
+        }
+        return norm;
+    }
+
+    /**
+     * Find the first small sub-diagonal element and returns its index.
+     *
+     * @param startIdx the starting index for the search
+     * @param norm the L1 norm of the matrix
+     * @return the index of the first small sub-diagonal element
+     */
+    private int findSmallSubDiagonalElement(final int startIdx, final double norm) {
+        int l = startIdx;
+        while (l > 0) {
+            double s = FastMath.abs(matrixT[l - 1][l - 1]) + FastMath.abs(matrixT[l][l]);
+            if (s == 0.0) {
+                s = norm;
+            }
+            if (FastMath.abs(matrixT[l][l - 1]) < epsilon * s) {
+                break;
+            }
+            l--;
+        }
+        return l;
+    }
+
+    /**
+     * Compute the shift for the current iteration.
+     *
+     * @param l the index of the small sub-diagonal element
+     * @param idx the current eigenvalue index
+     * @param iteration the current iteration
+     * @param shift holder for shift information
+     */
+    private void computeShift(
+            final int l, final int idx, final int iteration, final ShiftInfo shift) {
+        // Form shift
+        shift.x = matrixT[idx][idx];
+        shift.y = shift.w = 0.0;
+        if (l < idx) {
+            shift.y = matrixT[idx - 1][idx - 1];
+            shift.w = matrixT[idx][idx - 1] * matrixT[idx - 1][idx];
+        }
+
+        // Wilkinson's original ad hoc shift
+        if (iteration == 10) {
+            shift.exShift += shift.x;
+            for (int i = 0; i <= idx; i++) {
+                matrixT[i][i] -= shift.x;
+            }
+            final double s =
+                    FastMath.abs(matrixT[idx][idx - 1]) + FastMath.abs(matrixT[idx - 1][idx - 2]);
+            shift.x = 0.75 * s;
+            shift.y = 0.75 * s;
+            shift.w = -0.4375 * s * s;
+        }
+
+        // MATLAB's new ad hoc shift
+        if (iteration == 30) {
+            double s = (shift.y - shift.x) / 2.0;
+            s = s * s + shift.w;
+            if (s > 0.0) {
+                s = FastMath.sqrt(s);
+                if (shift.y < shift.x) {
+                    s = -s;
+                }
+                s = shift.x - shift.w / ((shift.y - shift.x) / 2.0 + s);
+                for (int i = 0; i <= idx; i++) {
+                    matrixT[i][i] -= s;
+                }
+                shift.exShift += s;
+                shift.x = shift.y = shift.w = 0.964;
+            }
+        }
+    }
+
+    /**
+     * Initialize the householder vectors for the QR step.
+     *
+     * @param il the index of the small sub-diagonal element
+     * @param iu the current eigenvalue index
+     * @param shift shift information holder
+     * @param hVec the initial houseHolder vector
+     * @return the start index for the QR step
+     */
+    private int initQRStep(int il, final int iu, final ShiftInfo shift, double[] hVec) {
+        // Look for two consecutive small sub-diagonal elements
+        int im = iu - 2;
+        while (im >= il) {
+            final double z = matrixT[im][im];
+            final double r = shift.x - z;
+            double s = shift.y - z;
+            hVec[0] = (r * s - shift.w) / matrixT[im + 1][im] + matrixT[im][im + 1];
+            hVec[1] = matrixT[im + 1][im + 1] - z - r - s;
+            hVec[2] = matrixT[im + 2][im + 1];
+
+            if (im == il) {
+                break;
+            }
+
+            final double lhs =
+                    FastMath.abs(matrixT[im][im - 1])
+                            * (FastMath.abs(hVec[1]) + FastMath.abs(hVec[2]));
+            final double rhs =
+                    FastMath.abs(hVec[0])
+                            * (FastMath.abs(matrixT[im - 1][im - 1])
+                                    + FastMath.abs(z)
+                                    + FastMath.abs(matrixT[im + 1][im + 1]));
+
+            if (lhs < epsilon * rhs) {
+                break;
+            }
+            im--;
+        }
+
+        return im;
+    }
+
+    /**
+     * Perform a double QR step involving rows l:idx and columns m:n
+     *
+     * @param il the index of the small sub-diagonal element
+     * @param im the start index for the QR step
+     * @param iu the current eigenvalue index
+     * @param shift shift information holder
+     * @param hVec the initial houseHolder vector
+     */
+    private void performDoubleQRStep(
+            final int il, final int im, final int iu, final ShiftInfo shift, final double[] hVec) {
+
+        final int n = matrixT.length;
+        double p = hVec[0];
+        double q = hVec[1];
+        double r = hVec[2];
+
+        for (int k = im; k <= iu - 1; k++) {
+            boolean notlast = k != (iu - 1);
+            if (k != im) {
+                p = matrixT[k][k - 1];
+                q = matrixT[k + 1][k - 1];
+                r = notlast ? matrixT[k + 2][k - 1] : 0.0;
+                shift.x = FastMath.abs(p) + FastMath.abs(q) + FastMath.abs(r);
+                if (Precision.equals(shift.x, 0.0, epsilon)) {
+                    continue;
+                }
+                p /= shift.x;
+                q /= shift.x;
+                r /= shift.x;
+            }
+            double s = FastMath.sqrt(p * p + q * q + r * r);
+            if (p < 0.0) {
+                s = -s;
+            }
+            if (s != 0.0) {
+                if (k != im) {
+                    matrixT[k][k - 1] = -s * shift.x;
+                } else if (il != im) {
+                    matrixT[k][k - 1] = -matrixT[k][k - 1];
+                }
+                p += s;
+                shift.x = p / s;
+                shift.y = q / s;
+                double z = r / s;
+                q /= p;
+                r /= p;
+
+                // Row modification
+                for (int j = k; j < n; j++) {
+                    p = matrixT[k][j] + q * matrixT[k + 1][j];
+                    if (notlast) {
+                        p += r * matrixT[k + 2][j];
+                        matrixT[k + 2][j] -= p * z;
+                    }
+                    matrixT[k][j] -= p * shift.x;
+                    matrixT[k + 1][j] -= p * shift.y;
+                }
+
+                // Column modification
+                for (int i = 0; i <= FastMath.min(iu, k + 3); i++) {
+                    p = shift.x * matrixT[i][k] + shift.y * matrixT[i][k + 1];
+                    if (notlast) {
+                        p += z * matrixT[i][k + 2];
+                        matrixT[i][k + 2] -= p * r;
+                    }
+                    matrixT[i][k] -= p;
+                    matrixT[i][k + 1] -= p * q;
+                }
+
+                // Accumulate transformations
+                final int high = matrixT.length - 1;
+                for (int i = 0; i <= high; i++) {
+                    p = shift.x * matrixP[i][k] + shift.y * matrixP[i][k + 1];
+                    if (notlast) {
+                        p += z * matrixP[i][k + 2];
+                        matrixP[i][k + 2] -= p * r;
+                    }
+                    matrixP[i][k] -= p;
+                    matrixP[i][k + 1] -= p * q;
+                }
+            } // (s != 0)
+        } // k loop
+
+        // clean up pollution due to round-off errors
+        for (int i = im + 2; i <= iu; i++) {
+            matrixT[i][i - 2] = 0.0;
+            if (i > im + 2) {
+                matrixT[i][i - 3] = 0.0;
+            }
+        }
+    }
+
+    /**
+     * Internal data structure holding the current shift information. Contains variable names as
+     * present in the original JAMA code.
+     */
+    private static class ShiftInfo {
+        // CHECKSTYLE: stop all
+
+        /** x shift info */
+        double x;
+
+        /** y shift info */
+        double y;
+
+        /** w shift info */
+        double w;
+
+        /** Indicates an exceptional shift. */
+        double exShift;
+
+        // CHECKSTYLE: resume all
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/SingularMatrixException.java b/src/main/java/org/apache/commons/math3/linear/SingularMatrixException.java
new file mode 100644
index 0000000..ea6e189
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/SingularMatrixException.java
@@ -0,0 +1,35 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Exception to be thrown when a non-singular matrix is expected.
+ *
+ * @since 3.0
+ */
+public class SingularMatrixException extends MathIllegalArgumentException {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = -4206514844735401070L;
+
+    /** Construct an exception. */
+    public SingularMatrixException() {
+        super(LocalizedFormats.SINGULAR_MATRIX);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/SingularOperatorException.java b/src/main/java/org/apache/commons/math3/linear/SingularOperatorException.java
new file mode 100644
index 0000000..e211997
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/SingularOperatorException.java
@@ -0,0 +1,35 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Exception to be thrown when trying to invert a singular operator.
+ *
+ * @since 3.0
+ */
+public class SingularOperatorException extends MathIllegalArgumentException {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = -476049978595245033L;
+
+    /** Creates a new instance of this class. */
+    public SingularOperatorException() {
+        super(LocalizedFormats.SINGULAR_OPERATOR);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/SingularValueDecomposition.java b/src/main/java/org/apache/commons/math3/linear/SingularValueDecomposition.java
new file mode 100644
index 0000000..7d5c6b7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/SingularValueDecomposition.java
@@ -0,0 +1,778 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.Precision;
+
+/**
+ * Calculates the compact Singular Value Decomposition of a matrix.
+ *
+ * <p>The Singular Value Decomposition of matrix A is a set of three matrices: U, &Sigma; and V such
+ * that A = U &times; &Sigma; &times; V<sup>T</sup>. Let A be a m &times; n matrix, then U is a m
+ * &times; p orthogonal matrix, &Sigma; is a p &times; p diagonal matrix with positive or null
+ * elements, V is a p &times; n orthogonal matrix (hence V<sup>T</sup> is also orthogonal) where
+ * p=min(m,n).
+ *
+ * <p>This class is similar to the class with similar name from the <a
+ * href="http://math.nist.gov/javanumerics/jama/">JAMA</a> library, with the following changes:
+ *
+ * <ul>
+ *   <li>the {@code norm2} method which has been renamed as {@link #getNorm() getNorm},
+ *   <li>the {@code cond} method which has been renamed as {@link #getConditionNumber()
+ *       getConditionNumber},
+ *   <li>the {@code rank} method which has been renamed as {@link #getRank() getRank},
+ *   <li>a {@link #getUT() getUT} method has been added,
+ *   <li>a {@link #getVT() getVT} method has been added,
+ *   <li>a {@link #getSolver() getSolver} method has been added,
+ *   <li>a {@link #getCovariance(double) getCovariance} method has been added.
+ * </ul>
+ *
+ * @see <a href="http://mathworld.wolfram.com/SingularValueDecomposition.html">MathWorld</a>
+ * @see <a href="http://en.wikipedia.org/wiki/Singular_value_decomposition">Wikipedia</a>
+ * @since 2.0 (changed to concrete class in 3.0)
+ */
+public class SingularValueDecomposition {
+    /** Relative threshold for small singular values. */
+    private static final double EPS = 0x1.0p-52;
+
+    /** Absolute threshold for small singular values. */
+    private static final double TINY = 0x1.0p-966;
+
+    /** Computed singular values. */
+    private final double[] singularValues;
+
+    /** max(row dimension, column dimension). */
+    private final int m;
+
+    /** min(row dimension, column dimension). */
+    private final int n;
+
+    /** Indicator for transposed matrix. */
+    private final boolean transposed;
+
+    /** Cached value of U matrix. */
+    private final RealMatrix cachedU;
+
+    /** Cached value of transposed U matrix. */
+    private RealMatrix cachedUt;
+
+    /** Cached value of S (diagonal) matrix. */
+    private RealMatrix cachedS;
+
+    /** Cached value of V matrix. */
+    private final RealMatrix cachedV;
+
+    /** Cached value of transposed V matrix. */
+    private RealMatrix cachedVt;
+
+    /**
+     * Tolerance value for small singular values, calculated once we have populated
+     * "singularValues".
+     */
+    private final double tol;
+
+    /**
+     * Calculates the compact Singular Value Decomposition of the given matrix.
+     *
+     * @param matrix Matrix to decompose.
+     */
+    public SingularValueDecomposition(final RealMatrix matrix) {
+        final double[][] A;
+
+        // "m" is always the largest dimension.
+        if (matrix.getRowDimension() < matrix.getColumnDimension()) {
+            transposed = true;
+            A = matrix.transpose().getData();
+            m = matrix.getColumnDimension();
+            n = matrix.getRowDimension();
+        } else {
+            transposed = false;
+            A = matrix.getData();
+            m = matrix.getRowDimension();
+            n = matrix.getColumnDimension();
+        }
+
+        singularValues = new double[n];
+        final double[][] U = new double[m][n];
+        final double[][] V = new double[n][n];
+        final double[] e = new double[n];
+        final double[] work = new double[m];
+        // Reduce A to bidiagonal form, storing the diagonal elements
+        // in s and the super-diagonal elements in e.
+        final int nct = FastMath.min(m - 1, n);
+        final int nrt = FastMath.max(0, n - 2);
+        for (int k = 0; k < FastMath.max(nct, nrt); k++) {
+            if (k < nct) {
+                // Compute the transformation for the k-th column and
+                // place the k-th diagonal in s[k].
+                // Compute 2-norm of k-th column without under/overflow.
+                singularValues[k] = 0;
+                for (int i = k; i < m; i++) {
+                    singularValues[k] = FastMath.hypot(singularValues[k], A[i][k]);
+                }
+                if (singularValues[k] != 0) {
+                    if (A[k][k] < 0) {
+                        singularValues[k] = -singularValues[k];
+                    }
+                    for (int i = k; i < m; i++) {
+                        A[i][k] /= singularValues[k];
+                    }
+                    A[k][k] += 1;
+                }
+                singularValues[k] = -singularValues[k];
+            }
+            for (int j = k + 1; j < n; j++) {
+                if (k < nct && singularValues[k] != 0) {
+                    // Apply the transformation.
+                    double t = 0;
+                    for (int i = k; i < m; i++) {
+                        t += A[i][k] * A[i][j];
+                    }
+                    t = -t / A[k][k];
+                    for (int i = k; i < m; i++) {
+                        A[i][j] += t * A[i][k];
+                    }
+                }
+                // Place the k-th row of A into e for the
+                // subsequent calculation of the row transformation.
+                e[j] = A[k][j];
+            }
+            if (k < nct) {
+                // Place the transformation in U for subsequent back
+                // multiplication.
+                for (int i = k; i < m; i++) {
+                    U[i][k] = A[i][k];
+                }
+            }
+            if (k < nrt) {
+                // Compute the k-th row transformation and place the
+                // k-th super-diagonal in e[k].
+                // Compute 2-norm without under/overflow.
+                e[k] = 0;
+                for (int i = k + 1; i < n; i++) {
+                    e[k] = FastMath.hypot(e[k], e[i]);
+                }
+                if (e[k] != 0) {
+                    if (e[k + 1] < 0) {
+                        e[k] = -e[k];
+                    }
+                    for (int i = k + 1; i < n; i++) {
+                        e[i] /= e[k];
+                    }
+                    e[k + 1] += 1;
+                }
+                e[k] = -e[k];
+                if (k + 1 < m && e[k] != 0) {
+                    // Apply the transformation.
+                    for (int i = k + 1; i < m; i++) {
+                        work[i] = 0;
+                    }
+                    for (int j = k + 1; j < n; j++) {
+                        for (int i = k + 1; i < m; i++) {
+                            work[i] += e[j] * A[i][j];
+                        }
+                    }
+                    for (int j = k + 1; j < n; j++) {
+                        final double t = -e[j] / e[k + 1];
+                        for (int i = k + 1; i < m; i++) {
+                            A[i][j] += t * work[i];
+                        }
+                    }
+                }
+
+                // Place the transformation in V for subsequent
+                // back multiplication.
+                for (int i = k + 1; i < n; i++) {
+                    V[i][k] = e[i];
+                }
+            }
+        }
+        // Set up the final bidiagonal matrix or order p.
+        int p = n;
+        if (nct < n) {
+            singularValues[nct] = A[nct][nct];
+        }
+        if (m < p) {
+            singularValues[p - 1] = 0;
+        }
+        if (nrt + 1 < p) {
+            e[nrt] = A[nrt][p - 1];
+        }
+        e[p - 1] = 0;
+
+        // Generate U.
+        for (int j = nct; j < n; j++) {
+            for (int i = 0; i < m; i++) {
+                U[i][j] = 0;
+            }
+            U[j][j] = 1;
+        }
+        for (int k = nct - 1; k >= 0; k--) {
+            if (singularValues[k] != 0) {
+                for (int j = k + 1; j < n; j++) {
+                    double t = 0;
+                    for (int i = k; i < m; i++) {
+                        t += U[i][k] * U[i][j];
+                    }
+                    t = -t / U[k][k];
+                    for (int i = k; i < m; i++) {
+                        U[i][j] += t * U[i][k];
+                    }
+                }
+                for (int i = k; i < m; i++) {
+                    U[i][k] = -U[i][k];
+                }
+                U[k][k] = 1 + U[k][k];
+                for (int i = 0; i < k - 1; i++) {
+                    U[i][k] = 0;
+                }
+            } else {
+                for (int i = 0; i < m; i++) {
+                    U[i][k] = 0;
+                }
+                U[k][k] = 1;
+            }
+        }
+
+        // Generate V.
+        for (int k = n - 1; k >= 0; k--) {
+            if (k < nrt && e[k] != 0) {
+                for (int j = k + 1; j < n; j++) {
+                    double t = 0;
+                    for (int i = k + 1; i < n; i++) {
+                        t += V[i][k] * V[i][j];
+                    }
+                    t = -t / V[k + 1][k];
+                    for (int i = k + 1; i < n; i++) {
+                        V[i][j] += t * V[i][k];
+                    }
+                }
+            }
+            for (int i = 0; i < n; i++) {
+                V[i][k] = 0;
+            }
+            V[k][k] = 1;
+        }
+
+        // Main iteration loop for the singular values.
+        final int pp = p - 1;
+        while (p > 0) {
+            int k;
+            int kase;
+            // Here is where a test for too many iterations would go.
+            // This section of the program inspects for
+            // negligible elements in the s and e arrays.  On
+            // completion the variables kase and k are set as follows.
+            // kase = 1     if s(p) and e[k-1] are negligible and k<p
+            // kase = 2     if s(k) is negligible and k<p
+            // kase = 3     if e[k-1] is negligible, k<p, and
+            //              s(k), ..., s(p) are not negligible (qr step).
+            // kase = 4     if e(p-1) is negligible (convergence).
+            for (k = p - 2; k >= 0; k--) {
+                final double threshold =
+                        TINY
+                                + EPS
+                                        * (FastMath.abs(singularValues[k])
+                                                + FastMath.abs(singularValues[k + 1]));
+
+                // the following condition is written this way in order
+                // to break out of the loop when NaN occurs, writing it
+                // as "if (FastMath.abs(e[k]) <= threshold)" would loop
+                // indefinitely in case of NaNs because comparison on NaNs
+                // always return false, regardless of what is checked
+                // see issue MATH-947
+                if (!(FastMath.abs(e[k]) > threshold)) {
+                    e[k] = 0;
+                    break;
+                }
+            }
+
+            if (k == p - 2) {
+                kase = 4;
+            } else {
+                int ks;
+                for (ks = p - 1; ks >= k; ks--) {
+                    if (ks == k) {
+                        break;
+                    }
+                    final double t =
+                            (ks != p ? FastMath.abs(e[ks]) : 0)
+                                    + (ks != k + 1 ? FastMath.abs(e[ks - 1]) : 0);
+                    if (FastMath.abs(singularValues[ks]) <= TINY + EPS * t) {
+                        singularValues[ks] = 0;
+                        break;
+                    }
+                }
+                if (ks == k) {
+                    kase = 3;
+                } else if (ks == p - 1) {
+                    kase = 1;
+                } else {
+                    kase = 2;
+                    k = ks;
+                }
+            }
+            k++;
+            // Perform the task indicated by kase.
+            switch (kase) {
+                    // Deflate negligible s(p).
+                case 1:
+                    {
+                        double f = e[p - 2];
+                        e[p - 2] = 0;
+                        for (int j = p - 2; j >= k; j--) {
+                            double t = FastMath.hypot(singularValues[j], f);
+                            final double cs = singularValues[j] / t;
+                            final double sn = f / t;
+                            singularValues[j] = t;
+                            if (j != k) {
+                                f = -sn * e[j - 1];
+                                e[j - 1] = cs * e[j - 1];
+                            }
+
+                            for (int i = 0; i < n; i++) {
+                                t = cs * V[i][j] + sn * V[i][p - 1];
+                                V[i][p - 1] = -sn * V[i][j] + cs * V[i][p - 1];
+                                V[i][j] = t;
+                            }
+                        }
+                    }
+                    break;
+                    // Split at negligible s(k).
+                case 2:
+                    {
+                        double f = e[k - 1];
+                        e[k - 1] = 0;
+                        for (int j = k; j < p; j++) {
+                            double t = FastMath.hypot(singularValues[j], f);
+                            final double cs = singularValues[j] / t;
+                            final double sn = f / t;
+                            singularValues[j] = t;
+                            f = -sn * e[j];
+                            e[j] = cs * e[j];
+
+                            for (int i = 0; i < m; i++) {
+                                t = cs * U[i][j] + sn * U[i][k - 1];
+                                U[i][k - 1] = -sn * U[i][j] + cs * U[i][k - 1];
+                                U[i][j] = t;
+                            }
+                        }
+                    }
+                    break;
+                    // Perform one qr step.
+                case 3:
+                    {
+                        // Calculate the shift.
+                        final double maxPm1Pm2 =
+                                FastMath.max(
+                                        FastMath.abs(singularValues[p - 1]),
+                                        FastMath.abs(singularValues[p - 2]));
+                        final double scale =
+                                FastMath.max(
+                                        FastMath.max(
+                                                FastMath.max(maxPm1Pm2, FastMath.abs(e[p - 2])),
+                                                FastMath.abs(singularValues[k])),
+                                        FastMath.abs(e[k]));
+                        final double sp = singularValues[p - 1] / scale;
+                        final double spm1 = singularValues[p - 2] / scale;
+                        final double epm1 = e[p - 2] / scale;
+                        final double sk = singularValues[k] / scale;
+                        final double ek = e[k] / scale;
+                        final double b = ((spm1 + sp) * (spm1 - sp) + epm1 * epm1) / 2.0;
+                        final double c = (sp * epm1) * (sp * epm1);
+                        double shift = 0;
+                        if (b != 0 || c != 0) {
+                            shift = FastMath.sqrt(b * b + c);
+                            if (b < 0) {
+                                shift = -shift;
+                            }
+                            shift = c / (b + shift);
+                        }
+                        double f = (sk + sp) * (sk - sp) + shift;
+                        double g = sk * ek;
+                        // Chase zeros.
+                        for (int j = k; j < p - 1; j++) {
+                            double t = FastMath.hypot(f, g);
+                            double cs = f / t;
+                            double sn = g / t;
+                            if (j != k) {
+                                e[j - 1] = t;
+                            }
+                            f = cs * singularValues[j] + sn * e[j];
+                            e[j] = cs * e[j] - sn * singularValues[j];
+                            g = sn * singularValues[j + 1];
+                            singularValues[j + 1] = cs * singularValues[j + 1];
+
+                            for (int i = 0; i < n; i++) {
+                                t = cs * V[i][j] + sn * V[i][j + 1];
+                                V[i][j + 1] = -sn * V[i][j] + cs * V[i][j + 1];
+                                V[i][j] = t;
+                            }
+                            t = FastMath.hypot(f, g);
+                            cs = f / t;
+                            sn = g / t;
+                            singularValues[j] = t;
+                            f = cs * e[j] + sn * singularValues[j + 1];
+                            singularValues[j + 1] = -sn * e[j] + cs * singularValues[j + 1];
+                            g = sn * e[j + 1];
+                            e[j + 1] = cs * e[j + 1];
+                            if (j < m - 1) {
+                                for (int i = 0; i < m; i++) {
+                                    t = cs * U[i][j] + sn * U[i][j + 1];
+                                    U[i][j + 1] = -sn * U[i][j] + cs * U[i][j + 1];
+                                    U[i][j] = t;
+                                }
+                            }
+                        }
+                        e[p - 2] = f;
+                    }
+                    break;
+                    // Convergence.
+                default:
+                    {
+                        // Make the singular values positive.
+                        if (singularValues[k] <= 0) {
+                            singularValues[k] = singularValues[k] < 0 ? -singularValues[k] : 0;
+
+                            for (int i = 0; i <= pp; i++) {
+                                V[i][k] = -V[i][k];
+                            }
+                        }
+                        // Order the singular values.
+                        while (k < pp) {
+                            if (singularValues[k] >= singularValues[k + 1]) {
+                                break;
+                            }
+                            double t = singularValues[k];
+                            singularValues[k] = singularValues[k + 1];
+                            singularValues[k + 1] = t;
+                            if (k < n - 1) {
+                                for (int i = 0; i < n; i++) {
+                                    t = V[i][k + 1];
+                                    V[i][k + 1] = V[i][k];
+                                    V[i][k] = t;
+                                }
+                            }
+                            if (k < m - 1) {
+                                for (int i = 0; i < m; i++) {
+                                    t = U[i][k + 1];
+                                    U[i][k + 1] = U[i][k];
+                                    U[i][k] = t;
+                                }
+                            }
+                            k++;
+                        }
+                        p--;
+                    }
+                    break;
+            }
+        }
+
+        // Set the small value tolerance used to calculate rank and pseudo-inverse
+        tol = FastMath.max(m * singularValues[0] * EPS, FastMath.sqrt(Precision.SAFE_MIN));
+
+        if (!transposed) {
+            cachedU = MatrixUtils.createRealMatrix(U);
+            cachedV = MatrixUtils.createRealMatrix(V);
+        } else {
+            cachedU = MatrixUtils.createRealMatrix(V);
+            cachedV = MatrixUtils.createRealMatrix(U);
+        }
+    }
+
+    /**
+     * Returns the matrix U of the decomposition.
+     *
+     * <p>U is an orthogonal matrix, i.e. its transpose is also its inverse.
+     *
+     * @return the U matrix
+     * @see #getUT()
+     */
+    public RealMatrix getU() {
+        // return the cached matrix
+        return cachedU;
+    }
+
+    /**
+     * Returns the transpose of the matrix U of the decomposition.
+     *
+     * <p>U is an orthogonal matrix, i.e. its transpose is also its inverse.
+     *
+     * @return the U matrix (or null if decomposed matrix is singular)
+     * @see #getU()
+     */
+    public RealMatrix getUT() {
+        if (cachedUt == null) {
+            cachedUt = getU().transpose();
+        }
+        // return the cached matrix
+        return cachedUt;
+    }
+
+    /**
+     * Returns the diagonal matrix &Sigma; of the decomposition.
+     *
+     * <p>&Sigma; is a diagonal matrix. The singular values are provided in non-increasing order,
+     * for compatibility with Jama.
+     *
+     * @return the &Sigma; matrix
+     */
+    public RealMatrix getS() {
+        if (cachedS == null) {
+            // cache the matrix for subsequent calls
+            cachedS = MatrixUtils.createRealDiagonalMatrix(singularValues);
+        }
+        return cachedS;
+    }
+
+    /**
+     * Returns the diagonal elements of the matrix &Sigma; of the decomposition.
+     *
+     * <p>The singular values are provided in non-increasing order, for compatibility with Jama.
+     *
+     * @return the diagonal elements of the &Sigma; matrix
+     */
+    public double[] getSingularValues() {
+        return singularValues.clone();
+    }
+
+    /**
+     * Returns the matrix V of the decomposition.
+     *
+     * <p>V is an orthogonal matrix, i.e. its transpose is also its inverse.
+     *
+     * @return the V matrix (or null if decomposed matrix is singular)
+     * @see #getVT()
+     */
+    public RealMatrix getV() {
+        // return the cached matrix
+        return cachedV;
+    }
+
+    /**
+     * Returns the transpose of the matrix V of the decomposition.
+     *
+     * <p>V is an orthogonal matrix, i.e. its transpose is also its inverse.
+     *
+     * @return the V matrix (or null if decomposed matrix is singular)
+     * @see #getV()
+     */
+    public RealMatrix getVT() {
+        if (cachedVt == null) {
+            cachedVt = getV().transpose();
+        }
+        // return the cached matrix
+        return cachedVt;
+    }
+
+    /**
+     * Returns the n &times; n covariance matrix.
+     *
+     * <p>The covariance matrix is V &times; J &times; V<sup>T</sup> where J is the diagonal matrix
+     * of the inverse of the squares of the singular values.
+     *
+     * @param minSingularValue value below which singular values are ignored (a 0 or negative value
+     *     implies all singular value will be used)
+     * @return covariance matrix
+     * @exception IllegalArgumentException if minSingularValue is larger than the largest singular
+     *     value, meaning all singular values are ignored
+     */
+    public RealMatrix getCovariance(final double minSingularValue) {
+        // get the number of singular values to consider
+        final int p = singularValues.length;
+        int dimension = 0;
+        while (dimension < p && singularValues[dimension] >= minSingularValue) {
+            ++dimension;
+        }
+
+        if (dimension == 0) {
+            throw new NumberIsTooLargeException(
+                    LocalizedFormats.TOO_LARGE_CUTOFF_SINGULAR_VALUE,
+                    minSingularValue,
+                    singularValues[0],
+                    true);
+        }
+
+        final double[][] data = new double[dimension][p];
+        getVT().walkInOptimizedOrder(
+                        new DefaultRealMatrixPreservingVisitor() {
+                            /** {@inheritDoc} */
+                            @Override
+                            public void visit(final int row, final int column, final double value) {
+                                data[row][column] = value / singularValues[row];
+                            }
+                        },
+                        0,
+                        dimension - 1,
+                        0,
+                        p - 1);
+
+        RealMatrix jv = new Array2DRowRealMatrix(data, false);
+        return jv.transpose().multiply(jv);
+    }
+
+    /**
+     * Returns the L<sub>2</sub> norm of the matrix.
+     *
+     * <p>The L<sub>2</sub> norm is max(|A &times; u|<sub>2</sub> / |u|<sub>2</sub>), where
+     * |.|<sub>2</sub> denotes the vectorial 2-norm (i.e. the traditional euclidian norm).
+     *
+     * @return norm
+     */
+    public double getNorm() {
+        return singularValues[0];
+    }
+
+    /**
+     * Return the condition number of the matrix.
+     *
+     * @return condition number of the matrix
+     */
+    public double getConditionNumber() {
+        return singularValues[0] / singularValues[n - 1];
+    }
+
+    /**
+     * Computes the inverse of the condition number. In cases of rank deficiency, the {@link
+     * #getConditionNumber() condition number} will become undefined.
+     *
+     * @return the inverse of the condition number.
+     */
+    public double getInverseConditionNumber() {
+        return singularValues[n - 1] / singularValues[0];
+    }
+
+    /**
+     * Return the effective numerical matrix rank.
+     *
+     * <p>The effective numerical rank is the number of non-negligible singular values. The
+     * threshold used to identify non-negligible terms is max(m,n) &times; ulp(s<sub>1</sub>) where
+     * ulp(s<sub>1</sub>) is the least significant bit of the largest singular value.
+     *
+     * @return effective numerical matrix rank
+     */
+    public int getRank() {
+        int r = 0;
+        for (int i = 0; i < singularValues.length; i++) {
+            if (singularValues[i] > tol) {
+                r++;
+            }
+        }
+        return r;
+    }
+
+    /**
+     * Get a solver for finding the A &times; X = B solution in least square sense.
+     *
+     * @return a solver
+     */
+    public DecompositionSolver getSolver() {
+        return new Solver(singularValues, getUT(), getV(), getRank() == m, tol);
+    }
+
+    /** Specialized solver. */
+    private static class Solver implements DecompositionSolver {
+        /** Pseudo-inverse of the initial matrix. */
+        private final RealMatrix pseudoInverse;
+
+        /** Singularity indicator. */
+        private boolean nonSingular;
+
+        /**
+         * Build a solver from decomposed matrix.
+         *
+         * @param singularValues Singular values.
+         * @param uT U<sup>T</sup> matrix of the decomposition.
+         * @param v V matrix of the decomposition.
+         * @param nonSingular Singularity indicator.
+         * @param tol tolerance for singular values
+         */
+        private Solver(
+                final double[] singularValues,
+                final RealMatrix uT,
+                final RealMatrix v,
+                final boolean nonSingular,
+                final double tol) {
+            final double[][] suT = uT.getData();
+            for (int i = 0; i < singularValues.length; ++i) {
+                final double a;
+                if (singularValues[i] > tol) {
+                    a = 1 / singularValues[i];
+                } else {
+                    a = 0;
+                }
+                final double[] suTi = suT[i];
+                for (int j = 0; j < suTi.length; ++j) {
+                    suTi[j] *= a;
+                }
+            }
+            pseudoInverse = v.multiply(new Array2DRowRealMatrix(suT, false));
+            this.nonSingular = nonSingular;
+        }
+
+        /**
+         * Solve the linear equation A &times; X = B in least square sense.
+         *
+         * <p>The m&times;n matrix A may not be square, the solution X is such that ||A &times; X -
+         * B|| is minimal.
+         *
+         * @param b Right-hand side of the equation A &times; X = B
+         * @return a vector X that minimizes the two norm of A &times; X - B
+         * @throws org.apache.commons.math3.exception.DimensionMismatchException if the matrices
+         *     dimensions do not match.
+         */
+        public RealVector solve(final RealVector b) {
+            return pseudoInverse.operate(b);
+        }
+
+        /**
+         * Solve the linear equation A &times; X = B in least square sense.
+         *
+         * <p>The m&times;n matrix A may not be square, the solution X is such that ||A &times; X -
+         * B|| is minimal.
+         *
+         * @param b Right-hand side of the equation A &times; X = B
+         * @return a matrix X that minimizes the two norm of A &times; X - B
+         * @throws org.apache.commons.math3.exception.DimensionMismatchException if the matrices
+         *     dimensions do not match.
+         */
+        public RealMatrix solve(final RealMatrix b) {
+            return pseudoInverse.multiply(b);
+        }
+
+        /**
+         * Check if the decomposed matrix is non-singular.
+         *
+         * @return {@code true} if the decomposed matrix is non-singular.
+         */
+        public boolean isNonSingular() {
+            return nonSingular;
+        }
+
+        /**
+         * Get the pseudo-inverse of the decomposed matrix.
+         *
+         * @return the inverse matrix.
+         */
+        public RealMatrix getInverse() {
+            return pseudoInverse;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/SparseFieldMatrix.java b/src/main/java/org/apache/commons/math3/linear/SparseFieldMatrix.java
new file mode 100644
index 0000000..a1df939
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/SparseFieldMatrix.java
@@ -0,0 +1,184 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.FieldElement;
+import org.apache.commons.math3.util.OpenIntToFieldHashMap;
+
+/**
+ * Sparse matrix implementation based on an open addressed map.
+ *
+ * <p>Caveat: This implementation assumes that, for any {@code x}, the equality {@code x * 0d == 0d}
+ * holds. But it is is not true for {@code NaN}. Moreover, zero entries will lose their sign. Some
+ * operations (that involve {@code NaN} and/or infinities) may thus give incorrect results.
+ *
+ * @param <T> the type of the field elements
+ * @since 2.0
+ */
+public class SparseFieldMatrix<T extends FieldElement<T>> extends AbstractFieldMatrix<T> {
+
+    /** Storage for (sparse) matrix elements. */
+    private final OpenIntToFieldHashMap<T> entries;
+
+    /** Row dimension. */
+    private final int rows;
+
+    /** Column dimension. */
+    private final int columns;
+
+    /**
+     * Create a matrix with no data.
+     *
+     * @param field Field to which the elements belong.
+     */
+    public SparseFieldMatrix(final Field<T> field) {
+        super(field);
+        rows = 0;
+        columns = 0;
+        entries = new OpenIntToFieldHashMap<T>(field);
+    }
+
+    /**
+     * Create a new SparseFieldMatrix<T> with the supplied row and column dimensions.
+     *
+     * @param field Field to which the elements belong.
+     * @param rowDimension Number of rows in the new matrix.
+     * @param columnDimension Number of columns in the new matrix.
+     * @throws org.apache.commons.math3.exception.NotStrictlyPositiveException if row or column
+     *     dimension is not positive.
+     */
+    public SparseFieldMatrix(
+            final Field<T> field, final int rowDimension, final int columnDimension) {
+        super(field, rowDimension, columnDimension);
+        this.rows = rowDimension;
+        this.columns = columnDimension;
+        entries = new OpenIntToFieldHashMap<T>(field);
+    }
+
+    /**
+     * Copy constructor.
+     *
+     * @param other Instance to copy.
+     */
+    public SparseFieldMatrix(SparseFieldMatrix<T> other) {
+        super(other.getField(), other.getRowDimension(), other.getColumnDimension());
+        rows = other.getRowDimension();
+        columns = other.getColumnDimension();
+        entries = new OpenIntToFieldHashMap<T>(other.entries);
+    }
+
+    /**
+     * Generic copy constructor.
+     *
+     * @param other Instance to copy.
+     */
+    public SparseFieldMatrix(FieldMatrix<T> other) {
+        super(other.getField(), other.getRowDimension(), other.getColumnDimension());
+        rows = other.getRowDimension();
+        columns = other.getColumnDimension();
+        entries = new OpenIntToFieldHashMap<T>(getField());
+        for (int i = 0; i < rows; i++) {
+            for (int j = 0; j < columns; j++) {
+                setEntry(i, j, other.getEntry(i, j));
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addToEntry(int row, int column, T increment) {
+        checkRowIndex(row);
+        checkColumnIndex(column);
+        final int key = computeKey(row, column);
+        final T value = entries.get(key).add(increment);
+        if (getField().getZero().equals(value)) {
+            entries.remove(key);
+        } else {
+            entries.put(key, value);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public FieldMatrix<T> copy() {
+        return new SparseFieldMatrix<T>(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public FieldMatrix<T> createMatrix(int rowDimension, int columnDimension) {
+        return new SparseFieldMatrix<T>(getField(), rowDimension, columnDimension);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getColumnDimension() {
+        return columns;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public T getEntry(int row, int column) {
+        checkRowIndex(row);
+        checkColumnIndex(column);
+        return entries.get(computeKey(row, column));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getRowDimension() {
+        return rows;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void multiplyEntry(int row, int column, T factor) {
+        checkRowIndex(row);
+        checkColumnIndex(column);
+        final int key = computeKey(row, column);
+        final T value = entries.get(key).multiply(factor);
+        if (getField().getZero().equals(value)) {
+            entries.remove(key);
+        } else {
+            entries.put(key, value);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setEntry(int row, int column, T value) {
+        checkRowIndex(row);
+        checkColumnIndex(column);
+        if (getField().getZero().equals(value)) {
+            entries.remove(computeKey(row, column));
+        } else {
+            entries.put(computeKey(row, column), value);
+        }
+    }
+
+    /**
+     * Compute the key to access a matrix element.
+     *
+     * @param row Row index of the matrix element.
+     * @param column Column index of the matrix element.
+     * @return the key within the map to access the matrix element.
+     */
+    private int computeKey(int row, int column) {
+        return row * columns + column;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/SparseFieldVector.java b/src/main/java/org/apache/commons/math3/linear/SparseFieldVector.java
new file mode 100644
index 0000000..a86ea50
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/SparseFieldVector.java
@@ -0,0 +1,771 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.FieldElement;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.util.MathUtils;
+import org.apache.commons.math3.util.OpenIntToFieldHashMap;
+
+import java.io.Serializable;
+
+/**
+ * This class implements the {@link FieldVector} interface with a {@link OpenIntToFieldHashMap}
+ * backing store.
+ *
+ * <p>Caveat: This implementation assumes that, for any {@code x}, the equality {@code x * 0d == 0d}
+ * holds. But it is is not true for {@code NaN}. Moreover, zero entries will lose their sign. Some
+ * operations (that involve {@code NaN} and/or infinities) may thus give incorrect results.
+ *
+ * @param <T> the type of the field elements
+ * @since 2.0
+ */
+public class SparseFieldVector<T extends FieldElement<T>> implements FieldVector<T>, Serializable {
+    /** Serialization identifier. */
+    private static final long serialVersionUID = 7841233292190413362L;
+
+    /** Field to which the elements belong. */
+    private final Field<T> field;
+
+    /** Entries of the vector. */
+    private final OpenIntToFieldHashMap<T> entries;
+
+    /** Dimension of the vector. */
+    private final int virtualSize;
+
+    /**
+     * Build a 0-length vector. Zero-length vectors may be used to initialize construction of
+     * vectors by data gathering. We start with zero-length and use either the {@link
+     * #SparseFieldVector(SparseFieldVector, int)} constructor or one of the {@code append} method
+     * ({@link #append(FieldVector)} or {@link #append(SparseFieldVector)}) to gather data into this
+     * vector.
+     *
+     * @param field Field to which the elements belong.
+     */
+    public SparseFieldVector(Field<T> field) {
+        this(field, 0);
+    }
+
+    /**
+     * Construct a vector of zeroes.
+     *
+     * @param field Field to which the elements belong.
+     * @param dimension Size of the vector.
+     */
+    public SparseFieldVector(Field<T> field, int dimension) {
+        this.field = field;
+        virtualSize = dimension;
+        entries = new OpenIntToFieldHashMap<T>(field);
+    }
+
+    /**
+     * Build a resized vector, for use with append.
+     *
+     * @param v Original vector
+     * @param resize Amount to add.
+     */
+    protected SparseFieldVector(SparseFieldVector<T> v, int resize) {
+        field = v.field;
+        virtualSize = v.getDimension() + resize;
+        entries = new OpenIntToFieldHashMap<T>(v.entries);
+    }
+
+    /**
+     * Build a vector with known the sparseness (for advanced use only).
+     *
+     * @param field Field to which the elements belong.
+     * @param dimension Size of the vector.
+     * @param expectedSize Expected number of non-zero entries.
+     */
+    public SparseFieldVector(Field<T> field, int dimension, int expectedSize) {
+        this.field = field;
+        virtualSize = dimension;
+        entries = new OpenIntToFieldHashMap<T>(field, expectedSize);
+    }
+
+    /**
+     * Create from a Field array. Only non-zero entries will be stored.
+     *
+     * @param field Field to which the elements belong.
+     * @param values Set of values to create from.
+     * @exception NullArgumentException if values is null
+     */
+    public SparseFieldVector(Field<T> field, T[] values) throws NullArgumentException {
+        MathUtils.checkNotNull(values);
+        this.field = field;
+        virtualSize = values.length;
+        entries = new OpenIntToFieldHashMap<T>(field);
+        for (int key = 0; key < values.length; key++) {
+            T value = values[key];
+            entries.put(key, value);
+        }
+    }
+
+    /**
+     * Copy constructor.
+     *
+     * @param v Instance to copy.
+     */
+    public SparseFieldVector(SparseFieldVector<T> v) {
+        field = v.field;
+        virtualSize = v.getDimension();
+        entries = new OpenIntToFieldHashMap<T>(v.getEntries());
+    }
+
+    /**
+     * Get the entries of this instance.
+     *
+     * @return the entries of this instance
+     */
+    private OpenIntToFieldHashMap<T> getEntries() {
+        return entries;
+    }
+
+    /**
+     * Optimized method to add sparse vectors.
+     *
+     * @param v Vector to add.
+     * @return {@code this + v}.
+     * @throws DimensionMismatchException if {@code v} is not the same size as {@code this}.
+     */
+    public FieldVector<T> add(SparseFieldVector<T> v) throws DimensionMismatchException {
+        checkVectorDimensions(v.getDimension());
+        SparseFieldVector<T> res = (SparseFieldVector<T>) copy();
+        OpenIntToFieldHashMap<T>.Iterator iter = v.getEntries().iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            int key = iter.key();
+            T value = iter.value();
+            if (entries.containsKey(key)) {
+                res.setEntry(key, entries.get(key).add(value));
+            } else {
+                res.setEntry(key, value);
+            }
+        }
+        return res;
+    }
+
+    /**
+     * Construct a vector by appending a vector to this vector.
+     *
+     * @param v Vector to append to this one.
+     * @return a new vector.
+     */
+    public FieldVector<T> append(SparseFieldVector<T> v) {
+        SparseFieldVector<T> res = new SparseFieldVector<T>(this, v.getDimension());
+        OpenIntToFieldHashMap<T>.Iterator iter = v.entries.iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            res.setEntry(iter.key() + virtualSize, iter.value());
+        }
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> append(FieldVector<T> v) {
+        if (v instanceof SparseFieldVector<?>) {
+            return append((SparseFieldVector<T>) v);
+        } else {
+            final int n = v.getDimension();
+            FieldVector<T> res = new SparseFieldVector<T>(this, n);
+            for (int i = 0; i < n; i++) {
+                res.setEntry(i + virtualSize, v.getEntry(i));
+            }
+            return res;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @exception NullArgumentException if d is null
+     */
+    public FieldVector<T> append(T d) throws NullArgumentException {
+        MathUtils.checkNotNull(d);
+        FieldVector<T> res = new SparseFieldVector<T>(this, 1);
+        res.setEntry(virtualSize, d);
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> copy() {
+        return new SparseFieldVector<T>(this);
+    }
+
+    /** {@inheritDoc} */
+    public T dotProduct(FieldVector<T> v) throws DimensionMismatchException {
+        checkVectorDimensions(v.getDimension());
+        T res = field.getZero();
+        OpenIntToFieldHashMap<T>.Iterator iter = entries.iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            res = res.add(v.getEntry(iter.key()).multiply(iter.value()));
+        }
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> ebeDivide(FieldVector<T> v)
+            throws DimensionMismatchException, MathArithmeticException {
+        checkVectorDimensions(v.getDimension());
+        SparseFieldVector<T> res = new SparseFieldVector<T>(this);
+        OpenIntToFieldHashMap<T>.Iterator iter = res.entries.iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            res.setEntry(iter.key(), iter.value().divide(v.getEntry(iter.key())));
+        }
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> ebeMultiply(FieldVector<T> v) throws DimensionMismatchException {
+        checkVectorDimensions(v.getDimension());
+        SparseFieldVector<T> res = new SparseFieldVector<T>(this);
+        OpenIntToFieldHashMap<T>.Iterator iter = res.entries.iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            res.setEntry(iter.key(), iter.value().multiply(v.getEntry(iter.key())));
+        }
+        return res;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @deprecated as of 3.1, to be removed in 4.0. Please use the {@link #toArray()} method
+     *     instead.
+     */
+    @Deprecated
+    public T[] getData() {
+        return toArray();
+    }
+
+    /** {@inheritDoc} */
+    public int getDimension() {
+        return virtualSize;
+    }
+
+    /** {@inheritDoc} */
+    public T getEntry(int index) throws OutOfRangeException {
+        checkIndex(index);
+        return entries.get(index);
+    }
+
+    /** {@inheritDoc} */
+    public Field<T> getField() {
+        return field;
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> getSubVector(int index, int n)
+            throws OutOfRangeException, NotPositiveException {
+        if (n < 0) {
+            throw new NotPositiveException(
+                    LocalizedFormats.NUMBER_OF_ELEMENTS_SHOULD_BE_POSITIVE, n);
+        }
+        checkIndex(index);
+        checkIndex(index + n - 1);
+        SparseFieldVector<T> res = new SparseFieldVector<T>(field, n);
+        int end = index + n;
+        OpenIntToFieldHashMap<T>.Iterator iter = entries.iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            int key = iter.key();
+            if (key >= index && key < end) {
+                res.setEntry(key - index, iter.value());
+            }
+        }
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> mapAdd(T d) throws NullArgumentException {
+        return copy().mapAddToSelf(d);
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> mapAddToSelf(T d) throws NullArgumentException {
+        for (int i = 0; i < virtualSize; i++) {
+            setEntry(i, getEntry(i).add(d));
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> mapDivide(T d) throws NullArgumentException, MathArithmeticException {
+        return copy().mapDivideToSelf(d);
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> mapDivideToSelf(T d)
+            throws NullArgumentException, MathArithmeticException {
+        OpenIntToFieldHashMap<T>.Iterator iter = entries.iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            entries.put(iter.key(), iter.value().divide(d));
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> mapInv() throws MathArithmeticException {
+        return copy().mapInvToSelf();
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> mapInvToSelf() throws MathArithmeticException {
+        for (int i = 0; i < virtualSize; i++) {
+            setEntry(i, field.getOne().divide(getEntry(i)));
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> mapMultiply(T d) throws NullArgumentException {
+        return copy().mapMultiplyToSelf(d);
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> mapMultiplyToSelf(T d) throws NullArgumentException {
+        OpenIntToFieldHashMap<T>.Iterator iter = entries.iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            entries.put(iter.key(), iter.value().multiply(d));
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> mapSubtract(T d) throws NullArgumentException {
+        return copy().mapSubtractToSelf(d);
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> mapSubtractToSelf(T d) throws NullArgumentException {
+        return mapAddToSelf(field.getZero().subtract(d));
+    }
+
+    /**
+     * Optimized method to compute outer product when both vectors are sparse.
+     *
+     * @param v vector with which outer product should be computed
+     * @return the matrix outer product between instance and v
+     */
+    public FieldMatrix<T> outerProduct(SparseFieldVector<T> v) {
+        final int n = v.getDimension();
+        SparseFieldMatrix<T> res = new SparseFieldMatrix<T>(field, virtualSize, n);
+        OpenIntToFieldHashMap<T>.Iterator iter = entries.iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            OpenIntToFieldHashMap<T>.Iterator iter2 = v.entries.iterator();
+            while (iter2.hasNext()) {
+                iter2.advance();
+                res.setEntry(iter.key(), iter2.key(), iter.value().multiply(iter2.value()));
+            }
+        }
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    public FieldMatrix<T> outerProduct(FieldVector<T> v) {
+        if (v instanceof SparseFieldVector<?>) {
+            return outerProduct((SparseFieldVector<T>) v);
+        } else {
+            final int n = v.getDimension();
+            FieldMatrix<T> res = new SparseFieldMatrix<T>(field, virtualSize, n);
+            OpenIntToFieldHashMap<T>.Iterator iter = entries.iterator();
+            while (iter.hasNext()) {
+                iter.advance();
+                int row = iter.key();
+                FieldElement<T> value = iter.value();
+                for (int col = 0; col < n; col++) {
+                    res.setEntry(row, col, value.multiply(v.getEntry(col)));
+                }
+            }
+            return res;
+        }
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> projection(FieldVector<T> v)
+            throws DimensionMismatchException, MathArithmeticException {
+        checkVectorDimensions(v.getDimension());
+        return v.mapMultiply(dotProduct(v).divide(v.dotProduct(v)));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @exception NullArgumentException if value is null
+     */
+    public void set(T value) {
+        MathUtils.checkNotNull(value);
+        for (int i = 0; i < virtualSize; i++) {
+            setEntry(i, value);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @exception NullArgumentException if value is null
+     */
+    public void setEntry(int index, T value) throws NullArgumentException, OutOfRangeException {
+        MathUtils.checkNotNull(value);
+        checkIndex(index);
+        entries.put(index, value);
+    }
+
+    /** {@inheritDoc} */
+    public void setSubVector(int index, FieldVector<T> v) throws OutOfRangeException {
+        checkIndex(index);
+        checkIndex(index + v.getDimension() - 1);
+        final int n = v.getDimension();
+        for (int i = 0; i < n; i++) {
+            setEntry(i + index, v.getEntry(i));
+        }
+    }
+
+    /**
+     * Optimized method to compute {@code this} minus {@code v}.
+     *
+     * @param v vector to be subtracted
+     * @return {@code this - v}
+     * @throws DimensionMismatchException if {@code v} is not the same size as {@code this}.
+     */
+    public SparseFieldVector<T> subtract(SparseFieldVector<T> v) throws DimensionMismatchException {
+        checkVectorDimensions(v.getDimension());
+        SparseFieldVector<T> res = (SparseFieldVector<T>) copy();
+        OpenIntToFieldHashMap<T>.Iterator iter = v.getEntries().iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            int key = iter.key();
+            if (entries.containsKey(key)) {
+                res.setEntry(key, entries.get(key).subtract(iter.value()));
+            } else {
+                res.setEntry(key, field.getZero().subtract(iter.value()));
+            }
+        }
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> subtract(FieldVector<T> v) throws DimensionMismatchException {
+        if (v instanceof SparseFieldVector<?>) {
+            return subtract((SparseFieldVector<T>) v);
+        } else {
+            final int n = v.getDimension();
+            checkVectorDimensions(n);
+            SparseFieldVector<T> res = new SparseFieldVector<T>(this);
+            for (int i = 0; i < n; i++) {
+                if (entries.containsKey(i)) {
+                    res.setEntry(i, entries.get(i).subtract(v.getEntry(i)));
+                } else {
+                    res.setEntry(i, field.getZero().subtract(v.getEntry(i)));
+                }
+            }
+            return res;
+        }
+    }
+
+    /** {@inheritDoc} */
+    public T[] toArray() {
+        T[] res = MathArrays.buildArray(field, virtualSize);
+        OpenIntToFieldHashMap<T>.Iterator iter = entries.iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            res[iter.key()] = iter.value();
+        }
+        return res;
+    }
+
+    /**
+     * Check whether an index is valid.
+     *
+     * @param index Index to check.
+     * @throws OutOfRangeException if the index is not valid.
+     */
+    private void checkIndex(final int index) throws OutOfRangeException {
+        if (index < 0 || index >= getDimension()) {
+            throw new OutOfRangeException(index, 0, getDimension() - 1);
+        }
+    }
+
+    /**
+     * Checks that the indices of a subvector are valid.
+     *
+     * @param start the index of the first entry of the subvector
+     * @param end the index of the last entry of the subvector (inclusive)
+     * @throws OutOfRangeException if {@code start} of {@code end} are not valid
+     * @throws NumberIsTooSmallException if {@code end < start}
+     * @since 3.3
+     */
+    private void checkIndices(final int start, final int end)
+            throws NumberIsTooSmallException, OutOfRangeException {
+        final int dim = getDimension();
+        if ((start < 0) || (start >= dim)) {
+            throw new OutOfRangeException(LocalizedFormats.INDEX, start, 0, dim - 1);
+        }
+        if ((end < 0) || (end >= dim)) {
+            throw new OutOfRangeException(LocalizedFormats.INDEX, end, 0, dim - 1);
+        }
+        if (end < start) {
+            throw new NumberIsTooSmallException(
+                    LocalizedFormats.INITIAL_ROW_AFTER_FINAL_ROW, end, start, false);
+        }
+    }
+
+    /**
+     * Check if instance dimension is equal to some expected value.
+     *
+     * @param n Expected dimension.
+     * @throws DimensionMismatchException if the dimensions do not match.
+     */
+    protected void checkVectorDimensions(int n) throws DimensionMismatchException {
+        if (getDimension() != n) {
+            throw new DimensionMismatchException(getDimension(), n);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> add(FieldVector<T> v) throws DimensionMismatchException {
+        if (v instanceof SparseFieldVector<?>) {
+            return add((SparseFieldVector<T>) v);
+        } else {
+            final int n = v.getDimension();
+            checkVectorDimensions(n);
+            SparseFieldVector<T> res = new SparseFieldVector<T>(field, getDimension());
+            for (int i = 0; i < n; i++) {
+                res.setEntry(i, v.getEntry(i).add(getEntry(i)));
+            }
+            return res;
+        }
+    }
+
+    /**
+     * Visits (but does not alter) all entries of this vector in default order (increasing index).
+     *
+     * @param visitor the visitor to be used to process the entries of this vector
+     * @return the value returned by {@link FieldVectorPreservingVisitor#end()} at the end of the
+     *     walk
+     * @since 3.3
+     */
+    public T walkInDefaultOrder(final FieldVectorPreservingVisitor<T> visitor) {
+        final int dim = getDimension();
+        visitor.start(dim, 0, dim - 1);
+        for (int i = 0; i < dim; i++) {
+            visitor.visit(i, getEntry(i));
+        }
+        return visitor.end();
+    }
+
+    /**
+     * Visits (but does not alter) some entries of this vector in default order (increasing index).
+     *
+     * @param visitor visitor to be used to process the entries of this vector
+     * @param start the index of the first entry to be visited
+     * @param end the index of the last entry to be visited (inclusive)
+     * @return the value returned by {@link FieldVectorPreservingVisitor#end()} at the end of the
+     *     walk
+     * @throws NumberIsTooSmallException if {@code end < start}.
+     * @throws OutOfRangeException if the indices are not valid.
+     * @since 3.3
+     */
+    public T walkInDefaultOrder(
+            final FieldVectorPreservingVisitor<T> visitor, final int start, final int end)
+            throws NumberIsTooSmallException, OutOfRangeException {
+        checkIndices(start, end);
+        visitor.start(getDimension(), start, end);
+        for (int i = start; i <= end; i++) {
+            visitor.visit(i, getEntry(i));
+        }
+        return visitor.end();
+    }
+
+    /**
+     * Visits (but does not alter) all entries of this vector in optimized order. The order in which
+     * the entries are visited is selected so as to lead to the most efficient implementation; it
+     * might depend on the concrete implementation of this abstract class.
+     *
+     * @param visitor the visitor to be used to process the entries of this vector
+     * @return the value returned by {@link FieldVectorPreservingVisitor#end()} at the end of the
+     *     walk
+     * @since 3.3
+     */
+    public T walkInOptimizedOrder(final FieldVectorPreservingVisitor<T> visitor) {
+        return walkInDefaultOrder(visitor);
+    }
+
+    /**
+     * Visits (but does not alter) some entries of this vector in optimized order. The order in
+     * which the entries are visited is selected so as to lead to the most efficient implementation;
+     * it might depend on the concrete implementation of this abstract class.
+     *
+     * @param visitor visitor to be used to process the entries of this vector
+     * @param start the index of the first entry to be visited
+     * @param end the index of the last entry to be visited (inclusive)
+     * @return the value returned by {@link FieldVectorPreservingVisitor#end()} at the end of the
+     *     walk
+     * @throws NumberIsTooSmallException if {@code end < start}.
+     * @throws OutOfRangeException if the indices are not valid.
+     * @since 3.3
+     */
+    public T walkInOptimizedOrder(
+            final FieldVectorPreservingVisitor<T> visitor, final int start, final int end)
+            throws NumberIsTooSmallException, OutOfRangeException {
+        return walkInDefaultOrder(visitor, start, end);
+    }
+
+    /**
+     * Visits (and possibly alters) all entries of this vector in default order (increasing index).
+     *
+     * @param visitor the visitor to be used to process and modify the entries of this vector
+     * @return the value returned by {@link FieldVectorChangingVisitor#end()} at the end of the walk
+     * @since 3.3
+     */
+    public T walkInDefaultOrder(final FieldVectorChangingVisitor<T> visitor) {
+        final int dim = getDimension();
+        visitor.start(dim, 0, dim - 1);
+        for (int i = 0; i < dim; i++) {
+            setEntry(i, visitor.visit(i, getEntry(i)));
+        }
+        return visitor.end();
+    }
+
+    /**
+     * Visits (and possibly alters) some entries of this vector in default order (increasing index).
+     *
+     * @param visitor visitor to be used to process the entries of this vector
+     * @param start the index of the first entry to be visited
+     * @param end the index of the last entry to be visited (inclusive)
+     * @return the value returned by {@link FieldVectorChangingVisitor#end()} at the end of the walk
+     * @throws NumberIsTooSmallException if {@code end < start}.
+     * @throws OutOfRangeException if the indices are not valid.
+     * @since 3.3
+     */
+    public T walkInDefaultOrder(
+            final FieldVectorChangingVisitor<T> visitor, final int start, final int end)
+            throws NumberIsTooSmallException, OutOfRangeException {
+        checkIndices(start, end);
+        visitor.start(getDimension(), start, end);
+        for (int i = start; i <= end; i++) {
+            setEntry(i, visitor.visit(i, getEntry(i)));
+        }
+        return visitor.end();
+    }
+
+    /**
+     * Visits (and possibly alters) all entries of this vector in optimized order. The order in
+     * which the entries are visited is selected so as to lead to the most efficient implementation;
+     * it might depend on the concrete implementation of this abstract class.
+     *
+     * @param visitor the visitor to be used to process the entries of this vector
+     * @return the value returned by {@link FieldVectorChangingVisitor#end()} at the end of the walk
+     * @since 3.3
+     */
+    public T walkInOptimizedOrder(final FieldVectorChangingVisitor<T> visitor) {
+        return walkInDefaultOrder(visitor);
+    }
+
+    /**
+     * Visits (and possibly change) some entries of this vector in optimized order. The order in
+     * which the entries are visited is selected so as to lead to the most efficient implementation;
+     * it might depend on the concrete implementation of this abstract class.
+     *
+     * @param visitor visitor to be used to process the entries of this vector
+     * @param start the index of the first entry to be visited
+     * @param end the index of the last entry to be visited (inclusive)
+     * @return the value returned by {@link FieldVectorChangingVisitor#end()} at the end of the walk
+     * @throws NumberIsTooSmallException if {@code end < start}.
+     * @throws OutOfRangeException if the indices are not valid.
+     * @since 3.3
+     */
+    public T walkInOptimizedOrder(
+            final FieldVectorChangingVisitor<T> visitor, final int start, final int end)
+            throws NumberIsTooSmallException, OutOfRangeException {
+        return walkInDefaultOrder(visitor, start, end);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((field == null) ? 0 : field.hashCode());
+        result = prime * result + virtualSize;
+        OpenIntToFieldHashMap<T>.Iterator iter = entries.iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            int temp = iter.value().hashCode();
+            result = prime * result + temp;
+        }
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object obj) {
+
+        if (this == obj) {
+            return true;
+        }
+
+        if (!(obj instanceof SparseFieldVector<?>)) {
+            return false;
+        }
+
+        @SuppressWarnings("unchecked") // OK, because "else if" check below ensures that
+        // other must be the same type as this
+        SparseFieldVector<T> other = (SparseFieldVector<T>) obj;
+        if (field == null) {
+            if (other.field != null) {
+                return false;
+            }
+        } else if (!field.equals(other.field)) {
+            return false;
+        }
+        if (virtualSize != other.virtualSize) {
+            return false;
+        }
+
+        OpenIntToFieldHashMap<T>.Iterator iter = entries.iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            T test = other.getEntry(iter.key());
+            if (!test.equals(iter.value())) {
+                return false;
+            }
+        }
+        iter = other.getEntries().iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            T test = iter.value();
+            if (!test.equals(getEntry(iter.key()))) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/SparseRealMatrix.java b/src/main/java/org/apache/commons/math3/linear/SparseRealMatrix.java
new file mode 100644
index 0000000..093cadf
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/SparseRealMatrix.java
@@ -0,0 +1,30 @@
+/*
+ * 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.math3.linear;
+
+/**
+ * Marker interface for {@link RealMatrix} implementations that require sparse backing storage
+ *
+ * <p>Caveat: Implementation are allowed to assume that, for any {@code x}, the equality {@code x *
+ * 0d == 0d} holds. But it is is not true for {@code NaN}. Moreover, zero entries will lose their
+ * sign. Some operations (that involve {@code NaN} and/or infinities) may thus give incorrect
+ * results.
+ *
+ * @since 2.0
+ */
+public interface SparseRealMatrix extends RealMatrix {}
diff --git a/src/main/java/org/apache/commons/math3/linear/SparseRealVector.java b/src/main/java/org/apache/commons/math3/linear/SparseRealVector.java
new file mode 100644
index 0000000..4eb2ce5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/SparseRealVector.java
@@ -0,0 +1,29 @@
+/*
+ * 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.math3.linear;
+
+/**
+ * Marker class for RealVectors that require sparse backing storage
+ *
+ * <p>Caveat: Implementation are allowed to assume that, for any {@code x}, the equality {@code x *
+ * 0d == 0d} holds. But it is is not true for {@code NaN}. Moreover, zero entries will lose their
+ * sign. Some operations (that involve {@code NaN} and/or infinities) may thus give incorrect
+ * results, like multiplications, divisions or functions mapping.
+ *
+ * @since 2.0
+ */
+public abstract class SparseRealVector extends RealVector {}
diff --git a/src/main/java/org/apache/commons/math3/linear/SymmLQ.java b/src/main/java/org/apache/commons/math3/linear/SymmLQ.java
new file mode 100644
index 0000000..bc7be0b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/SymmLQ.java
@@ -0,0 +1,1182 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.util.ExceptionContext;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.IterationManager;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * Implementation of the SYMMLQ iterative linear solver proposed by <a href="#PAIG1975">Paige and
+ * Saunders (1975)</a>. This implementation is largely based on the FORTRAN code by Pr. Michael A.
+ * Saunders, available <a href="http://www.stanford.edu/group/SOL/software/symmlq/f77/">here</a>.
+ *
+ * <p>SYMMLQ is designed to solve the system of linear equations A &middot; x = b where A is an n
+ * &times; n self-adjoint linear operator (defined as a {@link RealLinearOperator}), and b is a
+ * given vector. The operator A is not required to be positive definite. If A is known to be
+ * definite, the method of conjugate gradients might be preferred, since it will require about the
+ * same number of iterations as SYMMLQ but slightly less work per iteration.
+ *
+ * <p>SYMMLQ is designed to solve the system (A - shift &middot; I) &middot; x = b, where shift is a
+ * specified scalar value. If shift and b are suitably chosen, the computed vector x may approximate
+ * an (unnormalized) eigenvector of A, as in the methods of inverse iteration and/or
+ * Rayleigh-quotient iteration. Again, the linear operator (A - shift &middot; I) need not be
+ * positive definite (but <em>must</em> be self-adjoint). The work per iteration is very slightly
+ * less if shift = 0.
+ *
+ * <h3>Preconditioning</h3>
+ *
+ * <p>Preconditioning may reduce the number of iterations required. The solver may be provided with
+ * a positive definite preconditioner M = P<sup>T</sup> &middot; P that is known to approximate (A -
+ * shift &middot; I)<sup>-1</sup> in some sense, where matrix-vector products of the form M &middot;
+ * y = x can be computed efficiently. Then SYMMLQ will implicitly solve the system of equations P
+ * &middot; (A - shift &middot; I) &middot; P<sup>T</sup> &middot; x<sub>hat</sub> = P &middot; b,
+ * i.e. A<sub>hat</sub> &middot; x<sub>hat</sub> = b<sub>hat</sub>, where A<sub>hat</sub> = P
+ * &middot; (A - shift &middot; I) &middot; P<sup>T</sup>, b<sub>hat</sub> = P &middot; b, and
+ * return the solution x = P<sup>T</sup> &middot; x<sub>hat</sub>. The associated residual is
+ * r<sub>hat</sub> = b<sub>hat</sub> - A<sub>hat</sub> &middot; x<sub>hat</sub> = P &middot; [b - (A
+ * - shift &middot; I) &middot; x] = P &middot; r.
+ *
+ * <p>In the case of preconditioning, the {@link IterativeLinearSolverEvent}s that this solver fires
+ * are such that {@link IterativeLinearSolverEvent#getNormOfResidual()} returns the norm of the
+ * <em>preconditioned</em>, updated residual, ||P &middot; r||, not the norm of the <em>true</em>
+ * residual ||r||.
+ *
+ * <h3><a id="stopcrit">Default stopping criterion</a></h3>
+ *
+ * <p>A default stopping criterion is implemented. The iterations stop when || rhat || &le; &delta;
+ * || Ahat || || xhat ||, where xhat is the current estimate of the solution of the transformed
+ * system, rhat the current estimate of the corresponding residual, and &delta; a user-specified
+ * tolerance.
+ *
+ * <h3>Iteration count</h3>
+ *
+ * <p>In the present context, an iteration should be understood as one evaluation of the
+ * matrix-vector product A &middot; x. The initialization phase therefore counts as one iteration.
+ * If the user requires checks on the symmetry of A, this entails one further matrix-vector product
+ * in the initial phase. This further product is <em>not</em> accounted for in the iteration count.
+ * In other words, the number of iterations required to reach convergence will be identical, whether
+ * checks have been required or not.
+ *
+ * <p>The present definition of the iteration count differs from that adopted in the original FOTRAN
+ * code, where the initialization phase was <em>not</em> taken into account.
+ *
+ * <h3><a id="initguess">Initial guess of the solution</a></h3>
+ *
+ * <p>The {@code x} parameter in
+ *
+ * <ul>
+ *   <li>{@link #solve(RealLinearOperator, RealVector, RealVector)},
+ *   <li>{@link #solve(RealLinearOperator, RealLinearOperator, RealVector, RealVector)}},
+ *   <li>{@link #solveInPlace(RealLinearOperator, RealVector, RealVector)},
+ *   <li>{@link #solveInPlace(RealLinearOperator, RealLinearOperator, RealVector, RealVector)},
+ *   <li>{@link #solveInPlace(RealLinearOperator, RealLinearOperator, RealVector, RealVector,
+ *       boolean, double)},
+ * </ul>
+ *
+ * should not be considered as an initial guess, as it is set to zero in the initial phase. If
+ * x<sub>0</sub> is known to be a good approximation to x, one should compute r<sub>0</sub> = b - A
+ * &middot; x, solve A &middot; dx = r0, and set x = x<sub>0</sub> + dx.
+ *
+ * <h3><a id="context">Exception context</a></h3>
+ *
+ * <p>Besides standard {@link DimensionMismatchException}, this class might throw {@link
+ * NonSelfAdjointOperatorException} if the linear operator or the preconditioner are not symmetric.
+ * In this case, the {@link ExceptionContext} provides more information
+ *
+ * <ul>
+ *   <li>key {@code "operator"} points to the offending linear operator, say L,
+ *   <li>key {@code "vector1"} points to the first offending vector, say x,
+ *   <li>key {@code "vector2"} points to the second offending vector, say y, such that x<sup>T</sup>
+ *       &middot; L &middot; y &ne; y<sup>T</sup> &middot; L &middot; x (within a certain accuracy).
+ * </ul>
+ *
+ * <p>{@link NonPositiveDefiniteOperatorException} might also be thrown in case the preconditioner
+ * is not positive definite. The relevant keys to the {@link ExceptionContext} are
+ *
+ * <ul>
+ *   <li>key {@code "operator"}, which points to the offending linear operator, say L,
+ *   <li>key {@code "vector"}, which points to the offending vector, say x, such that x<sup>T</sup>
+ *       &middot; L &middot; x < 0.
+ * </ul>
+ *
+ * <h3>References</h3>
+ *
+ * <dl>
+ *   <dt><a id="PAIG1975">Paige and Saunders (1975)</a>
+ *   <dd>C. C. Paige and M. A. Saunders, <a
+ *       href="http://www.stanford.edu/group/SOL/software/symmlq/PS75.pdf"><em> Solution of Sparse
+ *       Indefinite Systems of Linear Equations</em></a>, SIAM Journal on Numerical Analysis 12(4):
+ *       617-629, 1975
+ * </dl>
+ *
+ * @since 3.0
+ */
+public class SymmLQ extends PreconditionedIterativeLinearSolver {
+
+    /*
+     * IMPLEMENTATION NOTES
+     * --------------------
+     * The implementation follows as closely as possible the notations of Paige
+     * and Saunders (1975). Attention must be paid to the fact that some
+     * quantities which are relevant to iteration k can only be computed in
+     * iteration (k+1). Therefore, minute attention must be paid to the index of
+     * each state variable of this algorithm.
+     *
+     * 1. Preconditioning
+     *    ---------------
+     * The Lanczos iterations associated with Ahat and bhat read
+     *   beta[1] = ||P * b||
+     *   v[1] = P * b / beta[1]
+     *   beta[k+1] * v[k+1] = Ahat * v[k] - alpha[k] * v[k] - beta[k] * v[k-1]
+     *                      = P * (A - shift * I) * P' * v[k] - alpha[k] * v[k]
+     *                        - beta[k] * v[k-1]
+     * Multiplying both sides by P', we get
+     *   beta[k+1] * (P' * v)[k+1] = M * (A - shift * I) * (P' * v)[k]
+     *                               - alpha[k] * (P' * v)[k]
+     *                               - beta[k] * (P' * v[k-1]),
+     * and
+     *   alpha[k+1] = v[k+1]' * Ahat * v[k+1]
+     *              = v[k+1]' * P * (A - shift * I) * P' * v[k+1]
+     *              = (P' * v)[k+1]' * (A - shift * I) * (P' * v)[k+1].
+     *
+     * In other words, the Lanczos iterations are unchanged, except for the fact
+     * that we really compute (P' * v) instead of v. It can easily be checked
+     * that all other formulas are unchanged. It must be noted that P is never
+     * explicitly used, only matrix-vector products involving are invoked.
+     *
+     * 2. Accounting for the shift parameter
+     *    ----------------------------------
+     * Is trivial: each time A.operate(x) is invoked, one must subtract shift * x
+     * to the result.
+     *
+     * 3. Accounting for the goodb flag
+     *    -----------------------------
+     * When goodb is set to true, the component of xL along b is computed
+     * separately. From Paige and Saunders (1975), equation (5.9), we have
+     *   wbar[k+1] = s[k] * wbar[k] - c[k] * v[k+1],
+     *   wbar[1] = v[1].
+     * Introducing wbar2[k] = wbar[k] - s[1] * ... * s[k-1] * v[1], it can
+     * easily be verified by induction that wbar2 follows the same recursive
+     * relation
+     *   wbar2[k+1] = s[k] * wbar2[k] - c[k] * v[k+1],
+     *   wbar2[1] = 0,
+     * and we then have
+     *   w[k] = c[k] * wbar2[k] + s[k] * v[k+1]
+     *          + s[1] * ... * s[k-1] * c[k] * v[1].
+     * Introducing w2[k] = w[k] - s[1] * ... * s[k-1] * c[k] * v[1], we find,
+     * from (5.10)
+     *   xL[k] = zeta[1] * w[1] + ... + zeta[k] * w[k]
+     *         = zeta[1] * w2[1] + ... + zeta[k] * w2[k]
+     *           + (s[1] * c[2] * zeta[2] + ...
+     *           + s[1] * ... * s[k-1] * c[k] * zeta[k]) * v[1]
+     *         = xL2[k] + bstep[k] * v[1],
+     * where xL2[k] is defined by
+     *   xL2[0] = 0,
+     *   xL2[k+1] = xL2[k] + zeta[k+1] * w2[k+1],
+     * and bstep is defined by
+     *   bstep[1] = 0,
+     *   bstep[k] = bstep[k-1] + s[1] * ... * s[k-1] * c[k] * zeta[k].
+     * We also have, from (5.11)
+     *   xC[k] = xL[k-1] + zbar[k] * wbar[k]
+     *         = xL2[k-1] + zbar[k] * wbar2[k]
+     *           + (bstep[k-1] + s[1] * ... * s[k-1] * zbar[k]) * v[1].
+     */
+
+    /**
+     * A simple container holding the non-final variables used in the iterations. Making the current
+     * state of the solver visible from the outside is necessary, because during the iterations,
+     * {@code x} does not <em>exactly</em> hold the current estimate of the solution. Indeed, {@code
+     * x} needs in general to be moved from the LQ point to the CG point. Besides, additional
+     * upudates must be carried out in case {@code goodb} is set to {@code true}.
+     *
+     * <p>In all subsequent comments, the description of the state variables refer to their value
+     * after a call to {@link #update()}. In these comments, k is the current number of evaluations
+     * of matrix-vector products.
+     */
+    private static class State {
+        /** The cubic root of {@link #MACH_PREC}. */
+        static final double CBRT_MACH_PREC;
+
+        /** The machine precision. */
+        static final double MACH_PREC;
+
+        /** Reference to the linear operator. */
+        private final RealLinearOperator a;
+
+        /** Reference to the right-hand side vector. */
+        private final RealVector b;
+
+        /** {@code true} if symmetry of matrix and conditioner must be checked. */
+        private final boolean check;
+
+        /** The value of the custom tolerance &delta; for the default stopping criterion. */
+        private final double delta;
+
+        /** The value of beta[k+1]. */
+        private double beta;
+
+        /** The value of beta[1]. */
+        private double beta1;
+
+        /** The value of bstep[k-1]. */
+        private double bstep;
+
+        /** The estimate of the norm of P * rC[k]. */
+        private double cgnorm;
+
+        /** The value of dbar[k+1] = -beta[k+1] * c[k-1]. */
+        private double dbar;
+
+        /** The value of gamma[k] * zeta[k]. Was called {@code rhs1} in the initial code. */
+        private double gammaZeta;
+
+        /** The value of gbar[k]. */
+        private double gbar;
+
+        /** The value of max(|alpha[1]|, gamma[1], ..., gamma[k-1]). */
+        private double gmax;
+
+        /** The value of min(|alpha[1]|, gamma[1], ..., gamma[k-1]). */
+        private double gmin;
+
+        /** Copy of the {@code goodb} parameter. */
+        private final boolean goodb;
+
+        /** {@code true} if the default convergence criterion is verified. */
+        private boolean hasConverged;
+
+        /** The estimate of the norm of P * rL[k-1]. */
+        private double lqnorm;
+
+        /** Reference to the preconditioner, M. */
+        private final RealLinearOperator m;
+
+        /** The value of (-eps[k+1] * zeta[k-1]). Was called {@code rhs2} in the initial code. */
+        private double minusEpsZeta;
+
+        /** The value of M * b. */
+        private final RealVector mb;
+
+        /** The value of beta[k]. */
+        private double oldb;
+
+        /** The value of beta[k] * M^(-1) * P' * v[k]. */
+        private RealVector r1;
+
+        /** The value of beta[k+1] * M^(-1) * P' * v[k+1]. */
+        private RealVector r2;
+
+        /**
+         * The value of the updated, preconditioned residual P * r. This value is given by {@code
+         * min(}{@link #cgnorm}{@code , }{@link #lqnorm}{@code )}.
+         */
+        private double rnorm;
+
+        /** Copy of the {@code shift} parameter. */
+        private final double shift;
+
+        /** The value of s[1] * ... * s[k-1]. */
+        private double snprod;
+
+        /**
+         * An estimate of the square of the norm of A * V[k], based on Paige and Saunders (1975),
+         * equation (3.3).
+         */
+        private double tnorm;
+
+        /**
+         * The value of P' * wbar[k] or P' * (wbar[k] - s[1] * ... * s[k-1] * v[1]) if {@code goodb}
+         * is {@code true}. Was called {@code w} in the initial code.
+         */
+        private RealVector wbar;
+
+        /**
+         * A reference to the vector to be updated with the solution. Contains the value of xL[k-1]
+         * if {@code goodb} is {@code false}, (xL[k-1] - bstep[k-1] * v[1]) otherwise.
+         */
+        private final RealVector xL;
+
+        /** The value of beta[k+1] * P' * v[k+1]. */
+        private RealVector y;
+
+        /** The value of zeta[1]^2 + ... + zeta[k-1]^2. */
+        private double ynorm2;
+
+        /** The value of {@code b == 0} (exact floating-point equality). */
+        private boolean bIsNull;
+
+        static {
+            MACH_PREC = FastMath.ulp(1.);
+            CBRT_MACH_PREC = FastMath.cbrt(MACH_PREC);
+        }
+
+        /**
+         * Creates and inits to k = 1 a new instance of this class.
+         *
+         * @param a the linear operator A of the system
+         * @param m the preconditioner, M (can be {@code null})
+         * @param b the right-hand side vector
+         * @param goodb usually {@code false}, except if {@code x} is expected to contain a large
+         *     multiple of {@code b}
+         * @param shift the amount to be subtracted to all diagonal elements of A
+         * @param delta the &delta; parameter for the default stopping criterion
+         * @param check {@code true} if self-adjointedness of both matrix and preconditioner should
+         *     be checked
+         */
+        State(
+                final RealLinearOperator a,
+                final RealLinearOperator m,
+                final RealVector b,
+                final boolean goodb,
+                final double shift,
+                final double delta,
+                final boolean check) {
+            this.a = a;
+            this.m = m;
+            this.b = b;
+            this.xL = new ArrayRealVector(b.getDimension());
+            this.goodb = goodb;
+            this.shift = shift;
+            this.mb = m == null ? b : m.operate(b);
+            this.hasConverged = false;
+            this.check = check;
+            this.delta = delta;
+        }
+
+        /**
+         * Performs a symmetry check on the specified linear operator, and throws an exception in
+         * case this check fails. Given a linear operator L, and a vector x, this method checks that
+         * x' &middot; L &middot; y = y' &middot; L &middot; x (within a given accuracy), where y =
+         * L &middot; x.
+         *
+         * @param l the linear operator L
+         * @param x the candidate vector x
+         * @param y the candidate vector y = L &middot; x
+         * @param z the vector z = L &middot; y
+         * @throws NonSelfAdjointOperatorException when the test fails
+         */
+        private static void checkSymmetry(
+                final RealLinearOperator l,
+                final RealVector x,
+                final RealVector y,
+                final RealVector z)
+                throws NonSelfAdjointOperatorException {
+            final double s = y.dotProduct(y);
+            final double t = x.dotProduct(z);
+            final double epsa = (s + MACH_PREC) * CBRT_MACH_PREC;
+            if (FastMath.abs(s - t) > epsa) {
+                final NonSelfAdjointOperatorException e;
+                e = new NonSelfAdjointOperatorException();
+                final ExceptionContext context = e.getContext();
+                context.setValue(SymmLQ.OPERATOR, l);
+                context.setValue(SymmLQ.VECTOR1, x);
+                context.setValue(SymmLQ.VECTOR2, y);
+                context.setValue(SymmLQ.THRESHOLD, Double.valueOf(epsa));
+                throw e;
+            }
+        }
+
+        /**
+         * Throws a new {@link NonPositiveDefiniteOperatorException} with appropriate context.
+         *
+         * @param l the offending linear operator
+         * @param v the offending vector
+         * @throws NonPositiveDefiniteOperatorException in any circumstances
+         */
+        private static void throwNPDLOException(final RealLinearOperator l, final RealVector v)
+                throws NonPositiveDefiniteOperatorException {
+            final NonPositiveDefiniteOperatorException e;
+            e = new NonPositiveDefiniteOperatorException();
+            final ExceptionContext context = e.getContext();
+            context.setValue(OPERATOR, l);
+            context.setValue(VECTOR, v);
+            throw e;
+        }
+
+        /**
+         * A clone of the BLAS {@code DAXPY} function, which carries out the operation y &larr; a
+         * &middot; x + y. This is for internal use only: no dimension checks are provided.
+         *
+         * @param a the scalar by which {@code x} is to be multiplied
+         * @param x the vector to be added to {@code y}
+         * @param y the vector to be incremented
+         */
+        private static void daxpy(final double a, final RealVector x, final RealVector y) {
+            final int n = x.getDimension();
+            for (int i = 0; i < n; i++) {
+                y.setEntry(i, a * x.getEntry(i) + y.getEntry(i));
+            }
+        }
+
+        /**
+         * A BLAS-like function, for the operation z &larr; a &middot; x + b &middot; y + z. This is
+         * for internal use only: no dimension checks are provided.
+         *
+         * @param a the scalar by which {@code x} is to be multiplied
+         * @param x the first vector to be added to {@code z}
+         * @param b the scalar by which {@code y} is to be multiplied
+         * @param y the second vector to be added to {@code z}
+         * @param z the vector to be incremented
+         */
+        private static void daxpbypz(
+                final double a,
+                final RealVector x,
+                final double b,
+                final RealVector y,
+                final RealVector z) {
+            final int n = z.getDimension();
+            for (int i = 0; i < n; i++) {
+                final double zi;
+                zi = a * x.getEntry(i) + b * y.getEntry(i) + z.getEntry(i);
+                z.setEntry(i, zi);
+            }
+        }
+
+        /**
+         * Move to the CG point if it seems better. In this version of SYMMLQ, the convergence tests
+         * involve only cgnorm, so we're unlikely to stop at an LQ point, except if the iteration
+         * limit interferes.
+         *
+         * <p>Additional upudates are also carried out in case {@code goodb} is set to {@code true}.
+         *
+         * @param x the vector to be updated with the refined value of xL
+         */
+        void refineSolution(final RealVector x) {
+            final int n = this.xL.getDimension();
+            if (lqnorm < cgnorm) {
+                if (!goodb) {
+                    x.setSubVector(0, this.xL);
+                } else {
+                    final double step = bstep / beta1;
+                    for (int i = 0; i < n; i++) {
+                        final double bi = mb.getEntry(i);
+                        final double xi = this.xL.getEntry(i);
+                        x.setEntry(i, xi + step * bi);
+                    }
+                }
+            } else {
+                final double anorm = FastMath.sqrt(tnorm);
+                final double diag = gbar == 0. ? anorm * MACH_PREC : gbar;
+                final double zbar = gammaZeta / diag;
+                final double step = (bstep + snprod * zbar) / beta1;
+                // ynorm = FastMath.sqrt(ynorm2 + zbar * zbar);
+                if (!goodb) {
+                    for (int i = 0; i < n; i++) {
+                        final double xi = this.xL.getEntry(i);
+                        final double wi = wbar.getEntry(i);
+                        x.setEntry(i, xi + zbar * wi);
+                    }
+                } else {
+                    for (int i = 0; i < n; i++) {
+                        final double xi = this.xL.getEntry(i);
+                        final double wi = wbar.getEntry(i);
+                        final double bi = mb.getEntry(i);
+                        x.setEntry(i, xi + zbar * wi + step * bi);
+                    }
+                }
+            }
+        }
+
+        /**
+         * Performs the initial phase of the SYMMLQ algorithm. On return, the value of the state
+         * variables of {@code this} object correspond to k = 1.
+         */
+        void init() {
+            this.xL.set(0.);
+            /*
+             * Set up y for the first Lanczos vector. y and beta1 will be zero
+             * if b = 0.
+             */
+            this.r1 = this.b.copy();
+            this.y = this.m == null ? this.b.copy() : this.m.operate(this.r1);
+            if ((this.m != null) && this.check) {
+                checkSymmetry(this.m, this.r1, this.y, this.m.operate(this.y));
+            }
+
+            this.beta1 = this.r1.dotProduct(this.y);
+            if (this.beta1 < 0.) {
+                throwNPDLOException(this.m, this.y);
+            }
+            if (this.beta1 == 0.) {
+                /* If b = 0 exactly, stop with x = 0. */
+                this.bIsNull = true;
+                return;
+            }
+            this.bIsNull = false;
+            this.beta1 = FastMath.sqrt(this.beta1);
+            /* At this point
+             *   r1 = b,
+             *   y = M * b,
+             *   beta1 = beta[1].
+             */
+            final RealVector v = this.y.mapMultiply(1. / this.beta1);
+            this.y = this.a.operate(v);
+            if (this.check) {
+                checkSymmetry(this.a, v, this.y, this.a.operate(this.y));
+            }
+            /*
+             * Set up y for the second Lanczos vector. y and beta will be zero
+             * or very small if b is an eigenvector.
+             */
+            daxpy(-this.shift, v, this.y);
+            final double alpha = v.dotProduct(this.y);
+            daxpy(-alpha / this.beta1, this.r1, this.y);
+            /*
+             * At this point
+             *   alpha = alpha[1]
+             *   y     = beta[2] * M^(-1) * P' * v[2]
+             */
+            /* Make sure r2 will be orthogonal to the first v. */
+            final double vty = v.dotProduct(this.y);
+            final double vtv = v.dotProduct(v);
+            daxpy(-vty / vtv, v, this.y);
+            this.r2 = this.y.copy();
+            if (this.m != null) {
+                this.y = this.m.operate(this.r2);
+            }
+            this.oldb = this.beta1;
+            this.beta = this.r2.dotProduct(this.y);
+            if (this.beta < 0.) {
+                throwNPDLOException(this.m, this.y);
+            }
+            this.beta = FastMath.sqrt(this.beta);
+            /*
+             * At this point
+             *   oldb = beta[1]
+             *   beta = beta[2]
+             *   y  = beta[2] * P' * v[2]
+             *   r2 = beta[2] * M^(-1) * P' * v[2]
+             */
+            this.cgnorm = this.beta1;
+            this.gbar = alpha;
+            this.dbar = this.beta;
+            this.gammaZeta = this.beta1;
+            this.minusEpsZeta = 0.;
+            this.bstep = 0.;
+            this.snprod = 1.;
+            this.tnorm = alpha * alpha + this.beta * this.beta;
+            this.ynorm2 = 0.;
+            this.gmax = FastMath.abs(alpha) + MACH_PREC;
+            this.gmin = this.gmax;
+
+            if (this.goodb) {
+                this.wbar = new ArrayRealVector(this.a.getRowDimension());
+                this.wbar.set(0.);
+            } else {
+                this.wbar = v;
+            }
+            updateNorms();
+        }
+
+        /**
+         * Performs the next iteration of the algorithm. The iteration count should be incremented
+         * prior to calling this method. On return, the value of the state variables of {@code this}
+         * object correspond to the current iteration count {@code k}.
+         */
+        void update() {
+            final RealVector v = y.mapMultiply(1. / beta);
+            y = a.operate(v);
+            daxpbypz(-shift, v, -beta / oldb, r1, y);
+            final double alpha = v.dotProduct(y);
+            /*
+             * At this point
+             *   v     = P' * v[k],
+             *   y     = (A - shift * I) * P' * v[k] - beta[k] * M^(-1) * P' * v[k-1],
+             *   alpha = v'[k] * P * (A - shift * I) * P' * v[k]
+             *           - beta[k] * v[k]' * P * M^(-1) * P' * v[k-1]
+             *         = v'[k] * P * (A - shift * I) * P' * v[k]
+             *           - beta[k] * v[k]' * v[k-1]
+             *         = alpha[k].
+             */
+            daxpy(-alpha / beta, r2, y);
+            /*
+             * At this point
+             *   y = (A - shift * I) * P' * v[k] - alpha[k] * M^(-1) * P' * v[k]
+             *       - beta[k] * M^(-1) * P' * v[k-1]
+             *     = M^(-1) * P' * (P * (A - shift * I) * P' * v[k] -alpha[k] * v[k]
+             *       - beta[k] * v[k-1])
+             *     = beta[k+1] * M^(-1) * P' * v[k+1],
+             * from Paige and Saunders (1975), equation (3.2).
+             *
+             * WATCH-IT: the two following lines work only because y is no longer
+             * updated up to the end of the present iteration, and is
+             * reinitialized at the beginning of the next iteration.
+             */
+            r1 = r2;
+            r2 = y;
+            if (m != null) {
+                y = m.operate(r2);
+            }
+            oldb = beta;
+            beta = r2.dotProduct(y);
+            if (beta < 0.) {
+                throwNPDLOException(m, y);
+            }
+            beta = FastMath.sqrt(beta);
+            /*
+             * At this point
+             *   r1 = beta[k] * M^(-1) * P' * v[k],
+             *   r2 = beta[k+1] * M^(-1) * P' * v[k+1],
+             *   y  = beta[k+1] * P' * v[k+1],
+             *   oldb = beta[k],
+             *   beta = beta[k+1].
+             */
+            tnorm += alpha * alpha + oldb * oldb + beta * beta;
+            /*
+             * Compute the next plane rotation for Q. See Paige and Saunders
+             * (1975), equation (5.6), with
+             *   gamma = gamma[k-1],
+             *   c     = c[k-1],
+             *   s     = s[k-1].
+             */
+            final double gamma = FastMath.sqrt(gbar * gbar + oldb * oldb);
+            final double c = gbar / gamma;
+            final double s = oldb / gamma;
+            /*
+             * The relations
+             *   gbar[k] = s[k-1] * (-c[k-2] * beta[k]) - c[k-1] * alpha[k]
+             *           = s[k-1] * dbar[k] - c[k-1] * alpha[k],
+             *   delta[k] = c[k-1] * dbar[k] + s[k-1] * alpha[k],
+             * are not stated in Paige and Saunders (1975), but can be retrieved
+             * by expanding the (k, k-1) and (k, k) coefficients of the matrix in
+             * equation (5.5).
+             */
+            final double deltak = c * dbar + s * alpha;
+            gbar = s * dbar - c * alpha;
+            final double eps = s * beta;
+            dbar = -c * beta;
+            final double zeta = gammaZeta / gamma;
+            /*
+             * At this point
+             *   gbar   = gbar[k]
+             *   deltak = delta[k]
+             *   eps    = eps[k+1]
+             *   dbar   = dbar[k+1]
+             *   zeta   = zeta[k-1]
+             */
+            final double zetaC = zeta * c;
+            final double zetaS = zeta * s;
+            final int n = xL.getDimension();
+            for (int i = 0; i < n; i++) {
+                final double xi = xL.getEntry(i);
+                final double vi = v.getEntry(i);
+                final double wi = wbar.getEntry(i);
+                xL.setEntry(i, xi + wi * zetaC + vi * zetaS);
+                wbar.setEntry(i, wi * s - vi * c);
+            }
+            /*
+             * At this point
+             *   x = xL[k-1],
+             *   ptwbar = P' wbar[k],
+             * see Paige and Saunders (1975), equations (5.9) and (5.10).
+             */
+            bstep += snprod * c * zeta;
+            snprod *= s;
+            gmax = FastMath.max(gmax, gamma);
+            gmin = FastMath.min(gmin, gamma);
+            ynorm2 += zeta * zeta;
+            gammaZeta = minusEpsZeta - deltak * zeta;
+            minusEpsZeta = -eps * zeta;
+            /*
+             * At this point
+             *   snprod       = s[1] * ... * s[k-1],
+             *   gmax         = max(|alpha[1]|, gamma[1], ..., gamma[k-1]),
+             *   gmin         = min(|alpha[1]|, gamma[1], ..., gamma[k-1]),
+             *   ynorm2       = zeta[1]^2 + ... + zeta[k-1]^2,
+             *   gammaZeta    = gamma[k] * zeta[k],
+             *   minusEpsZeta = -eps[k+1] * zeta[k-1].
+             * The relation for gammaZeta can be retrieved from Paige and
+             * Saunders (1975), equation (5.4a), last line of the vector
+             * gbar[k] * zbar[k] = -eps[k] * zeta[k-2] - delta[k] * zeta[k-1].
+             */
+            updateNorms();
+        }
+
+        /**
+         * Computes the norms of the residuals, and checks for convergence. Updates {@link #lqnorm}
+         * and {@link #cgnorm}.
+         */
+        private void updateNorms() {
+            final double anorm = FastMath.sqrt(tnorm);
+            final double ynorm = FastMath.sqrt(ynorm2);
+            final double epsa = anorm * MACH_PREC;
+            final double epsx = anorm * ynorm * MACH_PREC;
+            final double epsr = anorm * ynorm * delta;
+            final double diag = gbar == 0. ? epsa : gbar;
+            lqnorm = FastMath.sqrt(gammaZeta * gammaZeta + minusEpsZeta * minusEpsZeta);
+            final double qrnorm = snprod * beta1;
+            cgnorm = qrnorm * beta / FastMath.abs(diag);
+
+            /*
+             * Estimate cond(A). In this version we look at the diagonals of L
+             * in the factorization of the tridiagonal matrix, T = L * Q.
+             * Sometimes, T[k] can be misleadingly ill-conditioned when T[k+1]
+             * is not, so we must be careful not to overestimate acond.
+             */
+            final double acond;
+            if (lqnorm <= cgnorm) {
+                acond = gmax / gmin;
+            } else {
+                acond = gmax / FastMath.min(gmin, FastMath.abs(diag));
+            }
+            if (acond * MACH_PREC >= 0.1) {
+                throw new IllConditionedOperatorException(acond);
+            }
+            if (beta1 <= epsx) {
+                /*
+                 * x has converged to an eigenvector of A corresponding to the
+                 * eigenvalue shift.
+                 */
+                throw new SingularOperatorException();
+            }
+            rnorm = FastMath.min(cgnorm, lqnorm);
+            hasConverged = (cgnorm <= epsx) || (cgnorm <= epsr);
+        }
+
+        /**
+         * Returns {@code true} if the default stopping criterion is fulfilled.
+         *
+         * @return {@code true} if convergence of the iterations has occurred
+         */
+        boolean hasConverged() {
+            return hasConverged;
+        }
+
+        /**
+         * Returns {@code true} if the right-hand side vector is zero exactly.
+         *
+         * @return the boolean value of {@code b == 0}
+         */
+        boolean bEqualsNullVector() {
+            return bIsNull;
+        }
+
+        /**
+         * Returns {@code true} if {@code beta} is essentially zero. This method is used to check
+         * for early stop of the iterations.
+         *
+         * @return {@code true} if {@code beta < }{@link #MACH_PREC}
+         */
+        boolean betaEqualsZero() {
+            return beta < MACH_PREC;
+        }
+
+        /**
+         * Returns the norm of the updated, preconditioned residual.
+         *
+         * @return the norm of the residual, ||P * r||
+         */
+        double getNormOfResidual() {
+            return rnorm;
+        }
+    }
+
+    /** Key for the exception context. */
+    private static final String OPERATOR = "operator";
+
+    /** Key for the exception context. */
+    private static final String THRESHOLD = "threshold";
+
+    /** Key for the exception context. */
+    private static final String VECTOR = "vector";
+
+    /** Key for the exception context. */
+    private static final String VECTOR1 = "vector1";
+
+    /** Key for the exception context. */
+    private static final String VECTOR2 = "vector2";
+
+    /** {@code true} if symmetry of matrix and conditioner must be checked. */
+    private final boolean check;
+
+    /** The value of the custom tolerance &delta; for the default stopping criterion. */
+    private final double delta;
+
+    /**
+     * Creates a new instance of this class, with <a href="#stopcrit">default stopping
+     * criterion</a>. Note that setting {@code check} to {@code true} entails an extra matrix-vector
+     * product in the initial phase.
+     *
+     * @param maxIterations the maximum number of iterations
+     * @param delta the &delta; parameter for the default stopping criterion
+     * @param check {@code true} if self-adjointedness of both matrix and preconditioner should be
+     *     checked
+     */
+    public SymmLQ(final int maxIterations, final double delta, final boolean check) {
+        super(maxIterations);
+        this.delta = delta;
+        this.check = check;
+    }
+
+    /**
+     * Creates a new instance of this class, with <a href="#stopcrit">default stopping criterion</a>
+     * and custom iteration manager. Note that setting {@code check} to {@code true} entails an
+     * extra matrix-vector product in the initial phase.
+     *
+     * @param manager the custom iteration manager
+     * @param delta the &delta; parameter for the default stopping criterion
+     * @param check {@code true} if self-adjointedness of both matrix and preconditioner should be
+     *     checked
+     */
+    public SymmLQ(final IterationManager manager, final double delta, final boolean check) {
+        super(manager);
+        this.delta = delta;
+        this.check = check;
+    }
+
+    /**
+     * Returns {@code true} if symmetry of the matrix, and symmetry as well as positive definiteness
+     * of the preconditioner should be checked.
+     *
+     * @return {@code true} if the tests are to be performed
+     */
+    public final boolean getCheck() {
+        return check;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws NonSelfAdjointOperatorException if {@link #getCheck()} is {@code true}, and {@code a}
+     *     or {@code m} is not self-adjoint
+     * @throws NonPositiveDefiniteOperatorException if {@code m} is not positive definite
+     * @throws IllConditionedOperatorException if {@code a} is ill-conditioned
+     */
+    @Override
+    public RealVector solve(
+            final RealLinearOperator a, final RealLinearOperator m, final RealVector b)
+            throws NullArgumentException,
+                    NonSquareOperatorException,
+                    DimensionMismatchException,
+                    MaxCountExceededException,
+                    NonSelfAdjointOperatorException,
+                    NonPositiveDefiniteOperatorException,
+                    IllConditionedOperatorException {
+        MathUtils.checkNotNull(a);
+        final RealVector x = new ArrayRealVector(a.getColumnDimension());
+        return solveInPlace(a, m, b, x, false, 0.);
+    }
+
+    /**
+     * Returns an estimate of the solution to the linear system (A - shift &middot; I) &middot; x =
+     * b.
+     *
+     * <p>If the solution x is expected to contain a large multiple of {@code b} (as in
+     * Rayleigh-quotient iteration), then better precision may be achieved with {@code goodb} set to
+     * {@code true}; this however requires an extra call to the preconditioner.
+     *
+     * <p>{@code shift} should be zero if the system A &middot; x = b is to be solved. Otherwise, it
+     * could be an approximation to an eigenvalue of A, such as the Rayleigh quotient b<sup>T</sup>
+     * &middot; A &middot; b / (b<sup>T</sup> &middot; b) corresponding to the vector b. If b is
+     * sufficiently like an eigenvector corresponding to an eigenvalue near shift, then the computed
+     * x may have very large components. When normalized, x may be closer to an eigenvector than b.
+     *
+     * @param a the linear operator A of the system
+     * @param m the preconditioner, M (can be {@code null})
+     * @param b the right-hand side vector
+     * @param goodb usually {@code false}, except if {@code x} is expected to contain a large
+     *     multiple of {@code b}
+     * @param shift the amount to be subtracted to all diagonal elements of A
+     * @return a reference to {@code x} (shallow copy)
+     * @throws NullArgumentException if one of the parameters is {@code null}
+     * @throws NonSquareOperatorException if {@code a} or {@code m} is not square
+     * @throws DimensionMismatchException if {@code m} or {@code b} have dimensions inconsistent
+     *     with {@code a}
+     * @throws MaxCountExceededException at exhaustion of the iteration count, unless a custom
+     *     {@link org.apache.commons.math3.util.Incrementor.MaxCountExceededCallback callback} has
+     *     been set at construction of the {@link IterationManager}
+     * @throws NonSelfAdjointOperatorException if {@link #getCheck()} is {@code true}, and {@code a}
+     *     or {@code m} is not self-adjoint
+     * @throws NonPositiveDefiniteOperatorException if {@code m} is not positive definite
+     * @throws IllConditionedOperatorException if {@code a} is ill-conditioned
+     */
+    public RealVector solve(
+            final RealLinearOperator a,
+            final RealLinearOperator m,
+            final RealVector b,
+            final boolean goodb,
+            final double shift)
+            throws NullArgumentException,
+                    NonSquareOperatorException,
+                    DimensionMismatchException,
+                    MaxCountExceededException,
+                    NonSelfAdjointOperatorException,
+                    NonPositiveDefiniteOperatorException,
+                    IllConditionedOperatorException {
+        MathUtils.checkNotNull(a);
+        final RealVector x = new ArrayRealVector(a.getColumnDimension());
+        return solveInPlace(a, m, b, x, goodb, shift);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @param x not meaningful in this implementation; should not be considered as an initial guess
+     *     (<a href="#initguess">more</a>)
+     * @throws NonSelfAdjointOperatorException if {@link #getCheck()} is {@code true}, and {@code a}
+     *     or {@code m} is not self-adjoint
+     * @throws NonPositiveDefiniteOperatorException if {@code m} is not positive definite
+     * @throws IllConditionedOperatorException if {@code a} is ill-conditioned
+     */
+    @Override
+    public RealVector solve(
+            final RealLinearOperator a,
+            final RealLinearOperator m,
+            final RealVector b,
+            final RealVector x)
+            throws NullArgumentException,
+                    NonSquareOperatorException,
+                    DimensionMismatchException,
+                    NonSelfAdjointOperatorException,
+                    NonPositiveDefiniteOperatorException,
+                    IllConditionedOperatorException,
+                    MaxCountExceededException {
+        MathUtils.checkNotNull(x);
+        return solveInPlace(a, m, b, x.copy(), false, 0.);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws NonSelfAdjointOperatorException if {@link #getCheck()} is {@code true}, and {@code a}
+     *     is not self-adjoint
+     * @throws IllConditionedOperatorException if {@code a} is ill-conditioned
+     */
+    @Override
+    public RealVector solve(final RealLinearOperator a, final RealVector b)
+            throws NullArgumentException,
+                    NonSquareOperatorException,
+                    DimensionMismatchException,
+                    NonSelfAdjointOperatorException,
+                    IllConditionedOperatorException,
+                    MaxCountExceededException {
+        MathUtils.checkNotNull(a);
+        final RealVector x = new ArrayRealVector(a.getColumnDimension());
+        x.set(0.);
+        return solveInPlace(a, null, b, x, false, 0.);
+    }
+
+    /**
+     * Returns the solution to the system (A - shift &middot; I) &middot; x = b.
+     *
+     * <p>If the solution x is expected to contain a large multiple of {@code b} (as in
+     * Rayleigh-quotient iteration), then better precision may be achieved with {@code goodb} set to
+     * {@code true}.
+     *
+     * <p>{@code shift} should be zero if the system A &middot; x = b is to be solved. Otherwise, it
+     * could be an approximation to an eigenvalue of A, such as the Rayleigh quotient b<sup>T</sup>
+     * &middot; A &middot; b / (b<sup>T</sup> &middot; b) corresponding to the vector b. If b is
+     * sufficiently like an eigenvector corresponding to an eigenvalue near shift, then the computed
+     * x may have very large components. When normalized, x may be closer to an eigenvector than b.
+     *
+     * @param a the linear operator A of the system
+     * @param b the right-hand side vector
+     * @param goodb usually {@code false}, except if {@code x} is expected to contain a large
+     *     multiple of {@code b}
+     * @param shift the amount to be subtracted to all diagonal elements of A
+     * @return a reference to {@code x}
+     * @throws NullArgumentException if one of the parameters is {@code null}
+     * @throws NonSquareOperatorException if {@code a} is not square
+     * @throws DimensionMismatchException if {@code b} has dimensions inconsistent with {@code a}
+     * @throws MaxCountExceededException at exhaustion of the iteration count, unless a custom
+     *     {@link org.apache.commons.math3.util.Incrementor.MaxCountExceededCallback callback} has
+     *     been set at construction of the {@link IterationManager}
+     * @throws NonSelfAdjointOperatorException if {@link #getCheck()} is {@code true}, and {@code a}
+     *     is not self-adjoint
+     * @throws IllConditionedOperatorException if {@code a} is ill-conditioned
+     */
+    public RealVector solve(
+            final RealLinearOperator a, final RealVector b, final boolean goodb, final double shift)
+            throws NullArgumentException,
+                    NonSquareOperatorException,
+                    DimensionMismatchException,
+                    NonSelfAdjointOperatorException,
+                    IllConditionedOperatorException,
+                    MaxCountExceededException {
+        MathUtils.checkNotNull(a);
+        final RealVector x = new ArrayRealVector(a.getColumnDimension());
+        return solveInPlace(a, null, b, x, goodb, shift);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @param x not meaningful in this implementation; should not be considered as an initial guess
+     *     (<a href="#initguess">more</a>)
+     * @throws NonSelfAdjointOperatorException if {@link #getCheck()} is {@code true}, and {@code a}
+     *     is not self-adjoint
+     * @throws IllConditionedOperatorException if {@code a} is ill-conditioned
+     */
+    @Override
+    public RealVector solve(final RealLinearOperator a, final RealVector b, final RealVector x)
+            throws NullArgumentException,
+                    NonSquareOperatorException,
+                    DimensionMismatchException,
+                    NonSelfAdjointOperatorException,
+                    IllConditionedOperatorException,
+                    MaxCountExceededException {
+        MathUtils.checkNotNull(x);
+        return solveInPlace(a, null, b, x.copy(), false, 0.);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @param x the vector to be updated with the solution; {@code x} should not be considered as an
+     *     initial guess (<a href="#initguess">more</a>)
+     * @throws NonSelfAdjointOperatorException if {@link #getCheck()} is {@code true}, and {@code a}
+     *     or {@code m} is not self-adjoint
+     * @throws NonPositiveDefiniteOperatorException if {@code m} is not positive definite
+     * @throws IllConditionedOperatorException if {@code a} is ill-conditioned
+     */
+    @Override
+    public RealVector solveInPlace(
+            final RealLinearOperator a,
+            final RealLinearOperator m,
+            final RealVector b,
+            final RealVector x)
+            throws NullArgumentException,
+                    NonSquareOperatorException,
+                    DimensionMismatchException,
+                    NonSelfAdjointOperatorException,
+                    NonPositiveDefiniteOperatorException,
+                    IllConditionedOperatorException,
+                    MaxCountExceededException {
+        return solveInPlace(a, m, b, x, false, 0.);
+    }
+
+    /**
+     * Returns an estimate of the solution to the linear system (A - shift &middot; I) &middot; x =
+     * b. The solution is computed in-place.
+     *
+     * <p>If the solution x is expected to contain a large multiple of {@code b} (as in
+     * Rayleigh-quotient iteration), then better precision may be achieved with {@code goodb} set to
+     * {@code true}; this however requires an extra call to the preconditioner.
+     *
+     * <p>{@code shift} should be zero if the system A &middot; x = b is to be solved. Otherwise, it
+     * could be an approximation to an eigenvalue of A, such as the Rayleigh quotient b<sup>T</sup>
+     * &middot; A &middot; b / (b<sup>T</sup> &middot; b) corresponding to the vector b. If b is
+     * sufficiently like an eigenvector corresponding to an eigenvalue near shift, then the computed
+     * x may have very large components. When normalized, x may be closer to an eigenvector than b.
+     *
+     * @param a the linear operator A of the system
+     * @param m the preconditioner, M (can be {@code null})
+     * @param b the right-hand side vector
+     * @param x the vector to be updated with the solution; {@code x} should not be considered as an
+     *     initial guess (<a href="#initguess">more</a>)
+     * @param goodb usually {@code false}, except if {@code x} is expected to contain a large
+     *     multiple of {@code b}
+     * @param shift the amount to be subtracted to all diagonal elements of A
+     * @return a reference to {@code x} (shallow copy).
+     * @throws NullArgumentException if one of the parameters is {@code null}
+     * @throws NonSquareOperatorException if {@code a} or {@code m} is not square
+     * @throws DimensionMismatchException if {@code m}, {@code b} or {@code x} have dimensions
+     *     inconsistent with {@code a}.
+     * @throws MaxCountExceededException at exhaustion of the iteration count, unless a custom
+     *     {@link org.apache.commons.math3.util.Incrementor.MaxCountExceededCallback callback} has
+     *     been set at construction of the {@link IterationManager}
+     * @throws NonSelfAdjointOperatorException if {@link #getCheck()} is {@code true}, and {@code a}
+     *     or {@code m} is not self-adjoint
+     * @throws NonPositiveDefiniteOperatorException if {@code m} is not positive definite
+     * @throws IllConditionedOperatorException if {@code a} is ill-conditioned
+     */
+    public RealVector solveInPlace(
+            final RealLinearOperator a,
+            final RealLinearOperator m,
+            final RealVector b,
+            final RealVector x,
+            final boolean goodb,
+            final double shift)
+            throws NullArgumentException,
+                    NonSquareOperatorException,
+                    DimensionMismatchException,
+                    NonSelfAdjointOperatorException,
+                    NonPositiveDefiniteOperatorException,
+                    IllConditionedOperatorException,
+                    MaxCountExceededException {
+        checkParameters(a, m, b, x);
+
+        final IterationManager manager = getIterationManager();
+        /* Initialization counts as an iteration. */
+        manager.resetIterationCount();
+        manager.incrementIterationCount();
+
+        final State state;
+        state = new State(a, m, b, goodb, shift, delta, check);
+        state.init();
+        state.refineSolution(x);
+        IterativeLinearSolverEvent event;
+        event =
+                new DefaultIterativeLinearSolverEvent(
+                        this, manager.getIterations(), x, b, state.getNormOfResidual());
+        if (state.bEqualsNullVector()) {
+            /* If b = 0 exactly, stop with x = 0. */
+            manager.fireTerminationEvent(event);
+            return x;
+        }
+        /* Cause termination if beta is essentially zero. */
+        final boolean earlyStop;
+        earlyStop = state.betaEqualsZero() || state.hasConverged();
+        manager.fireInitializationEvent(event);
+        if (!earlyStop) {
+            do {
+                manager.incrementIterationCount();
+                event =
+                        new DefaultIterativeLinearSolverEvent(
+                                this, manager.getIterations(), x, b, state.getNormOfResidual());
+                manager.fireIterationStartedEvent(event);
+                state.update();
+                state.refineSolution(x);
+                event =
+                        new DefaultIterativeLinearSolverEvent(
+                                this, manager.getIterations(), x, b, state.getNormOfResidual());
+                manager.fireIterationPerformedEvent(event);
+            } while (!state.hasConverged());
+        }
+        event =
+                new DefaultIterativeLinearSolverEvent(
+                        this, manager.getIterations(), x, b, state.getNormOfResidual());
+        manager.fireTerminationEvent(event);
+        return x;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @param x the vector to be updated with the solution; {@code x} should not be considered as an
+     *     initial guess (<a href="#initguess">more</a>)
+     * @throws NonSelfAdjointOperatorException if {@link #getCheck()} is {@code true}, and {@code a}
+     *     is not self-adjoint
+     * @throws IllConditionedOperatorException if {@code a} is ill-conditioned
+     */
+    @Override
+    public RealVector solveInPlace(
+            final RealLinearOperator a, final RealVector b, final RealVector x)
+            throws NullArgumentException,
+                    NonSquareOperatorException,
+                    DimensionMismatchException,
+                    NonSelfAdjointOperatorException,
+                    IllConditionedOperatorException,
+                    MaxCountExceededException {
+        return solveInPlace(a, null, b, x, false, 0.);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/TriDiagonalTransformer.java b/src/main/java/org/apache/commons/math3/linear/TriDiagonalTransformer.java
new file mode 100644
index 0000000..b2f8d33
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/TriDiagonalTransformer.java
@@ -0,0 +1,274 @@
+/*
+ * 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.math3.linear;
+
+import org.apache.commons.math3.util.FastMath;
+
+import java.util.Arrays;
+
+/**
+ * Class transforming a symmetrical matrix to tridiagonal shape.
+ *
+ * <p>A symmetrical m &times; m matrix A can be written as the product of three matrices: A = Q
+ * &times; T &times; Q<sup>T</sup> with Q an orthogonal matrix and T a symmetrical tridiagonal
+ * matrix. Both Q and T are m &times; m matrices.
+ *
+ * <p>This implementation only uses the upper part of the matrix, the part below the diagonal is not
+ * accessed at all.
+ *
+ * <p>Transformation to tridiagonal shape is often not a goal by itself, but it is an intermediate
+ * step in more general decomposition algorithms like {@link EigenDecomposition eigen
+ * decomposition}. This class is therefore intended for internal use by the library and is not
+ * public. As a consequence of this explicitly limited scope, many methods directly returns
+ * references to internal arrays, not copies.
+ *
+ * @since 2.0
+ */
+class TriDiagonalTransformer {
+    /** Householder vectors. */
+    private final double householderVectors[][];
+
+    /** Main diagonal. */
+    private final double[] main;
+
+    /** Secondary diagonal. */
+    private final double[] secondary;
+
+    /** Cached value of Q. */
+    private RealMatrix cachedQ;
+
+    /** Cached value of Qt. */
+    private RealMatrix cachedQt;
+
+    /** Cached value of T. */
+    private RealMatrix cachedT;
+
+    /**
+     * Build the transformation to tridiagonal shape of a symmetrical matrix.
+     *
+     * <p>The specified matrix is assumed to be symmetrical without any check. Only the upper
+     * triangular part of the matrix is used.
+     *
+     * @param matrix Symmetrical matrix to transform.
+     * @throws NonSquareMatrixException if the matrix is not square.
+     */
+    TriDiagonalTransformer(RealMatrix matrix) {
+        if (!matrix.isSquare()) {
+            throw new NonSquareMatrixException(
+                    matrix.getRowDimension(), matrix.getColumnDimension());
+        }
+
+        final int m = matrix.getRowDimension();
+        householderVectors = matrix.getData();
+        main = new double[m];
+        secondary = new double[m - 1];
+        cachedQ = null;
+        cachedQt = null;
+        cachedT = null;
+
+        // transform matrix
+        transform();
+    }
+
+    /**
+     * Returns the matrix Q of the transform.
+     *
+     * <p>Q is an orthogonal matrix, i.e. its transpose is also its inverse.
+     *
+     * @return the Q matrix
+     */
+    public RealMatrix getQ() {
+        if (cachedQ == null) {
+            cachedQ = getQT().transpose();
+        }
+        return cachedQ;
+    }
+
+    /**
+     * Returns the transpose of the matrix Q of the transform.
+     *
+     * <p>Q is an orthogonal matrix, i.e. its transpose is also its inverse.
+     *
+     * @return the Q matrix
+     */
+    public RealMatrix getQT() {
+        if (cachedQt == null) {
+            final int m = householderVectors.length;
+            double[][] qta = new double[m][m];
+
+            // build up first part of the matrix by applying Householder transforms
+            for (int k = m - 1; k >= 1; --k) {
+                final double[] hK = householderVectors[k - 1];
+                qta[k][k] = 1;
+                if (hK[k] != 0.0) {
+                    final double inv = 1.0 / (secondary[k - 1] * hK[k]);
+                    double beta = 1.0 / secondary[k - 1];
+                    qta[k][k] = 1 + beta * hK[k];
+                    for (int i = k + 1; i < m; ++i) {
+                        qta[k][i] = beta * hK[i];
+                    }
+                    for (int j = k + 1; j < m; ++j) {
+                        beta = 0;
+                        for (int i = k + 1; i < m; ++i) {
+                            beta += qta[j][i] * hK[i];
+                        }
+                        beta *= inv;
+                        qta[j][k] = beta * hK[k];
+                        for (int i = k + 1; i < m; ++i) {
+                            qta[j][i] += beta * hK[i];
+                        }
+                    }
+                }
+            }
+            qta[0][0] = 1;
+            cachedQt = MatrixUtils.createRealMatrix(qta);
+        }
+
+        // return the cached matrix
+        return cachedQt;
+    }
+
+    /**
+     * Returns the tridiagonal matrix T of the transform.
+     *
+     * @return the T matrix
+     */
+    public RealMatrix getT() {
+        if (cachedT == null) {
+            final int m = main.length;
+            double[][] ta = new double[m][m];
+            for (int i = 0; i < m; ++i) {
+                ta[i][i] = main[i];
+                if (i > 0) {
+                    ta[i][i - 1] = secondary[i - 1];
+                }
+                if (i < main.length - 1) {
+                    ta[i][i + 1] = secondary[i];
+                }
+            }
+            cachedT = MatrixUtils.createRealMatrix(ta);
+        }
+
+        // return the cached matrix
+        return cachedT;
+    }
+
+    /**
+     * Get the Householder vectors of the transform.
+     *
+     * <p>Note that since this class is only intended for internal use, it returns directly a
+     * reference to its internal arrays, not a copy.
+     *
+     * @return the main diagonal elements of the B matrix
+     */
+    double[][] getHouseholderVectorsRef() {
+        return householderVectors;
+    }
+
+    /**
+     * Get the main diagonal elements of the matrix T of the transform.
+     *
+     * <p>Note that since this class is only intended for internal use, it returns directly a
+     * reference to its internal arrays, not a copy.
+     *
+     * @return the main diagonal elements of the T matrix
+     */
+    double[] getMainDiagonalRef() {
+        return main;
+    }
+
+    /**
+     * Get the secondary diagonal elements of the matrix T of the transform.
+     *
+     * <p>Note that since this class is only intended for internal use, it returns directly a
+     * reference to its internal arrays, not a copy.
+     *
+     * @return the secondary diagonal elements of the T matrix
+     */
+    double[] getSecondaryDiagonalRef() {
+        return secondary;
+    }
+
+    /**
+     * Transform original matrix to tridiagonal form.
+     *
+     * <p>Transformation is done using Householder transforms.
+     */
+    private void transform() {
+        final int m = householderVectors.length;
+        final double[] z = new double[m];
+        for (int k = 0; k < m - 1; k++) {
+
+            // zero-out a row and a column simultaneously
+            final double[] hK = householderVectors[k];
+            main[k] = hK[k];
+            double xNormSqr = 0;
+            for (int j = k + 1; j < m; ++j) {
+                final double c = hK[j];
+                xNormSqr += c * c;
+            }
+            final double a = (hK[k + 1] > 0) ? -FastMath.sqrt(xNormSqr) : FastMath.sqrt(xNormSqr);
+            secondary[k] = a;
+            if (a != 0.0) {
+                // apply Householder transform from left and right simultaneously
+
+                hK[k + 1] -= a;
+                final double beta = -1 / (a * hK[k + 1]);
+
+                // compute a = beta A v, where v is the Householder vector
+                // this loop is written in such a way
+                //   1) only the upper triangular part of the matrix is accessed
+                //   2) access is cache-friendly for a matrix stored in rows
+                Arrays.fill(z, k + 1, m, 0);
+                for (int i = k + 1; i < m; ++i) {
+                    final double[] hI = householderVectors[i];
+                    final double hKI = hK[i];
+                    double zI = hI[i] * hKI;
+                    for (int j = i + 1; j < m; ++j) {
+                        final double hIJ = hI[j];
+                        zI += hIJ * hK[j];
+                        z[j] += hIJ * hKI;
+                    }
+                    z[i] = beta * (z[i] + zI);
+                }
+
+                // compute gamma = beta vT z / 2
+                double gamma = 0;
+                for (int i = k + 1; i < m; ++i) {
+                    gamma += z[i] * hK[i];
+                }
+                gamma *= beta / 2;
+
+                // compute z = z - gamma v
+                for (int i = k + 1; i < m; ++i) {
+                    z[i] -= gamma * hK[i];
+                }
+
+                // update matrix: A = A - v zT - z vT
+                // only the upper triangular part of the matrix is updated
+                for (int i = k + 1; i < m; ++i) {
+                    final double[] hI = householderVectors[i];
+                    for (int j = i; j < m; ++j) {
+                        hI[j] -= hK[i] * z[j] + z[i] * hK[j];
+                    }
+                }
+            }
+        }
+        main[m - 1] = householderVectors[m - 1][m - 1];
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/linear/package-info.java b/src/main/java/org/apache/commons/math3/linear/package-info.java
new file mode 100644
index 0000000..4b0c86b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/linear/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+/** Linear algebra support. */
+package org.apache.commons.math3.linear;
diff --git a/src/main/java/org/apache/commons/math3/ml/clustering/CentroidCluster.java b/src/main/java/org/apache/commons/math3/ml/clustering/CentroidCluster.java
new file mode 100644
index 0000000..5cfc7bc
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/clustering/CentroidCluster.java
@@ -0,0 +1,53 @@
+/*
+ * 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.math3.ml.clustering;
+
+/**
+ * A Cluster used by centroid-based clustering algorithms.
+ * <p>
+ * Defines additionally a cluster center which may not necessarily be a member
+ * of the original data set.
+ *
+ * @param <T> the type of points that can be clustered
+ * @since 3.2
+ */
+public class CentroidCluster<T extends Clusterable> extends Cluster<T> {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -3075288519071812288L;
+
+    /** Center of the cluster. */
+    private final Clusterable center;
+
+    /**
+     * Build a cluster centered at a specified point.
+     * @param center the point which is to be the center of this cluster
+     */
+    public CentroidCluster(final Clusterable center) {
+        super();
+        this.center = center;
+    }
+
+    /**
+     * Get the point chosen to be the center of this cluster.
+     * @return chosen cluster center
+     */
+    public Clusterable getCenter() {
+        return center;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/clustering/Cluster.java b/src/main/java/org/apache/commons/math3/ml/clustering/Cluster.java
new file mode 100644
index 0000000..fa6df94
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/clustering/Cluster.java
@@ -0,0 +1,60 @@
+/*
+ * 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.math3.ml.clustering;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Cluster holding a set of {@link Clusterable} points.
+ * @param <T> the type of points that can be clustered
+ * @since 3.2
+ */
+public class Cluster<T extends Clusterable> implements Serializable {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -3442297081515880464L;
+
+    /** The points contained in this cluster. */
+    private final List<T> points;
+
+    /**
+     * Build a cluster centered at a specified point.
+     */
+    public Cluster() {
+        points = new ArrayList<T>();
+    }
+
+    /**
+     * Add a point to this cluster.
+     * @param point point to add
+     */
+    public void addPoint(final T point) {
+        points.add(point);
+    }
+
+    /**
+     * Get the points contained in the cluster.
+     * @return points contained in the cluster
+     */
+    public List<T> getPoints() {
+        return points;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/clustering/Clusterable.java b/src/main/java/org/apache/commons/math3/ml/clustering/Clusterable.java
new file mode 100644
index 0000000..e712eb7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/clustering/Clusterable.java
@@ -0,0 +1,32 @@
+/*
+ * 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.math3.ml.clustering;
+
+/**
+ * Interface for n-dimensional points that can be clustered together.
+ * @since 3.2
+ */
+public interface Clusterable {
+
+    /**
+     * Gets the n-dimensional point.
+     *
+     * @return the point array
+     */
+    double[] getPoint();
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/clustering/Clusterer.java b/src/main/java/org/apache/commons/math3/ml/clustering/Clusterer.java
new file mode 100644
index 0000000..30e38c6
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/clustering/Clusterer.java
@@ -0,0 +1,80 @@
+/*
+ * 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.math3.ml.clustering;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.commons.math3.exception.ConvergenceException;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.ml.distance.DistanceMeasure;
+
+/**
+ * Base class for clustering algorithms.
+ *
+ * @param <T> the type of points that can be clustered
+ * @since 3.2
+ */
+public abstract class Clusterer<T extends Clusterable> {
+
+    /** The distance measure to use. */
+    private DistanceMeasure measure;
+
+    /**
+     * Build a new clusterer with the given {@link DistanceMeasure}.
+     *
+     * @param measure the distance measure to use
+     */
+    protected Clusterer(final DistanceMeasure measure) {
+        this.measure = measure;
+    }
+
+    /**
+     * Perform a cluster analysis on the given set of {@link Clusterable} instances.
+     *
+     * @param points the set of {@link Clusterable} instances
+     * @return a {@link List} of clusters
+     * @throws MathIllegalArgumentException if points are null or the number of
+     *   data points is not compatible with this clusterer
+     * @throws ConvergenceException if the algorithm has not yet converged after
+     *   the maximum number of iterations has been exceeded
+     */
+    public abstract List<? extends Cluster<T>> cluster(Collection<T> points)
+            throws MathIllegalArgumentException, ConvergenceException;
+
+    /**
+     * Returns the {@link DistanceMeasure} instance used by this clusterer.
+     *
+     * @return the distance measure
+     */
+    public DistanceMeasure getDistanceMeasure() {
+        return measure;
+    }
+
+    /**
+     * Calculates the distance between two {@link Clusterable} instances
+     * with the configured {@link DistanceMeasure}.
+     *
+     * @param p1 the first clusterable
+     * @param p2 the second clusterable
+     * @return the distance between the two clusterables
+     */
+    protected double distance(final Clusterable p1, final Clusterable p2) {
+        return measure.compute(p1.getPoint(), p2.getPoint());
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/clustering/DBSCANClusterer.java b/src/main/java/org/apache/commons/math3/ml/clustering/DBSCANClusterer.java
new file mode 100644
index 0000000..ce3d5cd
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/clustering/DBSCANClusterer.java
@@ -0,0 +1,233 @@
+/*
+ * 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.math3.ml.clustering;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.ml.distance.DistanceMeasure;
+import org.apache.commons.math3.ml.distance.EuclideanDistance;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * DBSCAN (density-based spatial clustering of applications with noise) algorithm.
+ * <p>
+ * The DBSCAN algorithm forms clusters based on the idea of density connectivity, i.e.
+ * a point p is density connected to another point q, if there exists a chain of
+ * points p<sub>i</sub>, with i = 1 .. n and p<sub>1</sub> = p and p<sub>n</sub> = q,
+ * such that each pair &lt;p<sub>i</sub>, p<sub>i+1</sub>&gt; is directly density-reachable.
+ * A point q is directly density-reachable from point p if it is in the &epsilon;-neighborhood
+ * of this point.
+ * <p>
+ * Any point that is not density-reachable from a formed cluster is treated as noise, and
+ * will thus not be present in the result.
+ * <p>
+ * The algorithm requires two parameters:
+ * <ul>
+ *   <li>eps: the distance that defines the &epsilon;-neighborhood of a point
+ *   <li>minPoints: the minimum number of density-connected points required to form a cluster
+ * </ul>
+ *
+ * @param <T> type of the points to cluster
+ * @see <a href="http://en.wikipedia.org/wiki/DBSCAN">DBSCAN (wikipedia)</a>
+ * @see <a href="http://www.dbs.ifi.lmu.de/Publikationen/Papers/KDD-96.final.frame.pdf">
+ * A Density-Based Algorithm for Discovering Clusters in Large Spatial Databases with Noise</a>
+ * @since 3.2
+ */
+public class DBSCANClusterer<T extends Clusterable> extends Clusterer<T> {
+
+    /** Maximum radius of the neighborhood to be considered. */
+    private final double              eps;
+
+    /** Minimum number of points needed for a cluster. */
+    private final int                 minPts;
+
+    /** Status of a point during the clustering process. */
+    private enum PointStatus {
+        /** The point has is considered to be noise. */
+        NOISE,
+        /** The point is already part of a cluster. */
+        PART_OF_CLUSTER
+    }
+
+    /**
+     * Creates a new instance of a DBSCANClusterer.
+     * <p>
+     * The euclidean distance will be used as default distance measure.
+     *
+     * @param eps maximum radius of the neighborhood to be considered
+     * @param minPts minimum number of points needed for a cluster
+     * @throws NotPositiveException if {@code eps < 0.0} or {@code minPts < 0}
+     */
+    public DBSCANClusterer(final double eps, final int minPts)
+        throws NotPositiveException {
+        this(eps, minPts, new EuclideanDistance());
+    }
+
+    /**
+     * Creates a new instance of a DBSCANClusterer.
+     *
+     * @param eps maximum radius of the neighborhood to be considered
+     * @param minPts minimum number of points needed for a cluster
+     * @param measure the distance measure to use
+     * @throws NotPositiveException if {@code eps < 0.0} or {@code minPts < 0}
+     */
+    public DBSCANClusterer(final double eps, final int minPts, final DistanceMeasure measure)
+        throws NotPositiveException {
+        super(measure);
+
+        if (eps < 0.0d) {
+            throw new NotPositiveException(eps);
+        }
+        if (minPts < 0) {
+            throw new NotPositiveException(minPts);
+        }
+        this.eps = eps;
+        this.minPts = minPts;
+    }
+
+    /**
+     * Returns the maximum radius of the neighborhood to be considered.
+     * @return maximum radius of the neighborhood
+     */
+    public double getEps() {
+        return eps;
+    }
+
+    /**
+     * Returns the minimum number of points needed for a cluster.
+     * @return minimum number of points needed for a cluster
+     */
+    public int getMinPts() {
+        return minPts;
+    }
+
+    /**
+     * Performs DBSCAN cluster analysis.
+     *
+     * @param points the points to cluster
+     * @return the list of clusters
+     * @throws NullArgumentException if the data points are null
+     */
+    @Override
+    public List<Cluster<T>> cluster(final Collection<T> points) throws NullArgumentException {
+
+        // sanity checks
+        MathUtils.checkNotNull(points);
+
+        final List<Cluster<T>> clusters = new ArrayList<Cluster<T>>();
+        final Map<Clusterable, PointStatus> visited = new HashMap<Clusterable, PointStatus>();
+
+        for (final T point : points) {
+            if (visited.get(point) != null) {
+                continue;
+            }
+            final List<T> neighbors = getNeighbors(point, points);
+            if (neighbors.size() >= minPts) {
+                // DBSCAN does not care about center points
+                final Cluster<T> cluster = new Cluster<T>();
+                clusters.add(expandCluster(cluster, point, neighbors, points, visited));
+            } else {
+                visited.put(point, PointStatus.NOISE);
+            }
+        }
+
+        return clusters;
+    }
+
+    /**
+     * Expands the cluster to include density-reachable items.
+     *
+     * @param cluster Cluster to expand
+     * @param point Point to add to cluster
+     * @param neighbors List of neighbors
+     * @param points the data set
+     * @param visited the set of already visited points
+     * @return the expanded cluster
+     */
+    private Cluster<T> expandCluster(final Cluster<T> cluster,
+                                     final T point,
+                                     final List<T> neighbors,
+                                     final Collection<T> points,
+                                     final Map<Clusterable, PointStatus> visited) {
+        cluster.addPoint(point);
+        visited.put(point, PointStatus.PART_OF_CLUSTER);
+
+        List<T> seeds = new ArrayList<T>(neighbors);
+        int index = 0;
+        while (index < seeds.size()) {
+            final T current = seeds.get(index);
+            PointStatus pStatus = visited.get(current);
+            // only check non-visited points
+            if (pStatus == null) {
+                final List<T> currentNeighbors = getNeighbors(current, points);
+                if (currentNeighbors.size() >= minPts) {
+                    seeds = merge(seeds, currentNeighbors);
+                }
+            }
+
+            if (pStatus != PointStatus.PART_OF_CLUSTER) {
+                visited.put(current, PointStatus.PART_OF_CLUSTER);
+                cluster.addPoint(current);
+            }
+
+            index++;
+        }
+        return cluster;
+    }
+
+    /**
+     * Returns a list of density-reachable neighbors of a {@code point}.
+     *
+     * @param point the point to look for
+     * @param points possible neighbors
+     * @return the List of neighbors
+     */
+    private List<T> getNeighbors(final T point, final Collection<T> points) {
+        final List<T> neighbors = new ArrayList<T>();
+        for (final T neighbor : points) {
+            if (point != neighbor && distance(neighbor, point) <= eps) {
+                neighbors.add(neighbor);
+            }
+        }
+        return neighbors;
+    }
+
+    /**
+     * Merges two lists together.
+     *
+     * @param one first list
+     * @param two second list
+     * @return merged lists
+     */
+    private List<T> merge(final List<T> one, final List<T> two) {
+        final Set<T> oneSet = new HashSet<T>(one);
+        for (T item : two) {
+            if (!oneSet.contains(item)) {
+                one.add(item);
+            }
+        }
+        return one;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/clustering/DoublePoint.java b/src/main/java/org/apache/commons/math3/ml/clustering/DoublePoint.java
new file mode 100644
index 0000000..4fb31f7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/clustering/DoublePoint.java
@@ -0,0 +1,86 @@
+/*
+ * 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.math3.ml.clustering;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+/**
+ * A simple implementation of {@link Clusterable} for points with double coordinates.
+ * @since 3.2
+ */
+public class DoublePoint implements Clusterable, Serializable {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 3946024775784901369L;
+
+    /** Point coordinates. */
+    private final double[] point;
+
+    /**
+     * Build an instance wrapping an double array.
+     * <p>
+     * The wrapped array is referenced, it is <em>not</em> copied.
+     *
+     * @param point the n-dimensional point in double space
+     */
+    public DoublePoint(final double[] point) {
+        this.point = point;
+    }
+
+    /**
+     * Build an instance wrapping an integer array.
+     * <p>
+     * The wrapped array is copied to an internal double array.
+     *
+     * @param point the n-dimensional point in integer space
+     */
+    public DoublePoint(final int[] point) {
+        this.point = new double[point.length];
+        for ( int i = 0; i < point.length; i++) {
+            this.point[i] = point[i];
+        }
+    }
+
+    /** {@inheritDoc} */
+    public double[] getPoint() {
+        return point;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(final Object other) {
+        if (!(other instanceof DoublePoint)) {
+            return false;
+        }
+        return Arrays.equals(point, ((DoublePoint) other).point);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(point);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return Arrays.toString(point);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/clustering/FuzzyKMeansClusterer.java b/src/main/java/org/apache/commons/math3/ml/clustering/FuzzyKMeansClusterer.java
new file mode 100644
index 0000000..5f89934
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/clustering/FuzzyKMeansClusterer.java
@@ -0,0 +1,426 @@
+/*
+ * 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.math3.ml.clustering;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.linear.MatrixUtils;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.ml.distance.DistanceMeasure;
+import org.apache.commons.math3.ml.distance.EuclideanDistance;
+import org.apache.commons.math3.random.JDKRandomGenerator;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * Fuzzy K-Means clustering algorithm.
+ * <p>
+ * The Fuzzy K-Means algorithm is a variation of the classical K-Means algorithm, with the
+ * major difference that a single data point is not uniquely assigned to a single cluster.
+ * Instead, each point i has a set of weights u<sub>ij</sub> which indicate the degree of membership
+ * to the cluster j.
+ * <p>
+ * The algorithm then tries to minimize the objective function:
+ * <pre>
+ * J = &#8721;<sub>i=1..C</sub>&#8721;<sub>k=1..N</sub> u<sub>ik</sub><sup>m</sup>d<sub>ik</sub><sup>2</sup>
+ * </pre>
+ * with d<sub>ik</sub> being the distance between data point i and the cluster center k.
+ * <p>
+ * The algorithm requires two parameters:
+ * <ul>
+ *   <li>k: the number of clusters
+ *   <li>fuzziness: determines the level of cluster fuzziness, larger values lead to fuzzier clusters
+ * </ul>
+ * Additional, optional parameters:
+ * <ul>
+ *   <li>maxIterations: the maximum number of iterations
+ *   <li>epsilon: the convergence criteria, default is 1e-3
+ * </ul>
+ * <p>
+ * The fuzzy variant of the K-Means algorithm is more robust with regard to the selection
+ * of the initial cluster centers.
+ *
+ * @param <T> type of the points to cluster
+ * @since 3.3
+ */
+public class FuzzyKMeansClusterer<T extends Clusterable> extends Clusterer<T> {
+
+    /** The default value for the convergence criteria. */
+    private static final double DEFAULT_EPSILON = 1e-3;
+
+    /** The number of clusters. */
+    private final int k;
+
+    /** The maximum number of iterations. */
+    private final int maxIterations;
+
+    /** The fuzziness factor. */
+    private final double fuzziness;
+
+    /** The convergence criteria. */
+    private final double epsilon;
+
+    /** Random generator for choosing initial centers. */
+    private final RandomGenerator random;
+
+    /** The membership matrix. */
+    private double[][] membershipMatrix;
+
+    /** The list of points used in the last call to {@link #cluster(Collection)}. */
+    private List<T> points;
+
+    /** The list of clusters resulting from the last call to {@link #cluster(Collection)}. */
+    private List<CentroidCluster<T>> clusters;
+
+    /**
+     * Creates a new instance of a FuzzyKMeansClusterer.
+     * <p>
+     * The euclidean distance will be used as default distance measure.
+     *
+     * @param k the number of clusters to split the data into
+     * @param fuzziness the fuzziness factor, must be &gt; 1.0
+     * @throws NumberIsTooSmallException if {@code fuzziness <= 1.0}
+     */
+    public FuzzyKMeansClusterer(final int k, final double fuzziness) throws NumberIsTooSmallException {
+        this(k, fuzziness, -1, new EuclideanDistance());
+    }
+
+    /**
+     * Creates a new instance of a FuzzyKMeansClusterer.
+     *
+     * @param k the number of clusters to split the data into
+     * @param fuzziness the fuzziness factor, must be &gt; 1.0
+     * @param maxIterations the maximum number of iterations to run the algorithm for.
+     *   If negative, no maximum will be used.
+     * @param measure the distance measure to use
+     * @throws NumberIsTooSmallException if {@code fuzziness <= 1.0}
+     */
+    public FuzzyKMeansClusterer(final int k, final double fuzziness,
+                                final int maxIterations, final DistanceMeasure measure)
+            throws NumberIsTooSmallException {
+        this(k, fuzziness, maxIterations, measure, DEFAULT_EPSILON, new JDKRandomGenerator());
+    }
+
+    /**
+     * Creates a new instance of a FuzzyKMeansClusterer.
+     *
+     * @param k the number of clusters to split the data into
+     * @param fuzziness the fuzziness factor, must be &gt; 1.0
+     * @param maxIterations the maximum number of iterations to run the algorithm for.
+     *   If negative, no maximum will be used.
+     * @param measure the distance measure to use
+     * @param epsilon the convergence criteria (default is 1e-3)
+     * @param random random generator to use for choosing initial centers
+     * @throws NumberIsTooSmallException if {@code fuzziness <= 1.0}
+     */
+    public FuzzyKMeansClusterer(final int k, final double fuzziness,
+                                final int maxIterations, final DistanceMeasure measure,
+                                final double epsilon, final RandomGenerator random)
+            throws NumberIsTooSmallException {
+
+        super(measure);
+
+        if (fuzziness <= 1.0d) {
+            throw new NumberIsTooSmallException(fuzziness, 1.0, false);
+        }
+        this.k = k;
+        this.fuzziness = fuzziness;
+        this.maxIterations = maxIterations;
+        this.epsilon = epsilon;
+        this.random = random;
+
+        this.membershipMatrix = null;
+        this.points = null;
+        this.clusters = null;
+    }
+
+    /**
+     * Return the number of clusters this instance will use.
+     * @return the number of clusters
+     */
+    public int getK() {
+        return k;
+    }
+
+    /**
+     * Returns the fuzziness factor used by this instance.
+     * @return the fuzziness factor
+     */
+    public double getFuzziness() {
+        return fuzziness;
+    }
+
+    /**
+     * Returns the maximum number of iterations this instance will use.
+     * @return the maximum number of iterations, or -1 if no maximum is set
+     */
+    public int getMaxIterations() {
+        return maxIterations;
+    }
+
+    /**
+     * Returns the convergence criteria used by this instance.
+     * @return the convergence criteria
+     */
+    public double getEpsilon() {
+        return epsilon;
+    }
+
+    /**
+     * Returns the random generator this instance will use.
+     * @return the random generator
+     */
+    public RandomGenerator getRandomGenerator() {
+        return random;
+    }
+
+    /**
+     * Returns the {@code nxk} membership matrix, where {@code n} is the number
+     * of data points and {@code k} the number of clusters.
+     * <p>
+     * The element U<sub>i,j</sub> represents the membership value for data point {@code i}
+     * to cluster {@code j}.
+     *
+     * @return the membership matrix
+     * @throws MathIllegalStateException if {@link #cluster(Collection)} has not been called before
+     */
+    public RealMatrix getMembershipMatrix() {
+        if (membershipMatrix == null) {
+            throw new MathIllegalStateException();
+        }
+        return MatrixUtils.createRealMatrix(membershipMatrix);
+    }
+
+    /**
+     * Returns an unmodifiable list of the data points used in the last
+     * call to {@link #cluster(Collection)}.
+     * @return the list of data points, or {@code null} if {@link #cluster(Collection)} has
+     *   not been called before.
+     */
+    public List<T> getDataPoints() {
+        return points;
+    }
+
+    /**
+     * Returns the list of clusters resulting from the last call to {@link #cluster(Collection)}.
+     * @return the list of clusters, or {@code null} if {@link #cluster(Collection)} has
+     *   not been called before.
+     */
+    public List<CentroidCluster<T>> getClusters() {
+        return clusters;
+    }
+
+    /**
+     * Get the value of the objective function.
+     * @return the objective function evaluation as double value
+     * @throws MathIllegalStateException if {@link #cluster(Collection)} has not been called before
+     */
+    public double getObjectiveFunctionValue() {
+        if (points == null || clusters == null) {
+            throw new MathIllegalStateException();
+        }
+
+        int i = 0;
+        double objFunction = 0.0;
+        for (final T point : points) {
+            int j = 0;
+            for (final CentroidCluster<T> cluster : clusters) {
+                final double dist = distance(point, cluster.getCenter());
+                objFunction += (dist * dist) * FastMath.pow(membershipMatrix[i][j], fuzziness);
+                j++;
+            }
+            i++;
+        }
+        return objFunction;
+    }
+
+    /**
+     * Performs Fuzzy K-Means cluster analysis.
+     *
+     * @param dataPoints the points to cluster
+     * @return the list of clusters
+     * @throws MathIllegalArgumentException if the data points are null or the number
+     *     of clusters is larger than the number of data points
+     */
+    @Override
+    public List<CentroidCluster<T>> cluster(final Collection<T> dataPoints)
+            throws MathIllegalArgumentException {
+
+        // sanity checks
+        MathUtils.checkNotNull(dataPoints);
+
+        final int size = dataPoints.size();
+
+        // number of clusters has to be smaller or equal the number of data points
+        if (size < k) {
+            throw new NumberIsTooSmallException(size, k, false);
+        }
+
+        // copy the input collection to an unmodifiable list with indexed access
+        points = Collections.unmodifiableList(new ArrayList<T>(dataPoints));
+        clusters = new ArrayList<CentroidCluster<T>>();
+        membershipMatrix = new double[size][k];
+        final double[][] oldMatrix = new double[size][k];
+
+        // if no points are provided, return an empty list of clusters
+        if (size == 0) {
+            return clusters;
+        }
+
+        initializeMembershipMatrix();
+
+        // there is at least one point
+        final int pointDimension = points.get(0).getPoint().length;
+        for (int i = 0; i < k; i++) {
+            clusters.add(new CentroidCluster<T>(new DoublePoint(new double[pointDimension])));
+        }
+
+        int iteration = 0;
+        final int max = (maxIterations < 0) ? Integer.MAX_VALUE : maxIterations;
+        double difference = 0.0;
+
+        do {
+            saveMembershipMatrix(oldMatrix);
+            updateClusterCenters();
+            updateMembershipMatrix();
+            difference = calculateMaxMembershipChange(oldMatrix);
+        } while (difference > epsilon && ++iteration < max);
+
+        return clusters;
+    }
+
+    /**
+     * Update the cluster centers.
+     */
+    private void updateClusterCenters() {
+        int j = 0;
+        final List<CentroidCluster<T>> newClusters = new ArrayList<CentroidCluster<T>>(k);
+        for (final CentroidCluster<T> cluster : clusters) {
+            final Clusterable center = cluster.getCenter();
+            int i = 0;
+            double[] arr = new double[center.getPoint().length];
+            double sum = 0.0;
+            for (final T point : points) {
+                final double u = FastMath.pow(membershipMatrix[i][j], fuzziness);
+                final double[] pointArr = point.getPoint();
+                for (int idx = 0; idx < arr.length; idx++) {
+                    arr[idx] += u * pointArr[idx];
+                }
+                sum += u;
+                i++;
+            }
+            MathArrays.scaleInPlace(1.0 / sum, arr);
+            newClusters.add(new CentroidCluster<T>(new DoublePoint(arr)));
+            j++;
+        }
+        clusters.clear();
+        clusters = newClusters;
+    }
+
+    /**
+     * Updates the membership matrix and assigns the points to the cluster with
+     * the highest membership.
+     */
+    private void updateMembershipMatrix() {
+        for (int i = 0; i < points.size(); i++) {
+            final T point = points.get(i);
+            double maxMembership = Double.MIN_VALUE;
+            int newCluster = -1;
+            for (int j = 0; j < clusters.size(); j++) {
+                double sum = 0.0;
+                final double distA = FastMath.abs(distance(point, clusters.get(j).getCenter()));
+
+                if (distA != 0.0) {
+                    for (final CentroidCluster<T> c : clusters) {
+                        final double distB = FastMath.abs(distance(point, c.getCenter()));
+                        if (distB == 0.0) {
+                            sum = Double.POSITIVE_INFINITY;
+                            break;
+                        }
+                        sum += FastMath.pow(distA / distB, 2.0 / (fuzziness - 1.0));
+                    }
+                }
+
+                double membership;
+                if (sum == 0.0) {
+                    membership = 1.0;
+                } else if (sum == Double.POSITIVE_INFINITY) {
+                    membership = 0.0;
+                } else {
+                    membership = 1.0 / sum;
+                }
+                membershipMatrix[i][j] = membership;
+
+                if (membershipMatrix[i][j] > maxMembership) {
+                    maxMembership = membershipMatrix[i][j];
+                    newCluster = j;
+                }
+            }
+            clusters.get(newCluster).addPoint(point);
+        }
+    }
+
+    /**
+     * Initialize the membership matrix with random values.
+     */
+    private void initializeMembershipMatrix() {
+        for (int i = 0; i < points.size(); i++) {
+            for (int j = 0; j < k; j++) {
+                membershipMatrix[i][j] = random.nextDouble();
+            }
+            membershipMatrix[i] = MathArrays.normalizeArray(membershipMatrix[i], 1.0);
+        }
+    }
+
+    /**
+     * Calculate the maximum element-by-element change of the membership matrix
+     * for the current iteration.
+     *
+     * @param matrix the membership matrix of the previous iteration
+     * @return the maximum membership matrix change
+     */
+    private double calculateMaxMembershipChange(final double[][] matrix) {
+        double maxMembership = 0.0;
+        for (int i = 0; i < points.size(); i++) {
+            for (int j = 0; j < clusters.size(); j++) {
+                double v = FastMath.abs(membershipMatrix[i][j] - matrix[i][j]);
+                maxMembership = FastMath.max(v, maxMembership);
+            }
+        }
+        return maxMembership;
+    }
+
+    /**
+     * Copy the membership matrix into the provided matrix.
+     *
+     * @param matrix the place to store the membership matrix
+     */
+    private void saveMembershipMatrix(final double[][] matrix) {
+        for (int i = 0; i < points.size(); i++) {
+            System.arraycopy(membershipMatrix[i], 0, matrix[i], 0, clusters.size());
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/clustering/KMeansPlusPlusClusterer.java b/src/main/java/org/apache/commons/math3/ml/clustering/KMeansPlusPlusClusterer.java
new file mode 100644
index 0000000..2e57fac
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/clustering/KMeansPlusPlusClusterer.java
@@ -0,0 +1,565 @@
+/*
+ * 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.math3.ml.clustering;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.math3.exception.ConvergenceException;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.ml.distance.DistanceMeasure;
+import org.apache.commons.math3.ml.distance.EuclideanDistance;
+import org.apache.commons.math3.random.JDKRandomGenerator;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.stat.descriptive.moment.Variance;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * Clustering algorithm based on David Arthur and Sergei Vassilvitski k-means++ algorithm.
+ * @param <T> type of the points to cluster
+ * @see <a href="http://en.wikipedia.org/wiki/K-means%2B%2B">K-means++ (wikipedia)</a>
+ * @since 3.2
+ */
+public class KMeansPlusPlusClusterer<T extends Clusterable> extends Clusterer<T> {
+
+    /** Strategies to use for replacing an empty cluster. */
+    public enum EmptyClusterStrategy {
+
+        /** Split the cluster with largest distance variance. */
+        LARGEST_VARIANCE,
+
+        /** Split the cluster with largest number of points. */
+        LARGEST_POINTS_NUMBER,
+
+        /** Create a cluster around the point farthest from its centroid. */
+        FARTHEST_POINT,
+
+        /** Generate an error. */
+        ERROR
+
+    }
+
+    /** The number of clusters. */
+    private final int k;
+
+    /** The maximum number of iterations. */
+    private final int maxIterations;
+
+    /** Random generator for choosing initial centers. */
+    private final RandomGenerator random;
+
+    /** Selected strategy for empty clusters. */
+    private final EmptyClusterStrategy emptyStrategy;
+
+    /** Build a clusterer.
+     * <p>
+     * The default strategy for handling empty clusters that may appear during
+     * algorithm iterations is to split the cluster with largest distance variance.
+     * <p>
+     * The euclidean distance will be used as default distance measure.
+     *
+     * @param k the number of clusters to split the data into
+     */
+    public KMeansPlusPlusClusterer(final int k) {
+        this(k, -1);
+    }
+
+    /** Build a clusterer.
+     * <p>
+     * The default strategy for handling empty clusters that may appear during
+     * algorithm iterations is to split the cluster with largest distance variance.
+     * <p>
+     * The euclidean distance will be used as default distance measure.
+     *
+     * @param k the number of clusters to split the data into
+     * @param maxIterations the maximum number of iterations to run the algorithm for.
+     *   If negative, no maximum will be used.
+     */
+    public KMeansPlusPlusClusterer(final int k, final int maxIterations) {
+        this(k, maxIterations, new EuclideanDistance());
+    }
+
+    /** Build a clusterer.
+     * <p>
+     * The default strategy for handling empty clusters that may appear during
+     * algorithm iterations is to split the cluster with largest distance variance.
+     *
+     * @param k the number of clusters to split the data into
+     * @param maxIterations the maximum number of iterations to run the algorithm for.
+     *   If negative, no maximum will be used.
+     * @param measure the distance measure to use
+     */
+    public KMeansPlusPlusClusterer(final int k, final int maxIterations, final DistanceMeasure measure) {
+        this(k, maxIterations, measure, new JDKRandomGenerator());
+    }
+
+    /** Build a clusterer.
+     * <p>
+     * The default strategy for handling empty clusters that may appear during
+     * algorithm iterations is to split the cluster with largest distance variance.
+     *
+     * @param k the number of clusters to split the data into
+     * @param maxIterations the maximum number of iterations to run the algorithm for.
+     *   If negative, no maximum will be used.
+     * @param measure the distance measure to use
+     * @param random random generator to use for choosing initial centers
+     */
+    public KMeansPlusPlusClusterer(final int k, final int maxIterations,
+                                   final DistanceMeasure measure,
+                                   final RandomGenerator random) {
+        this(k, maxIterations, measure, random, EmptyClusterStrategy.LARGEST_VARIANCE);
+    }
+
+    /** Build a clusterer.
+     *
+     * @param k the number of clusters to split the data into
+     * @param maxIterations the maximum number of iterations to run the algorithm for.
+     *   If negative, no maximum will be used.
+     * @param measure the distance measure to use
+     * @param random random generator to use for choosing initial centers
+     * @param emptyStrategy strategy to use for handling empty clusters that
+     * may appear during algorithm iterations
+     */
+    public KMeansPlusPlusClusterer(final int k, final int maxIterations,
+                                   final DistanceMeasure measure,
+                                   final RandomGenerator random,
+                                   final EmptyClusterStrategy emptyStrategy) {
+        super(measure);
+        this.k             = k;
+        this.maxIterations = maxIterations;
+        this.random        = random;
+        this.emptyStrategy = emptyStrategy;
+    }
+
+    /**
+     * Return the number of clusters this instance will use.
+     * @return the number of clusters
+     */
+    public int getK() {
+        return k;
+    }
+
+    /**
+     * Returns the maximum number of iterations this instance will use.
+     * @return the maximum number of iterations, or -1 if no maximum is set
+     */
+    public int getMaxIterations() {
+        return maxIterations;
+    }
+
+    /**
+     * Returns the random generator this instance will use.
+     * @return the random generator
+     */
+    public RandomGenerator getRandomGenerator() {
+        return random;
+    }
+
+    /**
+     * Returns the {@link EmptyClusterStrategy} used by this instance.
+     * @return the {@link EmptyClusterStrategy}
+     */
+    public EmptyClusterStrategy getEmptyClusterStrategy() {
+        return emptyStrategy;
+    }
+
+    /**
+     * Runs the K-means++ clustering algorithm.
+     *
+     * @param points the points to cluster
+     * @return a list of clusters containing the points
+     * @throws MathIllegalArgumentException if the data points are null or the number
+     *     of clusters is larger than the number of data points
+     * @throws ConvergenceException if an empty cluster is encountered and the
+     * {@link #emptyStrategy} is set to {@code ERROR}
+     */
+    @Override
+    public List<CentroidCluster<T>> cluster(final Collection<T> points)
+        throws MathIllegalArgumentException, ConvergenceException {
+
+        // sanity checks
+        MathUtils.checkNotNull(points);
+
+        // number of clusters has to be smaller or equal the number of data points
+        if (points.size() < k) {
+            throw new NumberIsTooSmallException(points.size(), k, false);
+        }
+
+        // create the initial clusters
+        List<CentroidCluster<T>> clusters = chooseInitialCenters(points);
+
+        // create an array containing the latest assignment of a point to a cluster
+        // no need to initialize the array, as it will be filled with the first assignment
+        int[] assignments = new int[points.size()];
+        assignPointsToClusters(clusters, points, assignments);
+
+        // iterate through updating the centers until we're done
+        final int max = (maxIterations < 0) ? Integer.MAX_VALUE : maxIterations;
+        for (int count = 0; count < max; count++) {
+            boolean emptyCluster = false;
+            List<CentroidCluster<T>> newClusters = new ArrayList<CentroidCluster<T>>();
+            for (final CentroidCluster<T> cluster : clusters) {
+                final Clusterable newCenter;
+                if (cluster.getPoints().isEmpty()) {
+                    switch (emptyStrategy) {
+                        case LARGEST_VARIANCE :
+                            newCenter = getPointFromLargestVarianceCluster(clusters);
+                            break;
+                        case LARGEST_POINTS_NUMBER :
+                            newCenter = getPointFromLargestNumberCluster(clusters);
+                            break;
+                        case FARTHEST_POINT :
+                            newCenter = getFarthestPoint(clusters);
+                            break;
+                        default :
+                            throw new ConvergenceException(LocalizedFormats.EMPTY_CLUSTER_IN_K_MEANS);
+                    }
+                    emptyCluster = true;
+                } else {
+                    newCenter = centroidOf(cluster.getPoints(), cluster.getCenter().getPoint().length);
+                }
+                newClusters.add(new CentroidCluster<T>(newCenter));
+            }
+            int changes = assignPointsToClusters(newClusters, points, assignments);
+            clusters = newClusters;
+
+            // if there were no more changes in the point-to-cluster assignment
+            // and there are no empty clusters left, return the current clusters
+            if (changes == 0 && !emptyCluster) {
+                return clusters;
+            }
+        }
+        return clusters;
+    }
+
+    /**
+     * Adds the given points to the closest {@link Cluster}.
+     *
+     * @param clusters the {@link Cluster}s to add the points to
+     * @param points the points to add to the given {@link Cluster}s
+     * @param assignments points assignments to clusters
+     * @return the number of points assigned to different clusters as the iteration before
+     */
+    private int assignPointsToClusters(final List<CentroidCluster<T>> clusters,
+                                       final Collection<T> points,
+                                       final int[] assignments) {
+        int assignedDifferently = 0;
+        int pointIndex = 0;
+        for (final T p : points) {
+            int clusterIndex = getNearestCluster(clusters, p);
+            if (clusterIndex != assignments[pointIndex]) {
+                assignedDifferently++;
+            }
+
+            CentroidCluster<T> cluster = clusters.get(clusterIndex);
+            cluster.addPoint(p);
+            assignments[pointIndex++] = clusterIndex;
+        }
+
+        return assignedDifferently;
+    }
+
+    /**
+     * Use K-means++ to choose the initial centers.
+     *
+     * @param points the points to choose the initial centers from
+     * @return the initial centers
+     */
+    private List<CentroidCluster<T>> chooseInitialCenters(final Collection<T> points) {
+
+        // Convert to list for indexed access. Make it unmodifiable, since removal of items
+        // would screw up the logic of this method.
+        final List<T> pointList = Collections.unmodifiableList(new ArrayList<T> (points));
+
+        // The number of points in the list.
+        final int numPoints = pointList.size();
+
+        // Set the corresponding element in this array to indicate when
+        // elements of pointList are no longer available.
+        final boolean[] taken = new boolean[numPoints];
+
+        // The resulting list of initial centers.
+        final List<CentroidCluster<T>> resultSet = new ArrayList<CentroidCluster<T>>();
+
+        // Choose one center uniformly at random from among the data points.
+        final int firstPointIndex = random.nextInt(numPoints);
+
+        final T firstPoint = pointList.get(firstPointIndex);
+
+        resultSet.add(new CentroidCluster<T>(firstPoint));
+
+        // Must mark it as taken
+        taken[firstPointIndex] = true;
+
+        // To keep track of the minimum distance squared of elements of
+        // pointList to elements of resultSet.
+        final double[] minDistSquared = new double[numPoints];
+
+        // Initialize the elements.  Since the only point in resultSet is firstPoint,
+        // this is very easy.
+        for (int i = 0; i < numPoints; i++) {
+            if (i != firstPointIndex) { // That point isn't considered
+                double d = distance(firstPoint, pointList.get(i));
+                minDistSquared[i] = d*d;
+            }
+        }
+
+        while (resultSet.size() < k) {
+
+            // Sum up the squared distances for the points in pointList not
+            // already taken.
+            double distSqSum = 0.0;
+
+            for (int i = 0; i < numPoints; i++) {
+                if (!taken[i]) {
+                    distSqSum += minDistSquared[i];
+                }
+            }
+
+            // Add one new data point as a center. Each point x is chosen with
+            // probability proportional to D(x)2
+            final double r = random.nextDouble() * distSqSum;
+
+            // The index of the next point to be added to the resultSet.
+            int nextPointIndex = -1;
+
+            // Sum through the squared min distances again, stopping when
+            // sum >= r.
+            double sum = 0.0;
+            for (int i = 0; i < numPoints; i++) {
+                if (!taken[i]) {
+                    sum += minDistSquared[i];
+                    if (sum >= r) {
+                        nextPointIndex = i;
+                        break;
+                    }
+                }
+            }
+
+            // If it's not set to >= 0, the point wasn't found in the previous
+            // for loop, probably because distances are extremely small.  Just pick
+            // the last available point.
+            if (nextPointIndex == -1) {
+                for (int i = numPoints - 1; i >= 0; i--) {
+                    if (!taken[i]) {
+                        nextPointIndex = i;
+                        break;
+                    }
+                }
+            }
+
+            // We found one.
+            if (nextPointIndex >= 0) {
+
+                final T p = pointList.get(nextPointIndex);
+
+                resultSet.add(new CentroidCluster<T> (p));
+
+                // Mark it as taken.
+                taken[nextPointIndex] = true;
+
+                if (resultSet.size() < k) {
+                    // Now update elements of minDistSquared.  We only have to compute
+                    // the distance to the new center to do this.
+                    for (int j = 0; j < numPoints; j++) {
+                        // Only have to worry about the points still not taken.
+                        if (!taken[j]) {
+                            double d = distance(p, pointList.get(j));
+                            double d2 = d * d;
+                            if (d2 < minDistSquared[j]) {
+                                minDistSquared[j] = d2;
+                            }
+                        }
+                    }
+                }
+
+            } else {
+                // None found --
+                // Break from the while loop to prevent
+                // an infinite loop.
+                break;
+            }
+        }
+
+        return resultSet;
+    }
+
+    /**
+     * Get a random point from the {@link Cluster} with the largest distance variance.
+     *
+     * @param clusters the {@link Cluster}s to search
+     * @return a random point from the selected cluster
+     * @throws ConvergenceException if clusters are all empty
+     */
+    private T getPointFromLargestVarianceCluster(final Collection<CentroidCluster<T>> clusters)
+            throws ConvergenceException {
+
+        double maxVariance = Double.NEGATIVE_INFINITY;
+        Cluster<T> selected = null;
+        for (final CentroidCluster<T> cluster : clusters) {
+            if (!cluster.getPoints().isEmpty()) {
+
+                // compute the distance variance of the current cluster
+                final Clusterable center = cluster.getCenter();
+                final Variance stat = new Variance();
+                for (final T point : cluster.getPoints()) {
+                    stat.increment(distance(point, center));
+                }
+                final double variance = stat.getResult();
+
+                // select the cluster with the largest variance
+                if (variance > maxVariance) {
+                    maxVariance = variance;
+                    selected = cluster;
+                }
+
+            }
+        }
+
+        // did we find at least one non-empty cluster ?
+        if (selected == null) {
+            throw new ConvergenceException(LocalizedFormats.EMPTY_CLUSTER_IN_K_MEANS);
+        }
+
+        // extract a random point from the cluster
+        final List<T> selectedPoints = selected.getPoints();
+        return selectedPoints.remove(random.nextInt(selectedPoints.size()));
+
+    }
+
+    /**
+     * Get a random point from the {@link Cluster} with the largest number of points
+     *
+     * @param clusters the {@link Cluster}s to search
+     * @return a random point from the selected cluster
+     * @throws ConvergenceException if clusters are all empty
+     */
+    private T getPointFromLargestNumberCluster(final Collection<? extends Cluster<T>> clusters)
+            throws ConvergenceException {
+
+        int maxNumber = 0;
+        Cluster<T> selected = null;
+        for (final Cluster<T> cluster : clusters) {
+
+            // get the number of points of the current cluster
+            final int number = cluster.getPoints().size();
+
+            // select the cluster with the largest number of points
+            if (number > maxNumber) {
+                maxNumber = number;
+                selected = cluster;
+            }
+
+        }
+
+        // did we find at least one non-empty cluster ?
+        if (selected == null) {
+            throw new ConvergenceException(LocalizedFormats.EMPTY_CLUSTER_IN_K_MEANS);
+        }
+
+        // extract a random point from the cluster
+        final List<T> selectedPoints = selected.getPoints();
+        return selectedPoints.remove(random.nextInt(selectedPoints.size()));
+
+    }
+
+    /**
+     * Get the point farthest to its cluster center
+     *
+     * @param clusters the {@link Cluster}s to search
+     * @return point farthest to its cluster center
+     * @throws ConvergenceException if clusters are all empty
+     */
+    private T getFarthestPoint(final Collection<CentroidCluster<T>> clusters) throws ConvergenceException {
+
+        double maxDistance = Double.NEGATIVE_INFINITY;
+        Cluster<T> selectedCluster = null;
+        int selectedPoint = -1;
+        for (final CentroidCluster<T> cluster : clusters) {
+
+            // get the farthest point
+            final Clusterable center = cluster.getCenter();
+            final List<T> points = cluster.getPoints();
+            for (int i = 0; i < points.size(); ++i) {
+                final double distance = distance(points.get(i), center);
+                if (distance > maxDistance) {
+                    maxDistance     = distance;
+                    selectedCluster = cluster;
+                    selectedPoint   = i;
+                }
+            }
+
+        }
+
+        // did we find at least one non-empty cluster ?
+        if (selectedCluster == null) {
+            throw new ConvergenceException(LocalizedFormats.EMPTY_CLUSTER_IN_K_MEANS);
+        }
+
+        return selectedCluster.getPoints().remove(selectedPoint);
+
+    }
+
+    /**
+     * Returns the nearest {@link Cluster} to the given point
+     *
+     * @param clusters the {@link Cluster}s to search
+     * @param point the point to find the nearest {@link Cluster} for
+     * @return the index of the nearest {@link Cluster} to the given point
+     */
+    private int getNearestCluster(final Collection<CentroidCluster<T>> clusters, final T point) {
+        double minDistance = Double.MAX_VALUE;
+        int clusterIndex = 0;
+        int minCluster = 0;
+        for (final CentroidCluster<T> c : clusters) {
+            final double distance = distance(point, c.getCenter());
+            if (distance < minDistance) {
+                minDistance = distance;
+                minCluster = clusterIndex;
+            }
+            clusterIndex++;
+        }
+        return minCluster;
+    }
+
+    /**
+     * Computes the centroid for a set of points.
+     *
+     * @param points the set of points
+     * @param dimension the point dimension
+     * @return the computed centroid for the set of points
+     */
+    private Clusterable centroidOf(final Collection<T> points, final int dimension) {
+        final double[] centroid = new double[dimension];
+        for (final T p : points) {
+            final double[] point = p.getPoint();
+            for (int i = 0; i < centroid.length; i++) {
+                centroid[i] += point[i];
+            }
+        }
+        for (int i = 0; i < centroid.length; i++) {
+            centroid[i] /= points.size();
+        }
+        return new DoublePoint(centroid);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/clustering/MultiKMeansPlusPlusClusterer.java b/src/main/java/org/apache/commons/math3/ml/clustering/MultiKMeansPlusPlusClusterer.java
new file mode 100644
index 0000000..796fc7a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/clustering/MultiKMeansPlusPlusClusterer.java
@@ -0,0 +1,135 @@
+/*
+ * 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.math3.ml.clustering;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.commons.math3.exception.ConvergenceException;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.ml.clustering.evaluation.ClusterEvaluator;
+import org.apache.commons.math3.ml.clustering.evaluation.SumOfClusterVariances;
+
+/**
+ * A wrapper around a k-means++ clustering algorithm which performs multiple trials
+ * and returns the best solution.
+ * @param <T> type of the points to cluster
+ * @since 3.2
+ */
+public class MultiKMeansPlusPlusClusterer<T extends Clusterable> extends Clusterer<T> {
+
+    /** The underlying k-means clusterer. */
+    private final KMeansPlusPlusClusterer<T> clusterer;
+
+    /** The number of trial runs. */
+    private final int numTrials;
+
+    /** The cluster evaluator to use. */
+    private final ClusterEvaluator<T> evaluator;
+
+    /** Build a clusterer.
+     * @param clusterer the k-means clusterer to use
+     * @param numTrials number of trial runs
+     */
+    public MultiKMeansPlusPlusClusterer(final KMeansPlusPlusClusterer<T> clusterer,
+                                        final int numTrials) {
+        this(clusterer, numTrials, new SumOfClusterVariances<T>(clusterer.getDistanceMeasure()));
+    }
+
+    /** Build a clusterer.
+     * @param clusterer the k-means clusterer to use
+     * @param numTrials number of trial runs
+     * @param evaluator the cluster evaluator to use
+     * @since 3.3
+     */
+    public MultiKMeansPlusPlusClusterer(final KMeansPlusPlusClusterer<T> clusterer,
+                                        final int numTrials,
+                                        final ClusterEvaluator<T> evaluator) {
+        super(clusterer.getDistanceMeasure());
+        this.clusterer = clusterer;
+        this.numTrials = numTrials;
+        this.evaluator = evaluator;
+    }
+
+    /**
+     * Returns the embedded k-means clusterer used by this instance.
+     * @return the embedded clusterer
+     */
+    public KMeansPlusPlusClusterer<T> getClusterer() {
+        return clusterer;
+    }
+
+    /**
+     * Returns the number of trials this instance will do.
+     * @return the number of trials
+     */
+    public int getNumTrials() {
+        return numTrials;
+    }
+
+    /**
+     * Returns the {@link ClusterEvaluator} used to determine the "best" clustering.
+     * @return the used {@link ClusterEvaluator}
+     * @since 3.3
+     */
+    public ClusterEvaluator<T> getClusterEvaluator() {
+       return evaluator;
+    }
+
+    /**
+     * Runs the K-means++ clustering algorithm.
+     *
+     * @param points the points to cluster
+     * @return a list of clusters containing the points
+     * @throws MathIllegalArgumentException if the data points are null or the number
+     *   of clusters is larger than the number of data points
+     * @throws ConvergenceException if an empty cluster is encountered and the
+     *   underlying {@link KMeansPlusPlusClusterer} has its
+     *   {@link KMeansPlusPlusClusterer.EmptyClusterStrategy} is set to {@code ERROR}.
+     */
+    @Override
+    public List<CentroidCluster<T>> cluster(final Collection<T> points)
+        throws MathIllegalArgumentException, ConvergenceException {
+
+        // at first, we have not found any clusters list yet
+        List<CentroidCluster<T>> best = null;
+        double bestVarianceSum = Double.POSITIVE_INFINITY;
+
+        // do several clustering trials
+        for (int i = 0; i < numTrials; ++i) {
+
+            // compute a clusters list
+            List<CentroidCluster<T>> clusters = clusterer.cluster(points);
+
+            // compute the variance of the current list
+            final double varianceSum = evaluator.score(clusters);
+
+            if (evaluator.isBetterScore(varianceSum, bestVarianceSum)) {
+                // this one is the best we have found so far, remember it
+                best            = clusters;
+                bestVarianceSum = varianceSum;
+            }
+
+        }
+
+        // return the best clusters list found
+        return best;
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/clustering/evaluation/ClusterEvaluator.java b/src/main/java/org/apache/commons/math3/ml/clustering/evaluation/ClusterEvaluator.java
new file mode 100644
index 0000000..2bb8ba3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/clustering/evaluation/ClusterEvaluator.java
@@ -0,0 +1,122 @@
+/*
+ * 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.math3.ml.clustering.evaluation;
+
+import java.util.List;
+
+import org.apache.commons.math3.ml.clustering.CentroidCluster;
+import org.apache.commons.math3.ml.clustering.Cluster;
+import org.apache.commons.math3.ml.clustering.Clusterable;
+import org.apache.commons.math3.ml.clustering.DoublePoint;
+import org.apache.commons.math3.ml.distance.DistanceMeasure;
+import org.apache.commons.math3.ml.distance.EuclideanDistance;
+
+/**
+ * Base class for cluster evaluation methods.
+ *
+ * @param <T> type of the clustered points
+ * @since 3.3
+ */
+public abstract class ClusterEvaluator<T extends Clusterable> {
+
+    /** The distance measure to use when evaluating the cluster. */
+    private final DistanceMeasure measure;
+
+    /**
+     * Creates a new cluster evaluator with an {@link EuclideanDistance}
+     * as distance measure.
+     */
+    public ClusterEvaluator() {
+        this(new EuclideanDistance());
+    }
+
+    /**
+     * Creates a new cluster evaluator with the given distance measure.
+     * @param measure the distance measure to use
+     */
+    public ClusterEvaluator(final DistanceMeasure measure) {
+        this.measure = measure;
+    }
+
+    /**
+     * Computes the evaluation score for the given list of clusters.
+     * @param clusters the clusters to evaluate
+     * @return the computed score
+     */
+    public abstract double score(List<? extends Cluster<T>> clusters);
+
+    /**
+     * Returns whether the first evaluation score is considered to be better
+     * than the second one by this evaluator.
+     * <p>
+     * Specific implementations shall override this method if the returned scores
+     * do not follow the same ordering, i.e. smaller score is better.
+     *
+     * @param score1 the first score
+     * @param score2 the second score
+     * @return {@code true} if the first score is considered to be better, {@code false} otherwise
+     */
+    public boolean isBetterScore(double score1, double score2) {
+        return score1 < score2;
+    }
+
+    /**
+     * Calculates the distance between two {@link Clusterable} instances
+     * with the configured {@link DistanceMeasure}.
+     *
+     * @param p1 the first clusterable
+     * @param p2 the second clusterable
+     * @return the distance between the two clusterables
+     */
+    protected double distance(final Clusterable p1, final Clusterable p2) {
+        return measure.compute(p1.getPoint(), p2.getPoint());
+    }
+
+    /**
+     * Computes the centroid for a cluster.
+     *
+     * @param cluster the cluster
+     * @return the computed centroid for the cluster,
+     * or {@code null} if the cluster does not contain any points
+     */
+    protected Clusterable centroidOf(final Cluster<T> cluster) {
+        final List<T> points = cluster.getPoints();
+        if (points.isEmpty()) {
+            return null;
+        }
+
+        // in case the cluster is of type CentroidCluster, no need to compute the centroid
+        if (cluster instanceof CentroidCluster) {
+            return ((CentroidCluster<T>) cluster).getCenter();
+        }
+
+        final int dimension = points.get(0).getPoint().length;
+        final double[] centroid = new double[dimension];
+        for (final T p : points) {
+            final double[] point = p.getPoint();
+            for (int i = 0; i < centroid.length; i++) {
+                centroid[i] += point[i];
+            }
+        }
+        for (int i = 0; i < centroid.length; i++) {
+            centroid[i] /= points.size();
+        }
+        return new DoublePoint(centroid);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/clustering/evaluation/SumOfClusterVariances.java b/src/main/java/org/apache/commons/math3/ml/clustering/evaluation/SumOfClusterVariances.java
new file mode 100644
index 0000000..b5b249c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/clustering/evaluation/SumOfClusterVariances.java
@@ -0,0 +1,69 @@
+/*
+ * 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.math3.ml.clustering.evaluation;
+
+import java.util.List;
+
+import org.apache.commons.math3.ml.clustering.Cluster;
+import org.apache.commons.math3.ml.clustering.Clusterable;
+import org.apache.commons.math3.ml.distance.DistanceMeasure;
+import org.apache.commons.math3.stat.descriptive.moment.Variance;
+
+/**
+ * Computes the sum of intra-cluster distance variances according to the formula:
+ * <pre>
+ * \( score = \sum\limits_{i=1}^n \sigma_i^2 \)
+ * </pre>
+ * where n is the number of clusters and \( \sigma_i^2 \) is the variance of
+ * intra-cluster distances of cluster \( c_i \).
+ *
+ * @param <T> the type of the clustered points
+ * @since 3.3
+ */
+public class SumOfClusterVariances<T extends Clusterable> extends ClusterEvaluator<T> {
+
+    /**
+     *
+     * @param measure the distance measure to use
+     */
+    public SumOfClusterVariances(final DistanceMeasure measure) {
+        super(measure);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double score(final List<? extends Cluster<T>> clusters) {
+        double varianceSum = 0.0;
+        for (final Cluster<T> cluster : clusters) {
+            if (!cluster.getPoints().isEmpty()) {
+
+                final Clusterable center = centroidOf(cluster);
+
+                // compute the distance variance of the current cluster
+                final Variance stat = new Variance();
+                for (final T point : cluster.getPoints()) {
+                    stat.increment(distance(point, center));
+                }
+                varianceSum += stat.getResult();
+
+            }
+        }
+        return varianceSum;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/clustering/evaluation/package-info.java b/src/main/java/org/apache/commons/math3/ml/clustering/evaluation/package-info.java
new file mode 100644
index 0000000..700f566
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/clustering/evaluation/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+/**
+ * Cluster evaluation methods.
+ */
+package org.apache.commons.math3.ml.clustering.evaluation;
diff --git a/src/main/java/org/apache/commons/math3/ml/clustering/package-info.java b/src/main/java/org/apache/commons/math3/ml/clustering/package-info.java
new file mode 100644
index 0000000..02f1d20
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/clustering/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+/**
+ * Clustering algorithms.
+ */
+package org.apache.commons.math3.ml.clustering;
diff --git a/src/main/java/org/apache/commons/math3/ml/distance/CanberraDistance.java b/src/main/java/org/apache/commons/math3/ml/distance/CanberraDistance.java
new file mode 100644
index 0000000..d467c3b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/distance/CanberraDistance.java
@@ -0,0 +1,46 @@
+/*
+ * 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.math3.ml.distance;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * Calculates the Canberra distance between two points.
+ *
+ * @since 3.2
+ */
+public class CanberraDistance implements DistanceMeasure {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -6972277381587032228L;
+
+    /** {@inheritDoc} */
+    public double compute(double[] a, double[] b)
+    throws DimensionMismatchException {
+        MathArrays.checkEqualLength(a, b);
+        double sum = 0;
+        for (int i = 0; i < a.length; i++) {
+            final double num = FastMath.abs(a[i] - b[i]);
+            final double denom = FastMath.abs(a[i]) + FastMath.abs(b[i]);
+            sum += num == 0.0 && denom == 0.0 ? 0.0 : num / denom;
+        }
+        return sum;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/distance/ChebyshevDistance.java b/src/main/java/org/apache/commons/math3/ml/distance/ChebyshevDistance.java
new file mode 100644
index 0000000..05dccb5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/distance/ChebyshevDistance.java
@@ -0,0 +1,38 @@
+/*
+ * 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.math3.ml.distance;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * Calculates the L<sub>&infin;</sub> (max of abs) distance between two points.
+ *
+ * @since 3.2
+ */
+public class ChebyshevDistance implements DistanceMeasure {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -4694868171115238296L;
+
+    /** {@inheritDoc} */
+    public double compute(double[] a, double[] b)
+    throws DimensionMismatchException {
+        return MathArrays.distanceInf(a, b);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/distance/DistanceMeasure.java b/src/main/java/org/apache/commons/math3/ml/distance/DistanceMeasure.java
new file mode 100644
index 0000000..ff9c27f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/distance/DistanceMeasure.java
@@ -0,0 +1,41 @@
+/*
+ * 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.math3.ml.distance;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+
+/**
+ * Interface for distance measures of n-dimensional vectors.
+ *
+ * @since 3.2
+ */
+public interface DistanceMeasure extends Serializable {
+
+    /**
+     * Compute the distance between two n-dimensional vectors.
+     * <p>
+     * The two vectors are required to have the same dimension.
+     *
+     * @param a the first vector
+     * @param b the second vector
+     * @return the distance between the two vectors
+     * @throws DimensionMismatchException if the array lengths differ.
+     */
+    double compute(double[] a, double[] b) throws DimensionMismatchException;
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/distance/EarthMoversDistance.java b/src/main/java/org/apache/commons/math3/ml/distance/EarthMoversDistance.java
new file mode 100644
index 0000000..2518624
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/distance/EarthMoversDistance.java
@@ -0,0 +1,48 @@
+/*
+ * 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.math3.ml.distance;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * Calculates the Earh Mover's distance (also known as Wasserstein metric) between two distributions.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Earth_mover's_distance">Earth Mover's distance (Wikipedia)</a>
+ *
+ * @since 3.3
+ */
+public class EarthMoversDistance implements DistanceMeasure {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -5406732779747414922L;
+
+    /** {@inheritDoc} */
+    public double compute(double[] a, double[] b)
+    throws DimensionMismatchException {
+        MathArrays.checkEqualLength(a, b);
+        double lastDistance = 0;
+        double totalDistance = 0;
+        for (int i = 0; i < a.length; i++) {
+            final double currentDistance = (a[i] + lastDistance) - b[i];
+            totalDistance += FastMath.abs(currentDistance);
+            lastDistance = currentDistance;
+        }
+        return totalDistance;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/distance/EuclideanDistance.java b/src/main/java/org/apache/commons/math3/ml/distance/EuclideanDistance.java
new file mode 100644
index 0000000..187badc
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/distance/EuclideanDistance.java
@@ -0,0 +1,38 @@
+/*
+ * 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.math3.ml.distance;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * Calculates the L<sub>2</sub> (Euclidean) distance between two points.
+ *
+ * @since 3.2
+ */
+public class EuclideanDistance implements DistanceMeasure {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 1717556319784040040L;
+
+    /** {@inheritDoc} */
+    public double compute(double[] a, double[] b)
+    throws DimensionMismatchException {
+        return MathArrays.distance(a, b);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/distance/ManhattanDistance.java b/src/main/java/org/apache/commons/math3/ml/distance/ManhattanDistance.java
new file mode 100644
index 0000000..2eebe1b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/distance/ManhattanDistance.java
@@ -0,0 +1,38 @@
+/*
+ * 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.math3.ml.distance;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * Calculates the L<sub>1</sub> (sum of abs) distance between two points.
+ *
+ * @since 3.2
+ */
+public class ManhattanDistance implements DistanceMeasure {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -9108154600539125566L;
+
+    /** {@inheritDoc} */
+    public double compute(double[] a, double[] b)
+    throws DimensionMismatchException {
+        return MathArrays.distance1(a, b);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/distance/package-info.java b/src/main/java/org/apache/commons/math3/ml/distance/package-info.java
new file mode 100644
index 0000000..f6d124a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/distance/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+/**
+ * Common distance measures.
+ */
+package org.apache.commons.math3.ml.distance;
diff --git a/src/main/java/org/apache/commons/math3/ml/neuralnet/FeatureInitializer.java b/src/main/java/org/apache/commons/math3/ml/neuralnet/FeatureInitializer.java
new file mode 100644
index 0000000..1f48d45
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/neuralnet/FeatureInitializer.java
@@ -0,0 +1,32 @@
+/*
+ * 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.math3.ml.neuralnet;
+
+/**
+ * Defines how to assign the first value of a neuron's feature.
+ *
+ * @since 3.3
+ */
+public interface FeatureInitializer {
+    /**
+     * Selects the initial value.
+     *
+     * @return the initial value.
+     */
+    double value();
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/neuralnet/FeatureInitializerFactory.java b/src/main/java/org/apache/commons/math3/ml/neuralnet/FeatureInitializerFactory.java
new file mode 100644
index 0000000..f5569b1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/neuralnet/FeatureInitializerFactory.java
@@ -0,0 +1,114 @@
+/*
+ * 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.math3.ml.neuralnet;
+
+import org.apache.commons.math3.distribution.RealDistribution;
+import org.apache.commons.math3.distribution.UniformRealDistribution;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.function.Constant;
+import org.apache.commons.math3.random.RandomGenerator;
+
+/**
+ * Creates functions that will select the initial values of a neuron's
+ * features.
+ *
+ * @since 3.3
+ */
+public class FeatureInitializerFactory {
+    /** Class contains only static methods. */
+    private FeatureInitializerFactory() {}
+
+    /**
+     * Uniform sampling of the given range.
+     *
+     * @param min Lower bound of the range.
+     * @param max Upper bound of the range.
+     * @param rng Random number generator used to draw samples from a
+     * uniform distribution.
+     * @return an initializer such that the features will be initialized with
+     * values within the given range.
+     * @throws org.apache.commons.math3.exception.NumberIsTooLargeException
+     * if {@code min >= max}.
+     */
+    public static FeatureInitializer uniform(final RandomGenerator rng,
+                                             final double min,
+                                             final double max) {
+        return randomize(new UniformRealDistribution(rng, min, max),
+                         function(new Constant(0), 0, 0));
+    }
+
+    /**
+     * Uniform sampling of the given range.
+     *
+     * @param min Lower bound of the range.
+     * @param max Upper bound of the range.
+     * @return an initializer such that the features will be initialized with
+     * values within the given range.
+     * @throws org.apache.commons.math3.exception.NumberIsTooLargeException
+     * if {@code min >= max}.
+     */
+    public static FeatureInitializer uniform(final double min,
+                                             final double max) {
+        return randomize(new UniformRealDistribution(min, max),
+                         function(new Constant(0), 0, 0));
+    }
+
+    /**
+     * Creates an initializer from a univariate function {@code f(x)}.
+     * The argument {@code x} is set to {@code init} at the first call
+     * and will be incremented at each call.
+     *
+     * @param f Function.
+     * @param init Initial value.
+     * @param inc Increment
+     * @return the initializer.
+     */
+    public static FeatureInitializer function(final UnivariateFunction f,
+                                              final double init,
+                                              final double inc) {
+        return new FeatureInitializer() {
+            /** Argument. */
+            private double arg = init;
+
+            /** {@inheritDoc} */
+            public double value() {
+                final double result = f.value(arg);
+                arg += inc;
+                return result;
+            }
+        };
+    }
+
+    /**
+     * Adds some amount of random data to the given initializer.
+     *
+     * @param random Random variable distribution.
+     * @param orig Original initializer.
+     * @return an initializer whose {@link FeatureInitializer#value() value}
+     * method will return {@code orig.value() + random.sample()}.
+     */
+    public static FeatureInitializer randomize(final RealDistribution random,
+                                               final FeatureInitializer orig) {
+        return new FeatureInitializer() {
+            /** {@inheritDoc} */
+            public double value() {
+                return orig.value() + random.sample();
+            }
+        };
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/neuralnet/MapUtils.java b/src/main/java/org/apache/commons/math3/ml/neuralnet/MapUtils.java
new file mode 100644
index 0000000..0b7a675
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/neuralnet/MapUtils.java
@@ -0,0 +1,326 @@
+/*
+ * 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.math3.ml.neuralnet;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Comparator;
+
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.ml.distance.DistanceMeasure;
+import org.apache.commons.math3.ml.neuralnet.twod.NeuronSquareMesh2D;
+import org.apache.commons.math3.util.Pair;
+
+/**
+ * Utilities for network maps.
+ *
+ * @since 3.3
+ */
+public class MapUtils {
+    /**
+     * Class contains only static methods.
+     */
+    private MapUtils() {}
+
+    /**
+     * Finds the neuron that best matches the given features.
+     *
+     * @param features Data.
+     * @param neurons List of neurons to scan. If the list is empty
+     * {@code null} will be returned.
+     * @param distance Distance function. The neuron's features are
+     * passed as the first argument to {@link DistanceMeasure#compute(double[],double[])}.
+     * @return the neuron whose features are closest to the given data.
+     * @throws org.apache.commons.math3.exception.DimensionMismatchException
+     * if the size of the input is not compatible with the neurons features
+     * size.
+     */
+    public static Neuron findBest(double[] features,
+                                  Iterable<Neuron> neurons,
+                                  DistanceMeasure distance) {
+        Neuron best = null;
+        double min = Double.POSITIVE_INFINITY;
+        for (final Neuron n : neurons) {
+            final double d = distance.compute(n.getFeatures(), features);
+            if (d < min) {
+                min = d;
+                best = n;
+            }
+        }
+
+        return best;
+    }
+
+    /**
+     * Finds the two neurons that best match the given features.
+     *
+     * @param features Data.
+     * @param neurons List of neurons to scan. If the list is empty
+     * {@code null} will be returned.
+     * @param distance Distance function. The neuron's features are
+     * passed as the first argument to {@link DistanceMeasure#compute(double[],double[])}.
+     * @return the two neurons whose features are closest to the given data.
+     * @throws org.apache.commons.math3.exception.DimensionMismatchException
+     * if the size of the input is not compatible with the neurons features
+     * size.
+     */
+    public static Pair<Neuron, Neuron> findBestAndSecondBest(double[] features,
+                                                             Iterable<Neuron> neurons,
+                                                             DistanceMeasure distance) {
+        Neuron[] best = { null, null };
+        double[] min = { Double.POSITIVE_INFINITY,
+                         Double.POSITIVE_INFINITY };
+        for (final Neuron n : neurons) {
+            final double d = distance.compute(n.getFeatures(), features);
+            if (d < min[0]) {
+                // Replace second best with old best.
+                min[1] = min[0];
+                best[1] = best[0];
+
+                // Store current as new best.
+                min[0] = d;
+                best[0] = n;
+            } else if (d < min[1]) {
+                // Replace old second best with current.
+                min[1] = d;
+                best[1] = n;
+            }
+        }
+
+        return new Pair<Neuron, Neuron>(best[0], best[1]);
+    }
+
+    /**
+     * Creates a list of neurons sorted in increased order of the distance
+     * to the given {@code features}.
+     *
+     * @param features Data.
+     * @param neurons List of neurons to scan. If it is empty, an empty array
+     * will be returned.
+     * @param distance Distance function.
+     * @return the neurons, sorted in increasing order of distance in data
+     * space.
+     * @throws org.apache.commons.math3.exception.DimensionMismatchException
+     * if the size of the input is not compatible with the neurons features
+     * size.
+     *
+     * @see #findBest(double[],Iterable,DistanceMeasure)
+     * @see #findBestAndSecondBest(double[],Iterable,DistanceMeasure)
+     *
+     * @since 3.6
+     */
+    public static Neuron[] sort(double[] features,
+                                Iterable<Neuron> neurons,
+                                DistanceMeasure distance) {
+        final List<PairNeuronDouble> list = new ArrayList<PairNeuronDouble>();
+
+        for (final Neuron n : neurons) {
+            final double d = distance.compute(n.getFeatures(), features);
+            list.add(new PairNeuronDouble(n, d));
+        }
+
+        Collections.sort(list, PairNeuronDouble.COMPARATOR);
+
+        final int len = list.size();
+        final Neuron[] sorted = new Neuron[len];
+
+        for (int i = 0; i < len; i++) {
+            sorted[i] = list.get(i).getNeuron();
+        }
+        return sorted;
+    }
+
+    /**
+     * Computes the <a href="http://en.wikipedia.org/wiki/U-Matrix">
+     *  U-matrix</a> of a two-dimensional map.
+     *
+     * @param map Network.
+     * @param distance Function to use for computing the average
+     * distance from a neuron to its neighbours.
+     * @return the matrix of average distances.
+     */
+    public static double[][] computeU(NeuronSquareMesh2D map,
+                                      DistanceMeasure distance) {
+        final int numRows = map.getNumberOfRows();
+        final int numCols = map.getNumberOfColumns();
+        final double[][] uMatrix = new double[numRows][numCols];
+
+        final Network net = map.getNetwork();
+
+        for (int i = 0; i < numRows; i++) {
+            for (int j = 0; j < numCols; j++) {
+                final Neuron neuron = map.getNeuron(i, j);
+                final Collection<Neuron> neighbours = net.getNeighbours(neuron);
+                final double[] features = neuron.getFeatures();
+
+                double d = 0;
+                int count = 0;
+                for (Neuron n : neighbours) {
+                    ++count;
+                    d += distance.compute(features, n.getFeatures());
+                }
+
+                uMatrix[i][j] = d / count;
+            }
+        }
+
+        return uMatrix;
+    }
+
+    /**
+     * Computes the "hit" histogram of a two-dimensional map.
+     *
+     * @param data Feature vectors.
+     * @param map Network.
+     * @param distance Function to use for determining the best matching unit.
+     * @return the number of hits for each neuron in the map.
+     */
+    public static int[][] computeHitHistogram(Iterable<double[]> data,
+                                              NeuronSquareMesh2D map,
+                                              DistanceMeasure distance) {
+        final HashMap<Neuron, Integer> hit = new HashMap<Neuron, Integer>();
+        final Network net = map.getNetwork();
+
+        for (double[] f : data) {
+            final Neuron best = findBest(f, net, distance);
+            final Integer count = hit.get(best);
+            if (count == null) {
+                hit.put(best, 1);
+            } else {
+                hit.put(best, count + 1);
+            }
+        }
+
+        // Copy the histogram data into a 2D map.
+        final int numRows = map.getNumberOfRows();
+        final int numCols = map.getNumberOfColumns();
+        final int[][] histo = new int[numRows][numCols];
+
+        for (int i = 0; i < numRows; i++) {
+            for (int j = 0; j < numCols; j++) {
+                final Neuron neuron = map.getNeuron(i, j);
+                final Integer count = hit.get(neuron);
+                if (count == null) {
+                    histo[i][j] = 0;
+                } else {
+                    histo[i][j] = count;
+                }
+            }
+        }
+
+        return histo;
+    }
+
+    /**
+     * Computes the quantization error.
+     * The quantization error is the average distance between a feature vector
+     * and its "best matching unit" (closest neuron).
+     *
+     * @param data Feature vectors.
+     * @param neurons List of neurons to scan.
+     * @param distance Distance function.
+     * @return the error.
+     * @throws NoDataException if {@code data} is empty.
+     */
+    public static double computeQuantizationError(Iterable<double[]> data,
+                                                  Iterable<Neuron> neurons,
+                                                  DistanceMeasure distance) {
+        double d = 0;
+        int count = 0;
+        for (double[] f : data) {
+            ++count;
+            d += distance.compute(f, findBest(f, neurons, distance).getFeatures());
+        }
+
+        if (count == 0) {
+            throw new NoDataException();
+        }
+
+        return d / count;
+    }
+
+    /**
+     * Computes the topographic error.
+     * The topographic error is the proportion of data for which first and
+     * second best matching units are not adjacent in the map.
+     *
+     * @param data Feature vectors.
+     * @param net Network.
+     * @param distance Distance function.
+     * @return the error.
+     * @throws NoDataException if {@code data} is empty.
+     */
+    public static double computeTopographicError(Iterable<double[]> data,
+                                                 Network net,
+                                                 DistanceMeasure distance) {
+        int notAdjacentCount = 0;
+        int count = 0;
+        for (double[] f : data) {
+            ++count;
+            final Pair<Neuron, Neuron> p = findBestAndSecondBest(f, net, distance);
+            if (!net.getNeighbours(p.getFirst()).contains(p.getSecond())) {
+                // Increment count if first and second best matching units
+                // are not neighbours.
+                ++notAdjacentCount;
+            }
+        }
+
+        if (count == 0) {
+            throw new NoDataException();
+        }
+
+        return ((double) notAdjacentCount) / count;
+    }
+
+    /**
+     * Helper data structure holding a (Neuron, double) pair.
+     */
+    private static class PairNeuronDouble {
+        /** Comparator. */
+        static final Comparator<PairNeuronDouble> COMPARATOR
+            = new Comparator<PairNeuronDouble>() {
+            /** {@inheritDoc} */
+            public int compare(PairNeuronDouble o1,
+                               PairNeuronDouble o2) {
+                return Double.compare(o1.value, o2.value);
+            }
+        };
+        /** Key. */
+        private final Neuron neuron;
+        /** Value. */
+        private final double value;
+
+        /**
+         * @param neuron Neuron.
+         * @param value Value.
+         */
+        PairNeuronDouble(Neuron neuron, double value) {
+            this.neuron = neuron;
+            this.value = value;
+        }
+
+        /** @return the neuron. */
+        public Neuron getNeuron() {
+            return neuron;
+        }
+
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/neuralnet/Network.java b/src/main/java/org/apache/commons/math3/ml/neuralnet/Network.java
new file mode 100644
index 0000000..4b208a3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/neuralnet/Network.java
@@ -0,0 +1,499 @@
+/*
+ * 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.math3.ml.neuralnet;
+
+import java.io.Serializable;
+import java.io.ObjectInputStream;
+import java.util.NoSuchElementException;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Comparator;
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathIllegalStateException;
+
+/**
+ * Neural network, composed of {@link Neuron} instances and the links
+ * between them.
+ *
+ * Although updating a neuron's state is thread-safe, modifying the
+ * network's topology (adding or removing links) is not.
+ *
+ * @since 3.3
+ */
+public class Network
+    implements Iterable<Neuron>,
+               Serializable {
+    /** Serializable. */
+    private static final long serialVersionUID = 20130207L;
+    /** Neurons. */
+    private final ConcurrentHashMap<Long, Neuron> neuronMap
+        = new ConcurrentHashMap<Long, Neuron>();
+    /** Next available neuron identifier. */
+    private final AtomicLong nextId;
+    /** Neuron's features set size. */
+    private final int featureSize;
+    /** Links. */
+    private final ConcurrentHashMap<Long, Set<Long>> linkMap
+        = new ConcurrentHashMap<Long, Set<Long>>();
+
+    /**
+     * Comparator that prescribes an order of the neurons according
+     * to the increasing order of their identifier.
+     */
+    public static class NeuronIdentifierComparator
+        implements Comparator<Neuron>,
+                   Serializable {
+        /** Version identifier. */
+        private static final long serialVersionUID = 20130207L;
+
+        /** {@inheritDoc} */
+        public int compare(Neuron a,
+                           Neuron b) {
+            final long aId = a.getIdentifier();
+            final long bId = b.getIdentifier();
+            return aId < bId ? -1 :
+                aId > bId ? 1 : 0;
+        }
+    }
+
+    /**
+     * Constructor with restricted access, solely used for deserialization.
+     *
+     * @param nextId Next available identifier.
+     * @param featureSize Number of features.
+     * @param neuronList Neurons.
+     * @param neighbourIdList Links associated to each of the neurons in
+     * {@code neuronList}.
+     * @throws MathIllegalStateException if an inconsistency is detected
+     * (which probably means that the serialized form has been corrupted).
+     */
+    Network(long nextId,
+            int featureSize,
+            Neuron[] neuronList,
+            long[][] neighbourIdList) {
+        final int numNeurons = neuronList.length;
+        if (numNeurons != neighbourIdList.length) {
+            throw new MathIllegalStateException();
+        }
+
+        for (int i = 0; i < numNeurons; i++) {
+            final Neuron n = neuronList[i];
+            final long id = n.getIdentifier();
+            if (id >= nextId) {
+                throw new MathIllegalStateException();
+            }
+            neuronMap.put(id, n);
+            linkMap.put(id, new HashSet<Long>());
+        }
+
+        for (int i = 0; i < numNeurons; i++) {
+            final long aId = neuronList[i].getIdentifier();
+            final Set<Long> aLinks = linkMap.get(aId);
+            for (Long bId : neighbourIdList[i]) {
+                if (neuronMap.get(bId) == null) {
+                    throw new MathIllegalStateException();
+                }
+                addLinkToLinkSet(aLinks, bId);
+            }
+        }
+
+        this.nextId = new AtomicLong(nextId);
+        this.featureSize = featureSize;
+    }
+
+    /**
+     * @param initialIdentifier Identifier for the first neuron that
+     * will be added to this network.
+     * @param featureSize Size of the neuron's features.
+     */
+    public Network(long initialIdentifier,
+                   int featureSize) {
+        nextId = new AtomicLong(initialIdentifier);
+        this.featureSize = featureSize;
+    }
+
+    /**
+     * Performs a deep copy of this instance.
+     * Upon return, the copied and original instances will be independent:
+     * Updating one will not affect the other.
+     *
+     * @return a new instance with the same state as this instance.
+     * @since 3.6
+     */
+    public synchronized Network copy() {
+        final Network copy = new Network(nextId.get(),
+                                         featureSize);
+
+
+        for (Map.Entry<Long, Neuron> e : neuronMap.entrySet()) {
+            copy.neuronMap.put(e.getKey(), e.getValue().copy());
+        }
+
+        for (Map.Entry<Long, Set<Long>> e : linkMap.entrySet()) {
+            copy.linkMap.put(e.getKey(), new HashSet<Long>(e.getValue()));
+        }
+
+        return copy;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Iterator<Neuron> iterator() {
+        return neuronMap.values().iterator();
+    }
+
+    /**
+     * Creates a list of the neurons, sorted in a custom order.
+     *
+     * @param comparator {@link Comparator} used for sorting the neurons.
+     * @return a list of neurons, sorted in the order prescribed by the
+     * given {@code comparator}.
+     * @see NeuronIdentifierComparator
+     */
+    public Collection<Neuron> getNeurons(Comparator<Neuron> comparator) {
+        final List<Neuron> neurons = new ArrayList<Neuron>();
+        neurons.addAll(neuronMap.values());
+
+        Collections.sort(neurons, comparator);
+
+        return neurons;
+    }
+
+    /**
+     * Creates a neuron and assigns it a unique identifier.
+     *
+     * @param features Initial values for the neuron's features.
+     * @return the neuron's identifier.
+     * @throws DimensionMismatchException if the length of {@code features}
+     * is different from the expected size (as set by the
+     * {@link #Network(long,int) constructor}).
+     */
+    public long createNeuron(double[] features) {
+        if (features.length != featureSize) {
+            throw new DimensionMismatchException(features.length, featureSize);
+        }
+
+        final long id = createNextId();
+        neuronMap.put(id, new Neuron(id, features));
+        linkMap.put(id, new HashSet<Long>());
+        return id;
+    }
+
+    /**
+     * Deletes a neuron.
+     * Links from all neighbours to the removed neuron will also be
+     * {@link #deleteLink(Neuron,Neuron) deleted}.
+     *
+     * @param neuron Neuron to be removed from this network.
+     * @throws NoSuchElementException if {@code n} does not belong to
+     * this network.
+     */
+    public void deleteNeuron(Neuron neuron) {
+        final Collection<Neuron> neighbours = getNeighbours(neuron);
+
+        // Delete links to from neighbours.
+        for (Neuron n : neighbours) {
+            deleteLink(n, neuron);
+        }
+
+        // Remove neuron.
+        neuronMap.remove(neuron.getIdentifier());
+    }
+
+    /**
+     * Gets the size of the neurons' features set.
+     *
+     * @return the size of the features set.
+     */
+    public int getFeaturesSize() {
+        return featureSize;
+    }
+
+    /**
+     * Adds a link from neuron {@code a} to neuron {@code b}.
+     * Note: the link is not bi-directional; if a bi-directional link is
+     * required, an additional call must be made with {@code a} and
+     * {@code b} exchanged in the argument list.
+     *
+     * @param a Neuron.
+     * @param b Neuron.
+     * @throws NoSuchElementException if the neurons do not exist in the
+     * network.
+     */
+    public void addLink(Neuron a,
+                        Neuron b) {
+        final long aId = a.getIdentifier();
+        final long bId = b.getIdentifier();
+
+        // Check that the neurons belong to this network.
+        if (a != getNeuron(aId)) {
+            throw new NoSuchElementException(Long.toString(aId));
+        }
+        if (b != getNeuron(bId)) {
+            throw new NoSuchElementException(Long.toString(bId));
+        }
+
+        // Add link from "a" to "b".
+        addLinkToLinkSet(linkMap.get(aId), bId);
+    }
+
+    /**
+     * Adds a link to neuron {@code id} in given {@code linkSet}.
+     * Note: no check verifies that the identifier indeed belongs
+     * to this network.
+     *
+     * @param linkSet Neuron identifier.
+     * @param id Neuron identifier.
+     */
+    private void addLinkToLinkSet(Set<Long> linkSet,
+                                  long id) {
+        linkSet.add(id);
+    }
+
+    /**
+     * Deletes the link between neurons {@code a} and {@code b}.
+     *
+     * @param a Neuron.
+     * @param b Neuron.
+     * @throws NoSuchElementException if the neurons do not exist in the
+     * network.
+     */
+    public void deleteLink(Neuron a,
+                           Neuron b) {
+        final long aId = a.getIdentifier();
+        final long bId = b.getIdentifier();
+
+        // Check that the neurons belong to this network.
+        if (a != getNeuron(aId)) {
+            throw new NoSuchElementException(Long.toString(aId));
+        }
+        if (b != getNeuron(bId)) {
+            throw new NoSuchElementException(Long.toString(bId));
+        }
+
+        // Delete link from "a" to "b".
+        deleteLinkFromLinkSet(linkMap.get(aId), bId);
+    }
+
+    /**
+     * Deletes a link to neuron {@code id} in given {@code linkSet}.
+     * Note: no check verifies that the identifier indeed belongs
+     * to this network.
+     *
+     * @param linkSet Neuron identifier.
+     * @param id Neuron identifier.
+     */
+    private void deleteLinkFromLinkSet(Set<Long> linkSet,
+                                       long id) {
+        linkSet.remove(id);
+    }
+
+    /**
+     * Retrieves the neuron with the given (unique) {@code id}.
+     *
+     * @param id Identifier.
+     * @return the neuron associated with the given {@code id}.
+     * @throws NoSuchElementException if the neuron does not exist in the
+     * network.
+     */
+    public Neuron getNeuron(long id) {
+        final Neuron n = neuronMap.get(id);
+        if (n == null) {
+            throw new NoSuchElementException(Long.toString(id));
+        }
+        return n;
+    }
+
+    /**
+     * Retrieves the neurons in the neighbourhood of any neuron in the
+     * {@code neurons} list.
+     * @param neurons Neurons for which to retrieve the neighbours.
+     * @return the list of neighbours.
+     * @see #getNeighbours(Iterable,Iterable)
+     */
+    public Collection<Neuron> getNeighbours(Iterable<Neuron> neurons) {
+        return getNeighbours(neurons, null);
+    }
+
+    /**
+     * Retrieves the neurons in the neighbourhood of any neuron in the
+     * {@code neurons} list.
+     * The {@code exclude} list allows to retrieve the "concentric"
+     * neighbourhoods by removing the neurons that belong to the inner
+     * "circles".
+     *
+     * @param neurons Neurons for which to retrieve the neighbours.
+     * @param exclude Neurons to exclude from the returned list.
+     * Can be {@code null}.
+     * @return the list of neighbours.
+     */
+    public Collection<Neuron> getNeighbours(Iterable<Neuron> neurons,
+                                            Iterable<Neuron> exclude) {
+        final Set<Long> idList = new HashSet<Long>();
+
+        for (Neuron n : neurons) {
+            idList.addAll(linkMap.get(n.getIdentifier()));
+        }
+        if (exclude != null) {
+            for (Neuron n : exclude) {
+                idList.remove(n.getIdentifier());
+            }
+        }
+
+        final List<Neuron> neuronList = new ArrayList<Neuron>();
+        for (Long id : idList) {
+            neuronList.add(getNeuron(id));
+        }
+
+        return neuronList;
+    }
+
+    /**
+     * Retrieves the neighbours of the given neuron.
+     *
+     * @param neuron Neuron for which to retrieve the neighbours.
+     * @return the list of neighbours.
+     * @see #getNeighbours(Neuron,Iterable)
+     */
+    public Collection<Neuron> getNeighbours(Neuron neuron) {
+        return getNeighbours(neuron, null);
+    }
+
+    /**
+     * Retrieves the neighbours of the given neuron.
+     *
+     * @param neuron Neuron for which to retrieve the neighbours.
+     * @param exclude Neurons to exclude from the returned list.
+     * Can be {@code null}.
+     * @return the list of neighbours.
+     */
+    public Collection<Neuron> getNeighbours(Neuron neuron,
+                                            Iterable<Neuron> exclude) {
+        final Set<Long> idList = linkMap.get(neuron.getIdentifier());
+        if (exclude != null) {
+            for (Neuron n : exclude) {
+                idList.remove(n.getIdentifier());
+            }
+        }
+
+        final List<Neuron> neuronList = new ArrayList<Neuron>();
+        for (Long id : idList) {
+            neuronList.add(getNeuron(id));
+        }
+
+        return neuronList;
+    }
+
+    /**
+     * Creates a neuron identifier.
+     *
+     * @return a value that will serve as a unique identifier.
+     */
+    private Long createNextId() {
+        return nextId.getAndIncrement();
+    }
+
+    /**
+     * Prevents proxy bypass.
+     *
+     * @param in Input stream.
+     */
+    private void readObject(ObjectInputStream in) {
+        throw new IllegalStateException();
+    }
+
+    /**
+     * Custom serialization.
+     *
+     * @return the proxy instance that will be actually serialized.
+     */
+    private Object writeReplace() {
+        final Neuron[] neuronList = neuronMap.values().toArray(new Neuron[0]);
+        final long[][] neighbourIdList = new long[neuronList.length][];
+
+        for (int i = 0; i < neuronList.length; i++) {
+            final Collection<Neuron> neighbours = getNeighbours(neuronList[i]);
+            final long[] neighboursId = new long[neighbours.size()];
+            int count = 0;
+            for (Neuron n : neighbours) {
+                neighboursId[count] = n.getIdentifier();
+                ++count;
+            }
+            neighbourIdList[i] = neighboursId;
+        }
+
+        return new SerializationProxy(nextId.get(),
+                                      featureSize,
+                                      neuronList,
+                                      neighbourIdList);
+    }
+
+    /**
+     * Serialization.
+     */
+    private static class SerializationProxy implements Serializable {
+        /** Serializable. */
+        private static final long serialVersionUID = 20130207L;
+        /** Next identifier. */
+        private final long nextId;
+        /** Number of features. */
+        private final int featureSize;
+        /** Neurons. */
+        private final Neuron[] neuronList;
+        /** Links. */
+        private final long[][] neighbourIdList;
+
+        /**
+         * @param nextId Next available identifier.
+         * @param featureSize Number of features.
+         * @param neuronList Neurons.
+         * @param neighbourIdList Links associated to each of the neurons in
+         * {@code neuronList}.
+         */
+        SerializationProxy(long nextId,
+                           int featureSize,
+                           Neuron[] neuronList,
+                           long[][] neighbourIdList) {
+            this.nextId = nextId;
+            this.featureSize = featureSize;
+            this.neuronList = neuronList;
+            this.neighbourIdList = neighbourIdList;
+        }
+
+        /**
+         * Custom serialization.
+         *
+         * @return the {@link Network} for which this instance is the proxy.
+         */
+        private Object readResolve() {
+            return new Network(nextId,
+                               featureSize,
+                               neuronList,
+                               neighbourIdList);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/neuralnet/Neuron.java b/src/main/java/org/apache/commons/math3/ml/neuralnet/Neuron.java
new file mode 100644
index 0000000..8cae3ea
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/neuralnet/Neuron.java
@@ -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.math3.ml.neuralnet;
+
+import java.io.Serializable;
+import java.io.ObjectInputStream;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.util.Precision;
+
+
+/**
+ * Describes a neuron element of a neural network.
+ *
+ * This class aims to be thread-safe.
+ *
+ * @since 3.3
+ */
+public class Neuron implements Serializable {
+    /** Serializable. */
+    private static final long serialVersionUID = 20130207L;
+    /** Identifier. */
+    private final long identifier;
+    /** Length of the feature set. */
+    private final int size;
+    /** Neuron data. */
+    private final AtomicReference<double[]> features;
+    /** Number of attempts to update a neuron. */
+    private final AtomicLong numberOfAttemptedUpdates = new AtomicLong(0);
+    /** Number of successful updates  of a neuron. */
+    private final AtomicLong numberOfSuccessfulUpdates = new AtomicLong(0);
+
+    /**
+     * Creates a neuron.
+     * The size of the feature set is fixed to the length of the given
+     * argument.
+     * <br/>
+     * Constructor is package-private: Neurons must be
+     * {@link Network#createNeuron(double[]) created} by the network
+     * instance to which they will belong.
+     *
+     * @param identifier Identifier (assigned by the {@link Network}).
+     * @param features Initial values of the feature set.
+     */
+    Neuron(long identifier,
+           double[] features) {
+        this.identifier = identifier;
+        this.size = features.length;
+        this.features = new AtomicReference<double[]>(features.clone());
+    }
+
+    /**
+     * Performs a deep copy of this instance.
+     * Upon return, the copied and original instances will be independent:
+     * Updating one will not affect the other.
+     *
+     * @return a new instance with the same state as this instance.
+     * @since 3.6
+     */
+    public synchronized Neuron copy() {
+        final Neuron copy = new Neuron(getIdentifier(),
+                                       getFeatures());
+        copy.numberOfAttemptedUpdates.set(numberOfAttemptedUpdates.get());
+        copy.numberOfSuccessfulUpdates.set(numberOfSuccessfulUpdates.get());
+
+        return copy;
+    }
+
+    /**
+     * Gets the neuron's identifier.
+     *
+     * @return the identifier.
+     */
+    public long getIdentifier() {
+        return identifier;
+    }
+
+    /**
+     * Gets the length of the feature set.
+     *
+     * @return the number of features.
+     */
+    public int getSize() {
+        return size;
+    }
+
+    /**
+     * Gets the neuron's features.
+     *
+     * @return a copy of the neuron's features.
+     */
+    public double[] getFeatures() {
+        return features.get().clone();
+    }
+
+    /**
+     * Tries to atomically update the neuron's features.
+     * Update will be performed only if the expected values match the
+     * current values.<br/>
+     * In effect, when concurrent threads call this method, the state
+     * could be modified by one, so that it does not correspond to the
+     * the state assumed by another.
+     * Typically, a caller {@link #getFeatures() retrieves the current state},
+     * and uses it to compute the new state.
+     * During this computation, another thread might have done the same
+     * thing, and updated the state: If the current thread were to proceed
+     * with its own update, it would overwrite the new state (which might
+     * already have been used by yet other threads).
+     * To prevent this, the method does not perform the update when a
+     * concurrent modification has been detected, and returns {@code false}.
+     * When this happens, the caller should fetch the new current state,
+     * redo its computation, and call this method again.
+     *
+     * @param expect Current values of the features, as assumed by the caller.
+     * Update will never succeed if the contents of this array does not match
+     * the values returned by {@link #getFeatures()}.
+     * @param update Features's new values.
+     * @return {@code true} if the update was successful, {@code false}
+     * otherwise.
+     * @throws DimensionMismatchException if the length of {@code update} is
+     * not the same as specified in the {@link #Neuron(long,double[])
+     * constructor}.
+     */
+    public boolean compareAndSetFeatures(double[] expect,
+                                         double[] update) {
+        if (update.length != size) {
+            throw new DimensionMismatchException(update.length, size);
+        }
+
+        // Get the internal reference. Note that this must not be a copy;
+        // otherwise the "compareAndSet" below will always fail.
+        final double[] current = features.get();
+        if (!containSameValues(current, expect)) {
+            // Some other thread already modified the state.
+            return false;
+        }
+
+        // Increment attempt counter.
+        numberOfAttemptedUpdates.incrementAndGet();
+
+        if (features.compareAndSet(current, update.clone())) {
+            // The current thread could atomically update the state (attempt succeeded).
+            numberOfSuccessfulUpdates.incrementAndGet();
+            return true;
+        } else {
+            // Some other thread came first (attempt failed).
+            return false;
+        }
+    }
+
+    /**
+     * Retrieves the number of calls to the
+     * {@link #compareAndSetFeatures(double[],double[]) compareAndSetFeatures}
+     * method.
+     * Note that if the caller wants to use this method in combination with
+     * {@link #getNumberOfSuccessfulUpdates()}, additional synchronization
+     * may be required to ensure consistency.
+     *
+     * @return the number of update attempts.
+     * @since 3.6
+     */
+    public long getNumberOfAttemptedUpdates() {
+        return numberOfAttemptedUpdates.get();
+    }
+
+    /**
+     * Retrieves the number of successful calls to the
+     * {@link #compareAndSetFeatures(double[],double[]) compareAndSetFeatures}
+     * method.
+     * Note that if the caller wants to use this method in combination with
+     * {@link #getNumberOfAttemptedUpdates()}, additional synchronization
+     * may be required to ensure consistency.
+     *
+     * @return the number of successful updates.
+     * @since 3.6
+     */
+    public long getNumberOfSuccessfulUpdates() {
+        return numberOfSuccessfulUpdates.get();
+    }
+
+    /**
+     * Checks whether the contents of both arrays is the same.
+     *
+     * @param current Current values.
+     * @param expect Expected values.
+     * @throws DimensionMismatchException if the length of {@code expected}
+     * is not the same as specified in the {@link #Neuron(long,double[])
+     * constructor}.
+     * @return {@code true} if the arrays contain the same values.
+     */
+    private boolean containSameValues(double[] current,
+                                      double[] expect) {
+        if (expect.length != size) {
+            throw new DimensionMismatchException(expect.length, size);
+        }
+
+        for (int i = 0; i < size; i++) {
+            if (!Precision.equals(current[i], expect[i])) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Prevents proxy bypass.
+     *
+     * @param in Input stream.
+     */
+    private void readObject(ObjectInputStream in) {
+        throw new IllegalStateException();
+    }
+
+    /**
+     * Custom serialization.
+     *
+     * @return the proxy instance that will be actually serialized.
+     */
+    private Object writeReplace() {
+        return new SerializationProxy(identifier,
+                                      features.get());
+    }
+
+    /**
+     * Serialization.
+     */
+    private static class SerializationProxy implements Serializable {
+        /** Serializable. */
+        private static final long serialVersionUID = 20130207L;
+        /** Features. */
+        private final double[] features;
+        /** Identifier. */
+        private final long identifier;
+
+        /**
+         * @param identifier Identifier.
+         * @param features Features.
+         */
+        SerializationProxy(long identifier,
+                           double[] features) {
+            this.identifier = identifier;
+            this.features = features;
+        }
+
+        /**
+         * Custom serialization.
+         *
+         * @return the {@link Neuron} for which this instance is the proxy.
+         */
+        private Object readResolve() {
+            return new Neuron(identifier,
+                              features);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/neuralnet/SquareNeighbourhood.java b/src/main/java/org/apache/commons/math3/ml/neuralnet/SquareNeighbourhood.java
new file mode 100644
index 0000000..a3c0d95
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/neuralnet/SquareNeighbourhood.java
@@ -0,0 +1,38 @@
+/*
+ * 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.math3.ml.neuralnet;
+
+/**
+ * Defines neighbourhood types.
+ *
+ * @since 3.3
+ */
+public enum SquareNeighbourhood {
+    /**
+     * <a href="http://en.wikipedia.org/wiki/Von_Neumann_neighborhood"
+     *  Von Neumann neighbourhood</a>: in two dimensions, each (internal)
+     * neuron has four neighbours.
+     */
+    VON_NEUMANN,
+    /**
+     * <a href="http://en.wikipedia.org/wiki/Moore_neighborhood"
+     *  Moore neighbourhood</a>: in two dimensions, each (internal)
+     * neuron has eight neighbours.
+     */
+    MOORE,
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/neuralnet/UpdateAction.java b/src/main/java/org/apache/commons/math3/ml/neuralnet/UpdateAction.java
new file mode 100644
index 0000000..041d3d6
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/neuralnet/UpdateAction.java
@@ -0,0 +1,34 @@
+/*
+ * 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.math3.ml.neuralnet;
+
+/**
+ * Describes how to update the network in response to a training
+ * sample.
+ *
+ * @since 3.3
+ */
+public interface UpdateAction {
+    /**
+     * Updates the network in response to the sample {@code features}.
+     *
+     * @param net Network.
+     * @param features Training data.
+     */
+    void update(Network net, double[] features);
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/neuralnet/oned/NeuronString.java b/src/main/java/org/apache/commons/math3/ml/neuralnet/oned/NeuronString.java
new file mode 100644
index 0000000..fad6042
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/neuralnet/oned/NeuronString.java
@@ -0,0 +1,238 @@
+/*
+ * 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.math3.ml.neuralnet.oned;
+
+import java.io.Serializable;
+import java.io.ObjectInputStream;
+import org.apache.commons.math3.ml.neuralnet.Network;
+import org.apache.commons.math3.ml.neuralnet.FeatureInitializer;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+
+/**
+ * Neural network with the topology of a one-dimensional line.
+ * Each neuron defines one point on the line.
+ *
+ * @since 3.3
+ */
+public class NeuronString implements Serializable {
+    /** Serial version ID */
+    private static final long serialVersionUID = 1L;
+    /** Underlying network. */
+    private final Network network;
+    /** Number of neurons. */
+    private final int size;
+    /** Wrap. */
+    private final boolean wrap;
+
+    /**
+     * Mapping of the 1D coordinate to the neuron identifiers
+     * (attributed by the {@link #network} instance).
+     */
+    private final long[] identifiers;
+
+    /**
+     * Constructor with restricted access, solely used for deserialization.
+     *
+     * @param wrap Whether to wrap the dimension (i.e the first and last
+     * neurons will be linked together).
+     * @param featuresList Arrays that will initialize the features sets of
+     * the network's neurons.
+     * @throws NumberIsTooSmallException if {@code num < 2}.
+     */
+    NeuronString(boolean wrap,
+                 double[][] featuresList) {
+        size = featuresList.length;
+
+        if (size < 2) {
+            throw new NumberIsTooSmallException(size, 2, true);
+        }
+
+        this.wrap = wrap;
+
+        final int fLen = featuresList[0].length;
+        network = new Network(0, fLen);
+        identifiers = new long[size];
+
+        // Add neurons.
+        for (int i = 0; i < size; i++) {
+            identifiers[i] = network.createNeuron(featuresList[i]);
+        }
+
+        // Add links.
+        createLinks();
+    }
+
+    /**
+     * Creates a one-dimensional network:
+     * Each neuron not located on the border of the mesh has two
+     * neurons linked to it.
+     * <br/>
+     * The links are bi-directional.
+     * Neurons created successively are neighbours (i.e. there are
+     * links between them).
+     * <br/>
+     * The topology of the network can also be a circle (if the
+     * dimension is wrapped).
+     *
+     * @param num Number of neurons.
+     * @param wrap Whether to wrap the dimension (i.e the first and last
+     * neurons will be linked together).
+     * @param featureInit Arrays that will initialize the features sets of
+     * the network's neurons.
+     * @throws NumberIsTooSmallException if {@code num < 2}.
+     */
+    public NeuronString(int num,
+                        boolean wrap,
+                        FeatureInitializer[] featureInit) {
+        if (num < 2) {
+            throw new NumberIsTooSmallException(num, 2, true);
+        }
+
+        size = num;
+        this.wrap = wrap;
+        identifiers = new long[num];
+
+        final int fLen = featureInit.length;
+        network = new Network(0, fLen);
+
+        // Add neurons.
+        for (int i = 0; i < num; i++) {
+            final double[] features = new double[fLen];
+            for (int fIndex = 0; fIndex < fLen; fIndex++) {
+                features[fIndex] = featureInit[fIndex].value();
+            }
+            identifiers[i] = network.createNeuron(features);
+        }
+
+        // Add links.
+        createLinks();
+    }
+
+    /**
+     * Retrieves the underlying network.
+     * A reference is returned (enabling, for example, the network to be
+     * trained).
+     * This also implies that calling methods that modify the {@link Network}
+     * topology may cause this class to become inconsistent.
+     *
+     * @return the network.
+     */
+    public Network getNetwork() {
+        return network;
+    }
+
+    /**
+     * Gets the number of neurons.
+     *
+     * @return the number of neurons.
+     */
+    public int getSize() {
+        return size;
+    }
+
+    /**
+     * Retrieves the features set from the neuron at location
+     * {@code i} in the map.
+     *
+     * @param i Neuron index.
+     * @return the features of the neuron at index {@code i}.
+     * @throws OutOfRangeException if {@code i} is out of range.
+     */
+    public double[] getFeatures(int i) {
+        if (i < 0 ||
+            i >= size) {
+            throw new OutOfRangeException(i, 0, size - 1);
+        }
+
+        return network.getNeuron(identifiers[i]).getFeatures();
+    }
+
+    /**
+     * Creates the neighbour relationships between neurons.
+     */
+    private void createLinks() {
+        for (int i = 0; i < size - 1; i++) {
+            network.addLink(network.getNeuron(i), network.getNeuron(i + 1));
+        }
+        for (int i = size - 1; i > 0; i--) {
+            network.addLink(network.getNeuron(i), network.getNeuron(i - 1));
+        }
+        if (wrap) {
+            network.addLink(network.getNeuron(0), network.getNeuron(size - 1));
+            network.addLink(network.getNeuron(size - 1), network.getNeuron(0));
+        }
+    }
+
+    /**
+     * Prevents proxy bypass.
+     *
+     * @param in Input stream.
+     */
+    private void readObject(ObjectInputStream in) {
+        throw new IllegalStateException();
+    }
+
+    /**
+     * Custom serialization.
+     *
+     * @return the proxy instance that will be actually serialized.
+     */
+    private Object writeReplace() {
+        final double[][] featuresList = new double[size][];
+        for (int i = 0; i < size; i++) {
+            featuresList[i] = getFeatures(i);
+        }
+
+        return new SerializationProxy(wrap,
+                                      featuresList);
+    }
+
+    /**
+     * Serialization.
+     */
+    private static class SerializationProxy implements Serializable {
+        /** Serializable. */
+        private static final long serialVersionUID = 20130226L;
+        /** Wrap. */
+        private final boolean wrap;
+        /** Neurons' features. */
+        private final double[][] featuresList;
+
+        /**
+         * @param wrap Whether the dimension is wrapped.
+         * @param featuresList List of neurons features.
+         * {@code neuronList}.
+         */
+        SerializationProxy(boolean wrap,
+                           double[][] featuresList) {
+            this.wrap = wrap;
+            this.featuresList = featuresList;
+        }
+
+        /**
+         * Custom serialization.
+         *
+         * @return the {@link Neuron} for which this instance is the proxy.
+         */
+        private Object readResolve() {
+            return new NeuronString(wrap,
+                                    featuresList);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/neuralnet/oned/package-info.java b/src/main/java/org/apache/commons/math3/ml/neuralnet/oned/package-info.java
new file mode 100644
index 0000000..0b47fae
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/neuralnet/oned/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+/**
+ * One-dimensional neural networks.
+ */
+
+package org.apache.commons.math3.ml.neuralnet.oned;
diff --git a/src/main/java/org/apache/commons/math3/ml/neuralnet/package-info.java b/src/main/java/org/apache/commons/math3/ml/neuralnet/package-info.java
new file mode 100644
index 0000000..d8e907e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/neuralnet/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+/**
+ * Neural networks.
+ */
+
+package org.apache.commons.math3.ml.neuralnet;
diff --git a/src/main/java/org/apache/commons/math3/ml/neuralnet/sofm/KohonenTrainingTask.java b/src/main/java/org/apache/commons/math3/ml/neuralnet/sofm/KohonenTrainingTask.java
new file mode 100644
index 0000000..9aa497d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/neuralnet/sofm/KohonenTrainingTask.java
@@ -0,0 +1,59 @@
+/*
+ * 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.math3.ml.neuralnet.sofm;
+
+import java.util.Iterator;
+import org.apache.commons.math3.ml.neuralnet.Network;
+
+/**
+ * Trainer for Kohonen's Self-Organizing Map.
+ *
+ * @since 3.3
+ */
+public class KohonenTrainingTask implements Runnable {
+    /** SOFM to be trained. */
+    private final Network net;
+    /** Training data. */
+    private final Iterator<double[]> featuresIterator;
+    /** Update procedure. */
+    private final KohonenUpdateAction updateAction;
+
+    /**
+     * Creates a (sequential) trainer for the given network.
+     *
+     * @param net Network to be trained with the SOFM algorithm.
+     * @param featuresIterator Training data iterator.
+     * @param updateAction SOFM update procedure.
+     */
+    public KohonenTrainingTask(Network net,
+                               Iterator<double[]> featuresIterator,
+                               KohonenUpdateAction updateAction) {
+        this.net = net;
+        this.featuresIterator = featuresIterator;
+        this.updateAction = updateAction;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void run() {
+        while (featuresIterator.hasNext()) {
+            updateAction.update(net, featuresIterator.next());
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/neuralnet/sofm/KohonenUpdateAction.java b/src/main/java/org/apache/commons/math3/ml/neuralnet/sofm/KohonenUpdateAction.java
new file mode 100644
index 0000000..0618aeb
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/neuralnet/sofm/KohonenUpdateAction.java
@@ -0,0 +1,225 @@
+/*
+ * 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.math3.ml.neuralnet.sofm;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.commons.math3.analysis.function.Gaussian;
+import org.apache.commons.math3.linear.ArrayRealVector;
+import org.apache.commons.math3.ml.distance.DistanceMeasure;
+import org.apache.commons.math3.ml.neuralnet.MapUtils;
+import org.apache.commons.math3.ml.neuralnet.Network;
+import org.apache.commons.math3.ml.neuralnet.Neuron;
+import org.apache.commons.math3.ml.neuralnet.UpdateAction;
+
+/**
+ * Update formula for <a href="http://en.wikipedia.org/wiki/Kohonen">
+ * Kohonen's Self-Organizing Map</a>.
+ * <br/>
+ * The {@link #update(Network,double[]) update} method modifies the
+ * features {@code w} of the "winning" neuron and its neighbours
+ * according to the following rule:
+ * <code>
+ *  w<sub>new</sub> = w<sub>old</sub> + &alpha; e<sup>(-d / &sigma;)</sup> * (sample - w<sub>old</sub>)
+ * </code>
+ * where
+ * <ul>
+ *  <li>&alpha; is the current <em>learning rate</em>, </li>
+ *  <li>&sigma; is the current <em>neighbourhood size</em>, and</li>
+ *  <li>{@code d} is the number of links to traverse in order to reach
+ *   the neuron from the winning neuron.</li>
+ * </ul>
+ * <br/>
+ * This class is thread-safe as long as the arguments passed to the
+ * {@link #KohonenUpdateAction(DistanceMeasure,LearningFactorFunction,
+ * NeighbourhoodSizeFunction) constructor} are instances of thread-safe
+ * classes.
+ * <br/>
+ * Each call to the {@link #update(Network,double[]) update} method
+ * will increment the internal counter used to compute the current
+ * values for
+ * <ul>
+ *  <li>the <em>learning rate</em>, and</li>
+ *  <li>the <em>neighbourhood size</em>.</li>
+ * </ul>
+ * Consequently, the function instances that compute those values (passed
+ * to the constructor of this class) must take into account whether this
+ * class's instance will be shared by multiple threads, as this will impact
+ * the training process.
+ *
+ * @since 3.3
+ */
+public class KohonenUpdateAction implements UpdateAction {
+    /** Distance function. */
+    private final DistanceMeasure distance;
+    /** Learning factor update function. */
+    private final LearningFactorFunction learningFactor;
+    /** Neighbourhood size update function. */
+    private final NeighbourhoodSizeFunction neighbourhoodSize;
+    /** Number of calls to {@link #update(Network,double[])}. */
+    private final AtomicLong numberOfCalls = new AtomicLong(0);
+
+    /**
+     * @param distance Distance function.
+     * @param learningFactor Learning factor update function.
+     * @param neighbourhoodSize Neighbourhood size update function.
+     */
+    public KohonenUpdateAction(DistanceMeasure distance,
+                               LearningFactorFunction learningFactor,
+                               NeighbourhoodSizeFunction neighbourhoodSize) {
+        this.distance = distance;
+        this.learningFactor = learningFactor;
+        this.neighbourhoodSize = neighbourhoodSize;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void update(Network net,
+                       double[] features) {
+        final long numCalls = numberOfCalls.incrementAndGet() - 1;
+        final double currentLearning = learningFactor.value(numCalls);
+        final Neuron best = findAndUpdateBestNeuron(net,
+                                                    features,
+                                                    currentLearning);
+
+        final int currentNeighbourhood = neighbourhoodSize.value(numCalls);
+        // The farther away the neighbour is from the winning neuron, the
+        // smaller the learning rate will become.
+        final Gaussian neighbourhoodDecay
+            = new Gaussian(currentLearning,
+                           0,
+                           currentNeighbourhood);
+
+        if (currentNeighbourhood > 0) {
+            // Initial set of neurons only contains the winning neuron.
+            Collection<Neuron> neighbours = new HashSet<Neuron>();
+            neighbours.add(best);
+            // Winning neuron must be excluded from the neighbours.
+            final HashSet<Neuron> exclude = new HashSet<Neuron>();
+            exclude.add(best);
+
+            int radius = 1;
+            do {
+                // Retrieve immediate neighbours of the current set of neurons.
+                neighbours = net.getNeighbours(neighbours, exclude);
+
+                // Update all the neighbours.
+                for (Neuron n : neighbours) {
+                    updateNeighbouringNeuron(n, features, neighbourhoodDecay.value(radius));
+                }
+
+                // Add the neighbours to the exclude list so that they will
+                // not be update more than once per training step.
+                exclude.addAll(neighbours);
+                ++radius;
+            } while (radius <= currentNeighbourhood);
+        }
+    }
+
+    /**
+     * Retrieves the number of calls to the {@link #update(Network,double[]) update}
+     * method.
+     *
+     * @return the current number of calls.
+     */
+    public long getNumberOfCalls() {
+        return numberOfCalls.get();
+    }
+
+    /**
+     * Tries to update a neuron.
+     *
+     * @param n Neuron to be updated.
+     * @param features Training data.
+     * @param learningRate Learning factor.
+     * @return {@code true} if the update succeeded, {@code true} if a
+     * concurrent update has been detected.
+     */
+    private boolean attemptNeuronUpdate(Neuron n,
+                                        double[] features,
+                                        double learningRate) {
+        final double[] expect = n.getFeatures();
+        final double[] update = computeFeatures(expect,
+                                                features,
+                                                learningRate);
+
+        return n.compareAndSetFeatures(expect, update);
+    }
+
+    /**
+     * Atomically updates the given neuron.
+     *
+     * @param n Neuron to be updated.
+     * @param features Training data.
+     * @param learningRate Learning factor.
+     */
+    private void updateNeighbouringNeuron(Neuron n,
+                                          double[] features,
+                                          double learningRate) {
+        while (true) {
+            if (attemptNeuronUpdate(n, features, learningRate)) {
+                break;
+            }
+        }
+    }
+
+    /**
+     * Searches for the neuron whose features are closest to the given
+     * sample, and atomically updates its features.
+     *
+     * @param net Network.
+     * @param features Sample data.
+     * @param learningRate Current learning factor.
+     * @return the winning neuron.
+     */
+    private Neuron findAndUpdateBestNeuron(Network net,
+                                           double[] features,
+                                           double learningRate) {
+        while (true) {
+            final Neuron best = MapUtils.findBest(features, net, distance);
+
+            if (attemptNeuronUpdate(best, features, learningRate)) {
+                return best;
+            }
+
+            // If another thread modified the state of the winning neuron,
+            // it may not be the best match anymore for the given training
+            // sample: Hence, the winner search is performed again.
+        }
+    }
+
+    /**
+     * Computes the new value of the features set.
+     *
+     * @param current Current values of the features.
+     * @param sample Training data.
+     * @param learningRate Learning factor.
+     * @return the new values for the features.
+     */
+    private double[] computeFeatures(double[] current,
+                                     double[] sample,
+                                     double learningRate) {
+        final ArrayRealVector c = new ArrayRealVector(current, false);
+        final ArrayRealVector s = new ArrayRealVector(sample, false);
+        // c + learningRate * (s - c)
+        return s.subtract(c).mapMultiplyToSelf(learningRate).add(c).toArray();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/neuralnet/sofm/LearningFactorFunction.java b/src/main/java/org/apache/commons/math3/ml/neuralnet/sofm/LearningFactorFunction.java
new file mode 100644
index 0000000..ba9d152
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/neuralnet/sofm/LearningFactorFunction.java
@@ -0,0 +1,34 @@
+/*
+ * 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.math3.ml.neuralnet.sofm;
+
+/**
+ * Provides the learning rate as a function of the number of calls
+ * already performed during the learning task.
+ *
+ * @since 3.3
+ */
+public interface LearningFactorFunction {
+    /**
+     * Computes the learning rate at the current call.
+     *
+     * @param numCall Current step of the training task.
+     * @return the value of the function at {@code numCall}.
+     */
+    double value(long numCall);
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/neuralnet/sofm/LearningFactorFunctionFactory.java b/src/main/java/org/apache/commons/math3/ml/neuralnet/sofm/LearningFactorFunctionFactory.java
new file mode 100644
index 0000000..9165e82
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/neuralnet/sofm/LearningFactorFunctionFactory.java
@@ -0,0 +1,117 @@
+/*
+ * 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.math3.ml.neuralnet.sofm;
+
+import org.apache.commons.math3.ml.neuralnet.sofm.util.ExponentialDecayFunction;
+import org.apache.commons.math3.ml.neuralnet.sofm.util.QuasiSigmoidDecayFunction;
+import org.apache.commons.math3.exception.OutOfRangeException;
+
+/**
+ * Factory for creating instances of {@link LearningFactorFunction}.
+ *
+ * @since 3.3
+ */
+public class LearningFactorFunctionFactory {
+    /** Class contains only static methods. */
+    private LearningFactorFunctionFactory() {}
+
+    /**
+     * Creates an exponential decay {@link LearningFactorFunction function}.
+     * It will compute <code>a e<sup>-x / b</sup></code>,
+     * where {@code x} is the (integer) independent variable and
+     * <ul>
+     *  <li><code>a = initValue</code>
+     *  <li><code>b = -numCall / ln(valueAtNumCall / initValue)</code>
+     * </ul>
+     *
+     * @param initValue Initial value, i.e.
+     * {@link LearningFactorFunction#value(long) value(0)}.
+     * @param valueAtNumCall Value of the function at {@code numCall}.
+     * @param numCall Argument for which the function returns
+     * {@code valueAtNumCall}.
+     * @return the learning factor function.
+     * @throws org.apache.commons.math3.exception.OutOfRangeException
+     * if {@code initValue <= 0} or {@code initValue > 1}.
+     * @throws org.apache.commons.math3.exception.NotStrictlyPositiveException
+     * if {@code valueAtNumCall <= 0}.
+     * @throws org.apache.commons.math3.exception.NumberIsTooLargeException
+     * if {@code valueAtNumCall >= initValue}.
+     * @throws org.apache.commons.math3.exception.NotStrictlyPositiveException
+     * if {@code numCall <= 0}.
+     */
+    public static LearningFactorFunction exponentialDecay(final double initValue,
+                                                          final double valueAtNumCall,
+                                                          final long numCall) {
+        if (initValue <= 0 ||
+            initValue > 1) {
+            throw new OutOfRangeException(initValue, 0, 1);
+        }
+
+        return new LearningFactorFunction() {
+            /** DecayFunction. */
+            private final ExponentialDecayFunction decay
+                = new ExponentialDecayFunction(initValue, valueAtNumCall, numCall);
+
+            /** {@inheritDoc} */
+            public double value(long n) {
+                return decay.value(n);
+            }
+        };
+    }
+
+    /**
+     * Creates an sigmoid-like {@code LearningFactorFunction function}.
+     * The function {@code f} will have the following properties:
+     * <ul>
+     *  <li>{@code f(0) = initValue}</li>
+     *  <li>{@code numCall} is the inflexion point</li>
+     *  <li>{@code slope = f'(numCall)}</li>
+     * </ul>
+     *
+     * @param initValue Initial value, i.e.
+     * {@link LearningFactorFunction#value(long) value(0)}.
+     * @param slope Value of the function derivative at {@code numCall}.
+     * @param numCall Inflexion point.
+     * @return the learning factor function.
+     * @throws org.apache.commons.math3.exception.OutOfRangeException
+     * if {@code initValue <= 0} or {@code initValue > 1}.
+     * @throws org.apache.commons.math3.exception.NumberIsTooLargeException
+     * if {@code slope >= 0}.
+     * @throws org.apache.commons.math3.exception.NotStrictlyPositiveException
+     * if {@code numCall <= 0}.
+     */
+    public static LearningFactorFunction quasiSigmoidDecay(final double initValue,
+                                                           final double slope,
+                                                           final long numCall) {
+        if (initValue <= 0 ||
+            initValue > 1) {
+            throw new OutOfRangeException(initValue, 0, 1);
+        }
+
+        return new LearningFactorFunction() {
+            /** DecayFunction. */
+            private final QuasiSigmoidDecayFunction decay
+                = new QuasiSigmoidDecayFunction(initValue, slope, numCall);
+
+            /** {@inheritDoc} */
+            public double value(long n) {
+                return decay.value(n);
+            }
+        };
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/neuralnet/sofm/NeighbourhoodSizeFunction.java b/src/main/java/org/apache/commons/math3/ml/neuralnet/sofm/NeighbourhoodSizeFunction.java
new file mode 100644
index 0000000..68149f7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/neuralnet/sofm/NeighbourhoodSizeFunction.java
@@ -0,0 +1,37 @@
+/*
+ * 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.math3.ml.neuralnet.sofm;
+
+/**
+ * Provides the network neighbourhood's size as a function of the
+ * number of calls already performed during the learning task.
+ * The "neighbourhood" is the set of neurons that can be reached
+ * by traversing at most the number of links returned by this
+ * function.
+ *
+ * @since 3.3
+ */
+public interface NeighbourhoodSizeFunction {
+    /**
+     * Computes the neighbourhood size at the current call.
+     *
+     * @param numCall Current step of the training task.
+     * @return the value of the function at {@code numCall}.
+     */
+    int value(long numCall);
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/neuralnet/sofm/NeighbourhoodSizeFunctionFactory.java b/src/main/java/org/apache/commons/math3/ml/neuralnet/sofm/NeighbourhoodSizeFunctionFactory.java
new file mode 100644
index 0000000..bdbfa2f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/neuralnet/sofm/NeighbourhoodSizeFunctionFactory.java
@@ -0,0 +1,107 @@
+/*
+ * 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.math3.ml.neuralnet.sofm;
+
+import org.apache.commons.math3.ml.neuralnet.sofm.util.ExponentialDecayFunction;
+import org.apache.commons.math3.ml.neuralnet.sofm.util.QuasiSigmoidDecayFunction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Factory for creating instances of {@link NeighbourhoodSizeFunction}.
+ *
+ * @since 3.3
+ */
+public class NeighbourhoodSizeFunctionFactory {
+    /** Class contains only static methods. */
+    private NeighbourhoodSizeFunctionFactory() {}
+
+    /**
+     * Creates an exponential decay {@link NeighbourhoodSizeFunction function}.
+     * It will compute <code>a e<sup>-x / b</sup></code>,
+     * where {@code x} is the (integer) independent variable and
+     * <ul>
+     *  <li><code>a = initValue</code>
+     *  <li><code>b = -numCall / ln(valueAtNumCall / initValue)</code>
+     * </ul>
+     *
+     * @param initValue Initial value, i.e.
+     * {@link NeighbourhoodSizeFunction#value(long) value(0)}.
+     * @param valueAtNumCall Value of the function at {@code numCall}.
+     * @param numCall Argument for which the function returns
+     * {@code valueAtNumCall}.
+     * @return the neighbourhood size function.
+     * @throws org.apache.commons.math3.exception.NotStrictlyPositiveException
+     * if {@code initValue <= 0}.
+     * @throws org.apache.commons.math3.exception.NotStrictlyPositiveException
+     * if {@code valueAtNumCall <= 0}.
+     * @throws org.apache.commons.math3.exception.NumberIsTooLargeException
+     * if {@code valueAtNumCall >= initValue}.
+     * @throws org.apache.commons.math3.exception.NotStrictlyPositiveException
+     * if {@code numCall <= 0}.
+     */
+    public static NeighbourhoodSizeFunction exponentialDecay(final double initValue,
+                                                             final double valueAtNumCall,
+                                                             final long numCall) {
+        return new NeighbourhoodSizeFunction() {
+            /** DecayFunction. */
+            private final ExponentialDecayFunction decay
+                = new ExponentialDecayFunction(initValue, valueAtNumCall, numCall);
+
+            /** {@inheritDoc} */
+            public int value(long n) {
+                return (int) FastMath.rint(decay.value(n));
+            }
+        };
+    }
+
+    /**
+     * Creates an sigmoid-like {@code NeighbourhoodSizeFunction function}.
+     * The function {@code f} will have the following properties:
+     * <ul>
+     *  <li>{@code f(0) = initValue}</li>
+     *  <li>{@code numCall} is the inflexion point</li>
+     *  <li>{@code slope = f'(numCall)}</li>
+     * </ul>
+     *
+     * @param initValue Initial value, i.e.
+     * {@link NeighbourhoodSizeFunction#value(long) value(0)}.
+     * @param slope Value of the function derivative at {@code numCall}.
+     * @param numCall Inflexion point.
+     * @return the neighbourhood size function.
+     * @throws org.apache.commons.math3.exception.NotStrictlyPositiveException
+     * if {@code initValue <= 0}.
+     * @throws org.apache.commons.math3.exception.NumberIsTooLargeException
+     * if {@code slope >= 0}.
+     * @throws org.apache.commons.math3.exception.NotStrictlyPositiveException
+     * if {@code numCall <= 0}.
+     */
+    public static NeighbourhoodSizeFunction quasiSigmoidDecay(final double initValue,
+                                                              final double slope,
+                                                              final long numCall) {
+        return new NeighbourhoodSizeFunction() {
+            /** DecayFunction. */
+            private final QuasiSigmoidDecayFunction decay
+                = new QuasiSigmoidDecayFunction(initValue, slope, numCall);
+
+            /** {@inheritDoc} */
+            public int value(long n) {
+                return (int) FastMath.rint(decay.value(n));
+            }
+        };
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/neuralnet/sofm/package-info.java b/src/main/java/org/apache/commons/math3/ml/neuralnet/sofm/package-info.java
new file mode 100644
index 0000000..60c3c61
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/neuralnet/sofm/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+/**
+ * Self Organizing Feature Map.
+ */
+
+package org.apache.commons.math3.ml.neuralnet.sofm;
diff --git a/src/main/java/org/apache/commons/math3/ml/neuralnet/sofm/util/ExponentialDecayFunction.java b/src/main/java/org/apache/commons/math3/ml/neuralnet/sofm/util/ExponentialDecayFunction.java
new file mode 100644
index 0000000..19e7380
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/neuralnet/sofm/util/ExponentialDecayFunction.java
@@ -0,0 +1,83 @@
+/*
+ * 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.math3.ml.neuralnet.sofm.util;
+
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Exponential decay function: <code>a e<sup>-x / b</sup></code>,
+ * where {@code x} is the (integer) independent variable.
+ * <br/>
+ * Class is immutable.
+ *
+ * @since 3.3
+ */
+public class ExponentialDecayFunction {
+    /** Factor {@code a}. */
+    private final double a;
+    /** Factor {@code 1 / b}. */
+    private final double oneOverB;
+
+    /**
+     * Creates an instance. It will be such that
+     * <ul>
+     *  <li>{@code a = initValue}</li>
+     *  <li>{@code b = -numCall / ln(valueAtNumCall / initValue)}</li>
+     * </ul>
+     *
+     * @param initValue Initial value, i.e. {@link #value(long) value(0)}.
+     * @param valueAtNumCall Value of the function at {@code numCall}.
+     * @param numCall Argument for which the function returns
+     * {@code valueAtNumCall}.
+     * @throws NotStrictlyPositiveException if {@code initValue <= 0}.
+     * @throws NotStrictlyPositiveException if {@code valueAtNumCall <= 0}.
+     * @throws NumberIsTooLargeException if {@code valueAtNumCall >= initValue}.
+     * @throws NotStrictlyPositiveException if {@code numCall <= 0}.
+     */
+    public ExponentialDecayFunction(double initValue,
+                                    double valueAtNumCall,
+                                    long numCall) {
+        if (initValue <= 0) {
+            throw new NotStrictlyPositiveException(initValue);
+        }
+        if (valueAtNumCall <= 0) {
+            throw new NotStrictlyPositiveException(valueAtNumCall);
+        }
+        if (valueAtNumCall >= initValue) {
+            throw new NumberIsTooLargeException(valueAtNumCall, initValue, false);
+        }
+        if (numCall <= 0) {
+            throw new NotStrictlyPositiveException(numCall);
+        }
+
+        a = initValue;
+        oneOverB = -FastMath.log(valueAtNumCall / initValue) / numCall;
+    }
+
+    /**
+     * Computes <code>a e<sup>-numCall / b</sup></code>.
+     *
+     * @param numCall Current step of the training task.
+     * @return the value of the function at {@code numCall}.
+     */
+    public double value(long numCall) {
+        return a * FastMath.exp(-numCall * oneOverB);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/neuralnet/sofm/util/QuasiSigmoidDecayFunction.java b/src/main/java/org/apache/commons/math3/ml/neuralnet/sofm/util/QuasiSigmoidDecayFunction.java
new file mode 100644
index 0000000..3d35c17
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/neuralnet/sofm/util/QuasiSigmoidDecayFunction.java
@@ -0,0 +1,87 @@
+/*
+ * 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.math3.ml.neuralnet.sofm.util;
+
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.analysis.function.Logistic;
+
+/**
+ * Decay function whose shape is similar to a sigmoid.
+ * <br/>
+ * Class is immutable.
+ *
+ * @since 3.3
+ */
+public class QuasiSigmoidDecayFunction {
+    /** Sigmoid. */
+    private final Logistic sigmoid;
+    /** See {@link #value(long)}. */
+    private final double scale;
+
+    /**
+     * Creates an instance.
+     * The function {@code f} will have the following properties:
+     * <ul>
+     *  <li>{@code f(0) = initValue}</li>
+     *  <li>{@code numCall} is the inflexion point</li>
+     *  <li>{@code slope = f'(numCall)}</li>
+     * </ul>
+     *
+     * @param initValue Initial value, i.e. {@link #value(long) value(0)}.
+     * @param slope Value of the function derivative at {@code numCall}.
+     * @param numCall Inflexion point.
+     * @throws NotStrictlyPositiveException if {@code initValue <= 0}.
+     * @throws NumberIsTooLargeException if {@code slope >= 0}.
+     * @throws NotStrictlyPositiveException if {@code numCall <= 0}.
+     */
+    public QuasiSigmoidDecayFunction(double initValue,
+                                     double slope,
+                                     long numCall) {
+        if (initValue <= 0) {
+            throw new NotStrictlyPositiveException(initValue);
+        }
+        if (slope >= 0) {
+            throw new NumberIsTooLargeException(slope, 0, false);
+        }
+        if (numCall <= 1) {
+            throw new NotStrictlyPositiveException(numCall);
+        }
+
+        final double k = initValue;
+        final double m = numCall;
+        final double b = 4 * slope / initValue;
+        final double q = 1;
+        final double a = 0;
+        final double n = 1;
+        sigmoid = new Logistic(k, m, b, q, a, n);
+
+        final double y0 = sigmoid.value(0);
+        scale = k / y0;
+    }
+
+    /**
+     * Computes the value of the learning factor.
+     *
+     * @param numCall Current step of the training task.
+     * @return the value of the function at {@code numCall}.
+     */
+    public double value(long numCall) {
+        return scale * sigmoid.value(numCall);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/neuralnet/sofm/util/package-info.java b/src/main/java/org/apache/commons/math3/ml/neuralnet/sofm/util/package-info.java
new file mode 100644
index 0000000..5078ed2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/neuralnet/sofm/util/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+/**
+ * Miscellaneous utilities.
+ */
+
+package org.apache.commons.math3.ml.neuralnet.sofm.util;
diff --git a/src/main/java/org/apache/commons/math3/ml/neuralnet/twod/NeuronSquareMesh2D.java b/src/main/java/org/apache/commons/math3/ml/neuralnet/twod/NeuronSquareMesh2D.java
new file mode 100644
index 0000000..5277bc5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/neuralnet/twod/NeuronSquareMesh2D.java
@@ -0,0 +1,628 @@
+/*
+ * 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.math3.ml.neuralnet.twod;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.io.Serializable;
+import java.io.ObjectInputStream;
+import org.apache.commons.math3.ml.neuralnet.Neuron;
+import org.apache.commons.math3.ml.neuralnet.Network;
+import org.apache.commons.math3.ml.neuralnet.FeatureInitializer;
+import org.apache.commons.math3.ml.neuralnet.SquareNeighbourhood;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.MathInternalError;
+
+/**
+ * Neural network with the topology of a two-dimensional surface.
+ * Each neuron defines one surface element.
+ * <br/>
+ * This network is primarily intended to represent a
+ * <a href="http://en.wikipedia.org/wiki/Kohonen">
+ *  Self Organizing Feature Map</a>.
+ *
+ * @see org.apache.commons.math3.ml.neuralnet.sofm
+ * @since 3.3
+ */
+public class NeuronSquareMesh2D
+    implements Iterable<Neuron>,
+               Serializable {
+    /** Serial version ID */
+    private static final long serialVersionUID = 1L;
+    /** Underlying network. */
+    private final Network network;
+    /** Number of rows. */
+    private final int numberOfRows;
+    /** Number of columns. */
+    private final int numberOfColumns;
+    /** Wrap. */
+    private final boolean wrapRows;
+    /** Wrap. */
+    private final boolean wrapColumns;
+    /** Neighbourhood type. */
+    private final SquareNeighbourhood neighbourhood;
+    /**
+     * Mapping of the 2D coordinates (in the rectangular mesh) to
+     * the neuron identifiers (attributed by the {@link #network}
+     * instance).
+     */
+    private final long[][] identifiers;
+
+    /**
+     * Horizontal (along row) direction.
+     * @since 3.6
+     */
+    public enum HorizontalDirection {
+        /** Column at the right of the current column. */
+       RIGHT,
+       /** Current column. */
+       CENTER,
+       /** Column at the left of the current column. */
+       LEFT,
+    }
+    /**
+     * Vertical (along column) direction.
+     * @since 3.6
+     */
+    public enum VerticalDirection {
+        /** Row above the current row. */
+        UP,
+        /** Current row. */
+        CENTER,
+        /** Row below the current row. */
+        DOWN,
+    }
+
+    /**
+     * Constructor with restricted access, solely used for deserialization.
+     *
+     * @param wrapRowDim Whether to wrap the first dimension (i.e the first
+     * and last neurons will be linked together).
+     * @param wrapColDim Whether to wrap the second dimension (i.e the first
+     * and last neurons will be linked together).
+     * @param neighbourhoodType Neighbourhood type.
+     * @param featuresList Arrays that will initialize the features sets of
+     * the network's neurons.
+     * @throws NumberIsTooSmallException if {@code numRows < 2} or
+     * {@code numCols < 2}.
+     */
+    NeuronSquareMesh2D(boolean wrapRowDim,
+                       boolean wrapColDim,
+                       SquareNeighbourhood neighbourhoodType,
+                       double[][][] featuresList) {
+        numberOfRows = featuresList.length;
+        numberOfColumns = featuresList[0].length;
+
+        if (numberOfRows < 2) {
+            throw new NumberIsTooSmallException(numberOfRows, 2, true);
+        }
+        if (numberOfColumns < 2) {
+            throw new NumberIsTooSmallException(numberOfColumns, 2, true);
+        }
+
+        wrapRows = wrapRowDim;
+        wrapColumns = wrapColDim;
+        neighbourhood = neighbourhoodType;
+
+        final int fLen = featuresList[0][0].length;
+        network = new Network(0, fLen);
+        identifiers = new long[numberOfRows][numberOfColumns];
+
+        // Add neurons.
+        for (int i = 0; i < numberOfRows; i++) {
+            for (int j = 0; j < numberOfColumns; j++) {
+                identifiers[i][j] = network.createNeuron(featuresList[i][j]);
+            }
+        }
+
+        // Add links.
+        createLinks();
+    }
+
+    /**
+     * Creates a two-dimensional network composed of square cells:
+     * Each neuron not located on the border of the mesh has four
+     * neurons linked to it.
+     * <br/>
+     * The links are bi-directional.
+     * <br/>
+     * The topology of the network can also be a cylinder (if one
+     * of the dimensions is wrapped) or a torus (if both dimensions
+     * are wrapped).
+     *
+     * @param numRows Number of neurons in the first dimension.
+     * @param wrapRowDim Whether to wrap the first dimension (i.e the first
+     * and last neurons will be linked together).
+     * @param numCols Number of neurons in the second dimension.
+     * @param wrapColDim Whether to wrap the second dimension (i.e the first
+     * and last neurons will be linked together).
+     * @param neighbourhoodType Neighbourhood type.
+     * @param featureInit Array of functions that will initialize the
+     * corresponding element of the features set of each newly created
+     * neuron. In particular, the size of this array defines the size of
+     * feature set.
+     * @throws NumberIsTooSmallException if {@code numRows < 2} or
+     * {@code numCols < 2}.
+     */
+    public NeuronSquareMesh2D(int numRows,
+                              boolean wrapRowDim,
+                              int numCols,
+                              boolean wrapColDim,
+                              SquareNeighbourhood neighbourhoodType,
+                              FeatureInitializer[] featureInit) {
+        if (numRows < 2) {
+            throw new NumberIsTooSmallException(numRows, 2, true);
+        }
+        if (numCols < 2) {
+            throw new NumberIsTooSmallException(numCols, 2, true);
+        }
+
+        numberOfRows = numRows;
+        wrapRows = wrapRowDim;
+        numberOfColumns = numCols;
+        wrapColumns = wrapColDim;
+        neighbourhood = neighbourhoodType;
+        identifiers = new long[numberOfRows][numberOfColumns];
+
+        final int fLen = featureInit.length;
+        network = new Network(0, fLen);
+
+        // Add neurons.
+        for (int i = 0; i < numRows; i++) {
+            for (int j = 0; j < numCols; j++) {
+                final double[] features = new double[fLen];
+                for (int fIndex = 0; fIndex < fLen; fIndex++) {
+                    features[fIndex] = featureInit[fIndex].value();
+                }
+                identifiers[i][j] = network.createNeuron(features);
+            }
+        }
+
+        // Add links.
+        createLinks();
+    }
+
+    /**
+     * Constructor with restricted access, solely used for making a
+     * {@link #copy() deep copy}.
+     *
+     * @param wrapRowDim Whether to wrap the first dimension (i.e the first
+     * and last neurons will be linked together).
+     * @param wrapColDim Whether to wrap the second dimension (i.e the first
+     * and last neurons will be linked together).
+     * @param neighbourhoodType Neighbourhood type.
+     * @param net Underlying network.
+     * @param idGrid Neuron identifiers.
+     */
+    private NeuronSquareMesh2D(boolean wrapRowDim,
+                               boolean wrapColDim,
+                               SquareNeighbourhood neighbourhoodType,
+                               Network net,
+                               long[][] idGrid) {
+        numberOfRows = idGrid.length;
+        numberOfColumns = idGrid[0].length;
+        wrapRows = wrapRowDim;
+        wrapColumns = wrapColDim;
+        neighbourhood = neighbourhoodType;
+        network = net;
+        identifiers = idGrid;
+    }
+
+    /**
+     * Performs a deep copy of this instance.
+     * Upon return, the copied and original instances will be independent:
+     * Updating one will not affect the other.
+     *
+     * @return a new instance with the same state as this instance.
+     * @since 3.6
+     */
+    public synchronized NeuronSquareMesh2D copy() {
+        final long[][] idGrid = new long[numberOfRows][numberOfColumns];
+        for (int r = 0; r < numberOfRows; r++) {
+            for (int c = 0; c < numberOfColumns; c++) {
+                idGrid[r][c] = identifiers[r][c];
+            }
+        }
+
+        return new NeuronSquareMesh2D(wrapRows,
+                                      wrapColumns,
+                                      neighbourhood,
+                                      network.copy(),
+                                      idGrid);
+    }
+
+    /**
+     * {@inheritDoc}
+     *  @since 3.6
+     */
+    public Iterator<Neuron> iterator() {
+        return network.iterator();
+    }
+
+    /**
+     * Retrieves the underlying network.
+     * A reference is returned (enabling, for example, the network to be
+     * trained).
+     * This also implies that calling methods that modify the {@link Network}
+     * topology may cause this class to become inconsistent.
+     *
+     * @return the network.
+     */
+    public Network getNetwork() {
+        return network;
+    }
+
+    /**
+     * Gets the number of neurons in each row of this map.
+     *
+     * @return the number of rows.
+     */
+    public int getNumberOfRows() {
+        return numberOfRows;
+    }
+
+    /**
+     * Gets the number of neurons in each column of this map.
+     *
+     * @return the number of column.
+     */
+    public int getNumberOfColumns() {
+        return numberOfColumns;
+    }
+
+    /**
+     * Retrieves the neuron at location {@code (i, j)} in the map.
+     * The neuron at position {@code (0, 0)} is located at the upper-left
+     * corner of the map.
+     *
+     * @param i Row index.
+     * @param j Column index.
+     * @return the neuron at {@code (i, j)}.
+     * @throws OutOfRangeException if {@code i} or {@code j} is
+     * out of range.
+     *
+     * @see #getNeuron(int,int,HorizontalDirection,VerticalDirection)
+     */
+    public Neuron getNeuron(int i,
+                            int j) {
+        if (i < 0 ||
+            i >= numberOfRows) {
+            throw new OutOfRangeException(i, 0, numberOfRows - 1);
+        }
+        if (j < 0 ||
+            j >= numberOfColumns) {
+            throw new OutOfRangeException(j, 0, numberOfColumns - 1);
+        }
+
+        return network.getNeuron(identifiers[i][j]);
+    }
+
+    /**
+     * Retrieves the neuron at {@code (location[0], location[1])} in the map.
+     * The neuron at position {@code (0, 0)} is located at the upper-left
+     * corner of the map.
+     *
+     * @param row Row index.
+     * @param col Column index.
+     * @param alongRowDir Direction along the given {@code row} (i.e. an
+     * offset will be added to the given <em>column</em> index.
+     * @param alongColDir Direction along the given {@code col} (i.e. an
+     * offset will be added to the given <em>row</em> index.
+     * @return the neuron at the requested location, or {@code null} if
+     * the location is not on the map.
+     *
+     * @see #getNeuron(int,int)
+     */
+    public Neuron getNeuron(int row,
+                            int col,
+                            HorizontalDirection alongRowDir,
+                            VerticalDirection alongColDir) {
+        final int[] location = getLocation(row, col, alongRowDir, alongColDir);
+
+        return location == null ? null : getNeuron(location[0], location[1]);
+    }
+
+    /**
+     * Computes the location of a neighbouring neuron.
+     * It will return {@code null} if the resulting location is not part
+     * of the map.
+     * Position {@code (0, 0)} is at the upper-left corner of the map.
+     *
+     * @param row Row index.
+     * @param col Column index.
+     * @param alongRowDir Direction along the given {@code row} (i.e. an
+     * offset will be added to the given <em>column</em> index.
+     * @param alongColDir Direction along the given {@code col} (i.e. an
+     * offset will be added to the given <em>row</em> index.
+     * @return an array of length 2 containing the indices of the requested
+     * location, or {@code null} if that location is not part of the map.
+     *
+     * @see #getNeuron(int,int)
+     */
+    private int[] getLocation(int row,
+                              int col,
+                              HorizontalDirection alongRowDir,
+                              VerticalDirection alongColDir) {
+        final int colOffset;
+        switch (alongRowDir) {
+        case LEFT:
+            colOffset = -1;
+            break;
+        case RIGHT:
+            colOffset = 1;
+            break;
+        case CENTER:
+            colOffset = 0;
+            break;
+        default:
+            // Should never happen.
+            throw new MathInternalError();
+        }
+        int colIndex = col + colOffset;
+        if (wrapColumns) {
+            if (colIndex < 0) {
+                colIndex += numberOfColumns;
+            } else {
+                colIndex %= numberOfColumns;
+            }
+        }
+
+        final int rowOffset;
+        switch (alongColDir) {
+        case UP:
+            rowOffset = -1;
+            break;
+        case DOWN:
+            rowOffset = 1;
+            break;
+        case CENTER:
+            rowOffset = 0;
+            break;
+        default:
+            // Should never happen.
+            throw new MathInternalError();
+        }
+        int rowIndex = row + rowOffset;
+        if (wrapRows) {
+            if (rowIndex < 0) {
+                rowIndex += numberOfRows;
+            } else {
+                rowIndex %= numberOfRows;
+            }
+        }
+
+        if (rowIndex < 0 ||
+            rowIndex >= numberOfRows ||
+            colIndex < 0 ||
+            colIndex >= numberOfColumns) {
+            return null;
+        } else {
+            return new int[] { rowIndex, colIndex };
+        }
+    }
+
+    /**
+     * Creates the neighbour relationships between neurons.
+     */
+    private void createLinks() {
+        // "linkEnd" will store the identifiers of the "neighbours".
+        final List<Long> linkEnd = new ArrayList<Long>();
+        final int iLast = numberOfRows - 1;
+        final int jLast = numberOfColumns - 1;
+        for (int i = 0; i < numberOfRows; i++) {
+            for (int j = 0; j < numberOfColumns; j++) {
+                linkEnd.clear();
+
+                switch (neighbourhood) {
+
+                case MOORE:
+                    // Add links to "diagonal" neighbours.
+                    if (i > 0) {
+                        if (j > 0) {
+                            linkEnd.add(identifiers[i - 1][j - 1]);
+                        }
+                        if (j < jLast) {
+                            linkEnd.add(identifiers[i - 1][j + 1]);
+                        }
+                    }
+                    if (i < iLast) {
+                        if (j > 0) {
+                            linkEnd.add(identifiers[i + 1][j - 1]);
+                        }
+                        if (j < jLast) {
+                            linkEnd.add(identifiers[i + 1][j + 1]);
+                        }
+                    }
+                    if (wrapRows) {
+                        if (i == 0) {
+                            if (j > 0) {
+                                linkEnd.add(identifiers[iLast][j - 1]);
+                            }
+                            if (j < jLast) {
+                                linkEnd.add(identifiers[iLast][j + 1]);
+                            }
+                        } else if (i == iLast) {
+                            if (j > 0) {
+                                linkEnd.add(identifiers[0][j - 1]);
+                            }
+                            if (j < jLast) {
+                                linkEnd.add(identifiers[0][j + 1]);
+                            }
+                        }
+                    }
+                    if (wrapColumns) {
+                        if (j == 0) {
+                            if (i > 0) {
+                                linkEnd.add(identifiers[i - 1][jLast]);
+                            }
+                            if (i < iLast) {
+                                linkEnd.add(identifiers[i + 1][jLast]);
+                            }
+                        } else if (j == jLast) {
+                             if (i > 0) {
+                                 linkEnd.add(identifiers[i - 1][0]);
+                             }
+                             if (i < iLast) {
+                                 linkEnd.add(identifiers[i + 1][0]);
+                             }
+                        }
+                    }
+                    if (wrapRows &&
+                        wrapColumns) {
+                        if (i == 0 &&
+                            j == 0) {
+                            linkEnd.add(identifiers[iLast][jLast]);
+                        } else if (i == 0 &&
+                                   j == jLast) {
+                            linkEnd.add(identifiers[iLast][0]);
+                        } else if (i == iLast &&
+                                   j == 0) {
+                            linkEnd.add(identifiers[0][jLast]);
+                        } else if (i == iLast &&
+                                   j == jLast) {
+                            linkEnd.add(identifiers[0][0]);
+                        }
+                    }
+
+                    // Case falls through since the "Moore" neighbourhood
+                    // also contains the neurons that belong to the "Von
+                    // Neumann" neighbourhood.
+
+                    // fallthru (CheckStyle)
+                case VON_NEUMANN:
+                    // Links to preceding and following "row".
+                    if (i > 0) {
+                        linkEnd.add(identifiers[i - 1][j]);
+                    }
+                    if (i < iLast) {
+                        linkEnd.add(identifiers[i + 1][j]);
+                    }
+                    if (wrapRows) {
+                        if (i == 0) {
+                            linkEnd.add(identifiers[iLast][j]);
+                        } else if (i == iLast) {
+                            linkEnd.add(identifiers[0][j]);
+                        }
+                    }
+
+                    // Links to preceding and following "column".
+                    if (j > 0) {
+                        linkEnd.add(identifiers[i][j - 1]);
+                    }
+                    if (j < jLast) {
+                        linkEnd.add(identifiers[i][j + 1]);
+                    }
+                    if (wrapColumns) {
+                        if (j == 0) {
+                            linkEnd.add(identifiers[i][jLast]);
+                        } else if (j == jLast) {
+                            linkEnd.add(identifiers[i][0]);
+                        }
+                    }
+                    break;
+
+                default:
+                    throw new MathInternalError(); // Cannot happen.
+                }
+
+                final Neuron aNeuron = network.getNeuron(identifiers[i][j]);
+                for (long b : linkEnd) {
+                    final Neuron bNeuron = network.getNeuron(b);
+                    // Link to all neighbours.
+                    // The reverse links will be added as the loop proceeds.
+                    network.addLink(aNeuron, bNeuron);
+                }
+            }
+        }
+    }
+
+    /**
+     * Prevents proxy bypass.
+     *
+     * @param in Input stream.
+     */
+    private void readObject(ObjectInputStream in) {
+        throw new IllegalStateException();
+    }
+
+    /**
+     * Custom serialization.
+     *
+     * @return the proxy instance that will be actually serialized.
+     */
+    private Object writeReplace() {
+        final double[][][] featuresList = new double[numberOfRows][numberOfColumns][];
+        for (int i = 0; i < numberOfRows; i++) {
+            for (int j = 0; j < numberOfColumns; j++) {
+                featuresList[i][j] = getNeuron(i, j).getFeatures();
+            }
+        }
+
+        return new SerializationProxy(wrapRows,
+                                      wrapColumns,
+                                      neighbourhood,
+                                      featuresList);
+    }
+
+    /**
+     * Serialization.
+     */
+    private static class SerializationProxy implements Serializable {
+        /** Serializable. */
+        private static final long serialVersionUID = 20130226L;
+        /** Wrap. */
+        private final boolean wrapRows;
+        /** Wrap. */
+        private final boolean wrapColumns;
+        /** Neighbourhood type. */
+        private final SquareNeighbourhood neighbourhood;
+        /** Neurons' features. */
+        private final double[][][] featuresList;
+
+        /**
+         * @param wrapRows Whether the row dimension is wrapped.
+         * @param wrapColumns Whether the column dimension is wrapped.
+         * @param neighbourhood Neighbourhood type.
+         * @param featuresList List of neurons features.
+         * {@code neuronList}.
+         */
+        SerializationProxy(boolean wrapRows,
+                           boolean wrapColumns,
+                           SquareNeighbourhood neighbourhood,
+                           double[][][] featuresList) {
+            this.wrapRows = wrapRows;
+            this.wrapColumns = wrapColumns;
+            this.neighbourhood = neighbourhood;
+            this.featuresList = featuresList;
+        }
+
+        /**
+         * Custom serialization.
+         *
+         * @return the {@link Neuron} for which this instance is the proxy.
+         */
+        private Object readResolve() {
+            return new NeuronSquareMesh2D(wrapRows,
+                                          wrapColumns,
+                                          neighbourhood,
+                                          featuresList);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/neuralnet/twod/package-info.java b/src/main/java/org/apache/commons/math3/ml/neuralnet/twod/package-info.java
new file mode 100644
index 0000000..41535e8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/neuralnet/twod/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+/**
+ * Two-dimensional neural networks.
+ */
+
+package org.apache.commons.math3.ml.neuralnet.twod;
diff --git a/src/main/java/org/apache/commons/math3/ml/neuralnet/twod/util/HitHistogram.java b/src/main/java/org/apache/commons/math3/ml/neuralnet/twod/util/HitHistogram.java
new file mode 100644
index 0000000..06cee98
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/neuralnet/twod/util/HitHistogram.java
@@ -0,0 +1,83 @@
+/*
+ * 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.math3.ml.neuralnet.twod.util;
+
+import org.apache.commons.math3.ml.neuralnet.MapUtils;
+import org.apache.commons.math3.ml.neuralnet.Neuron;
+import org.apache.commons.math3.ml.neuralnet.twod.NeuronSquareMesh2D;
+import org.apache.commons.math3.ml.distance.DistanceMeasure;
+
+/**
+ * Computes the hit histogram.
+ * Each bin will contain the number of data for which the corresponding
+ * neuron is the best matching unit.
+ * @since 3.6
+ */
+public class HitHistogram implements MapDataVisualization {
+    /** Distance. */
+    private final DistanceMeasure distance;
+    /** Whether to compute relative bin counts. */
+    private final boolean normalizeCount;
+
+    /**
+     * @param normalizeCount Whether to compute relative bin counts.
+     * If {@code true}, the data count in each bin will be divided by the total
+     * number of samples.
+     * @param distance Distance.
+     */
+    public HitHistogram(boolean normalizeCount,
+                        DistanceMeasure distance) {
+        this.normalizeCount = normalizeCount;
+        this.distance = distance;
+    }
+
+    /** {@inheritDoc} */
+    public double[][] computeImage(NeuronSquareMesh2D map,
+                                   Iterable<double[]> data) {
+        final int nR = map.getNumberOfRows();
+        final int nC = map.getNumberOfColumns();
+
+        final LocationFinder finder = new LocationFinder(map);
+
+        // Total number of samples.
+        int numSamples = 0;
+        // Hit bins.
+        final double[][] hit = new double[nR][nC];
+
+        for (double[] sample : data) {
+            final Neuron best = MapUtils.findBest(sample, map, distance);
+
+            final LocationFinder.Location loc = finder.getLocation(best);
+            final int row = loc.getRow();
+            final int col = loc.getColumn();
+            hit[row][col] += 1;
+
+            ++numSamples;
+        }
+
+        if (normalizeCount) {
+            for (int r = 0; r < nR; r++) {
+                for (int c = 0; c < nC; c++) {
+                    hit[r][c] /= numSamples;
+                }
+            }
+        }
+
+        return hit;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/neuralnet/twod/util/LocationFinder.java b/src/main/java/org/apache/commons/math3/ml/neuralnet/twod/util/LocationFinder.java
new file mode 100644
index 0000000..e4ece61
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/neuralnet/twod/util/LocationFinder.java
@@ -0,0 +1,105 @@
+/*
+ * 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.math3.ml.neuralnet.twod.util;
+
+import java.util.Map;
+import java.util.HashMap;
+import org.apache.commons.math3.ml.neuralnet.Neuron;
+import org.apache.commons.math3.ml.neuralnet.twod.NeuronSquareMesh2D;
+import org.apache.commons.math3.exception.MathIllegalStateException;
+
+/**
+ * Helper class to find the grid coordinates of a neuron.
+ * @since 3.6
+ */
+public class LocationFinder {
+    /** Identifier to location mapping. */
+    private final Map<Long, Location> locations = new HashMap<Long, Location>();
+
+    /**
+     * Container holding a (row, column) pair.
+     */
+    public static class Location {
+        /** Row index. */
+        private final int row;
+        /** Column index. */
+        private final int column;
+
+        /**
+         * @param row Row index.
+         * @param column Column index.
+         */
+        public Location(int row,
+                        int column) {
+            this.row = row;
+            this.column = column;
+        }
+
+        /**
+         * @return the row index.
+         */
+        public int getRow() {
+            return row;
+        }
+
+        /**
+         * @return the column index.
+         */
+        public int getColumn() {
+            return column;
+        }
+    }
+
+    /**
+     * Builds a finder to retrieve the locations of neurons that
+     * belong to the given {@code map}.
+     *
+     * @param map Map.
+     *
+     * @throws MathIllegalStateException if the network contains non-unique
+     * identifiers.  This indicates an inconsistent state due to a bug in
+     * the construction code of the underlying
+     * {@link org.apache.commons.math3.ml.neuralnet.Network network}.
+     */
+    public LocationFinder(NeuronSquareMesh2D map) {
+        final int nR = map.getNumberOfRows();
+        final int nC = map.getNumberOfColumns();
+
+        for (int r = 0; r < nR; r++) {
+            for (int c = 0; c < nC; c++) {
+                final Long id = map.getNeuron(r, c).getIdentifier();
+                if (locations.get(id) != null) {
+                    throw new MathIllegalStateException();
+                }
+                locations.put(id, new Location(r, c));
+            }
+        }
+    }
+
+    /**
+     * Retrieves a neuron's grid coordinates.
+     *
+     * @param n Neuron.
+     * @return the (row, column) coordinates of {@code n}, or {@code null}
+     * if no such neuron belongs to the {@link #LocationFinder(NeuronSquareMesh2D)
+     * map used to build this instance}.
+     */
+    public Location getLocation(Neuron n) {
+        return locations.get(n.getIdentifier());
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/neuralnet/twod/util/MapDataVisualization.java b/src/main/java/org/apache/commons/math3/ml/neuralnet/twod/util/MapDataVisualization.java
new file mode 100644
index 0000000..71fab43
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/neuralnet/twod/util/MapDataVisualization.java
@@ -0,0 +1,38 @@
+/*
+ * 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.math3.ml.neuralnet.twod.util;
+
+import org.apache.commons.math3.ml.neuralnet.twod.NeuronSquareMesh2D;
+
+/**
+ * Interface for algorithms that compute some metrics of the projection of
+ * data on a 2D-map.
+ * @since 3.6
+ */
+public interface MapDataVisualization {
+    /**
+     * Creates an image of the {@code data} metrics when represented by the
+     * {@code map}.
+     *
+     * @param map Map.
+     * @param data Data.
+     * @return a 2D-array (in row major order) representing the metrics.
+     */
+    double[][] computeImage(NeuronSquareMesh2D map,
+                            Iterable<double[]> data);
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/neuralnet/twod/util/MapVisualization.java b/src/main/java/org/apache/commons/math3/ml/neuralnet/twod/util/MapVisualization.java
new file mode 100644
index 0000000..9304d76
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/neuralnet/twod/util/MapVisualization.java
@@ -0,0 +1,34 @@
+/*
+ * 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.math3.ml.neuralnet.twod.util;
+
+import org.apache.commons.math3.ml.neuralnet.twod.NeuronSquareMesh2D;
+
+/**
+ * Interface for algorithms that compute some property of a 2D-map.
+ * @since 3.6
+ */
+public interface MapVisualization {
+    /**
+     * Creates an image of the {@code map}.
+     *
+     * @param map Map.
+     * @return a 2D-array (in row major order) representing the property.
+     */
+    double[][] computeImage(NeuronSquareMesh2D map);
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/neuralnet/twod/util/QuantizationError.java b/src/main/java/org/apache/commons/math3/ml/neuralnet/twod/util/QuantizationError.java
new file mode 100644
index 0000000..8ec1da3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/neuralnet/twod/util/QuantizationError.java
@@ -0,0 +1,76 @@
+/*
+ * 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.math3.ml.neuralnet.twod.util;
+
+import org.apache.commons.math3.ml.neuralnet.MapUtils;
+import org.apache.commons.math3.ml.neuralnet.Neuron;
+import org.apache.commons.math3.ml.neuralnet.twod.NeuronSquareMesh2D;
+import org.apache.commons.math3.ml.distance.DistanceMeasure;
+
+/**
+ * Computes the quantization error histogram.
+ * Each bin will contain the average of the distances between samples
+ * mapped to the corresponding unit and the weight vector of that unit.
+ * @since 3.6
+ */
+public class QuantizationError implements MapDataVisualization {
+    /** Distance. */
+    private final DistanceMeasure distance;
+
+    /**
+     * @param distance Distance.
+     */
+    public QuantizationError(DistanceMeasure distance) {
+        this.distance = distance;
+    }
+
+    /** {@inheritDoc} */
+    public double[][] computeImage(NeuronSquareMesh2D map,
+                                   Iterable<double[]> data) {
+        final int nR = map.getNumberOfRows();
+        final int nC = map.getNumberOfColumns();
+
+        final LocationFinder finder = new LocationFinder(map);
+
+        // Hit bins.
+        final int[][] hit = new int[nR][nC];
+        // Error bins.
+        final double[][] error = new double[nR][nC];
+
+        for (double[] sample : data) {
+            final Neuron best = MapUtils.findBest(sample, map, distance);
+
+            final LocationFinder.Location loc = finder.getLocation(best);
+            final int row = loc.getRow();
+            final int col = loc.getColumn();
+            hit[row][col] += 1;
+            error[row][col] += distance.compute(sample, best.getFeatures());
+        }
+
+        for (int r = 0; r < nR; r++) {
+            for (int c = 0; c < nC; c++) {
+                final int count = hit[r][c];
+                if (count != 0) {
+                    error[r][c] /= count;
+                }
+            }
+        }
+
+        return error;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/neuralnet/twod/util/SmoothedDataHistogram.java b/src/main/java/org/apache/commons/math3/ml/neuralnet/twod/util/SmoothedDataHistogram.java
new file mode 100644
index 0000000..b8e552c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/neuralnet/twod/util/SmoothedDataHistogram.java
@@ -0,0 +1,97 @@
+/*
+ * 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.math3.ml.neuralnet.twod.util;
+
+import org.apache.commons.math3.ml.neuralnet.MapUtils;
+import org.apache.commons.math3.ml.neuralnet.Neuron;
+import org.apache.commons.math3.ml.neuralnet.twod.NeuronSquareMesh2D;
+import org.apache.commons.math3.ml.distance.DistanceMeasure;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+
+/**
+ * Visualization of high-dimensional data projection on a 2D-map.
+ * The method is described in
+ * <quote>
+ *  <em>Using Smoothed Data Histograms for Cluster Visualization in Self-Organizing Maps</em>
+ *  <br>
+ *  by Elias Pampalk, Andreas Rauber and Dieter Merkl.
+ * </quote>
+ * @since 3.6
+ */
+public class SmoothedDataHistogram implements MapDataVisualization {
+    /** Smoothing parameter. */
+    private final int smoothingBins;
+    /** Distance. */
+    private final DistanceMeasure distance;
+    /** Normalization factor. */
+    private final double membershipNormalization;
+
+    /**
+     * @param smoothingBins Number of bins.
+     * @param distance Distance.
+     */
+    public SmoothedDataHistogram(int smoothingBins,
+                                 DistanceMeasure distance) {
+        this.smoothingBins = smoothingBins;
+        this.distance = distance;
+
+        double sum = 0;
+        for (int i = 0; i < smoothingBins; i++) {
+            sum += smoothingBins - i;
+        }
+
+        this.membershipNormalization = 1d / sum;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws NumberIsTooSmallException if the size of the {@code map}
+     * is smaller than the number of {@link #SmoothedDataHistogram(int,DistanceMeasure)
+     * smoothing bins}.
+     */
+    public double[][] computeImage(NeuronSquareMesh2D map,
+                                   Iterable<double[]> data) {
+        final int nR = map.getNumberOfRows();
+        final int nC = map.getNumberOfColumns();
+
+        final int mapSize = nR * nC;
+        if (mapSize < smoothingBins) {
+            throw new NumberIsTooSmallException(mapSize, smoothingBins, true);
+        }
+
+        final LocationFinder finder = new LocationFinder(map);
+
+        // Histogram bins.
+        final double[][] histo = new double[nR][nC];
+
+        for (double[] sample : data) {
+            final Neuron[] sorted = MapUtils.sort(sample,
+                                                  map.getNetwork(),
+                                                  distance);
+            for (int i = 0; i < smoothingBins; i++) {
+                final LocationFinder.Location loc = finder.getLocation(sorted[i]);
+                final int row = loc.getRow();
+                final int col = loc.getColumn();
+                histo[row][col] += (smoothingBins - i) * membershipNormalization;
+            }
+        }
+
+        return histo;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/neuralnet/twod/util/TopographicErrorHistogram.java b/src/main/java/org/apache/commons/math3/ml/neuralnet/twod/util/TopographicErrorHistogram.java
new file mode 100644
index 0000000..b831de8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/neuralnet/twod/util/TopographicErrorHistogram.java
@@ -0,0 +1,91 @@
+/*
+ * 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.math3.ml.neuralnet.twod.util;
+
+import org.apache.commons.math3.ml.neuralnet.MapUtils;
+import org.apache.commons.math3.ml.neuralnet.Neuron;
+import org.apache.commons.math3.ml.neuralnet.Network;
+import org.apache.commons.math3.ml.neuralnet.twod.NeuronSquareMesh2D;
+import org.apache.commons.math3.ml.distance.DistanceMeasure;
+import org.apache.commons.math3.util.Pair;
+
+/**
+ * Computes the topographic error histogram.
+ * Each bin will contain the number of data for which the first and
+ * second best matching units are not adjacent in the map.
+ * @since 3.6
+ */
+public class TopographicErrorHistogram implements MapDataVisualization {
+    /** Distance. */
+    private final DistanceMeasure distance;
+    /** Whether to compute relative bin counts. */
+    private final boolean relativeCount;
+
+    /**
+     * @param relativeCount Whether to compute relative bin counts.
+     * If {@code true}, the data count in each bin will be divided by the total
+     * number of samples mapped to the neuron represented by that bin.
+     * @param distance Distance.
+     */
+    public TopographicErrorHistogram(boolean relativeCount,
+                                     DistanceMeasure distance) {
+        this.relativeCount = relativeCount;
+        this.distance = distance;
+    }
+
+    /** {@inheritDoc} */
+    public double[][] computeImage(NeuronSquareMesh2D map,
+                                   Iterable<double[]> data) {
+        final int nR = map.getNumberOfRows();
+        final int nC = map.getNumberOfColumns();
+
+        final Network net = map.getNetwork();
+        final LocationFinder finder = new LocationFinder(map);
+
+        // Hit bins.
+        final int[][] hit = new int[nR][nC];
+        // Error bins.
+        final double[][] error = new double[nR][nC];
+
+        for (double[] sample : data) {
+            final Pair<Neuron, Neuron> p = MapUtils.findBestAndSecondBest(sample, map, distance);
+            final Neuron best = p.getFirst();
+
+            final LocationFinder.Location loc = finder.getLocation(best);
+            final int row = loc.getRow();
+            final int col = loc.getColumn();
+            hit[row][col] += 1;
+
+            if (!net.getNeighbours(best).contains(p.getSecond())) {
+                // Increment count if first and second best matching units
+                // are not neighbours.
+                error[row][col] += 1;
+            }
+        }
+
+        if (relativeCount) {
+            for (int r = 0; r < nR; r++) {
+                for (int c = 0; c < nC; c++) {
+                    error[r][c] /= hit[r][c];
+                }
+            }
+        }
+
+        return error;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/neuralnet/twod/util/UnifiedDistanceMatrix.java b/src/main/java/org/apache/commons/math3/ml/neuralnet/twod/util/UnifiedDistanceMatrix.java
new file mode 100644
index 0000000..aee982a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/neuralnet/twod/util/UnifiedDistanceMatrix.java
@@ -0,0 +1,209 @@
+/*
+ * 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.math3.ml.neuralnet.twod.util;
+
+import java.util.Collection;
+import org.apache.commons.math3.ml.neuralnet.Neuron;
+import org.apache.commons.math3.ml.neuralnet.Network;
+import org.apache.commons.math3.ml.neuralnet.twod.NeuronSquareMesh2D;
+import org.apache.commons.math3.ml.distance.DistanceMeasure;
+
+/**
+ * <a href="http://en.wikipedia.org/wiki/U-Matrix">U-Matrix</a>
+ * visualization of high-dimensional data projection.
+ * @since 3.6
+ */
+public class UnifiedDistanceMatrix implements MapVisualization {
+    /** Whether to show distance between each pair of neighbouring units. */
+    private final boolean individualDistances;
+    /** Distance. */
+    private final DistanceMeasure distance;
+
+    /**
+     * Simple constructor.
+     *
+     * @param individualDistances If {@code true}, the 8 individual
+     * inter-units distances will be {@link #computeImage(NeuronSquareMesh2D)
+     * computed}.  They will be stored in additional pixels around each of
+     * the original units of the 2D-map.  The additional pixels that lie
+     * along a "diagonal" are shared by <em>two</em> pairs of units: their
+     * value will be set to the average distance between the units belonging
+     * to each of the pairs.  The value zero will be stored in the pixel
+     * corresponding to the location of a unit of the 2D-map.
+     * <br>
+     * If {@code false}, only the average distance between a unit and all its
+     * neighbours will be computed (and stored in the pixel corresponding to
+     * that unit of the 2D-map).  In that case, the number of neighbours taken
+     * into account depends on the network's
+     * {@link org.apache.commons.math3.ml.neuralnet.SquareNeighbourhood
+     * neighbourhood type}.
+     * @param distance Distance.
+     */
+    public UnifiedDistanceMatrix(boolean individualDistances,
+                                 DistanceMeasure distance) {
+        this.individualDistances = individualDistances;
+        this.distance = distance;
+    }
+
+    /** {@inheritDoc} */
+    public double[][] computeImage(NeuronSquareMesh2D map) {
+        if (individualDistances) {
+            return individualDistances(map);
+        } else {
+            return averageDistances(map);
+        }
+    }
+
+    /**
+     * Computes the distances between a unit of the map and its
+     * neighbours.
+     * The image will contain more pixels than the number of neurons
+     * in the given {@code map} because each neuron has 8 neighbours.
+     * The value zero will be stored in the pixels corresponding to
+     * the location of a map unit.
+     *
+     * @param map Map.
+     * @return an image representing the individual distances.
+     */
+    private double[][] individualDistances(NeuronSquareMesh2D map) {
+        final int numRows = map.getNumberOfRows();
+        final int numCols = map.getNumberOfColumns();
+
+        final double[][] uMatrix = new double[numRows * 2 + 1][numCols * 2 + 1];
+
+        // 1.
+        // Fill right and bottom slots of each unit's location with the
+        // distance between the current unit and each of the two neighbours,
+        // respectively.
+        for (int i = 0; i < numRows; i++) {
+            // Current unit's row index in result image.
+            final int iR = 2 * i + 1;
+
+            for (int j = 0; j < numCols; j++) {
+                // Current unit's column index in result image.
+                final int jR = 2 * j + 1;
+
+                final double[] current = map.getNeuron(i, j).getFeatures();
+                Neuron neighbour;
+
+                // Right neighbour.
+                neighbour = map.getNeuron(i, j,
+                                          NeuronSquareMesh2D.HorizontalDirection.RIGHT,
+                                          NeuronSquareMesh2D.VerticalDirection.CENTER);
+                if (neighbour != null) {
+                    uMatrix[iR][jR + 1] = distance.compute(current,
+                                                           neighbour.getFeatures());
+                }
+
+                // Bottom-center neighbour.
+                neighbour = map.getNeuron(i, j,
+                                          NeuronSquareMesh2D.HorizontalDirection.CENTER,
+                                          NeuronSquareMesh2D.VerticalDirection.DOWN);
+                if (neighbour != null) {
+                    uMatrix[iR + 1][jR] = distance.compute(current,
+                                                           neighbour.getFeatures());
+                }
+            }
+        }
+
+        // 2.
+        // Fill the bottom-rigth slot of each unit's location with the average
+        // of the distances between
+        //  * the current unit and its bottom-right neighbour, and
+        //  * the bottom-center neighbour and the right neighbour.
+        for (int i = 0; i < numRows; i++) {
+            // Current unit's row index in result image.
+            final int iR = 2 * i + 1;
+
+            for (int j = 0; j < numCols; j++) {
+                // Current unit's column index in result image.
+                final int jR = 2 * j + 1;
+
+                final Neuron current = map.getNeuron(i, j);
+                final Neuron right = map.getNeuron(i, j,
+                                                   NeuronSquareMesh2D.HorizontalDirection.RIGHT,
+                                                   NeuronSquareMesh2D.VerticalDirection.CENTER);
+                final Neuron bottom = map.getNeuron(i, j,
+                                                    NeuronSquareMesh2D.HorizontalDirection.CENTER,
+                                                    NeuronSquareMesh2D.VerticalDirection.DOWN);
+                final Neuron bottomRight = map.getNeuron(i, j,
+                                                         NeuronSquareMesh2D.HorizontalDirection.RIGHT,
+                                                         NeuronSquareMesh2D.VerticalDirection.DOWN);
+
+                final double current2BottomRight = bottomRight == null ?
+                    0 :
+                    distance.compute(current.getFeatures(),
+                                     bottomRight.getFeatures());
+                final double right2Bottom = (right == null ||
+                                             bottom == null) ?
+                    0 :
+                    distance.compute(right.getFeatures(),
+                                     bottom.getFeatures());
+
+                // Bottom-right slot.
+                uMatrix[iR + 1][jR + 1] = 0.5 * (current2BottomRight + right2Bottom);
+            }
+        }
+
+        // 3. Copy last row into first row.
+        final int lastRow = uMatrix.length - 1;
+        uMatrix[0] = uMatrix[lastRow];
+
+        // 4.
+        // Copy last column into first column.
+        final int lastCol = uMatrix[0].length - 1;
+        for (int r = 0; r < lastRow; r++) {
+            uMatrix[r][0] = uMatrix[r][lastCol];
+        }
+
+        return uMatrix;
+    }
+
+    /**
+     * Computes the distances between a unit of the map and its neighbours.
+     *
+     * @param map Map.
+     * @return an image representing the average distances.
+     */
+    private double[][] averageDistances(NeuronSquareMesh2D map) {
+        final int numRows = map.getNumberOfRows();
+        final int numCols = map.getNumberOfColumns();
+        final double[][] uMatrix = new double[numRows][numCols];
+
+        final Network net = map.getNetwork();
+
+        for (int i = 0; i < numRows; i++) {
+            for (int j = 0; j < numCols; j++) {
+                final Neuron neuron = map.getNeuron(i, j);
+                final Collection<Neuron> neighbours = net.getNeighbours(neuron);
+                final double[] features = neuron.getFeatures();
+
+                double d = 0;
+                int count = 0;
+                for (Neuron n : neighbours) {
+                    ++count;
+                    d += distance.compute(features, n.getFeatures());
+                }
+
+                uMatrix[i][j] = d / count;
+            }
+        }
+
+        return uMatrix;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ml/neuralnet/twod/util/package-info.java b/src/main/java/org/apache/commons/math3/ml/neuralnet/twod/util/package-info.java
new file mode 100644
index 0000000..cd4aab0
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/neuralnet/twod/util/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+/**
+ * Utilities to visualize two-dimensional neural networks.
+ */
+
+package org.apache.commons.math3.ml.neuralnet.twod.util;
diff --git a/src/main/java/org/apache/commons/math3/ml/package-info.java b/src/main/java/org/apache/commons/math3/ml/package-info.java
new file mode 100644
index 0000000..394aad2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ml/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+/** Base package for machine learning algorithms. */
+package org.apache.commons.math3.ml;
diff --git a/src/main/java/org/apache/commons/math3/ode/AbstractFieldIntegrator.java b/src/main/java/org/apache/commons/math3/ode/AbstractFieldIntegrator.java
new file mode 100644
index 0000000..efcd08a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/AbstractFieldIntegrator.java
@@ -0,0 +1,510 @@
+/*
+ * 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.math3.ode;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.analysis.solvers.BracketedRealFieldUnivariateSolver;
+import org.apache.commons.math3.analysis.solvers.FieldBracketingNthOrderBrentSolver;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NoBracketingException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.ode.events.FieldEventHandler;
+import org.apache.commons.math3.ode.events.FieldEventState;
+import org.apache.commons.math3.ode.sampling.AbstractFieldStepInterpolator;
+import org.apache.commons.math3.ode.sampling.FieldStepHandler;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.IntegerSequence;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * Base class managing common boilerplate for all integrators.
+ *
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+public abstract class AbstractFieldIntegrator<T extends RealFieldElement<T>>
+        implements FirstOrderFieldIntegrator<T> {
+
+    /** Default relative accuracy. */
+    private static final double DEFAULT_RELATIVE_ACCURACY = 1e-14;
+
+    /** Default function value accuracy. */
+    private static final double DEFAULT_FUNCTION_VALUE_ACCURACY = 1e-15;
+
+    /** Step handler. */
+    private Collection<FieldStepHandler<T>> stepHandlers;
+
+    /** Current step start. */
+    private FieldODEStateAndDerivative<T> stepStart;
+
+    /** Current stepsize. */
+    private T stepSize;
+
+    /** Indicator for last step. */
+    private boolean isLastStep;
+
+    /** Indicator that a state or derivative reset was triggered by some event. */
+    private boolean resetOccurred;
+
+    /** Field to which the time and state vector elements belong. */
+    private final Field<T> field;
+
+    /** Events states. */
+    private Collection<FieldEventState<T>> eventsStates;
+
+    /** Initialization indicator of events states. */
+    private boolean statesInitialized;
+
+    /** Name of the method. */
+    private final String name;
+
+    /** Counter for number of evaluations. */
+    private IntegerSequence.Incrementor evaluations;
+
+    /** Differential equations to integrate. */
+    private transient FieldExpandableODE<T> equations;
+
+    /**
+     * Build an instance.
+     *
+     * @param field field to which the time and state vector elements belong
+     * @param name name of the method
+     */
+    protected AbstractFieldIntegrator(final Field<T> field, final String name) {
+        this.field = field;
+        this.name = name;
+        stepHandlers = new ArrayList<FieldStepHandler<T>>();
+        stepStart = null;
+        stepSize = null;
+        eventsStates = new ArrayList<FieldEventState<T>>();
+        statesInitialized = false;
+        evaluations = IntegerSequence.Incrementor.create().withMaximalCount(Integer.MAX_VALUE);
+    }
+
+    /**
+     * Get the field to which state vector elements belong.
+     *
+     * @return field to which state vector elements belong
+     */
+    public Field<T> getField() {
+        return field;
+    }
+
+    /** {@inheritDoc} */
+    public String getName() {
+        return name;
+    }
+
+    /** {@inheritDoc} */
+    public void addStepHandler(final FieldStepHandler<T> handler) {
+        stepHandlers.add(handler);
+    }
+
+    /** {@inheritDoc} */
+    public Collection<FieldStepHandler<T>> getStepHandlers() {
+        return Collections.unmodifiableCollection(stepHandlers);
+    }
+
+    /** {@inheritDoc} */
+    public void clearStepHandlers() {
+        stepHandlers.clear();
+    }
+
+    /** {@inheritDoc} */
+    public void addEventHandler(
+            final FieldEventHandler<T> handler,
+            final double maxCheckInterval,
+            final double convergence,
+            final int maxIterationCount) {
+        addEventHandler(
+                handler,
+                maxCheckInterval,
+                convergence,
+                maxIterationCount,
+                new FieldBracketingNthOrderBrentSolver<T>(
+                        field.getZero().add(DEFAULT_RELATIVE_ACCURACY),
+                        field.getZero().add(convergence),
+                        field.getZero().add(DEFAULT_FUNCTION_VALUE_ACCURACY),
+                        5));
+    }
+
+    /** {@inheritDoc} */
+    public void addEventHandler(
+            final FieldEventHandler<T> handler,
+            final double maxCheckInterval,
+            final double convergence,
+            final int maxIterationCount,
+            final BracketedRealFieldUnivariateSolver<T> solver) {
+        eventsStates.add(
+                new FieldEventState<T>(
+                        handler,
+                        maxCheckInterval,
+                        field.getZero().add(convergence),
+                        maxIterationCount,
+                        solver));
+    }
+
+    /** {@inheritDoc} */
+    public Collection<FieldEventHandler<T>> getEventHandlers() {
+        final List<FieldEventHandler<T>> list =
+                new ArrayList<FieldEventHandler<T>>(eventsStates.size());
+        for (FieldEventState<T> state : eventsStates) {
+            list.add(state.getEventHandler());
+        }
+        return Collections.unmodifiableCollection(list);
+    }
+
+    /** {@inheritDoc} */
+    public void clearEventHandlers() {
+        eventsStates.clear();
+    }
+
+    /** {@inheritDoc} */
+    public FieldODEStateAndDerivative<T> getCurrentStepStart() {
+        return stepStart;
+    }
+
+    /** {@inheritDoc} */
+    public T getCurrentSignedStepsize() {
+        return stepSize;
+    }
+
+    /** {@inheritDoc} */
+    public void setMaxEvaluations(int maxEvaluations) {
+        evaluations =
+                evaluations.withMaximalCount(
+                        (maxEvaluations < 0) ? Integer.MAX_VALUE : maxEvaluations);
+    }
+
+    /** {@inheritDoc} */
+    public int getMaxEvaluations() {
+        return evaluations.getMaximalCount();
+    }
+
+    /** {@inheritDoc} */
+    public int getEvaluations() {
+        return evaluations.getCount();
+    }
+
+    /**
+     * Prepare the start of an integration.
+     *
+     * @param eqn equations to integrate
+     * @param t0 start value of the independent <i>time</i> variable
+     * @param y0 array containing the start value of the state vector
+     * @param t target time for the integration
+     * @return initial state with derivatives added
+     */
+    protected FieldODEStateAndDerivative<T> initIntegration(
+            final FieldExpandableODE<T> eqn, final T t0, final T[] y0, final T t) {
+
+        this.equations = eqn;
+        evaluations = evaluations.withStart(0);
+
+        // initialize ODE
+        eqn.init(t0, y0, t);
+
+        // set up derivatives of initial state
+        final T[] y0Dot = computeDerivatives(t0, y0);
+        final FieldODEStateAndDerivative<T> state0 =
+                new FieldODEStateAndDerivative<T>(t0, y0, y0Dot);
+
+        // initialize event handlers
+        for (final FieldEventState<T> state : eventsStates) {
+            state.getEventHandler().init(state0, t);
+        }
+
+        // initialize step handlers
+        for (FieldStepHandler<T> handler : stepHandlers) {
+            handler.init(state0, t);
+        }
+
+        setStateInitialized(false);
+
+        return state0;
+    }
+
+    /**
+     * Get the differential equations to integrate.
+     *
+     * @return differential equations to integrate
+     */
+    protected FieldExpandableODE<T> getEquations() {
+        return equations;
+    }
+
+    /**
+     * Get the evaluations counter.
+     *
+     * @return evaluations counter
+     */
+    protected IntegerSequence.Incrementor getEvaluationsCounter() {
+        return evaluations;
+    }
+
+    /**
+     * Compute the derivatives and check the number of evaluations.
+     *
+     * @param t current value of the independent <I>time</I> variable
+     * @param y array containing the current value of the state vector
+     * @return state completed with derivatives
+     * @exception DimensionMismatchException if arrays dimensions do not match equations settings
+     * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+     * @exception NullPointerException if the ODE equations have not been set (i.e. if this method
+     *     is called outside of a call to {@link #integrate(FieldExpandableODE, FieldODEState,
+     *     RealFieldElement) integrate}
+     */
+    public T[] computeDerivatives(final T t, final T[] y)
+            throws DimensionMismatchException, MaxCountExceededException, NullPointerException {
+        evaluations.increment();
+        return equations.computeDerivatives(t, y);
+    }
+
+    /**
+     * Set the stateInitialized flag.
+     *
+     * <p>This method must be called by integrators with the value {@code false} before they start
+     * integration, so a proper lazy initialization is done automatically on the first step.
+     *
+     * @param stateInitialized new value for the flag
+     */
+    protected void setStateInitialized(final boolean stateInitialized) {
+        this.statesInitialized = stateInitialized;
+    }
+
+    /**
+     * Accept a step, triggering events and step handlers.
+     *
+     * @param interpolator step interpolator
+     * @param tEnd final integration time
+     * @return state at end of step
+     * @exception MaxCountExceededException if the interpolator throws one because the number of
+     *     functions evaluations is exceeded
+     * @exception NoBracketingException if the location of an event cannot be bracketed
+     * @exception DimensionMismatchException if arrays dimensions do not match equations settings
+     */
+    protected FieldODEStateAndDerivative<T> acceptStep(
+            final AbstractFieldStepInterpolator<T> interpolator, final T tEnd)
+            throws MaxCountExceededException, DimensionMismatchException, NoBracketingException {
+
+        FieldODEStateAndDerivative<T> previousState = interpolator.getGlobalPreviousState();
+        final FieldODEStateAndDerivative<T> currentState = interpolator.getGlobalCurrentState();
+
+        // initialize the events states if needed
+        if (!statesInitialized) {
+            for (FieldEventState<T> state : eventsStates) {
+                state.reinitializeBegin(interpolator);
+            }
+            statesInitialized = true;
+        }
+
+        // search for next events that may occur during the step
+        final int orderingSign = interpolator.isForward() ? +1 : -1;
+        SortedSet<FieldEventState<T>> occurringEvents =
+                new TreeSet<FieldEventState<T>>(
+                        new Comparator<FieldEventState<T>>() {
+
+                            /** {@inheritDoc} */
+                            public int compare(FieldEventState<T> es0, FieldEventState<T> es1) {
+                                return orderingSign
+                                        * Double.compare(
+                                                es0.getEventTime().getReal(),
+                                                es1.getEventTime().getReal());
+                            }
+                        });
+
+        for (final FieldEventState<T> state : eventsStates) {
+            if (state.evaluateStep(interpolator)) {
+                // the event occurs during the current step
+                occurringEvents.add(state);
+            }
+        }
+
+        AbstractFieldStepInterpolator<T> restricted = interpolator;
+        while (!occurringEvents.isEmpty()) {
+
+            // handle the chronologically first event
+            final Iterator<FieldEventState<T>> iterator = occurringEvents.iterator();
+            final FieldEventState<T> currentEvent = iterator.next();
+            iterator.remove();
+
+            // get state at event time
+            final FieldODEStateAndDerivative<T> eventState =
+                    restricted.getInterpolatedState(currentEvent.getEventTime());
+
+            // restrict the interpolator to the first part of the step, up to the event
+            restricted = restricted.restrictStep(previousState, eventState);
+
+            // advance all event states to current time
+            for (final FieldEventState<T> state : eventsStates) {
+                state.stepAccepted(eventState);
+                isLastStep = isLastStep || state.stop();
+            }
+
+            // handle the first part of the step, up to the event
+            for (final FieldStepHandler<T> handler : stepHandlers) {
+                handler.handleStep(restricted, isLastStep);
+            }
+
+            if (isLastStep) {
+                // the event asked to stop integration
+                return eventState;
+            }
+
+            FieldODEState<T> newState = null;
+            resetOccurred = false;
+            for (final FieldEventState<T> state : eventsStates) {
+                newState = state.reset(eventState);
+                if (newState != null) {
+                    // some event handler has triggered changes that
+                    // invalidate the derivatives, we need to recompute them
+                    final T[] y = equations.getMapper().mapState(newState);
+                    final T[] yDot = computeDerivatives(newState.getTime(), y);
+                    resetOccurred = true;
+                    return equations.getMapper().mapStateAndDerivative(newState.getTime(), y, yDot);
+                }
+            }
+
+            // prepare handling of the remaining part of the step
+            previousState = eventState;
+            restricted = restricted.restrictStep(eventState, currentState);
+
+            // check if the same event occurs again in the remaining part of the step
+            if (currentEvent.evaluateStep(restricted)) {
+                // the event occurs during the current step
+                occurringEvents.add(currentEvent);
+            }
+        }
+
+        // last part of the step, after the last event
+        for (final FieldEventState<T> state : eventsStates) {
+            state.stepAccepted(currentState);
+            isLastStep = isLastStep || state.stop();
+        }
+        isLastStep =
+                isLastStep
+                        || currentState.getTime().subtract(tEnd).abs().getReal()
+                                <= FastMath.ulp(tEnd.getReal());
+
+        // handle the remaining part of the step, after all events if any
+        for (FieldStepHandler<T> handler : stepHandlers) {
+            handler.handleStep(restricted, isLastStep);
+        }
+
+        return currentState;
+    }
+
+    /**
+     * Check the integration span.
+     *
+     * @param eqn set of differential equations
+     * @param t target time for the integration
+     * @exception NumberIsTooSmallException if integration span is too small
+     * @exception DimensionMismatchException if adaptive step size integrators tolerance arrays
+     *     dimensions are not compatible with equations settings
+     */
+    protected void sanityChecks(final FieldODEState<T> eqn, final T t)
+            throws NumberIsTooSmallException, DimensionMismatchException {
+
+        final double threshold =
+                1000
+                        * FastMath.ulp(
+                                FastMath.max(
+                                        FastMath.abs(eqn.getTime().getReal()),
+                                        FastMath.abs(t.getReal())));
+        final double dt = eqn.getTime().subtract(t).abs().getReal();
+        if (dt <= threshold) {
+            throw new NumberIsTooSmallException(
+                    LocalizedFormats.TOO_SMALL_INTEGRATION_INTERVAL, dt, threshold, false);
+        }
+    }
+
+    /**
+     * Check if a reset occurred while last step was accepted.
+     *
+     * @return true if a reset occurred while last step was accepted
+     */
+    protected boolean resetOccurred() {
+        return resetOccurred;
+    }
+
+    /**
+     * Set the current step size.
+     *
+     * @param stepSize step size to set
+     */
+    protected void setStepSize(final T stepSize) {
+        this.stepSize = stepSize;
+    }
+
+    /**
+     * Get the current step size.
+     *
+     * @return current step size
+     */
+    protected T getStepSize() {
+        return stepSize;
+    }
+
+    /**
+     * Set current step start.
+     *
+     * @param stepStart step start
+     */
+    protected void setStepStart(final FieldODEStateAndDerivative<T> stepStart) {
+        this.stepStart = stepStart;
+    }
+
+    /**
+     * Getcurrent step start.
+     *
+     * @return current step start
+     */
+    protected FieldODEStateAndDerivative<T> getStepStart() {
+        return stepStart;
+    }
+
+    /**
+     * Set the last state flag.
+     *
+     * @param isLastStep if true, this step is the last one
+     */
+    protected void setIsLastStep(final boolean isLastStep) {
+        this.isLastStep = isLastStep;
+    }
+
+    /**
+     * Check if this step is the last one.
+     *
+     * @return true if this step is the last one
+     */
+    protected boolean isLastStep() {
+        return isLastStep;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/AbstractIntegrator.java b/src/main/java/org/apache/commons/math3/ode/AbstractIntegrator.java
new file mode 100644
index 0000000..f3fed1f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/AbstractIntegrator.java
@@ -0,0 +1,509 @@
+/*
+ * 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.math3.ode;
+
+import org.apache.commons.math3.analysis.solvers.BracketingNthOrderBrentSolver;
+import org.apache.commons.math3.analysis.solvers.UnivariateSolver;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NoBracketingException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.ode.events.EventHandler;
+import org.apache.commons.math3.ode.events.EventState;
+import org.apache.commons.math3.ode.sampling.AbstractStepInterpolator;
+import org.apache.commons.math3.ode.sampling.StepHandler;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.IntegerSequence;
+import org.apache.commons.math3.util.Precision;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * Base class managing common boilerplate for all integrators.
+ *
+ * @since 2.0
+ */
+public abstract class AbstractIntegrator implements FirstOrderIntegrator {
+
+    /** Step handler. */
+    protected Collection<StepHandler> stepHandlers;
+
+    /** Current step start time. */
+    protected double stepStart;
+
+    /** Current stepsize. */
+    protected double stepSize;
+
+    /** Indicator for last step. */
+    protected boolean isLastStep;
+
+    /** Indicator that a state or derivative reset was triggered by some event. */
+    protected boolean resetOccurred;
+
+    /** Events states. */
+    private Collection<EventState> eventsStates;
+
+    /** Initialization indicator of events states. */
+    private boolean statesInitialized;
+
+    /** Name of the method. */
+    private final String name;
+
+    /** Counter for number of evaluations. */
+    private IntegerSequence.Incrementor evaluations;
+
+    /** Differential equations to integrate. */
+    private transient ExpandableStatefulODE expandable;
+
+    /**
+     * Build an instance.
+     *
+     * @param name name of the method
+     */
+    public AbstractIntegrator(final String name) {
+        this.name = name;
+        stepHandlers = new ArrayList<StepHandler>();
+        stepStart = Double.NaN;
+        stepSize = Double.NaN;
+        eventsStates = new ArrayList<EventState>();
+        statesInitialized = false;
+        evaluations = IntegerSequence.Incrementor.create().withMaximalCount(Integer.MAX_VALUE);
+    }
+
+    /** Build an instance with a null name. */
+    protected AbstractIntegrator() {
+        this(null);
+    }
+
+    /** {@inheritDoc} */
+    public String getName() {
+        return name;
+    }
+
+    /** {@inheritDoc} */
+    public void addStepHandler(final StepHandler handler) {
+        stepHandlers.add(handler);
+    }
+
+    /** {@inheritDoc} */
+    public Collection<StepHandler> getStepHandlers() {
+        return Collections.unmodifiableCollection(stepHandlers);
+    }
+
+    /** {@inheritDoc} */
+    public void clearStepHandlers() {
+        stepHandlers.clear();
+    }
+
+    /** {@inheritDoc} */
+    public void addEventHandler(
+            final EventHandler handler,
+            final double maxCheckInterval,
+            final double convergence,
+            final int maxIterationCount) {
+        addEventHandler(
+                handler,
+                maxCheckInterval,
+                convergence,
+                maxIterationCount,
+                new BracketingNthOrderBrentSolver(convergence, 5));
+    }
+
+    /** {@inheritDoc} */
+    public void addEventHandler(
+            final EventHandler handler,
+            final double maxCheckInterval,
+            final double convergence,
+            final int maxIterationCount,
+            final UnivariateSolver solver) {
+        eventsStates.add(
+                new EventState(handler, maxCheckInterval, convergence, maxIterationCount, solver));
+    }
+
+    /** {@inheritDoc} */
+    public Collection<EventHandler> getEventHandlers() {
+        final List<EventHandler> list = new ArrayList<EventHandler>(eventsStates.size());
+        for (EventState state : eventsStates) {
+            list.add(state.getEventHandler());
+        }
+        return Collections.unmodifiableCollection(list);
+    }
+
+    /** {@inheritDoc} */
+    public void clearEventHandlers() {
+        eventsStates.clear();
+    }
+
+    /** {@inheritDoc} */
+    public double getCurrentStepStart() {
+        return stepStart;
+    }
+
+    /** {@inheritDoc} */
+    public double getCurrentSignedStepsize() {
+        return stepSize;
+    }
+
+    /** {@inheritDoc} */
+    public void setMaxEvaluations(int maxEvaluations) {
+        evaluations =
+                evaluations.withMaximalCount(
+                        (maxEvaluations < 0) ? Integer.MAX_VALUE : maxEvaluations);
+    }
+
+    /** {@inheritDoc} */
+    public int getMaxEvaluations() {
+        return evaluations.getMaximalCount();
+    }
+
+    /** {@inheritDoc} */
+    public int getEvaluations() {
+        return evaluations.getCount();
+    }
+
+    /**
+     * Prepare the start of an integration.
+     *
+     * @param t0 start value of the independent <i>time</i> variable
+     * @param y0 array containing the start value of the state vector
+     * @param t target time for the integration
+     */
+    protected void initIntegration(final double t0, final double[] y0, final double t) {
+
+        evaluations = evaluations.withStart(0);
+
+        for (final EventState state : eventsStates) {
+            state.setExpandable(expandable);
+            state.getEventHandler().init(t0, y0, t);
+        }
+
+        for (StepHandler handler : stepHandlers) {
+            handler.init(t0, y0, t);
+        }
+
+        setStateInitialized(false);
+    }
+
+    /**
+     * Set the equations.
+     *
+     * @param equations equations to set
+     */
+    protected void setEquations(final ExpandableStatefulODE equations) {
+        this.expandable = equations;
+    }
+
+    /**
+     * Get the differential equations to integrate.
+     *
+     * @return differential equations to integrate
+     * @since 3.2
+     */
+    protected ExpandableStatefulODE getExpandable() {
+        return expandable;
+    }
+
+    /**
+     * Get the evaluations counter.
+     *
+     * @return evaluations counter
+     * @since 3.2
+     * @deprecated as of 3.6 replaced with {@link #getCounter()}
+     */
+    @Deprecated
+    protected org.apache.commons.math3.util.Incrementor getEvaluationsCounter() {
+        return org.apache.commons.math3.util.Incrementor.wrap(evaluations);
+    }
+
+    /**
+     * Get the evaluations counter.
+     *
+     * @return evaluations counter
+     * @since 3.6
+     */
+    protected IntegerSequence.Incrementor getCounter() {
+        return evaluations;
+    }
+
+    /** {@inheritDoc} */
+    public double integrate(
+            final FirstOrderDifferentialEquations equations,
+            final double t0,
+            final double[] y0,
+            final double t,
+            final double[] y)
+            throws DimensionMismatchException,
+                    NumberIsTooSmallException,
+                    MaxCountExceededException,
+                    NoBracketingException {
+
+        if (y0.length != equations.getDimension()) {
+            throw new DimensionMismatchException(y0.length, equations.getDimension());
+        }
+        if (y.length != equations.getDimension()) {
+            throw new DimensionMismatchException(y.length, equations.getDimension());
+        }
+
+        // prepare expandable stateful equations
+        final ExpandableStatefulODE expandableODE = new ExpandableStatefulODE(equations);
+        expandableODE.setTime(t0);
+        expandableODE.setPrimaryState(y0);
+
+        // perform integration
+        integrate(expandableODE, t);
+
+        // extract results back from the stateful equations
+        System.arraycopy(expandableODE.getPrimaryState(), 0, y, 0, y.length);
+        return expandableODE.getTime();
+    }
+
+    /**
+     * Integrate a set of differential equations up to the given time.
+     *
+     * <p>This method solves an Initial Value Problem (IVP).
+     *
+     * <p>The set of differential equations is composed of a main set, which can be extended by some
+     * sets of secondary equations. The set of equations must be already set up with initial time
+     * and partial states. At integration completion, the final time and partial states will be
+     * available in the same object.
+     *
+     * <p>Since this method stores some internal state variables made available in its public
+     * interface during integration ({@link #getCurrentSignedStepsize()}), it is <em>not</em>
+     * thread-safe.
+     *
+     * @param equations complete set of differential equations to integrate
+     * @param t target time for the integration (can be set to a value smaller than <code>t0</code>
+     *     for backward integration)
+     * @exception NumberIsTooSmallException if integration step is too small
+     * @throws DimensionMismatchException if the dimension of the complete state does not match the
+     *     complete equations sets dimension
+     * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+     * @exception NoBracketingException if the location of an event cannot be bracketed
+     */
+    public abstract void integrate(ExpandableStatefulODE equations, double t)
+            throws NumberIsTooSmallException,
+                    DimensionMismatchException,
+                    MaxCountExceededException,
+                    NoBracketingException;
+
+    /**
+     * Compute the derivatives and check the number of evaluations.
+     *
+     * @param t current value of the independent <I>time</I> variable
+     * @param y array containing the current value of the state vector
+     * @param yDot placeholder array where to put the time derivative of the state vector
+     * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+     * @exception DimensionMismatchException if arrays dimensions do not match equations settings
+     * @exception NullPointerException if the ODE equations have not been set (i.e. if this method
+     *     is called outside of a call to {@link #integrate(ExpandableStatefulODE, double)} or
+     *     {@link #integrate(FirstOrderDifferentialEquations, double, double[], double, double[])})
+     */
+    public void computeDerivatives(final double t, final double[] y, final double[] yDot)
+            throws MaxCountExceededException, DimensionMismatchException, NullPointerException {
+        evaluations.increment();
+        expandable.computeDerivatives(t, y, yDot);
+    }
+
+    /**
+     * Set the stateInitialized flag.
+     *
+     * <p>This method must be called by integrators with the value {@code false} before they start
+     * integration, so a proper lazy initialization is done automatically on the first step.
+     *
+     * @param stateInitialized new value for the flag
+     * @since 2.2
+     */
+    protected void setStateInitialized(final boolean stateInitialized) {
+        this.statesInitialized = stateInitialized;
+    }
+
+    /**
+     * Accept a step, triggering events and step handlers.
+     *
+     * @param interpolator step interpolator
+     * @param y state vector at step end time, must be reset if an event asks for resetting or if an
+     *     events stops integration during the step
+     * @param yDot placeholder array where to put the time derivative of the state vector
+     * @param tEnd final integration time
+     * @return time at end of step
+     * @exception MaxCountExceededException if the interpolator throws one because the number of
+     *     functions evaluations is exceeded
+     * @exception NoBracketingException if the location of an event cannot be bracketed
+     * @exception DimensionMismatchException if arrays dimensions do not match equations settings
+     * @since 2.2
+     */
+    protected double acceptStep(
+            final AbstractStepInterpolator interpolator,
+            final double[] y,
+            final double[] yDot,
+            final double tEnd)
+            throws MaxCountExceededException, DimensionMismatchException, NoBracketingException {
+
+        double previousT = interpolator.getGlobalPreviousTime();
+        final double currentT = interpolator.getGlobalCurrentTime();
+
+        // initialize the events states if needed
+        if (!statesInitialized) {
+            for (EventState state : eventsStates) {
+                state.reinitializeBegin(interpolator);
+            }
+            statesInitialized = true;
+        }
+
+        // search for next events that may occur during the step
+        final int orderingSign = interpolator.isForward() ? +1 : -1;
+        SortedSet<EventState> occurringEvents =
+                new TreeSet<EventState>(
+                        new Comparator<EventState>() {
+
+                            /** {@inheritDoc} */
+                            public int compare(EventState es0, EventState es1) {
+                                return orderingSign
+                                        * Double.compare(es0.getEventTime(), es1.getEventTime());
+                            }
+                        });
+
+        for (final EventState state : eventsStates) {
+            if (state.evaluateStep(interpolator)) {
+                // the event occurs during the current step
+                occurringEvents.add(state);
+            }
+        }
+
+        while (!occurringEvents.isEmpty()) {
+
+            // handle the chronologically first event
+            final Iterator<EventState> iterator = occurringEvents.iterator();
+            final EventState currentEvent = iterator.next();
+            iterator.remove();
+
+            // restrict the interpolator to the first part of the step, up to the event
+            final double eventT = currentEvent.getEventTime();
+            interpolator.setSoftPreviousTime(previousT);
+            interpolator.setSoftCurrentTime(eventT);
+
+            // get state at event time
+            interpolator.setInterpolatedTime(eventT);
+            final double[] eventYComplete = new double[y.length];
+            expandable
+                    .getPrimaryMapper()
+                    .insertEquationData(interpolator.getInterpolatedState(), eventYComplete);
+            int index = 0;
+            for (EquationsMapper secondary : expandable.getSecondaryMappers()) {
+                secondary.insertEquationData(
+                        interpolator.getInterpolatedSecondaryState(index++), eventYComplete);
+            }
+
+            // advance all event states to current time
+            for (final EventState state : eventsStates) {
+                state.stepAccepted(eventT, eventYComplete);
+                isLastStep = isLastStep || state.stop();
+            }
+
+            // handle the first part of the step, up to the event
+            for (final StepHandler handler : stepHandlers) {
+                handler.handleStep(interpolator, isLastStep);
+            }
+
+            if (isLastStep) {
+                // the event asked to stop integration
+                System.arraycopy(eventYComplete, 0, y, 0, y.length);
+                return eventT;
+            }
+
+            boolean needReset = false;
+            resetOccurred = false;
+            needReset = currentEvent.reset(eventT, eventYComplete);
+            if (needReset) {
+                // some event handler has triggered changes that
+                // invalidate the derivatives, we need to recompute them
+                interpolator.setInterpolatedTime(eventT);
+                System.arraycopy(eventYComplete, 0, y, 0, y.length);
+                computeDerivatives(eventT, y, yDot);
+                resetOccurred = true;
+                return eventT;
+            }
+
+            // prepare handling of the remaining part of the step
+            previousT = eventT;
+            interpolator.setSoftPreviousTime(eventT);
+            interpolator.setSoftCurrentTime(currentT);
+
+            // check if the same event occurs again in the remaining part of the step
+            if (currentEvent.evaluateStep(interpolator)) {
+                // the event occurs during the current step
+                occurringEvents.add(currentEvent);
+            }
+        }
+
+        // last part of the step, after the last event
+        interpolator.setInterpolatedTime(currentT);
+        final double[] currentY = new double[y.length];
+        expandable
+                .getPrimaryMapper()
+                .insertEquationData(interpolator.getInterpolatedState(), currentY);
+        int index = 0;
+        for (EquationsMapper secondary : expandable.getSecondaryMappers()) {
+            secondary.insertEquationData(
+                    interpolator.getInterpolatedSecondaryState(index++), currentY);
+        }
+        for (final EventState state : eventsStates) {
+            state.stepAccepted(currentT, currentY);
+            isLastStep = isLastStep || state.stop();
+        }
+        isLastStep = isLastStep || Precision.equals(currentT, tEnd, 1);
+
+        // handle the remaining part of the step, after all events if any
+        for (StepHandler handler : stepHandlers) {
+            handler.handleStep(interpolator, isLastStep);
+        }
+
+        return currentT;
+    }
+
+    /**
+     * Check the integration span.
+     *
+     * @param equations set of differential equations
+     * @param t target time for the integration
+     * @exception NumberIsTooSmallException if integration span is too small
+     * @exception DimensionMismatchException if adaptive step size integrators tolerance arrays
+     *     dimensions are not compatible with equations settings
+     */
+    protected void sanityChecks(final ExpandableStatefulODE equations, final double t)
+            throws NumberIsTooSmallException, DimensionMismatchException {
+
+        final double threshold =
+                1000
+                        * FastMath.ulp(
+                                FastMath.max(FastMath.abs(equations.getTime()), FastMath.abs(t)));
+        final double dt = FastMath.abs(equations.getTime() - t);
+        if (dt <= threshold) {
+            throw new NumberIsTooSmallException(
+                    LocalizedFormats.TOO_SMALL_INTEGRATION_INTERVAL, dt, threshold, false);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/AbstractParameterizable.java b/src/main/java/org/apache/commons/math3/ode/AbstractParameterizable.java
new file mode 100644
index 0000000..8b0a27e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/AbstractParameterizable.java
@@ -0,0 +1,81 @@
+/*
+ * 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.math3.ode;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * This abstract class provides boilerplate parameters list.
+ *
+ * @since 3.0
+ */
+public abstract class AbstractParameterizable implements Parameterizable {
+
+    /** List of the parameters names. */
+    private final Collection<String> parametersNames;
+
+    /**
+     * Simple constructor.
+     *
+     * @param names names of the supported parameters
+     */
+    protected AbstractParameterizable(final String... names) {
+        parametersNames = new ArrayList<String>();
+        for (final String name : names) {
+            parametersNames.add(name);
+        }
+    }
+
+    /**
+     * Simple constructor.
+     *
+     * @param names names of the supported parameters
+     */
+    protected AbstractParameterizable(final Collection<String> names) {
+        parametersNames = new ArrayList<String>();
+        parametersNames.addAll(names);
+    }
+
+    /** {@inheritDoc} */
+    public Collection<String> getParametersNames() {
+        return parametersNames;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupported(final String name) {
+        for (final String supportedName : parametersNames) {
+            if (supportedName.equals(name)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Check if a parameter is supported and throw an IllegalArgumentException if not.
+     *
+     * @param name name of the parameter to check
+     * @exception UnknownParameterException if the parameter is not supported
+     * @see #isSupported(String)
+     */
+    public void complainIfNotSupported(final String name) throws UnknownParameterException {
+        if (!isSupported(name)) {
+            throw new UnknownParameterException(name);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/ContinuousOutputFieldModel.java b/src/main/java/org/apache/commons/math3/ode/ContinuousOutputFieldModel.java
new file mode 100644
index 0000000..e6950d2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/ContinuousOutputFieldModel.java
@@ -0,0 +1,360 @@
+/*
+ * 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.math3.ode;
+
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.ode.sampling.FieldStepHandler;
+import org.apache.commons.math3.ode.sampling.FieldStepInterpolator;
+import org.apache.commons.math3.util.FastMath;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class stores all information provided by an ODE integrator during the integration process
+ * and build a continuous model of the solution from this.
+ *
+ * <p>This class act as a step handler from the integrator point of view. It is called iteratively
+ * during the integration process and stores a copy of all steps information in a sorted collection
+ * for later use. Once the integration process is over, the user can use the {@link
+ * #getInterpolatedState(RealFieldElement) getInterpolatedState} method to retrieve this information
+ * at any time. It is important to wait for the integration to be over before attempting to call
+ * {@link #getInterpolatedState(RealFieldElement)} because some internal variables are set only once
+ * the last step has been handled.
+ *
+ * <p>This is useful for example if the main loop of the user application should remain independent
+ * from the integration process or if one needs to mimic the behaviour of an analytical model
+ * despite a numerical model is used (i.e. one needs the ability to get the model value at any time
+ * or to navigate through the data).
+ *
+ * <p>If problem modeling is done with several separate integration phases for contiguous intervals,
+ * the same ContinuousOutputModel can be used as step handler for all integration phases as long as
+ * they are performed in order and in the same direction. As an example, one can extrapolate the
+ * trajectory of a satellite with one model (i.e. one set of differential equations) up to the
+ * beginning of a maneuver, use another more complex model including thrusters modeling and accurate
+ * attitude control during the maneuver, and revert to the first model after the end of the
+ * maneuver. If the same continuous output model handles the steps of all integration phases, the
+ * user do not need to bother when the maneuver begins or ends, he has all the data available in a
+ * transparent manner.
+ *
+ * <p>One should be aware that the amount of data stored in a ContinuousOutputFieldModel instance
+ * can be important if the state vector is large, if the integration interval is long or if the
+ * steps are small (which can result from small tolerance settings in {@link
+ * org.apache.commons.math3.ode.nonstiff.AdaptiveStepsizeFieldIntegrator adaptive step size
+ * integrators}).
+ *
+ * @see FieldStepHandler
+ * @see FieldStepInterpolator
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+public class ContinuousOutputFieldModel<T extends RealFieldElement<T>>
+        implements FieldStepHandler<T> {
+
+    /** Initial integration time. */
+    private T initialTime;
+
+    /** Final integration time. */
+    private T finalTime;
+
+    /** Integration direction indicator. */
+    private boolean forward;
+
+    /** Current interpolator index. */
+    private int index;
+
+    /** Steps table. */
+    private List<FieldStepInterpolator<T>> steps;
+
+    /** Simple constructor. Build an empty continuous output model. */
+    public ContinuousOutputFieldModel() {
+        steps = new ArrayList<FieldStepInterpolator<T>>();
+        initialTime = null;
+        finalTime = null;
+        forward = true;
+        index = 0;
+    }
+
+    /**
+     * Append another model at the end of the instance.
+     *
+     * @param model model to add at the end of the instance
+     * @exception MathIllegalArgumentException if the model to append is not compatible with the
+     *     instance (dimension of the state vector, propagation direction, hole between the dates)
+     * @exception DimensionMismatchException if the dimensions of the states or the number of
+     *     secondary states do not match
+     * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+     *     during step finalization
+     */
+    public void append(final ContinuousOutputFieldModel<T> model)
+            throws MathIllegalArgumentException, MaxCountExceededException {
+
+        if (model.steps.size() == 0) {
+            return;
+        }
+
+        if (steps.size() == 0) {
+            initialTime = model.initialTime;
+            forward = model.forward;
+        } else {
+
+            // safety checks
+            final FieldODEStateAndDerivative<T> s1 = steps.get(0).getPreviousState();
+            final FieldODEStateAndDerivative<T> s2 = model.steps.get(0).getPreviousState();
+            checkDimensionsEquality(s1.getStateDimension(), s2.getStateDimension());
+            checkDimensionsEquality(
+                    s1.getNumberOfSecondaryStates(), s2.getNumberOfSecondaryStates());
+            for (int i = 0; i < s1.getNumberOfSecondaryStates(); ++i) {
+                checkDimensionsEquality(
+                        s1.getSecondaryStateDimension(i), s2.getSecondaryStateDimension(i));
+            }
+
+            if (forward ^ model.forward) {
+                throw new MathIllegalArgumentException(
+                        LocalizedFormats.PROPAGATION_DIRECTION_MISMATCH);
+            }
+
+            final FieldStepInterpolator<T> lastInterpolator = steps.get(index);
+            final T current = lastInterpolator.getCurrentState().getTime();
+            final T previous = lastInterpolator.getPreviousState().getTime();
+            final T step = current.subtract(previous);
+            final T gap = model.getInitialTime().subtract(current);
+            if (gap.abs().subtract(step.abs().multiply(1.0e-3)).getReal() > 0) {
+                throw new MathIllegalArgumentException(
+                        LocalizedFormats.HOLE_BETWEEN_MODELS_TIME_RANGES, gap.abs().getReal());
+            }
+        }
+
+        for (FieldStepInterpolator<T> interpolator : model.steps) {
+            steps.add(interpolator);
+        }
+
+        index = steps.size() - 1;
+        finalTime = (steps.get(index)).getCurrentState().getTime();
+    }
+
+    /**
+     * Check dimensions equality.
+     *
+     * @param d1 first dimension
+     * @param d2 second dimansion
+     * @exception DimensionMismatchException if dimensions do not match
+     */
+    private void checkDimensionsEquality(final int d1, final int d2)
+            throws DimensionMismatchException {
+        if (d1 != d2) {
+            throw new DimensionMismatchException(d2, d1);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void init(final FieldODEStateAndDerivative<T> initialState, final T t) {
+        initialTime = initialState.getTime();
+        finalTime = t;
+        forward = true;
+        index = 0;
+        steps.clear();
+    }
+
+    /**
+     * Handle the last accepted step. A copy of the information provided by the last step is stored
+     * in the instance for later use.
+     *
+     * @param interpolator interpolator for the last accepted step.
+     * @param isLast true if the step is the last one
+     * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+     *     during step finalization
+     */
+    public void handleStep(final FieldStepInterpolator<T> interpolator, final boolean isLast)
+            throws MaxCountExceededException {
+
+        if (steps.size() == 0) {
+            initialTime = interpolator.getPreviousState().getTime();
+            forward = interpolator.isForward();
+        }
+
+        steps.add(interpolator);
+
+        if (isLast) {
+            finalTime = interpolator.getCurrentState().getTime();
+            index = steps.size() - 1;
+        }
+    }
+
+    /**
+     * Get the initial integration time.
+     *
+     * @return initial integration time
+     */
+    public T getInitialTime() {
+        return initialTime;
+    }
+
+    /**
+     * Get the final integration time.
+     *
+     * @return final integration time
+     */
+    public T getFinalTime() {
+        return finalTime;
+    }
+
+    /**
+     * Get the state at interpolated time.
+     *
+     * @param time time of the interpolated point
+     * @return state at interpolated time
+     */
+    public FieldODEStateAndDerivative<T> getInterpolatedState(final T time) {
+
+        // initialize the search with the complete steps table
+        int iMin = 0;
+        final FieldStepInterpolator<T> sMin = steps.get(iMin);
+        T tMin =
+                sMin.getPreviousState()
+                        .getTime()
+                        .add(sMin.getCurrentState().getTime())
+                        .multiply(0.5);
+
+        int iMax = steps.size() - 1;
+        final FieldStepInterpolator<T> sMax = steps.get(iMax);
+        T tMax =
+                sMax.getPreviousState()
+                        .getTime()
+                        .add(sMax.getCurrentState().getTime())
+                        .multiply(0.5);
+
+        // handle points outside of the integration interval
+        // or in the first and last step
+        if (locatePoint(time, sMin) <= 0) {
+            index = iMin;
+            return sMin.getInterpolatedState(time);
+        }
+        if (locatePoint(time, sMax) >= 0) {
+            index = iMax;
+            return sMax.getInterpolatedState(time);
+        }
+
+        // reduction of the table slice size
+        while (iMax - iMin > 5) {
+
+            // use the last estimated index as the splitting index
+            final FieldStepInterpolator<T> si = steps.get(index);
+            final int location = locatePoint(time, si);
+            if (location < 0) {
+                iMax = index;
+                tMax =
+                        si.getPreviousState()
+                                .getTime()
+                                .add(si.getCurrentState().getTime())
+                                .multiply(0.5);
+            } else if (location > 0) {
+                iMin = index;
+                tMin =
+                        si.getPreviousState()
+                                .getTime()
+                                .add(si.getCurrentState().getTime())
+                                .multiply(0.5);
+            } else {
+                // we have found the target step, no need to continue searching
+                return si.getInterpolatedState(time);
+            }
+
+            // compute a new estimate of the index in the reduced table slice
+            final int iMed = (iMin + iMax) / 2;
+            final FieldStepInterpolator<T> sMed = steps.get(iMed);
+            final T tMed =
+                    sMed.getPreviousState()
+                            .getTime()
+                            .add(sMed.getCurrentState().getTime())
+                            .multiply(0.5);
+
+            if (tMed.subtract(tMin).abs().subtract(1.0e-6).getReal() < 0
+                    || tMax.subtract(tMed).abs().subtract(1.0e-6).getReal() < 0) {
+                // too close to the bounds, we estimate using a simple dichotomy
+                index = iMed;
+            } else {
+                // estimate the index using a reverse quadratic polynomial
+                // (reverse means we have i = P(t), thus allowing to simply
+                // compute index = P(time) rather than solving a quadratic equation)
+                final T d12 = tMax.subtract(tMed);
+                final T d23 = tMed.subtract(tMin);
+                final T d13 = tMax.subtract(tMin);
+                final T dt1 = time.subtract(tMax);
+                final T dt2 = time.subtract(tMed);
+                final T dt3 = time.subtract(tMin);
+                final T iLagrange =
+                        dt2.multiply(dt3)
+                                .multiply(d23)
+                                .multiply(iMax)
+                                .subtract(dt1.multiply(dt3).multiply(d13).multiply(iMed))
+                                .add(dt1.multiply(dt2).multiply(d12).multiply(iMin))
+                                .divide(d12.multiply(d23).multiply(d13));
+                index = (int) FastMath.rint(iLagrange.getReal());
+            }
+
+            // force the next size reduction to be at least one tenth
+            final int low = FastMath.max(iMin + 1, (9 * iMin + iMax) / 10);
+            final int high = FastMath.min(iMax - 1, (iMin + 9 * iMax) / 10);
+            if (index < low) {
+                index = low;
+            } else if (index > high) {
+                index = high;
+            }
+        }
+
+        // now the table slice is very small, we perform an iterative search
+        index = iMin;
+        while (index <= iMax && locatePoint(time, steps.get(index)) > 0) {
+            ++index;
+        }
+
+        return steps.get(index).getInterpolatedState(time);
+    }
+
+    /**
+     * Compare a step interval and a double.
+     *
+     * @param time point to locate
+     * @param interval step interval
+     * @return -1 if the double is before the interval, 0 if it is in the interval, and +1 if it is
+     *     after the interval, according to the interval direction
+     */
+    private int locatePoint(final T time, final FieldStepInterpolator<T> interval) {
+        if (forward) {
+            if (time.subtract(interval.getPreviousState().getTime()).getReal() < 0) {
+                return -1;
+            } else if (time.subtract(interval.getCurrentState().getTime()).getReal() > 0) {
+                return +1;
+            } else {
+                return 0;
+            }
+        }
+        if (time.subtract(interval.getPreviousState().getTime()).getReal() > 0) {
+            return -1;
+        } else if (time.subtract(interval.getCurrentState().getTime()).getReal() < 0) {
+            return +1;
+        } else {
+            return 0;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/ContinuousOutputModel.java b/src/main/java/org/apache/commons/math3/ode/ContinuousOutputModel.java
new file mode 100644
index 0000000..6bb637d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/ContinuousOutputModel.java
@@ -0,0 +1,435 @@
+/*
+ * 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.math3.ode;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.ode.sampling.StepHandler;
+import org.apache.commons.math3.ode.sampling.StepInterpolator;
+import org.apache.commons.math3.util.FastMath;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class stores all information provided by an ODE integrator during the integration process
+ * and build a continuous model of the solution from this.
+ *
+ * <p>This class act as a step handler from the integrator point of view. It is called iteratively
+ * during the integration process and stores a copy of all steps information in a sorted collection
+ * for later use. Once the integration process is over, the user can use the {@link
+ * #setInterpolatedTime setInterpolatedTime} and {@link #getInterpolatedState getInterpolatedState}
+ * to retrieve this information at any time. It is important to wait for the integration to be over
+ * before attempting to call {@link #setInterpolatedTime setInterpolatedTime} because some internal
+ * variables are set only once the last step has been handled.
+ *
+ * <p>This is useful for example if the main loop of the user application should remain independent
+ * from the integration process or if one needs to mimic the behaviour of an analytical model
+ * despite a numerical model is used (i.e. one needs the ability to get the model value at any time
+ * or to navigate through the data).
+ *
+ * <p>If problem modeling is done with several separate integration phases for contiguous intervals,
+ * the same ContinuousOutputModel can be used as step handler for all integration phases as long as
+ * they are performed in order and in the same direction. As an example, one can extrapolate the
+ * trajectory of a satellite with one model (i.e. one set of differential equations) up to the
+ * beginning of a maneuver, use another more complex model including thrusters modeling and accurate
+ * attitude control during the maneuver, and revert to the first model after the end of the
+ * maneuver. If the same continuous output model handles the steps of all integration phases, the
+ * user do not need to bother when the maneuver begins or ends, he has all the data available in a
+ * transparent manner.
+ *
+ * <p>An important feature of this class is that it implements the <code>Serializable</code>
+ * interface. This means that the result of an integration can be serialized and reused later (if
+ * stored into a persistent medium like a filesystem or a database) or elsewhere (if sent to another
+ * application). Only the result of the integration is stored, there is no reference to the
+ * integrated problem by itself.
+ *
+ * <p>One should be aware that the amount of data stored in a ContinuousOutputModel instance can be
+ * important if the state vector is large, if the integration interval is long or if the steps are
+ * small (which can result from small tolerance settings in {@link
+ * org.apache.commons.math3.ode.nonstiff.AdaptiveStepsizeIntegrator adaptive step size
+ * integrators}).
+ *
+ * @see StepHandler
+ * @see StepInterpolator
+ * @since 1.2
+ */
+public class ContinuousOutputModel implements StepHandler, Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -1417964919405031606L;
+
+    /** Initial integration time. */
+    private double initialTime;
+
+    /** Final integration time. */
+    private double finalTime;
+
+    /** Integration direction indicator. */
+    private boolean forward;
+
+    /** Current interpolator index. */
+    private int index;
+
+    /** Steps table. */
+    private List<StepInterpolator> steps;
+
+    /** Simple constructor. Build an empty continuous output model. */
+    public ContinuousOutputModel() {
+        steps = new ArrayList<StepInterpolator>();
+        initialTime = Double.NaN;
+        finalTime = Double.NaN;
+        forward = true;
+        index = 0;
+    }
+
+    /**
+     * Append another model at the end of the instance.
+     *
+     * @param model model to add at the end of the instance
+     * @exception MathIllegalArgumentException if the model to append is not compatible with the
+     *     instance (dimension of the state vector, propagation direction, hole between the dates)
+     * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+     *     during step finalization
+     */
+    public void append(final ContinuousOutputModel model)
+            throws MathIllegalArgumentException, MaxCountExceededException {
+
+        if (model.steps.size() == 0) {
+            return;
+        }
+
+        if (steps.size() == 0) {
+            initialTime = model.initialTime;
+            forward = model.forward;
+        } else {
+
+            if (getInterpolatedState().length != model.getInterpolatedState().length) {
+                throw new DimensionMismatchException(
+                        model.getInterpolatedState().length, getInterpolatedState().length);
+            }
+
+            if (forward ^ model.forward) {
+                throw new MathIllegalArgumentException(
+                        LocalizedFormats.PROPAGATION_DIRECTION_MISMATCH);
+            }
+
+            final StepInterpolator lastInterpolator = steps.get(index);
+            final double current = lastInterpolator.getCurrentTime();
+            final double previous = lastInterpolator.getPreviousTime();
+            final double step = current - previous;
+            final double gap = model.getInitialTime() - current;
+            if (FastMath.abs(gap) > 1.0e-3 * FastMath.abs(step)) {
+                throw new MathIllegalArgumentException(
+                        LocalizedFormats.HOLE_BETWEEN_MODELS_TIME_RANGES, FastMath.abs(gap));
+            }
+        }
+
+        for (StepInterpolator interpolator : model.steps) {
+            steps.add(interpolator.copy());
+        }
+
+        index = steps.size() - 1;
+        finalTime = (steps.get(index)).getCurrentTime();
+    }
+
+    /** {@inheritDoc} */
+    public void init(double t0, double[] y0, double t) {
+        initialTime = Double.NaN;
+        finalTime = Double.NaN;
+        forward = true;
+        index = 0;
+        steps.clear();
+    }
+
+    /**
+     * Handle the last accepted step. A copy of the information provided by the last step is stored
+     * in the instance for later use.
+     *
+     * @param interpolator interpolator for the last accepted step.
+     * @param isLast true if the step is the last one
+     * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+     *     during step finalization
+     */
+    public void handleStep(final StepInterpolator interpolator, final boolean isLast)
+            throws MaxCountExceededException {
+
+        if (steps.size() == 0) {
+            initialTime = interpolator.getPreviousTime();
+            forward = interpolator.isForward();
+        }
+
+        steps.add(interpolator.copy());
+
+        if (isLast) {
+            finalTime = interpolator.getCurrentTime();
+            index = steps.size() - 1;
+        }
+    }
+
+    /**
+     * Get the initial integration time.
+     *
+     * @return initial integration time
+     */
+    public double getInitialTime() {
+        return initialTime;
+    }
+
+    /**
+     * Get the final integration time.
+     *
+     * @return final integration time
+     */
+    public double getFinalTime() {
+        return finalTime;
+    }
+
+    /**
+     * Get the time of the interpolated point. If {@link #setInterpolatedTime} has not been called,
+     * it returns the final integration time.
+     *
+     * @return interpolation point time
+     */
+    public double getInterpolatedTime() {
+        return steps.get(index).getInterpolatedTime();
+    }
+
+    /**
+     * Set the time of the interpolated point.
+     *
+     * <p>This method should <strong>not</strong> be called before the integration is over because
+     * some internal variables are set only once the last step has been handled.
+     *
+     * <p>Setting the time outside of the integration interval is now allowed, but should be used
+     * with care since the accuracy of the interpolator will probably be very poor far from this
+     * interval. This allowance has been added to simplify implementation of search algorithms near
+     * the interval endpoints.
+     *
+     * <p>Note that each time this method is called, the internal arrays returned in {@link
+     * #getInterpolatedState()}, {@link #getInterpolatedDerivatives()} and {@link
+     * #getInterpolatedSecondaryState(int)} <em>will</em> be overwritten. So if their content must
+     * be preserved across several calls, user must copy them.
+     *
+     * @param time time of the interpolated point
+     * @see #getInterpolatedState()
+     * @see #getInterpolatedDerivatives()
+     * @see #getInterpolatedSecondaryState(int)
+     */
+    public void setInterpolatedTime(final double time) {
+
+        // initialize the search with the complete steps table
+        int iMin = 0;
+        final StepInterpolator sMin = steps.get(iMin);
+        double tMin = 0.5 * (sMin.getPreviousTime() + sMin.getCurrentTime());
+
+        int iMax = steps.size() - 1;
+        final StepInterpolator sMax = steps.get(iMax);
+        double tMax = 0.5 * (sMax.getPreviousTime() + sMax.getCurrentTime());
+
+        // handle points outside of the integration interval
+        // or in the first and last step
+        if (locatePoint(time, sMin) <= 0) {
+            index = iMin;
+            sMin.setInterpolatedTime(time);
+            return;
+        }
+        if (locatePoint(time, sMax) >= 0) {
+            index = iMax;
+            sMax.setInterpolatedTime(time);
+            return;
+        }
+
+        // reduction of the table slice size
+        while (iMax - iMin > 5) {
+
+            // use the last estimated index as the splitting index
+            final StepInterpolator si = steps.get(index);
+            final int location = locatePoint(time, si);
+            if (location < 0) {
+                iMax = index;
+                tMax = 0.5 * (si.getPreviousTime() + si.getCurrentTime());
+            } else if (location > 0) {
+                iMin = index;
+                tMin = 0.5 * (si.getPreviousTime() + si.getCurrentTime());
+            } else {
+                // we have found the target step, no need to continue searching
+                si.setInterpolatedTime(time);
+                return;
+            }
+
+            // compute a new estimate of the index in the reduced table slice
+            final int iMed = (iMin + iMax) / 2;
+            final StepInterpolator sMed = steps.get(iMed);
+            final double tMed = 0.5 * (sMed.getPreviousTime() + sMed.getCurrentTime());
+
+            if ((FastMath.abs(tMed - tMin) < 1e-6) || (FastMath.abs(tMax - tMed) < 1e-6)) {
+                // too close to the bounds, we estimate using a simple dichotomy
+                index = iMed;
+            } else {
+                // estimate the index using a reverse quadratic polynom
+                // (reverse means we have i = P(t), thus allowing to simply
+                // compute index = P(time) rather than solving a quadratic equation)
+                final double d12 = tMax - tMed;
+                final double d23 = tMed - tMin;
+                final double d13 = tMax - tMin;
+                final double dt1 = time - tMax;
+                final double dt2 = time - tMed;
+                final double dt3 = time - tMin;
+                final double iLagrange =
+                        ((dt2 * dt3 * d23) * iMax
+                                        - (dt1 * dt3 * d13) * iMed
+                                        + (dt1 * dt2 * d12) * iMin)
+                                / (d12 * d23 * d13);
+                index = (int) FastMath.rint(iLagrange);
+            }
+
+            // force the next size reduction to be at least one tenth
+            final int low = FastMath.max(iMin + 1, (9 * iMin + iMax) / 10);
+            final int high = FastMath.min(iMax - 1, (iMin + 9 * iMax) / 10);
+            if (index < low) {
+                index = low;
+            } else if (index > high) {
+                index = high;
+            }
+        }
+
+        // now the table slice is very small, we perform an iterative search
+        index = iMin;
+        while ((index <= iMax) && (locatePoint(time, steps.get(index)) > 0)) {
+            ++index;
+        }
+
+        steps.get(index).setInterpolatedTime(time);
+    }
+
+    /**
+     * Get the state vector of the interpolated point.
+     *
+     * <p>The returned vector is a reference to a reused array, so it should not be modified and it
+     * should be copied if it needs to be preserved across several calls to the associated {@link
+     * #setInterpolatedTime(double)} method.
+     *
+     * @return state vector at time {@link #getInterpolatedTime}
+     * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+     * @see #setInterpolatedTime(double)
+     * @see #getInterpolatedDerivatives()
+     * @see #getInterpolatedSecondaryState(int)
+     * @see #getInterpolatedSecondaryDerivatives(int)
+     */
+    public double[] getInterpolatedState() throws MaxCountExceededException {
+        return steps.get(index).getInterpolatedState();
+    }
+
+    /**
+     * Get the derivatives of the state vector of the interpolated point.
+     *
+     * <p>The returned vector is a reference to a reused array, so it should not be modified and it
+     * should be copied if it needs to be preserved across several calls to the associated {@link
+     * #setInterpolatedTime(double)} method.
+     *
+     * @return derivatives of the state vector at time {@link #getInterpolatedTime}
+     * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+     * @see #setInterpolatedTime(double)
+     * @see #getInterpolatedState()
+     * @see #getInterpolatedSecondaryState(int)
+     * @see #getInterpolatedSecondaryDerivatives(int)
+     * @since 3.4
+     */
+    public double[] getInterpolatedDerivatives() throws MaxCountExceededException {
+        return steps.get(index).getInterpolatedDerivatives();
+    }
+
+    /**
+     * Get the interpolated secondary state corresponding to the secondary equations.
+     *
+     * <p>The returned vector is a reference to a reused array, so it should not be modified and it
+     * should be copied if it needs to be preserved across several calls to the associated {@link
+     * #setInterpolatedTime(double)} method.
+     *
+     * @param secondaryStateIndex index of the secondary set, as returned by {@link
+     *     org.apache.commons.math3.ode.ExpandableStatefulODE#addSecondaryEquations(
+     *     org.apache.commons.math3.ode.SecondaryEquations)
+     *     ExpandableStatefulODE.addSecondaryEquations(SecondaryEquations)}
+     * @return interpolated secondary state at the current interpolation date
+     * @see #setInterpolatedTime(double)
+     * @see #getInterpolatedState()
+     * @see #getInterpolatedDerivatives()
+     * @see #getInterpolatedSecondaryDerivatives(int)
+     * @since 3.2
+     * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+     */
+    public double[] getInterpolatedSecondaryState(final int secondaryStateIndex)
+            throws MaxCountExceededException {
+        return steps.get(index).getInterpolatedSecondaryState(secondaryStateIndex);
+    }
+
+    /**
+     * Get the interpolated secondary derivatives corresponding to the secondary equations.
+     *
+     * <p>The returned vector is a reference to a reused array, so it should not be modified and it
+     * should be copied if it needs to be preserved across several calls to the associated {@link
+     * #setInterpolatedTime(double)} method.
+     *
+     * @param secondaryStateIndex index of the secondary set, as returned by {@link
+     *     org.apache.commons.math3.ode.ExpandableStatefulODE#addSecondaryEquations(
+     *     org.apache.commons.math3.ode.SecondaryEquations)
+     *     ExpandableStatefulODE.addSecondaryEquations(SecondaryEquations)}
+     * @return interpolated secondary derivatives at the current interpolation date
+     * @see #setInterpolatedTime(double)
+     * @see #getInterpolatedState()
+     * @see #getInterpolatedDerivatives()
+     * @see #getInterpolatedSecondaryState(int)
+     * @since 3.4
+     * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+     */
+    public double[] getInterpolatedSecondaryDerivatives(final int secondaryStateIndex)
+            throws MaxCountExceededException {
+        return steps.get(index).getInterpolatedSecondaryDerivatives(secondaryStateIndex);
+    }
+
+    /**
+     * Compare a step interval and a double.
+     *
+     * @param time point to locate
+     * @param interval step interval
+     * @return -1 if the double is before the interval, 0 if it is in the interval, and +1 if it is
+     *     after the interval, according to the interval direction
+     */
+    private int locatePoint(final double time, final StepInterpolator interval) {
+        if (forward) {
+            if (time < interval.getPreviousTime()) {
+                return -1;
+            } else if (time > interval.getCurrentTime()) {
+                return +1;
+            } else {
+                return 0;
+            }
+        }
+        if (time > interval.getPreviousTime()) {
+            return -1;
+        } else if (time < interval.getCurrentTime()) {
+            return +1;
+        } else {
+            return 0;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/EquationsMapper.java b/src/main/java/org/apache/commons/math3/ode/EquationsMapper.java
new file mode 100644
index 0000000..c123cec
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/EquationsMapper.java
@@ -0,0 +1,106 @@
+/*
+ * 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.math3.ode;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+
+import java.io.Serializable;
+
+/**
+ * Class mapping the part of a complete state or derivative that pertains to a specific differential
+ * equation.
+ *
+ * <p>Instances of this class are guaranteed to be immutable.
+ *
+ * @see SecondaryEquations
+ * @since 3.0
+ */
+public class EquationsMapper implements Serializable {
+
+    /** Serializable UID. */
+    private static final long serialVersionUID = 20110925L;
+
+    /** Index of the first equation element in complete state arrays. */
+    private final int firstIndex;
+
+    /** Dimension of the secondary state parameters. */
+    private final int dimension;
+
+    /**
+     * simple constructor.
+     *
+     * @param firstIndex index of the first equation element in complete state arrays
+     * @param dimension dimension of the secondary state parameters
+     */
+    public EquationsMapper(final int firstIndex, final int dimension) {
+        this.firstIndex = firstIndex;
+        this.dimension = dimension;
+    }
+
+    /**
+     * Get the index of the first equation element in complete state arrays.
+     *
+     * @return index of the first equation element in complete state arrays
+     */
+    public int getFirstIndex() {
+        return firstIndex;
+    }
+
+    /**
+     * Get the dimension of the secondary state parameters.
+     *
+     * @return dimension of the secondary state parameters
+     */
+    public int getDimension() {
+        return dimension;
+    }
+
+    /**
+     * Extract equation data from a complete state or derivative array.
+     *
+     * @param complete complete state or derivative array from which equation data should be
+     *     retrieved
+     * @param equationData placeholder where to put equation data
+     * @throws DimensionMismatchException if the dimension of the equation data does not match the
+     *     mapper dimension
+     */
+    public void extractEquationData(double[] complete, double[] equationData)
+            throws DimensionMismatchException {
+        if (equationData.length != dimension) {
+            throw new DimensionMismatchException(equationData.length, dimension);
+        }
+        System.arraycopy(complete, firstIndex, equationData, 0, dimension);
+    }
+
+    /**
+     * Insert equation data into a complete state or derivative array.
+     *
+     * @param equationData equation data to be inserted into the complete array
+     * @param complete placeholder where to put equation data (only the part corresponding to the
+     *     equation will be overwritten)
+     * @throws DimensionMismatchException if the dimension of the equation data does not match the
+     *     mapper dimension
+     */
+    public void insertEquationData(double[] equationData, double[] complete)
+            throws DimensionMismatchException {
+        if (equationData.length != dimension) {
+            throw new DimensionMismatchException(equationData.length, dimension);
+        }
+        System.arraycopy(equationData, 0, complete, firstIndex, dimension);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/ExpandableStatefulODE.java b/src/main/java/org/apache/commons/math3/ode/ExpandableStatefulODE.java
new file mode 100644
index 0000000..e4b7ef5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/ExpandableStatefulODE.java
@@ -0,0 +1,349 @@
+/*
+ * 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.math3.ode;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class represents a combined set of first order differential equations, with at least a
+ * primary set of equations expandable by some sets of secondary equations.
+ *
+ * <p>One typical use case is the computation of the Jacobian matrix for some ODE. In this case, the
+ * primary set of equations corresponds to the raw ODE, and we add to this set another bunch of
+ * secondary equations which represent the Jacobian matrix of the primary set.
+ *
+ * <p>We want the integrator to use <em>only</em> the primary set to estimate the errors and hence
+ * the step sizes. It should <em>not</em> use the secondary equations in this computation. The
+ * {@link AbstractIntegrator integrator} will be able to know where the primary set ends and so
+ * where the secondary sets begin.
+ *
+ * @see FirstOrderDifferentialEquations
+ * @see JacobianMatrices
+ * @since 3.0
+ */
+public class ExpandableStatefulODE {
+
+    /** Primary differential equation. */
+    private final FirstOrderDifferentialEquations primary;
+
+    /** Mapper for primary equation. */
+    private final EquationsMapper primaryMapper;
+
+    /** Time. */
+    private double time;
+
+    /** State. */
+    private final double[] primaryState;
+
+    /** State derivative. */
+    private final double[] primaryStateDot;
+
+    /** Components of the expandable ODE. */
+    private List<SecondaryComponent> components;
+
+    /**
+     * Build an expandable set from its primary ODE set.
+     *
+     * @param primary the primary set of differential equations to be integrated.
+     */
+    public ExpandableStatefulODE(final FirstOrderDifferentialEquations primary) {
+        final int n = primary.getDimension();
+        this.primary = primary;
+        this.primaryMapper = new EquationsMapper(0, n);
+        this.time = Double.NaN;
+        this.primaryState = new double[n];
+        this.primaryStateDot = new double[n];
+        this.components = new ArrayList<ExpandableStatefulODE.SecondaryComponent>();
+    }
+
+    /**
+     * Get the primary set of differential equations.
+     *
+     * @return primary set of differential equations
+     */
+    public FirstOrderDifferentialEquations getPrimary() {
+        return primary;
+    }
+
+    /**
+     * Return the dimension of the complete set of equations.
+     *
+     * <p>The complete set of equations correspond to the primary set plus all secondary sets.
+     *
+     * @return dimension of the complete set of equations
+     */
+    public int getTotalDimension() {
+        if (components.isEmpty()) {
+            // there are no secondary equations, the complete set is limited to the primary set
+            return primaryMapper.getDimension();
+        } else {
+            // there are secondary equations, the complete set ends after the last set
+            final EquationsMapper lastMapper = components.get(components.size() - 1).mapper;
+            return lastMapper.getFirstIndex() + lastMapper.getDimension();
+        }
+    }
+
+    /**
+     * Get the current time derivative of the complete state vector.
+     *
+     * @param t current value of the independent <I>time</I> variable
+     * @param y array containing the current value of the complete state vector
+     * @param yDot placeholder array where to put the time derivative of the complete state vector
+     * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+     * @exception DimensionMismatchException if arrays dimensions do not match equations settings
+     */
+    public void computeDerivatives(final double t, final double[] y, final double[] yDot)
+            throws MaxCountExceededException, DimensionMismatchException {
+
+        // compute derivatives of the primary equations
+        primaryMapper.extractEquationData(y, primaryState);
+        primary.computeDerivatives(t, primaryState, primaryStateDot);
+
+        // Add contribution for secondary equations
+        for (final SecondaryComponent component : components) {
+            component.mapper.extractEquationData(y, component.state);
+            component.equation.computeDerivatives(
+                    t, primaryState, primaryStateDot, component.state, component.stateDot);
+            component.mapper.insertEquationData(component.stateDot, yDot);
+        }
+
+        primaryMapper.insertEquationData(primaryStateDot, yDot);
+    }
+
+    /**
+     * Add a set of secondary equations to be integrated along with the primary set.
+     *
+     * @param secondary secondary equations set
+     * @return index of the secondary equation in the expanded state
+     */
+    public int addSecondaryEquations(final SecondaryEquations secondary) {
+
+        final int firstIndex;
+        if (components.isEmpty()) {
+            // lazy creation of the components list
+            components = new ArrayList<ExpandableStatefulODE.SecondaryComponent>();
+            firstIndex = primary.getDimension();
+        } else {
+            final SecondaryComponent last = components.get(components.size() - 1);
+            firstIndex = last.mapper.getFirstIndex() + last.mapper.getDimension();
+        }
+
+        components.add(new SecondaryComponent(secondary, firstIndex));
+
+        return components.size() - 1;
+    }
+
+    /**
+     * Get an equations mapper for the primary equations set.
+     *
+     * @return mapper for the primary set
+     * @see #getSecondaryMappers()
+     */
+    public EquationsMapper getPrimaryMapper() {
+        return primaryMapper;
+    }
+
+    /**
+     * Get the equations mappers for the secondary equations sets.
+     *
+     * @return equations mappers for the secondary equations sets
+     * @see #getPrimaryMapper()
+     */
+    public EquationsMapper[] getSecondaryMappers() {
+        final EquationsMapper[] mappers = new EquationsMapper[components.size()];
+        for (int i = 0; i < mappers.length; ++i) {
+            mappers[i] = components.get(i).mapper;
+        }
+        return mappers;
+    }
+
+    /**
+     * Set current time.
+     *
+     * @param time current time
+     */
+    public void setTime(final double time) {
+        this.time = time;
+    }
+
+    /**
+     * Get current time.
+     *
+     * @return current time
+     */
+    public double getTime() {
+        return time;
+    }
+
+    /**
+     * Set primary part of the current state.
+     *
+     * @param primaryState primary part of the current state
+     * @throws DimensionMismatchException if the dimension of the array does not match the primary
+     *     set
+     */
+    public void setPrimaryState(final double[] primaryState) throws DimensionMismatchException {
+
+        // safety checks
+        if (primaryState.length != this.primaryState.length) {
+            throw new DimensionMismatchException(primaryState.length, this.primaryState.length);
+        }
+
+        // set the data
+        System.arraycopy(primaryState, 0, this.primaryState, 0, primaryState.length);
+    }
+
+    /**
+     * Get primary part of the current state.
+     *
+     * @return primary part of the current state
+     */
+    public double[] getPrimaryState() {
+        return primaryState.clone();
+    }
+
+    /**
+     * Get primary part of the current state derivative.
+     *
+     * @return primary part of the current state derivative
+     */
+    public double[] getPrimaryStateDot() {
+        return primaryStateDot.clone();
+    }
+
+    /**
+     * Set secondary part of the current state.
+     *
+     * @param index index of the part to set as returned by {@link
+     *     #addSecondaryEquations(SecondaryEquations)}
+     * @param secondaryState secondary part of the current state
+     * @throws DimensionMismatchException if the dimension of the partial state does not match the
+     *     selected equations set dimension
+     */
+    public void setSecondaryState(final int index, final double[] secondaryState)
+            throws DimensionMismatchException {
+
+        // get either the secondary state
+        double[] localArray = components.get(index).state;
+
+        // safety checks
+        if (secondaryState.length != localArray.length) {
+            throw new DimensionMismatchException(secondaryState.length, localArray.length);
+        }
+
+        // set the data
+        System.arraycopy(secondaryState, 0, localArray, 0, secondaryState.length);
+    }
+
+    /**
+     * Get secondary part of the current state.
+     *
+     * @param index index of the part to set as returned by {@link
+     *     #addSecondaryEquations(SecondaryEquations)}
+     * @return secondary part of the current state
+     */
+    public double[] getSecondaryState(final int index) {
+        return components.get(index).state.clone();
+    }
+
+    /**
+     * Get secondary part of the current state derivative.
+     *
+     * @param index index of the part to set as returned by {@link
+     *     #addSecondaryEquations(SecondaryEquations)}
+     * @return secondary part of the current state derivative
+     */
+    public double[] getSecondaryStateDot(final int index) {
+        return components.get(index).stateDot.clone();
+    }
+
+    /**
+     * Set the complete current state.
+     *
+     * @param completeState complete current state to copy data from
+     * @throws DimensionMismatchException if the dimension of the complete state does not match the
+     *     complete equations sets dimension
+     */
+    public void setCompleteState(final double[] completeState) throws DimensionMismatchException {
+
+        // safety checks
+        if (completeState.length != getTotalDimension()) {
+            throw new DimensionMismatchException(completeState.length, getTotalDimension());
+        }
+
+        // set the data
+        primaryMapper.extractEquationData(completeState, primaryState);
+        for (final SecondaryComponent component : components) {
+            component.mapper.extractEquationData(completeState, component.state);
+        }
+    }
+
+    /**
+     * Get the complete current state.
+     *
+     * @return complete current state
+     * @throws DimensionMismatchException if the dimension of the complete state does not match the
+     *     complete equations sets dimension
+     */
+    public double[] getCompleteState() throws DimensionMismatchException {
+
+        // allocate complete array
+        double[] completeState = new double[getTotalDimension()];
+
+        // set the data
+        primaryMapper.insertEquationData(primaryState, completeState);
+        for (final SecondaryComponent component : components) {
+            component.mapper.insertEquationData(component.state, completeState);
+        }
+
+        return completeState;
+    }
+
+    /** Components of the compound stateful ODE. */
+    private static class SecondaryComponent {
+
+        /** Secondary differential equation. */
+        private final SecondaryEquations equation;
+
+        /** Mapper between local and complete arrays. */
+        private final EquationsMapper mapper;
+
+        /** State. */
+        private final double[] state;
+
+        /** State derivative. */
+        private final double[] stateDot;
+
+        /**
+         * Simple constructor.
+         *
+         * @param equation secondary differential equation
+         * @param firstIndex index to use for the first element in the complete arrays
+         */
+        SecondaryComponent(final SecondaryEquations equation, final int firstIndex) {
+            final int n = equation.getDimension();
+            this.equation = equation;
+            mapper = new EquationsMapper(firstIndex, n);
+            state = new double[n];
+            stateDot = new double[n];
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/FieldEquationsMapper.java b/src/main/java/org/apache/commons/math3/ode/FieldEquationsMapper.java
new file mode 100644
index 0000000..e28f5be
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/FieldEquationsMapper.java
@@ -0,0 +1,220 @@
+/*
+ * 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.math3.ode;
+
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.MathArrays;
+
+import java.io.Serializable;
+
+/**
+ * Class mapping the part of a complete state or derivative that pertains to a set of differential
+ * equations.
+ *
+ * <p>Instances of this class are guaranteed to be immutable.
+ *
+ * @see FieldExpandableODE
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+public class FieldEquationsMapper<T extends RealFieldElement<T>> implements Serializable {
+
+    /** Serializable UID. */
+    private static final long serialVersionUID = 20151114L;
+
+    /** Start indices of the components. */
+    private final int[] start;
+
+    /**
+     * Create a mapper by adding a new equation to another mapper.
+     *
+     * <p>The new equation will have index {@code mapper.}{@link #getNumberOfEquations()}, or 0 if
+     * {@code mapper} is null.
+     *
+     * @param mapper former mapper, with one equation less (null for first equation)
+     * @param dimension dimension of the equation state vector
+     */
+    FieldEquationsMapper(final FieldEquationsMapper<T> mapper, final int dimension) {
+        final int index = (mapper == null) ? 0 : mapper.getNumberOfEquations();
+        this.start = new int[index + 2];
+        if (mapper == null) {
+            start[0] = 0;
+        } else {
+            System.arraycopy(mapper.start, 0, start, 0, index + 1);
+        }
+        start[index + 1] = start[index] + dimension;
+    }
+
+    /**
+     * Get the number of equations mapped.
+     *
+     * @return number of equations mapped
+     */
+    public int getNumberOfEquations() {
+        return start.length - 1;
+    }
+
+    /**
+     * Return the dimension of the complete set of equations.
+     *
+     * <p>The complete set of equations correspond to the primary set plus all secondary sets.
+     *
+     * @return dimension of the complete set of equations
+     */
+    public int getTotalDimension() {
+        return start[start.length - 1];
+    }
+
+    /**
+     * Map a state to a complete flat array.
+     *
+     * @param state state to map
+     * @return flat array containing the mapped state, including primary and secondary components
+     */
+    public T[] mapState(final FieldODEState<T> state) {
+        final T[] y = MathArrays.buildArray(state.getTime().getField(), getTotalDimension());
+        int index = 0;
+        insertEquationData(index, state.getState(), y);
+        while (++index < getNumberOfEquations()) {
+            insertEquationData(index, state.getSecondaryState(index), y);
+        }
+        return y;
+    }
+
+    /**
+     * Map a state derivative to a complete flat array.
+     *
+     * @param state state to map
+     * @return flat array containing the mapped state derivative, including primary and secondary
+     *     components
+     */
+    public T[] mapDerivative(final FieldODEStateAndDerivative<T> state) {
+        final T[] yDot = MathArrays.buildArray(state.getTime().getField(), getTotalDimension());
+        int index = 0;
+        insertEquationData(index, state.getDerivative(), yDot);
+        while (++index < getNumberOfEquations()) {
+            insertEquationData(index, state.getSecondaryDerivative(index), yDot);
+        }
+        return yDot;
+    }
+
+    /**
+     * Map flat arrays to a state and derivative.
+     *
+     * @param t time
+     * @param y state array to map, including primary and secondary components
+     * @param yDot state derivative array to map, including primary and secondary components
+     * @return mapped state
+     * @exception DimensionMismatchException if an array does not match total dimension
+     */
+    public FieldODEStateAndDerivative<T> mapStateAndDerivative(
+            final T t, final T[] y, final T[] yDot) throws DimensionMismatchException {
+
+        if (y.length != getTotalDimension()) {
+            throw new DimensionMismatchException(y.length, getTotalDimension());
+        }
+
+        if (yDot.length != getTotalDimension()) {
+            throw new DimensionMismatchException(yDot.length, getTotalDimension());
+        }
+
+        final int n = getNumberOfEquations();
+        int index = 0;
+        final T[] state = extractEquationData(index, y);
+        final T[] derivative = extractEquationData(index, yDot);
+        if (n < 2) {
+            return new FieldODEStateAndDerivative<T>(t, state, derivative);
+        } else {
+            final T[][] secondaryState = MathArrays.buildArray(t.getField(), n - 1, -1);
+            final T[][] secondaryDerivative = MathArrays.buildArray(t.getField(), n - 1, -1);
+            while (++index < getNumberOfEquations()) {
+                secondaryState[index - 1] = extractEquationData(index, y);
+                secondaryDerivative[index - 1] = extractEquationData(index, yDot);
+            }
+            return new FieldODEStateAndDerivative<T>(
+                    t, state, derivative, secondaryState, secondaryDerivative);
+        }
+    }
+
+    /**
+     * Extract equation data from a complete state or derivative array.
+     *
+     * @param index index of the equation, must be between 0 included and {@link
+     *     #getNumberOfEquations()} (excluded)
+     * @param complete complete state or derivative array from which equation data should be
+     *     retrieved
+     * @return equation data
+     * @exception MathIllegalArgumentException if index is out of range
+     * @exception DimensionMismatchException if complete state has not enough elements
+     */
+    public T[] extractEquationData(final int index, final T[] complete)
+            throws MathIllegalArgumentException, DimensionMismatchException {
+        checkIndex(index);
+        final int begin = start[index];
+        final int end = start[index + 1];
+        if (complete.length < end) {
+            throw new DimensionMismatchException(complete.length, end);
+        }
+        final int dimension = end - begin;
+        final T[] equationData = MathArrays.buildArray(complete[0].getField(), dimension);
+        System.arraycopy(complete, begin, equationData, 0, dimension);
+        return equationData;
+    }
+
+    /**
+     * Insert equation data into a complete state or derivative array.
+     *
+     * @param index index of the equation, must be between 0 included and {@link
+     *     #getNumberOfEquations()} (excluded)
+     * @param equationData equation data to be inserted into the complete array
+     * @param complete placeholder where to put equation data (only the part corresponding to the
+     *     equation will be overwritten)
+     * @exception DimensionMismatchException if either array has not enough elements
+     */
+    public void insertEquationData(final int index, T[] equationData, T[] complete)
+            throws DimensionMismatchException {
+        checkIndex(index);
+        final int begin = start[index];
+        final int end = start[index + 1];
+        final int dimension = end - begin;
+        if (complete.length < end) {
+            throw new DimensionMismatchException(complete.length, end);
+        }
+        if (equationData.length != dimension) {
+            throw new DimensionMismatchException(equationData.length, dimension);
+        }
+        System.arraycopy(equationData, 0, complete, begin, dimension);
+    }
+
+    /**
+     * Check equation index.
+     *
+     * @param index index of the equation, must be between 0 included and {@link
+     *     #getNumberOfEquations()} (excluded)
+     * @exception MathIllegalArgumentException if index is out of range
+     */
+    private void checkIndex(final int index) throws MathIllegalArgumentException {
+        if (index < 0 || index > start.length - 2) {
+            throw new MathIllegalArgumentException(
+                    LocalizedFormats.ARGUMENT_OUTSIDE_DOMAIN, index, 0, start.length - 2);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/FieldExpandableODE.java b/src/main/java/org/apache/commons/math3/ode/FieldExpandableODE.java
new file mode 100644
index 0000000..a9f079e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/FieldExpandableODE.java
@@ -0,0 +1,148 @@
+/*
+ * 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.math3.ode;
+
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.util.MathArrays;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class represents a combined set of first order differential equations, with at least a
+ * primary set of equations expandable by some sets of secondary equations.
+ *
+ * <p>One typical use case is the computation of the Jacobian matrix for some ODE. In this case, the
+ * primary set of equations corresponds to the raw ODE, and we add to this set another bunch of
+ * secondary equations which represent the Jacobian matrix of the primary set.
+ *
+ * <p>We want the integrator to use <em>only</em> the primary set to estimate the errors and hence
+ * the step sizes. It should <em>not</em> use the secondary equations in this computation. The
+ * {@link FirstOrderFieldIntegrator integrator} will be able to know where the primary set ends and
+ * so where the secondary sets begin.
+ *
+ * @see FirstOrderFieldDifferentialEquations
+ * @see FieldSecondaryEquations
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+public class FieldExpandableODE<T extends RealFieldElement<T>> {
+
+    /** Primary differential equation. */
+    private final FirstOrderFieldDifferentialEquations<T> primary;
+
+    /** Components of the expandable ODE. */
+    private List<FieldSecondaryEquations<T>> components;
+
+    /** Mapper for all equations. */
+    private FieldEquationsMapper<T> mapper;
+
+    /**
+     * Build an expandable set from its primary ODE set.
+     *
+     * @param primary the primary set of differential equations to be integrated.
+     */
+    public FieldExpandableODE(final FirstOrderFieldDifferentialEquations<T> primary) {
+        this.primary = primary;
+        this.components = new ArrayList<FieldSecondaryEquations<T>>();
+        this.mapper = new FieldEquationsMapper<T>(null, primary.getDimension());
+    }
+
+    /**
+     * Get the mapper for the set of equations.
+     *
+     * @return mapper for the set of equations
+     */
+    public FieldEquationsMapper<T> getMapper() {
+        return mapper;
+    }
+
+    /**
+     * Add a set of secondary equations to be integrated along with the primary set.
+     *
+     * @param secondary secondary equations set
+     * @return index of the secondary equation in the expanded state, to be used as the parameter to
+     *     {@link FieldODEState#getSecondaryState(int)} and {@link
+     *     FieldODEStateAndDerivative#getSecondaryDerivative(int)} (beware index 0 corresponds to
+     *     main state, additional states start at 1)
+     */
+    public int addSecondaryEquations(final FieldSecondaryEquations<T> secondary) {
+
+        components.add(secondary);
+        mapper = new FieldEquationsMapper<T>(mapper, secondary.getDimension());
+
+        return components.size();
+    }
+
+    /**
+     * Initialize equations at the start of an ODE integration.
+     *
+     * @param t0 value of the independent <I>time</I> variable at integration start
+     * @param y0 array containing the value of the state vector at integration start
+     * @param finalTime target time for the integration
+     * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+     * @exception DimensionMismatchException if arrays dimensions do not match equations settings
+     */
+    public void init(final T t0, final T[] y0, final T finalTime) {
+
+        // initialize primary equations
+        int index = 0;
+        final T[] primary0 = mapper.extractEquationData(index, y0);
+        primary.init(t0, primary0, finalTime);
+
+        // initialize secondary equations
+        while (++index < mapper.getNumberOfEquations()) {
+            final T[] secondary0 = mapper.extractEquationData(index, y0);
+            components.get(index - 1).init(t0, primary0, secondary0, finalTime);
+        }
+    }
+
+    /**
+     * Get the current time derivative of the complete state vector.
+     *
+     * @param t current value of the independent <I>time</I> variable
+     * @param y array containing the current value of the complete state vector
+     * @return time derivative of the complete state vector
+     * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+     * @exception DimensionMismatchException if arrays dimensions do not match equations settings
+     */
+    public T[] computeDerivatives(final T t, final T[] y)
+            throws MaxCountExceededException, DimensionMismatchException {
+
+        final T[] yDot = MathArrays.buildArray(t.getField(), mapper.getTotalDimension());
+
+        // compute derivatives of the primary equations
+        int index = 0;
+        final T[] primaryState = mapper.extractEquationData(index, y);
+        final T[] primaryStateDot = primary.computeDerivatives(t, primaryState);
+        mapper.insertEquationData(index, primaryStateDot, yDot);
+
+        // Add contribution for secondary equations
+        while (++index < mapper.getNumberOfEquations()) {
+            final T[] componentState = mapper.extractEquationData(index, y);
+            final T[] componentStateDot =
+                    components
+                            .get(index - 1)
+                            .computeDerivatives(t, primaryState, primaryStateDot, componentState);
+            mapper.insertEquationData(index, componentStateDot, yDot);
+        }
+
+        return yDot;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/FieldODEState.java b/src/main/java/org/apache/commons/math3/ode/FieldODEState.java
new file mode 100644
index 0000000..baa7c96
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/FieldODEState.java
@@ -0,0 +1,155 @@
+/*
+ * 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.math3.ode;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * Container for time, main and secondary state vectors.
+ *
+ * @see FirstOrderFieldDifferentialEquations
+ * @see FieldSecondaryEquations
+ * @see FirstOrderFieldIntegrator
+ * @see FieldODEStateAndDerivative
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+public class FieldODEState<T extends RealFieldElement<T>> {
+
+    /** Time. */
+    private final T time;
+
+    /** Main state at time. */
+    private final T[] state;
+
+    /** Secondary state at time. */
+    private final T[][] secondaryState;
+
+    /**
+     * Simple constructor.
+     *
+     * <p>Calling this constructor is equivalent to call {@link #FieldODEState(RealFieldElement,
+     * RealFieldElement[], RealFieldElement[][]) FieldODEState(time, state, null)}.
+     *
+     * @param time time
+     * @param state state at time
+     */
+    public FieldODEState(T time, T[] state) {
+        this(time, state, null);
+    }
+
+    /**
+     * Simple constructor.
+     *
+     * @param time time
+     * @param state state at time
+     * @param secondaryState state at time (may be null)
+     */
+    public FieldODEState(T time, T[] state, T[][] secondaryState) {
+        this.time = time;
+        this.state = state.clone();
+        this.secondaryState = copy(time.getField(), secondaryState);
+    }
+
+    /**
+     * Copy a two-dimensions array.
+     *
+     * @param field field to which elements belong
+     * @param original original array (may be null)
+     * @return copied array or null if original array was null
+     */
+    protected T[][] copy(final Field<T> field, final T[][] original) {
+
+        // special handling of null arrays
+        if (original == null) {
+            return null;
+        }
+
+        // allocate the array
+        final T[][] copied = MathArrays.buildArray(field, original.length, -1);
+
+        // copy content
+        for (int i = 0; i < original.length; ++i) {
+            copied[i] = original[i].clone();
+        }
+
+        return copied;
+    }
+
+    /**
+     * Get time.
+     *
+     * @return time
+     */
+    public T getTime() {
+        return time;
+    }
+
+    /**
+     * Get main state dimension.
+     *
+     * @return main state dimension
+     */
+    public int getStateDimension() {
+        return state.length;
+    }
+
+    /**
+     * Get main state at time.
+     *
+     * @return main state at time
+     */
+    public T[] getState() {
+        return state.clone();
+    }
+
+    /**
+     * Get the number of secondary states.
+     *
+     * @return number of secondary states.
+     */
+    public int getNumberOfSecondaryStates() {
+        return secondaryState == null ? 0 : secondaryState.length;
+    }
+
+    /**
+     * Get secondary state dimension.
+     *
+     * @param index index of the secondary set as returned by {@link
+     *     FieldExpandableODE#addSecondaryEquations(FieldSecondaryEquations)} (beware index 0
+     *     corresponds to main state, additional states start at 1)
+     * @return secondary state dimension
+     */
+    public int getSecondaryStateDimension(final int index) {
+        return index == 0 ? state.length : secondaryState[index - 1].length;
+    }
+
+    /**
+     * Get secondary state at time.
+     *
+     * @param index index of the secondary set as returned by {@link
+     *     FieldExpandableODE#addSecondaryEquations(FieldSecondaryEquations)} (beware index 0
+     *     corresponds to main state, additional states start at 1)
+     * @return secondary state at time
+     */
+    public T[] getSecondaryState(final int index) {
+        return index == 0 ? state.clone() : secondaryState[index - 1].clone();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/FieldODEStateAndDerivative.java b/src/main/java/org/apache/commons/math3/ode/FieldODEStateAndDerivative.java
new file mode 100644
index 0000000..8759a77
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/FieldODEStateAndDerivative.java
@@ -0,0 +1,91 @@
+/*
+ * 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.math3.ode;
+
+import org.apache.commons.math3.RealFieldElement;
+
+/**
+ * Container for time, main and secondary state vectors as well as their derivatives.
+ *
+ * @see FirstOrderFieldDifferentialEquations
+ * @see FieldSecondaryEquations
+ * @see FirstOrderFieldIntegrator
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+public class FieldODEStateAndDerivative<T extends RealFieldElement<T>> extends FieldODEState<T> {
+
+    /** Derivative of the main state at time. */
+    private final T[] derivative;
+
+    /** Derivative of the secondary state at time. */
+    private final T[][] secondaryDerivative;
+
+    /**
+     * Simple constructor.
+     *
+     * <p>Calling this constructor is equivalent to call {@link
+     * #FieldODEStateAndDerivative(RealFieldElement, RealFieldElement[], RealFieldElement[],
+     * RealFieldElement[][], RealFieldElement[][]) FieldODEStateAndDerivative(time, state,
+     * derivative, null, null)}.
+     *
+     * @param time time
+     * @param state state at time
+     * @param derivative derivative of the state at time
+     */
+    public FieldODEStateAndDerivative(T time, T[] state, T[] derivative) {
+        this(time, state, derivative, null, null);
+    }
+
+    /**
+     * Simple constructor.
+     *
+     * @param time time
+     * @param state state at time
+     * @param derivative derivative of the state at time
+     * @param secondaryState state at time (may be null)
+     * @param secondaryDerivative derivative of the state at time (may be null)
+     */
+    public FieldODEStateAndDerivative(
+            T time, T[] state, T[] derivative, T[][] secondaryState, T[][] secondaryDerivative) {
+        super(time, state, secondaryState);
+        this.derivative = derivative.clone();
+        this.secondaryDerivative = copy(time.getField(), secondaryDerivative);
+    }
+
+    /**
+     * Get derivative of the main state at time.
+     *
+     * @return derivative of the main state at time
+     */
+    public T[] getDerivative() {
+        return derivative.clone();
+    }
+
+    /**
+     * Get derivative of the secondary state at time.
+     *
+     * @param index index of the secondary set as returned by {@link
+     *     FieldExpandableODE#addSecondaryEquations(FieldSecondaryEquations)} (beware index 0
+     *     corresponds to main state, additional states start at 1)
+     * @return derivative of the secondary state at time
+     */
+    public T[] getSecondaryDerivative(final int index) {
+        return index == 0 ? derivative.clone() : secondaryDerivative[index - 1].clone();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/FieldSecondaryEquations.java b/src/main/java/org/apache/commons/math3/ode/FieldSecondaryEquations.java
new file mode 100644
index 0000000..9c5384f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/FieldSecondaryEquations.java
@@ -0,0 +1,77 @@
+/*
+ * 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.math3.ode;
+
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+
+/**
+ * This interface allows users to add secondary differential equations to a primary set of
+ * differential equations.
+ *
+ * <p>In some cases users may need to integrate some problem-specific equations along with a primary
+ * set of differential equations. One example is optimal control where adjoined parameters linked to
+ * the minimized Hamiltonian must be integrated.
+ *
+ * <p>This interface allows users to add such equations to a primary set of {@link
+ * FirstOrderFieldDifferentialEquations first order differential equations} thanks to the {@link
+ * FieldExpandableODE#addSecondaryEquations(FieldSecondaryEquations)} method.
+ *
+ * @see FirstOrderFieldDifferentialEquations
+ * @see FieldExpandableODE
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+public interface FieldSecondaryEquations<T extends RealFieldElement<T>> {
+
+    /**
+     * Get the dimension of the secondary state parameters.
+     *
+     * @return dimension of the secondary state parameters
+     */
+    int getDimension();
+
+    /**
+     * Initialize equations at the start of an ODE integration.
+     *
+     * <p>This method is called once at the start of the integration. It may be used by the
+     * equations to initialize some internal data if needed.
+     *
+     * @param t0 value of the independent <I>time</I> variable at integration start
+     * @param primary0 array containing the value of the primary state vector at integration start
+     * @param secondary0 array containing the value of the secondary state vector at integration
+     *     start
+     * @param finalTime target time for the integration
+     */
+    void init(T t0, T[] primary0, T[] secondary0, T finalTime);
+
+    /**
+     * Compute the derivatives related to the secondary state parameters.
+     *
+     * @param t current value of the independent <I>time</I> variable
+     * @param primary array containing the current value of the primary state vector
+     * @param primaryDot array containing the derivative of the primary state vector
+     * @param secondary array containing the current value of the secondary state vector
+     * @return derivative of the secondary state vector
+     * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+     * @exception DimensionMismatchException if arrays dimensions do not match equations settings
+     */
+    T[] computeDerivatives(T t, T[] primary, T[] primaryDot, T[] secondary)
+            throws MaxCountExceededException, DimensionMismatchException;
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/FirstOrderConverter.java b/src/main/java/org/apache/commons/math3/ode/FirstOrderConverter.java
new file mode 100644
index 0000000..cfa0343
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/FirstOrderConverter.java
@@ -0,0 +1,109 @@
+/*
+ * 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.math3.ode;
+
+/**
+ * This class converts second order differential equations to first order ones.
+ *
+ * <p>This class is a wrapper around a {@link SecondOrderDifferentialEquations} which allow to use a
+ * {@link FirstOrderIntegrator} to integrate it.
+ *
+ * <p>The transformation is done by changing the n dimension state vector to a 2n dimension vector,
+ * where the first n components are the initial state variables and the n last components are their
+ * first time derivative. The first time derivative of this state vector then really contains both
+ * the first and second time derivative of the initial state vector, which can be handled by the
+ * underlying second order equations set.
+ *
+ * <p>One should be aware that the data is duplicated during the transformation process and that for
+ * each call to {@link #computeDerivatives computeDerivatives}, this wrapper does copy 4n scalars :
+ * 2n before the call to {@link SecondOrderDifferentialEquations#computeSecondDerivatives
+ * computeSecondDerivatives} in order to dispatch the y state vector into z and zDot, and 2n after
+ * the call to gather zDot and zDDot into yDot. Since the underlying problem by itself perhaps also
+ * needs to copy data and dispatch the arrays into domain objects, this has an impact on both memory
+ * and CPU usage. The only way to avoid this duplication is to perform the transformation at the
+ * problem level, i.e. to implement the problem as a first order one and then avoid using this
+ * class.
+ *
+ * @see FirstOrderIntegrator
+ * @see FirstOrderDifferentialEquations
+ * @see SecondOrderDifferentialEquations
+ * @since 1.2
+ */
+public class FirstOrderConverter implements FirstOrderDifferentialEquations {
+
+    /** Underlying second order equations set. */
+    private final SecondOrderDifferentialEquations equations;
+
+    /** second order problem dimension. */
+    private final int dimension;
+
+    /** state vector. */
+    private final double[] z;
+
+    /** first time derivative of the state vector. */
+    private final double[] zDot;
+
+    /** second time derivative of the state vector. */
+    private final double[] zDDot;
+
+    /**
+     * Simple constructor. Build a converter around a second order equations set.
+     *
+     * @param equations second order equations set to convert
+     */
+    public FirstOrderConverter(final SecondOrderDifferentialEquations equations) {
+        this.equations = equations;
+        dimension = equations.getDimension();
+        z = new double[dimension];
+        zDot = new double[dimension];
+        zDDot = new double[dimension];
+    }
+
+    /**
+     * Get the dimension of the problem.
+     *
+     * <p>The dimension of the first order problem is twice the dimension of the underlying second
+     * order problem.
+     *
+     * @return dimension of the problem
+     */
+    public int getDimension() {
+        return 2 * dimension;
+    }
+
+    /**
+     * Get the current time derivative of the state vector.
+     *
+     * @param t current value of the independent <I>time</I> variable
+     * @param y array containing the current value of the state vector
+     * @param yDot placeholder array where to put the time derivative of the state vector
+     */
+    public void computeDerivatives(final double t, final double[] y, final double[] yDot) {
+
+        // split the state vector in two
+        System.arraycopy(y, 0, z, 0, dimension);
+        System.arraycopy(y, dimension, zDot, 0, dimension);
+
+        // apply the underlying equations set
+        equations.computeSecondDerivatives(t, z, zDot, zDDot);
+
+        // build the result state derivative
+        System.arraycopy(zDot, 0, yDot, 0, dimension);
+        System.arraycopy(zDDot, 0, yDot, dimension, dimension);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/FirstOrderDifferentialEquations.java b/src/main/java/org/apache/commons/math3/ode/FirstOrderDifferentialEquations.java
new file mode 100644
index 0000000..28bf169
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/FirstOrderDifferentialEquations.java
@@ -0,0 +1,63 @@
+/*
+ * 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.math3.ode;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+
+/**
+ * This interface represents a first order differential equations set.
+ *
+ * <p>This interface should be implemented by all real first order differential equation problems
+ * before they can be handled by the integrators {@link FirstOrderIntegrator#integrate} method.
+ *
+ * <p>A first order differential equations problem, as seen by an integrator is the time derivative
+ * <code>dY/dt</code> of a state vector <code>Y</code>, both being one dimensional arrays. From the
+ * integrator point of view, this derivative depends only on the current time <code>t</code> and on
+ * the state vector <code>Y</code>.
+ *
+ * <p>For real problems, the derivative depends also on parameters that do not belong to the state
+ * vector (dynamical model constants for example). These constants are completely outside of the
+ * scope of this interface, the classes that implement it are allowed to handle them as they want.
+ *
+ * @see FirstOrderIntegrator
+ * @see FirstOrderConverter
+ * @see SecondOrderDifferentialEquations
+ * @since 1.2
+ */
+public interface FirstOrderDifferentialEquations {
+
+    /**
+     * Get the dimension of the problem.
+     *
+     * @return dimension of the problem
+     */
+    int getDimension();
+
+    /**
+     * Get the current time derivative of the state vector.
+     *
+     * @param t current value of the independent <I>time</I> variable
+     * @param y array containing the current value of the state vector
+     * @param yDot placeholder array where to put the time derivative of the state vector
+     * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+     * @exception DimensionMismatchException if arrays dimensions do not match equations settings
+     */
+    void computeDerivatives(double t, double[] y, double[] yDot)
+            throws MaxCountExceededException, DimensionMismatchException;
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/FirstOrderFieldDifferentialEquations.java b/src/main/java/org/apache/commons/math3/ode/FirstOrderFieldDifferentialEquations.java
new file mode 100644
index 0000000..a430067
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/FirstOrderFieldDifferentialEquations.java
@@ -0,0 +1,70 @@
+/*
+ * 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.math3.ode;
+
+import org.apache.commons.math3.RealFieldElement;
+
+/**
+ * This interface represents a first order differential equations set.
+ *
+ * <p>This interface should be implemented by all real first order differential equation problems
+ * before they can be handled by the integrators {@link FirstOrderIntegrator#integrate} method.
+ *
+ * <p>A first order differential equations problem, as seen by an integrator is the time derivative
+ * <code>dY/dt</code> of a state vector <code>Y</code>, both being one dimensional arrays. From the
+ * integrator point of view, this derivative depends only on the current time <code>t</code> and on
+ * the state vector <code>Y</code>.
+ *
+ * <p>For real problems, the derivative depends also on parameters that do not belong to the state
+ * vector (dynamical model constants for example). These constants are completely outside of the
+ * scope of this interface, the classes that implement it are allowed to handle them as they want.
+ *
+ * @see FirstOrderFieldIntegrator
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+public interface FirstOrderFieldDifferentialEquations<T extends RealFieldElement<T>> {
+
+    /**
+     * Get the dimension of the problem.
+     *
+     * @return dimension of the problem
+     */
+    int getDimension();
+
+    /**
+     * Initialize equations at the start of an ODE integration.
+     *
+     * <p>This method is called once at the start of the integration. It may be used by the
+     * equations to initialize some internal data if needed.
+     *
+     * @param t0 value of the independent <I>time</I> variable at integration start
+     * @param y0 array containing the value of the state vector at integration start
+     * @param finalTime target time for the integration
+     */
+    void init(T t0, T[] y0, T finalTime);
+
+    /**
+     * Get the current time derivative of the state vector.
+     *
+     * @param t current value of the independent <I>time</I> variable
+     * @param y array containing the current value of the state vector
+     * @return time derivative of the state vector
+     */
+    T[] computeDerivatives(T t, T[] y);
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/FirstOrderFieldIntegrator.java b/src/main/java/org/apache/commons/math3/ode/FirstOrderFieldIntegrator.java
new file mode 100644
index 0000000..8d26495
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/FirstOrderFieldIntegrator.java
@@ -0,0 +1,217 @@
+/*
+ * 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.math3.ode;
+
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.analysis.solvers.BracketedRealFieldUnivariateSolver;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NoBracketingException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.ode.events.FieldEventHandler;
+import org.apache.commons.math3.ode.sampling.FieldStepHandler;
+
+import java.util.Collection;
+
+/**
+ * This interface represents a first order integrator for differential equations.
+ *
+ * <p>The classes which are devoted to solve first order differential equations should implement
+ * this interface. The problems which can be handled should implement the {@link
+ * FirstOrderDifferentialEquations} interface.
+ *
+ * @see FirstOrderFieldDifferentialEquations
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+public interface FirstOrderFieldIntegrator<T extends RealFieldElement<T>> {
+
+    /**
+     * Get the name of the method.
+     *
+     * @return name of the method
+     */
+    String getName();
+
+    /**
+     * Add a step handler to this integrator.
+     *
+     * <p>The handler will be called by the integrator for each accepted step.
+     *
+     * @param handler handler for the accepted steps
+     * @see #getStepHandlers()
+     * @see #clearStepHandlers()
+     */
+    void addStepHandler(FieldStepHandler<T> handler);
+
+    /**
+     * Get all the step handlers that have been added to the integrator.
+     *
+     * @return an unmodifiable collection of the added events handlers
+     * @see #addStepHandler(FieldStepHandler)
+     * @see #clearStepHandlers()
+     */
+    Collection<FieldStepHandler<T>> getStepHandlers();
+
+    /**
+     * Remove all the step handlers that have been added to the integrator.
+     *
+     * @see #addStepHandler(FieldStepHandler)
+     * @see #getStepHandlers()
+     */
+    void clearStepHandlers();
+
+    /**
+     * Add an event handler to the integrator.
+     *
+     * <p>The default solver is a 5<sup>th</sup> order {@link
+     * org.apache.commons.math3.analysis.solvers.FieldBracketingNthOrderBrentSolver}.
+     *
+     * @param handler event handler
+     * @param maxCheckInterval maximal time interval between switching function checks (this
+     *     interval prevents missing sign changes in case the integration steps becomes very large)
+     * @param convergence convergence threshold in the event time search
+     * @param maxIterationCount upper limit of the iteration count in the event time search events.
+     * @see #addEventHandler(FieldEventHandler, double, double, int,
+     *     org.apache.commons.math3.analysis.solvers.BracketedRealFieldUnivariateSolver)
+     * @see #getEventHandlers()
+     * @see #clearEventHandlers()
+     */
+    void addEventHandler(
+            FieldEventHandler<T> handler,
+            double maxCheckInterval,
+            double convergence,
+            int maxIterationCount);
+
+    /**
+     * Add an event handler to the integrator.
+     *
+     * @param handler event handler
+     * @param maxCheckInterval maximal time interval between switching function checks (this
+     *     interval prevents missing sign changes in case the integration steps becomes very large)
+     * @param convergence convergence threshold in the event time search
+     * @param maxIterationCount upper limit of the iteration count in the event time search events.
+     * @param solver solver to use to locate the event
+     * @see #addEventHandler(FieldEventHandler, double, double, int)
+     * @see #getEventHandlers()
+     * @see #clearEventHandlers()
+     */
+    void addEventHandler(
+            FieldEventHandler<T> handler,
+            double maxCheckInterval,
+            double convergence,
+            int maxIterationCount,
+            BracketedRealFieldUnivariateSolver<T> solver);
+
+    /**
+     * Get all the event handlers that have been added to the integrator.
+     *
+     * @return an unmodifiable collection of the added events handlers
+     * @see #addEventHandler(FieldEventHandler, double, double, int)
+     * @see #clearEventHandlers()
+     */
+    Collection<FieldEventHandler<T>> getEventHandlers();
+
+    /**
+     * Remove all the event handlers that have been added to the integrator.
+     *
+     * @see #addEventHandler(FieldEventHandler, double, double, int)
+     * @see #getEventHandlers()
+     */
+    void clearEventHandlers();
+
+    /**
+     * Get the current value of the step start time t<sub>i</sub>.
+     *
+     * <p>This method can be called during integration (typically by the object implementing the
+     * {@link FirstOrderDifferentialEquations differential equations} problem) if the value of the
+     * current step that is attempted is needed.
+     *
+     * <p>The result is undefined if the method is called outside of calls to <code>integrate</code>
+     * .
+     *
+     * @return current value of the state at step start time t<sub>i</sub>
+     */
+    FieldODEStateAndDerivative<T> getCurrentStepStart();
+
+    /**
+     * Get the current signed value of the integration stepsize.
+     *
+     * <p>This method can be called during integration (typically by the object implementing the
+     * {@link FirstOrderDifferentialEquations differential equations} problem) if the signed value
+     * of the current stepsize that is tried is needed.
+     *
+     * <p>The result is undefined if the method is called outside of calls to <code>integrate</code>
+     * .
+     *
+     * @return current signed value of the stepsize
+     */
+    T getCurrentSignedStepsize();
+
+    /**
+     * Set the maximal number of differential equations function evaluations.
+     *
+     * <p>The purpose of this method is to avoid infinite loops which can occur for example when
+     * stringent error constraints are set or when lots of discrete events are triggered, thus
+     * leading to many rejected steps.
+     *
+     * @param maxEvaluations maximal number of function evaluations (negative values are silently
+     *     converted to maximal integer value, thus representing almost unlimited evaluations)
+     */
+    void setMaxEvaluations(int maxEvaluations);
+
+    /**
+     * Get the maximal number of functions evaluations.
+     *
+     * @return maximal number of functions evaluations
+     */
+    int getMaxEvaluations();
+
+    /**
+     * Get the number of evaluations of the differential equations function.
+     *
+     * <p>The number of evaluations corresponds to the last call to the <code>integrate</code>
+     * method. It is 0 if the method has not been called yet.
+     *
+     * @return number of evaluations of the differential equations function
+     */
+    int getEvaluations();
+
+    /**
+     * Integrate the differential equations up to the given time.
+     *
+     * <p>This method solves an Initial Value Problem (IVP).
+     *
+     * <p>Since this method stores some internal state variables made available in its public
+     * interface during integration ({@link #getCurrentSignedStepsize()}), it is <em>not</em>
+     * thread-safe.
+     *
+     * @param equations differential equations to integrate
+     * @param initialState initial state (time, primary and secondary state vectors)
+     * @param finalTime target time for the integration (can be set to a value smaller than {@code
+     *     t0} for backward integration)
+     * @return final state, its time will be the same as {@code finalTime} if integration reached
+     *     its target, but may be different if some {@link
+     *     org.apache.commons.math3.ode.events.FieldEventHandler} stops it at some point.
+     * @exception NumberIsTooSmallException if integration step is too small
+     * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+     * @exception NoBracketingException if the location of an event cannot be bracketed
+     */
+    FieldODEStateAndDerivative<T> integrate(
+            FieldExpandableODE<T> equations, FieldODEState<T> initialState, T finalTime)
+            throws NumberIsTooSmallException, MaxCountExceededException, NoBracketingException;
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/FirstOrderIntegrator.java b/src/main/java/org/apache/commons/math3/ode/FirstOrderIntegrator.java
new file mode 100644
index 0000000..786a41b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/FirstOrderIntegrator.java
@@ -0,0 +1,69 @@
+/*
+ * 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.math3.ode;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NoBracketingException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+
+/**
+ * This interface represents a first order integrator for differential equations.
+ *
+ * <p>The classes which are devoted to solve first order differential equations should implement
+ * this interface. The problems which can be handled should implement the {@link
+ * FirstOrderDifferentialEquations} interface.
+ *
+ * @see FirstOrderDifferentialEquations
+ * @see org.apache.commons.math3.ode.sampling.StepHandler
+ * @see org.apache.commons.math3.ode.events.EventHandler
+ * @since 1.2
+ */
+public interface FirstOrderIntegrator extends ODEIntegrator {
+
+    /**
+     * Integrate the differential equations up to the given time.
+     *
+     * <p>This method solves an Initial Value Problem (IVP).
+     *
+     * <p>Since this method stores some internal state variables made available in its public
+     * interface during integration ({@link #getCurrentSignedStepsize()}), it is <em>not</em>
+     * thread-safe.
+     *
+     * @param equations differential equations to integrate
+     * @param t0 initial time
+     * @param y0 initial value of the state vector at t0
+     * @param t target time for the integration (can be set to a value smaller than <code>t0</code>
+     *     for backward integration)
+     * @param y placeholder where to put the state vector at each successful step (and hence at the
+     *     end of integration), can be the same object as y0
+     * @return stop time, will be the same as target time if integration reached its target, but may
+     *     be different if some {@link org.apache.commons.math3.ode.events.EventHandler} stops it at
+     *     some point.
+     * @exception DimensionMismatchException if arrays dimension do not match equations settings
+     * @exception NumberIsTooSmallException if integration step is too small
+     * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+     * @exception NoBracketingException if the location of an event cannot be bracketed
+     */
+    double integrate(
+            FirstOrderDifferentialEquations equations, double t0, double[] y0, double t, double[] y)
+            throws DimensionMismatchException,
+                    NumberIsTooSmallException,
+                    MaxCountExceededException,
+                    NoBracketingException;
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/JacobianMatrices.java b/src/main/java/org/apache/commons/math3/ode/JacobianMatrices.java
new file mode 100644
index 0000000..eecdce7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/JacobianMatrices.java
@@ -0,0 +1,510 @@
+/*
+ * 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.math3.ode;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * This class defines a set of {@link SecondaryEquations secondary equations} to compute the
+ * Jacobian matrices with respect to the initial state vector and, if any, to some parameters of the
+ * primary ODE set.
+ *
+ * <p>It is intended to be packed into an {@link ExpandableStatefulODE} in conjunction with a
+ * primary set of ODE, which may be:
+ *
+ * <ul>
+ *   <li>a {@link FirstOrderDifferentialEquations}
+ *   <li>a {@link MainStateJacobianProvider}
+ * </ul>
+ *
+ * In order to compute Jacobian matrices with respect to some parameters of the primary ODE set, the
+ * following parameter Jacobian providers may be set:
+ *
+ * <ul>
+ *   <li>a {@link ParameterJacobianProvider}
+ *   <li>a {@link ParameterizedODE}
+ * </ul>
+ *
+ * @see ExpandableStatefulODE
+ * @see FirstOrderDifferentialEquations
+ * @see MainStateJacobianProvider
+ * @see ParameterJacobianProvider
+ * @see ParameterizedODE
+ * @since 3.0
+ */
+public class JacobianMatrices {
+
+    /** Expandable first order differential equation. */
+    private ExpandableStatefulODE efode;
+
+    /** Index of the instance in the expandable set. */
+    private int index;
+
+    /** FODE with exact primary Jacobian computation skill. */
+    private MainStateJacobianProvider jode;
+
+    /** FODE without exact parameter Jacobian computation skill. */
+    private ParameterizedODE pode;
+
+    /** Main state vector dimension. */
+    private int stateDim;
+
+    /** Selected parameters for parameter Jacobian computation. */
+    private ParameterConfiguration[] selectedParameters;
+
+    /** FODE with exact parameter Jacobian computation skill. */
+    private List<ParameterJacobianProvider> jacobianProviders;
+
+    /** Parameters dimension. */
+    private int paramDim;
+
+    /** Boolean for selected parameters consistency. */
+    private boolean dirtyParameter;
+
+    /** State and parameters Jacobian matrices in a row. */
+    private double[] matricesData;
+
+    /**
+     * Simple constructor for a secondary equations set computing Jacobian matrices.
+     *
+     * <p>Parameters must belong to the supported ones given by {@link
+     * Parameterizable#getParametersNames()}, so the primary set of differential equations must be
+     * {@link Parameterizable}.
+     *
+     * <p>Note that each selection clears the previous selected parameters.
+     *
+     * @param fode the primary first order differential equations set to extend
+     * @param hY step used for finite difference computation with respect to state vector
+     * @param parameters parameters to consider for Jacobian matrices processing (may be null if
+     *     parameters Jacobians is not desired)
+     * @exception DimensionMismatchException if there is a dimension mismatch between the steps
+     *     array {@code hY} and the equation dimension
+     */
+    public JacobianMatrices(
+            final FirstOrderDifferentialEquations fode,
+            final double[] hY,
+            final String... parameters)
+            throws DimensionMismatchException {
+        this(new MainStateJacobianWrapper(fode, hY), parameters);
+    }
+
+    /**
+     * Simple constructor for a secondary equations set computing Jacobian matrices.
+     *
+     * <p>Parameters must belong to the supported ones given by {@link
+     * Parameterizable#getParametersNames()}, so the primary set of differential equations must be
+     * {@link Parameterizable}.
+     *
+     * <p>Note that each selection clears the previous selected parameters.
+     *
+     * @param jode the primary first order differential equations set to extend
+     * @param parameters parameters to consider for Jacobian matrices processing (may be null if
+     *     parameters Jacobians is not desired)
+     */
+    public JacobianMatrices(final MainStateJacobianProvider jode, final String... parameters) {
+
+        this.efode = null;
+        this.index = -1;
+
+        this.jode = jode;
+        this.pode = null;
+
+        this.stateDim = jode.getDimension();
+
+        if (parameters == null) {
+            selectedParameters = null;
+            paramDim = 0;
+        } else {
+            this.selectedParameters = new ParameterConfiguration[parameters.length];
+            for (int i = 0; i < parameters.length; ++i) {
+                selectedParameters[i] = new ParameterConfiguration(parameters[i], Double.NaN);
+            }
+            paramDim = parameters.length;
+        }
+        this.dirtyParameter = false;
+
+        this.jacobianProviders = new ArrayList<ParameterJacobianProvider>();
+
+        // set the default initial state Jacobian to the identity
+        // and the default initial parameters Jacobian to the null matrix
+        matricesData = new double[(stateDim + paramDim) * stateDim];
+        for (int i = 0; i < stateDim; ++i) {
+            matricesData[i * (stateDim + 1)] = 1.0;
+        }
+    }
+
+    /**
+     * Register the variational equations for the Jacobians matrices to the expandable set.
+     *
+     * @param expandable expandable set into which variational equations should be registered
+     * @throws DimensionMismatchException if the dimension of the partial state does not match the
+     *     selected equations set dimension
+     * @exception MismatchedEquations if the primary set of the expandable set does not match the
+     *     one used to build the instance
+     * @see ExpandableStatefulODE#addSecondaryEquations(SecondaryEquations)
+     */
+    public void registerVariationalEquations(final ExpandableStatefulODE expandable)
+            throws DimensionMismatchException, MismatchedEquations {
+
+        // safety checks
+        final FirstOrderDifferentialEquations ode =
+                (jode instanceof MainStateJacobianWrapper)
+                        ? ((MainStateJacobianWrapper) jode).ode
+                        : jode;
+        if (expandable.getPrimary() != ode) {
+            throw new MismatchedEquations();
+        }
+
+        efode = expandable;
+        index = efode.addSecondaryEquations(new JacobiansSecondaryEquations());
+        efode.setSecondaryState(index, matricesData);
+    }
+
+    /**
+     * Add a parameter Jacobian provider.
+     *
+     * @param provider the parameter Jacobian provider to compute exactly the parameter Jacobian
+     *     matrix
+     */
+    public void addParameterJacobianProvider(final ParameterJacobianProvider provider) {
+        jacobianProviders.add(provider);
+    }
+
+    /**
+     * Set a parameter Jacobian provider.
+     *
+     * @param parameterizedOde the parameterized ODE to compute the parameter Jacobian matrix using
+     *     finite differences
+     */
+    public void setParameterizedODE(final ParameterizedODE parameterizedOde) {
+        this.pode = parameterizedOde;
+        dirtyParameter = true;
+    }
+
+    /**
+     * Set the step associated to a parameter in order to compute by finite difference the Jacobian
+     * matrix.
+     *
+     * <p>Needed if and only if the primary ODE set is a {@link ParameterizedODE}.
+     *
+     * <p>Given a non zero parameter value pval for the parameter, a reasonable value for such a
+     * step is {@code pval * FastMath.sqrt(Precision.EPSILON)}.
+     *
+     * <p>A zero value for such a step doesn't enable to compute the parameter Jacobian matrix.
+     *
+     * @param parameter parameter to consider for Jacobian processing
+     * @param hP step for Jacobian finite difference computation w.r.t. the specified parameter
+     * @see ParameterizedODE
+     * @exception UnknownParameterException if the parameter is not supported
+     */
+    public void setParameterStep(final String parameter, final double hP)
+            throws UnknownParameterException {
+
+        for (ParameterConfiguration param : selectedParameters) {
+            if (parameter.equals(param.getParameterName())) {
+                param.setHP(hP);
+                dirtyParameter = true;
+                return;
+            }
+        }
+
+        throw new UnknownParameterException(parameter);
+    }
+
+    /**
+     * Set the initial value of the Jacobian matrix with respect to state.
+     *
+     * <p>If this method is not called, the initial value of the Jacobian matrix with respect to
+     * state is set to identity.
+     *
+     * @param dYdY0 initial Jacobian matrix w.r.t. state
+     * @exception DimensionMismatchException if matrix dimensions are incorrect
+     */
+    public void setInitialMainStateJacobian(final double[][] dYdY0)
+            throws DimensionMismatchException {
+
+        // Check dimensions
+        checkDimension(stateDim, dYdY0);
+        checkDimension(stateDim, dYdY0[0]);
+
+        // store the matrix in row major order as a single dimension array
+        int i = 0;
+        for (final double[] row : dYdY0) {
+            System.arraycopy(row, 0, matricesData, i, stateDim);
+            i += stateDim;
+        }
+
+        if (efode != null) {
+            efode.setSecondaryState(index, matricesData);
+        }
+    }
+
+    /**
+     * Set the initial value of a column of the Jacobian matrix with respect to one parameter.
+     *
+     * <p>If this method is not called for some parameter, the initial value of the column of the
+     * Jacobian matrix with respect to this parameter is set to zero.
+     *
+     * @param pName parameter name
+     * @param dYdP initial Jacobian column vector with respect to the parameter
+     * @exception UnknownParameterException if a parameter is not supported
+     * @throws DimensionMismatchException if the column vector does not match state dimension
+     */
+    public void setInitialParameterJacobian(final String pName, final double[] dYdP)
+            throws UnknownParameterException, DimensionMismatchException {
+
+        // Check dimensions
+        checkDimension(stateDim, dYdP);
+
+        // store the column in a global single dimension array
+        int i = stateDim * stateDim;
+        for (ParameterConfiguration param : selectedParameters) {
+            if (pName.equals(param.getParameterName())) {
+                System.arraycopy(dYdP, 0, matricesData, i, stateDim);
+                if (efode != null) {
+                    efode.setSecondaryState(index, matricesData);
+                }
+                return;
+            }
+            i += stateDim;
+        }
+
+        throw new UnknownParameterException(pName);
+    }
+
+    /**
+     * Get the current value of the Jacobian matrix with respect to state.
+     *
+     * @param dYdY0 current Jacobian matrix with respect to state.
+     */
+    public void getCurrentMainSetJacobian(final double[][] dYdY0) {
+
+        // get current state for this set of equations from the expandable fode
+        double[] p = efode.getSecondaryState(index);
+
+        int j = 0;
+        for (int i = 0; i < stateDim; i++) {
+            System.arraycopy(p, j, dYdY0[i], 0, stateDim);
+            j += stateDim;
+        }
+    }
+
+    /**
+     * Get the current value of the Jacobian matrix with respect to one parameter.
+     *
+     * @param pName name of the parameter for the computed Jacobian matrix
+     * @param dYdP current Jacobian matrix with respect to the named parameter
+     */
+    public void getCurrentParameterJacobian(String pName, final double[] dYdP) {
+
+        // get current state for this set of equations from the expandable fode
+        double[] p = efode.getSecondaryState(index);
+
+        int i = stateDim * stateDim;
+        for (ParameterConfiguration param : selectedParameters) {
+            if (param.getParameterName().equals(pName)) {
+                System.arraycopy(p, i, dYdP, 0, stateDim);
+                return;
+            }
+            i += stateDim;
+        }
+    }
+
+    /**
+     * Check array dimensions.
+     *
+     * @param expected expected dimension
+     * @param array (may be null if expected is 0)
+     * @throws DimensionMismatchException if the array dimension does not match the expected one
+     */
+    private void checkDimension(final int expected, final Object array)
+            throws DimensionMismatchException {
+        int arrayDimension = (array == null) ? 0 : Array.getLength(array);
+        if (arrayDimension != expected) {
+            throw new DimensionMismatchException(arrayDimension, expected);
+        }
+    }
+
+    /**
+     * Local implementation of secondary equations.
+     *
+     * <p>This class is an inner class to ensure proper scheduling of calls by forcing the use of
+     * {@link JacobianMatrices#registerVariationalEquations(ExpandableStatefulODE)}.
+     */
+    private class JacobiansSecondaryEquations implements SecondaryEquations {
+
+        /** {@inheritDoc} */
+        public int getDimension() {
+            return stateDim * (stateDim + paramDim);
+        }
+
+        /** {@inheritDoc} */
+        public void computeDerivatives(
+                final double t,
+                final double[] y,
+                final double[] yDot,
+                final double[] z,
+                final double[] zDot)
+                throws MaxCountExceededException, DimensionMismatchException {
+
+            // Lazy initialization
+            if (dirtyParameter && (paramDim != 0)) {
+                jacobianProviders.add(new ParameterJacobianWrapper(jode, pode, selectedParameters));
+                dirtyParameter = false;
+            }
+
+            // variational equations:
+            // from d[dy/dt]/dy0 and d[dy/dt]/dp to d[dy/dy0]/dt and d[dy/dp]/dt
+
+            // compute Jacobian matrix with respect to primary state
+            double[][] dFdY = new double[stateDim][stateDim];
+            jode.computeMainStateJacobian(t, y, yDot, dFdY);
+
+            // Dispatch Jacobian matrix in the compound secondary state vector
+            for (int i = 0; i < stateDim; ++i) {
+                final double[] dFdYi = dFdY[i];
+                for (int j = 0; j < stateDim; ++j) {
+                    double s = 0;
+                    final int startIndex = j;
+                    int zIndex = startIndex;
+                    for (int l = 0; l < stateDim; ++l) {
+                        s += dFdYi[l] * z[zIndex];
+                        zIndex += stateDim;
+                    }
+                    zDot[startIndex + i * stateDim] = s;
+                }
+            }
+
+            if (paramDim != 0) {
+                // compute Jacobian matrices with respect to parameters
+                double[] dFdP = new double[stateDim];
+                int startIndex = stateDim * stateDim;
+                for (ParameterConfiguration param : selectedParameters) {
+                    boolean found = false;
+                    for (int k = 0; (!found) && (k < jacobianProviders.size()); ++k) {
+                        final ParameterJacobianProvider provider = jacobianProviders.get(k);
+                        if (provider.isSupported(param.getParameterName())) {
+                            provider.computeParameterJacobian(
+                                    t, y, yDot, param.getParameterName(), dFdP);
+                            for (int i = 0; i < stateDim; ++i) {
+                                final double[] dFdYi = dFdY[i];
+                                int zIndex = startIndex;
+                                double s = dFdP[i];
+                                for (int l = 0; l < stateDim; ++l) {
+                                    s += dFdYi[l] * z[zIndex];
+                                    zIndex++;
+                                }
+                                zDot[startIndex + i] = s;
+                            }
+                            found = true;
+                        }
+                    }
+                    if (!found) {
+                        Arrays.fill(zDot, startIndex, startIndex + stateDim, 0.0);
+                    }
+                    startIndex += stateDim;
+                }
+            }
+        }
+    }
+
+    /**
+     * Wrapper class to compute jacobian matrices by finite differences for ODE which do not compute
+     * them by themselves.
+     */
+    private static class MainStateJacobianWrapper implements MainStateJacobianProvider {
+
+        /**
+         * Raw ODE without jacobians computation skill to be wrapped into a
+         * MainStateJacobianProvider.
+         */
+        private final FirstOrderDifferentialEquations ode;
+
+        /** Steps for finite difference computation of the jacobian df/dy w.r.t. state. */
+        private final double[] hY;
+
+        /**
+         * Wrap a {@link FirstOrderDifferentialEquations} into a {@link MainStateJacobianProvider}.
+         *
+         * @param ode original ODE problem, without jacobians computation skill
+         * @param hY step sizes to compute the jacobian df/dy
+         * @exception DimensionMismatchException if there is a dimension mismatch between the steps
+         *     array {@code hY} and the equation dimension
+         */
+        MainStateJacobianWrapper(final FirstOrderDifferentialEquations ode, final double[] hY)
+                throws DimensionMismatchException {
+            this.ode = ode;
+            this.hY = hY.clone();
+            if (hY.length != ode.getDimension()) {
+                throw new DimensionMismatchException(ode.getDimension(), hY.length);
+            }
+        }
+
+        /** {@inheritDoc} */
+        public int getDimension() {
+            return ode.getDimension();
+        }
+
+        /** {@inheritDoc} */
+        public void computeDerivatives(double t, double[] y, double[] yDot)
+                throws MaxCountExceededException, DimensionMismatchException {
+            ode.computeDerivatives(t, y, yDot);
+        }
+
+        /** {@inheritDoc} */
+        public void computeMainStateJacobian(double t, double[] y, double[] yDot, double[][] dFdY)
+                throws MaxCountExceededException, DimensionMismatchException {
+
+            final int n = ode.getDimension();
+            final double[] tmpDot = new double[n];
+
+            for (int j = 0; j < n; ++j) {
+                final double savedYj = y[j];
+                y[j] += hY[j];
+                ode.computeDerivatives(t, y, tmpDot);
+                for (int i = 0; i < n; ++i) {
+                    dFdY[i][j] = (tmpDot[i] - yDot[i]) / hY[j];
+                }
+                y[j] = savedYj;
+            }
+        }
+    }
+
+    /**
+     * Special exception for equations mismatch.
+     *
+     * @since 3.1
+     */
+    public static class MismatchedEquations extends MathIllegalArgumentException {
+
+        /** Serializable UID. */
+        private static final long serialVersionUID = 20120902L;
+
+        /** Simple constructor. */
+        public MismatchedEquations() {
+            super(LocalizedFormats.UNMATCHED_ODE_IN_EXPANDED_SET);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/MainStateJacobianProvider.java b/src/main/java/org/apache/commons/math3/ode/MainStateJacobianProvider.java
new file mode 100644
index 0000000..b1ac0ee
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/MainStateJacobianProvider.java
@@ -0,0 +1,45 @@
+/*
+ * 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.math3.ode;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+
+/**
+ * Interface expanding {@link FirstOrderDifferentialEquations first order differential equations} in
+ * order to compute exactly the main state jacobian matrix for {@link JacobianMatrices partial
+ * derivatives equations}.
+ *
+ * @since 3.0
+ */
+public interface MainStateJacobianProvider extends FirstOrderDifferentialEquations {
+
+    /**
+     * Compute the jacobian matrix of ODE with respect to main state.
+     *
+     * @param t current value of the independent <I>time</I> variable
+     * @param y array containing the current value of the main state vector
+     * @param yDot array containing the current value of the time derivative of the main state
+     *     vector
+     * @param dFdY placeholder array where to put the jacobian matrix of the ODE w.r.t. the main
+     *     state vector
+     * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+     * @exception DimensionMismatchException if arrays dimensions do not match equations settings
+     */
+    void computeMainStateJacobian(double t, double[] y, double[] yDot, double[][] dFdY)
+            throws MaxCountExceededException, DimensionMismatchException;
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/MultistepFieldIntegrator.java b/src/main/java/org/apache/commons/math3/ode/MultistepFieldIntegrator.java
new file mode 100644
index 0000000..44a8b81
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/MultistepFieldIntegrator.java
@@ -0,0 +1,482 @@
+/*
+ * 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.math3.ode;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NoBracketingException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.linear.Array2DRowFieldMatrix;
+import org.apache.commons.math3.ode.nonstiff.AdaptiveStepsizeFieldIntegrator;
+import org.apache.commons.math3.ode.nonstiff.DormandPrince853FieldIntegrator;
+import org.apache.commons.math3.ode.sampling.FieldStepHandler;
+import org.apache.commons.math3.ode.sampling.FieldStepInterpolator;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * This class is the base class for multistep integrators for Ordinary Differential Equations.
+ *
+ * <p>We define scaled derivatives s<sub>i</sub>(n) at step n as:
+ *
+ * <pre>
+ * s<sub>1</sub>(n) = h y'<sub>n</sub> for first derivative
+ * s<sub>2</sub>(n) = h<sup>2</sup>/2 y''<sub>n</sub> for second derivative
+ * s<sub>3</sub>(n) = h<sup>3</sup>/6 y'''<sub>n</sub> for third derivative
+ * ...
+ * s<sub>k</sub>(n) = h<sup>k</sup>/k! y<sup>(k)</sup><sub>n</sub> for k<sup>th</sup> derivative
+ * </pre>
+ *
+ * <p>Rather than storing several previous steps separately, this implementation uses the Nordsieck
+ * vector with higher degrees scaled derivatives all taken at the same step (y<sub>n</sub>,
+ * s<sub>1</sub>(n) and r<sub>n</sub>) where r<sub>n</sub> is defined as:
+ *
+ * <pre>
+ * r<sub>n</sub> = [ s<sub>2</sub>(n), s<sub>3</sub>(n) ... s<sub>k</sub>(n) ]<sup>T</sup>
+ * </pre>
+ *
+ * (we omit the k index in the notation for clarity)
+ *
+ * <p>Multistep integrators with Nordsieck representation are highly sensitive to large step changes
+ * because when the step is multiplied by factor a, the k<sup>th</sup> component of the Nordsieck
+ * vector is multiplied by a<sup>k</sup> and the last components are the least accurate ones. The
+ * default max growth factor is therefore set to a quite low value: 2<sup>1/order</sup>.
+ *
+ * @see org.apache.commons.math3.ode.nonstiff.AdamsBashforthFieldIntegrator
+ * @see org.apache.commons.math3.ode.nonstiff.AdamsMoultonFieldIntegrator
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+public abstract class MultistepFieldIntegrator<T extends RealFieldElement<T>>
+        extends AdaptiveStepsizeFieldIntegrator<T> {
+
+    /** First scaled derivative (h y'). */
+    protected T[] scaled;
+
+    /**
+     * Nordsieck matrix of the higher scaled derivatives.
+     *
+     * <p>(h<sup>2</sup>/2 y'', h<sup>3</sup>/6 y''' ..., h<sup>k</sup>/k! y<sup>(k)</sup>)
+     */
+    protected Array2DRowFieldMatrix<T> nordsieck;
+
+    /** Starter integrator. */
+    private FirstOrderFieldIntegrator<T> starter;
+
+    /** Number of steps of the multistep method (excluding the one being computed). */
+    private final int nSteps;
+
+    /** Stepsize control exponent. */
+    private double exp;
+
+    /** Safety factor for stepsize control. */
+    private double safety;
+
+    /** Minimal reduction factor for stepsize control. */
+    private double minReduction;
+
+    /** Maximal growth factor for stepsize control. */
+    private double maxGrowth;
+
+    /**
+     * Build a multistep integrator with the given stepsize bounds.
+     *
+     * <p>The default starter integrator is set to the {@link DormandPrince853FieldIntegrator
+     * Dormand-Prince 8(5,3)} integrator with some defaults settings.
+     *
+     * <p>The default max growth factor is set to a quite low value: 2<sup>1/order</sup>.
+     *
+     * @param field field to which the time and state vector elements belong
+     * @param name name of the method
+     * @param nSteps number of steps of the multistep method (excluding the one being computed)
+     * @param order order of the method
+     * @param minStep minimal step (must be positive even for backward integration), the last step
+     *     can be smaller than this
+     * @param maxStep maximal step (must be positive even for backward integration)
+     * @param scalAbsoluteTolerance allowed absolute error
+     * @param scalRelativeTolerance allowed relative error
+     * @exception NumberIsTooSmallException if number of steps is smaller than 2
+     */
+    protected MultistepFieldIntegrator(
+            final Field<T> field,
+            final String name,
+            final int nSteps,
+            final int order,
+            final double minStep,
+            final double maxStep,
+            final double scalAbsoluteTolerance,
+            final double scalRelativeTolerance)
+            throws NumberIsTooSmallException {
+
+        super(field, name, minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance);
+
+        if (nSteps < 2) {
+            throw new NumberIsTooSmallException(
+                    LocalizedFormats.INTEGRATION_METHOD_NEEDS_AT_LEAST_TWO_PREVIOUS_POINTS,
+                    nSteps,
+                    2,
+                    true);
+        }
+
+        starter =
+                new DormandPrince853FieldIntegrator<T>(
+                        field, minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance);
+        this.nSteps = nSteps;
+
+        exp = -1.0 / order;
+
+        // set the default values of the algorithm control parameters
+        setSafety(0.9);
+        setMinReduction(0.2);
+        setMaxGrowth(FastMath.pow(2.0, -exp));
+    }
+
+    /**
+     * Build a multistep integrator with the given stepsize bounds.
+     *
+     * <p>The default starter integrator is set to the {@link DormandPrince853FieldIntegrator
+     * Dormand-Prince 8(5,3)} integrator with some defaults settings.
+     *
+     * <p>The default max growth factor is set to a quite low value: 2<sup>1/order</sup>.
+     *
+     * @param field field to which the time and state vector elements belong
+     * @param name name of the method
+     * @param nSteps number of steps of the multistep method (excluding the one being computed)
+     * @param order order of the method
+     * @param minStep minimal step (must be positive even for backward integration), the last step
+     *     can be smaller than this
+     * @param maxStep maximal step (must be positive even for backward integration)
+     * @param vecAbsoluteTolerance allowed absolute error
+     * @param vecRelativeTolerance allowed relative error
+     */
+    protected MultistepFieldIntegrator(
+            final Field<T> field,
+            final String name,
+            final int nSteps,
+            final int order,
+            final double minStep,
+            final double maxStep,
+            final double[] vecAbsoluteTolerance,
+            final double[] vecRelativeTolerance) {
+        super(field, name, minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance);
+        starter =
+                new DormandPrince853FieldIntegrator<T>(
+                        field, minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance);
+        this.nSteps = nSteps;
+
+        exp = -1.0 / order;
+
+        // set the default values of the algorithm control parameters
+        setSafety(0.9);
+        setMinReduction(0.2);
+        setMaxGrowth(FastMath.pow(2.0, -exp));
+    }
+
+    /**
+     * Get the starter integrator.
+     *
+     * @return starter integrator
+     */
+    public FirstOrderFieldIntegrator<T> getStarterIntegrator() {
+        return starter;
+    }
+
+    /**
+     * Set the starter integrator.
+     *
+     * <p>The various step and event handlers for this starter integrator will be managed
+     * automatically by the multi-step integrator. Any user configuration for these elements will be
+     * cleared before use.
+     *
+     * @param starterIntegrator starter integrator
+     */
+    public void setStarterIntegrator(FirstOrderFieldIntegrator<T> starterIntegrator) {
+        this.starter = starterIntegrator;
+    }
+
+    /**
+     * Start the integration.
+     *
+     * <p>This method computes one step using the underlying starter integrator, and initializes the
+     * Nordsieck vector at step start. The starter integrator purpose is only to establish initial
+     * conditions, it does not really change time by itself. The top level multistep integrator
+     * remains in charge of handling time propagation and events handling as it will starts its own
+     * computation right from the beginning. In a sense, the starter integrator can be seen as a
+     * dummy one and so it will never trigger any user event nor call any user step handler.
+     *
+     * @param equations complete set of differential equations to integrate
+     * @param initialState initial state (time, primary and secondary state vectors)
+     * @param t target time for the integration (can be set to a value smaller than <code>t0</code>
+     *     for backward integration)
+     * @exception DimensionMismatchException if arrays dimension do not match equations settings
+     * @exception NumberIsTooSmallException if integration step is too small
+     * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+     * @exception NoBracketingException if the location of an event cannot be bracketed
+     */
+    protected void start(
+            final FieldExpandableODE<T> equations, final FieldODEState<T> initialState, final T t)
+            throws DimensionMismatchException,
+                    NumberIsTooSmallException,
+                    MaxCountExceededException,
+                    NoBracketingException {
+
+        // make sure NO user event nor user step handler is triggered,
+        // this is the task of the top level integrator, not the task
+        // of the starter integrator
+        starter.clearEventHandlers();
+        starter.clearStepHandlers();
+
+        // set up one specific step handler to extract initial Nordsieck vector
+        starter.addStepHandler(
+                new FieldNordsieckInitializer(equations.getMapper(), (nSteps + 3) / 2));
+
+        // start integration, expecting a InitializationCompletedMarkerException
+        try {
+
+            starter.integrate(equations, initialState, t);
+
+            // we should not reach this step
+            throw new MathIllegalStateException(LocalizedFormats.MULTISTEP_STARTER_STOPPED_EARLY);
+
+        } catch (InitializationCompletedMarkerException icme) { // NOPMD
+            // this is the expected nominal interruption of the start integrator
+
+            // count the evaluations used by the starter
+            getEvaluationsCounter().increment(starter.getEvaluations());
+        }
+
+        // remove the specific step handler
+        starter.clearStepHandlers();
+    }
+
+    /**
+     * Initialize the high order scaled derivatives at step start.
+     *
+     * @param h step size to use for scaling
+     * @param t first steps times
+     * @param y first steps states
+     * @param yDot first steps derivatives
+     * @return Nordieck vector at first step (h<sup>2</sup>/2 y''<sub>n</sub>, h<sup>3</sup>/6
+     *     y'''<sub>n</sub> ... h<sup>k</sup>/k! y<sup>(k)</sup><sub>n</sub>)
+     */
+    protected abstract Array2DRowFieldMatrix<T> initializeHighOrderDerivatives(
+            final T h, final T[] t, final T[][] y, final T[][] yDot);
+
+    /**
+     * Get the minimal reduction factor for stepsize control.
+     *
+     * @return minimal reduction factor
+     */
+    public double getMinReduction() {
+        return minReduction;
+    }
+
+    /**
+     * Set the minimal reduction factor for stepsize control.
+     *
+     * @param minReduction minimal reduction factor
+     */
+    public void setMinReduction(final double minReduction) {
+        this.minReduction = minReduction;
+    }
+
+    /**
+     * Get the maximal growth factor for stepsize control.
+     *
+     * @return maximal growth factor
+     */
+    public double getMaxGrowth() {
+        return maxGrowth;
+    }
+
+    /**
+     * Set the maximal growth factor for stepsize control.
+     *
+     * @param maxGrowth maximal growth factor
+     */
+    public void setMaxGrowth(final double maxGrowth) {
+        this.maxGrowth = maxGrowth;
+    }
+
+    /**
+     * Get the safety factor for stepsize control.
+     *
+     * @return safety factor
+     */
+    public double getSafety() {
+        return safety;
+    }
+
+    /**
+     * Set the safety factor for stepsize control.
+     *
+     * @param safety safety factor
+     */
+    public void setSafety(final double safety) {
+        this.safety = safety;
+    }
+
+    /**
+     * Get the number of steps of the multistep method (excluding the one being computed).
+     *
+     * @return number of steps of the multistep method (excluding the one being computed)
+     */
+    public int getNSteps() {
+        return nSteps;
+    }
+
+    /**
+     * Rescale the instance.
+     *
+     * <p>Since the scaled and Nordsieck arrays are shared with the caller, this method has the side
+     * effect of rescaling this arrays in the caller too.
+     *
+     * @param newStepSize new step size to use in the scaled and Nordsieck arrays
+     */
+    protected void rescale(final T newStepSize) {
+
+        final T ratio = newStepSize.divide(getStepSize());
+        for (int i = 0; i < scaled.length; ++i) {
+            scaled[i] = scaled[i].multiply(ratio);
+        }
+
+        final T[][] nData = nordsieck.getDataRef();
+        T power = ratio;
+        for (int i = 0; i < nData.length; ++i) {
+            power = power.multiply(ratio);
+            final T[] nDataI = nData[i];
+            for (int j = 0; j < nDataI.length; ++j) {
+                nDataI[j] = nDataI[j].multiply(power);
+            }
+        }
+
+        setStepSize(newStepSize);
+    }
+
+    /**
+     * Compute step grow/shrink factor according to normalized error.
+     *
+     * @param error normalized error of the current step
+     * @return grow/shrink factor for next step
+     */
+    protected T computeStepGrowShrinkFactor(final T error) {
+        return MathUtils.min(
+                error.getField().getZero().add(maxGrowth),
+                MathUtils.max(
+                        error.getField().getZero().add(minReduction),
+                        error.pow(exp).multiply(safety)));
+    }
+
+    /** Specialized step handler storing the first step. */
+    private class FieldNordsieckInitializer implements FieldStepHandler<T> {
+
+        /** Equation mapper. */
+        private final FieldEquationsMapper<T> mapper;
+
+        /** Steps counter. */
+        private int count;
+
+        /** Saved start. */
+        private FieldODEStateAndDerivative<T> savedStart;
+
+        /** First steps times. */
+        private final T[] t;
+
+        /** First steps states. */
+        private final T[][] y;
+
+        /** First steps derivatives. */
+        private final T[][] yDot;
+
+        /**
+         * Simple constructor.
+         *
+         * @param mapper equation mapper
+         * @param nbStartPoints number of start points (including the initial point)
+         */
+        FieldNordsieckInitializer(final FieldEquationsMapper<T> mapper, final int nbStartPoints) {
+            this.mapper = mapper;
+            this.count = 0;
+            this.t = MathArrays.buildArray(getField(), nbStartPoints);
+            this.y = MathArrays.buildArray(getField(), nbStartPoints, -1);
+            this.yDot = MathArrays.buildArray(getField(), nbStartPoints, -1);
+        }
+
+        /** {@inheritDoc} */
+        public void handleStep(FieldStepInterpolator<T> interpolator, boolean isLast)
+                throws MaxCountExceededException {
+
+            if (count == 0) {
+                // first step, we need to store also the point at the beginning of the step
+                final FieldODEStateAndDerivative<T> prev = interpolator.getPreviousState();
+                savedStart = prev;
+                t[count] = prev.getTime();
+                y[count] = mapper.mapState(prev);
+                yDot[count] = mapper.mapDerivative(prev);
+            }
+
+            // store the point at the end of the step
+            ++count;
+            final FieldODEStateAndDerivative<T> curr = interpolator.getCurrentState();
+            t[count] = curr.getTime();
+            y[count] = mapper.mapState(curr);
+            yDot[count] = mapper.mapDerivative(curr);
+
+            if (count == t.length - 1) {
+
+                // this was the last point we needed, we can compute the derivatives
+                setStepSize(t[t.length - 1].subtract(t[0]).divide(t.length - 1));
+
+                // first scaled derivative
+                scaled = MathArrays.buildArray(getField(), yDot[0].length);
+                for (int j = 0; j < scaled.length; ++j) {
+                    scaled[j] = yDot[0][j].multiply(getStepSize());
+                }
+
+                // higher order derivatives
+                nordsieck = initializeHighOrderDerivatives(getStepSize(), t, y, yDot);
+
+                // stop the integrator now that all needed steps have been handled
+                setStepStart(savedStart);
+                throw new InitializationCompletedMarkerException();
+            }
+        }
+
+        /** {@inheritDoc} */
+        public void init(final FieldODEStateAndDerivative<T> initialState, T finalTime) {
+            // nothing to do
+        }
+    }
+
+    /** Marker exception used ONLY to stop the starter integrator after first step. */
+    private static class InitializationCompletedMarkerException extends RuntimeException {
+
+        /** Serializable version identifier. */
+        private static final long serialVersionUID = -1914085471038046418L;
+
+        /** Simple constructor. */
+        InitializationCompletedMarkerException() {
+            super((Throwable) null);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/MultistepIntegrator.java b/src/main/java/org/apache/commons/math3/ode/MultistepIntegrator.java
new file mode 100644
index 0000000..fd76124
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/MultistepIntegrator.java
@@ -0,0 +1,496 @@
+/*
+ * 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.math3.ode;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NoBracketingException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.linear.Array2DRowRealMatrix;
+import org.apache.commons.math3.ode.nonstiff.AdaptiveStepsizeIntegrator;
+import org.apache.commons.math3.ode.nonstiff.DormandPrince853Integrator;
+import org.apache.commons.math3.ode.sampling.StepHandler;
+import org.apache.commons.math3.ode.sampling.StepInterpolator;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * This class is the base class for multistep integrators for Ordinary Differential Equations.
+ *
+ * <p>We define scaled derivatives s<sub>i</sub>(n) at step n as:
+ *
+ * <pre>
+ * s<sub>1</sub>(n) = h y'<sub>n</sub> for first derivative
+ * s<sub>2</sub>(n) = h<sup>2</sup>/2 y''<sub>n</sub> for second derivative
+ * s<sub>3</sub>(n) = h<sup>3</sup>/6 y'''<sub>n</sub> for third derivative
+ * ...
+ * s<sub>k</sub>(n) = h<sup>k</sup>/k! y<sup>(k)</sup><sub>n</sub> for k<sup>th</sup> derivative
+ * </pre>
+ *
+ * <p>Rather than storing several previous steps separately, this implementation uses the Nordsieck
+ * vector with higher degrees scaled derivatives all taken at the same step (y<sub>n</sub>,
+ * s<sub>1</sub>(n) and r<sub>n</sub>) where r<sub>n</sub> is defined as:
+ *
+ * <pre>
+ * r<sub>n</sub> = [ s<sub>2</sub>(n), s<sub>3</sub>(n) ... s<sub>k</sub>(n) ]<sup>T</sup>
+ * </pre>
+ *
+ * (we omit the k index in the notation for clarity)
+ *
+ * <p>Multistep integrators with Nordsieck representation are highly sensitive to large step changes
+ * because when the step is multiplied by factor a, the k<sup>th</sup> component of the Nordsieck
+ * vector is multiplied by a<sup>k</sup> and the last components are the least accurate ones. The
+ * default max growth factor is therefore set to a quite low value: 2<sup>1/order</sup>.
+ *
+ * @see org.apache.commons.math3.ode.nonstiff.AdamsBashforthIntegrator
+ * @see org.apache.commons.math3.ode.nonstiff.AdamsMoultonIntegrator
+ * @since 2.0
+ */
+public abstract class MultistepIntegrator extends AdaptiveStepsizeIntegrator {
+
+    /** First scaled derivative (h y'). */
+    protected double[] scaled;
+
+    /**
+     * Nordsieck matrix of the higher scaled derivatives.
+     *
+     * <p>(h<sup>2</sup>/2 y'', h<sup>3</sup>/6 y''' ..., h<sup>k</sup>/k! y<sup>(k)</sup>)
+     */
+    protected Array2DRowRealMatrix nordsieck;
+
+    /** Starter integrator. */
+    private FirstOrderIntegrator starter;
+
+    /** Number of steps of the multistep method (excluding the one being computed). */
+    private final int nSteps;
+
+    /** Stepsize control exponent. */
+    private double exp;
+
+    /** Safety factor for stepsize control. */
+    private double safety;
+
+    /** Minimal reduction factor for stepsize control. */
+    private double minReduction;
+
+    /** Maximal growth factor for stepsize control. */
+    private double maxGrowth;
+
+    /**
+     * Build a multistep integrator with the given stepsize bounds.
+     *
+     * <p>The default starter integrator is set to the {@link DormandPrince853Integrator
+     * Dormand-Prince 8(5,3)} integrator with some defaults settings.
+     *
+     * <p>The default max growth factor is set to a quite low value: 2<sup>1/order</sup>.
+     *
+     * @param name name of the method
+     * @param nSteps number of steps of the multistep method (excluding the one being computed)
+     * @param order order of the method
+     * @param minStep minimal step (must be positive even for backward integration), the last step
+     *     can be smaller than this
+     * @param maxStep maximal step (must be positive even for backward integration)
+     * @param scalAbsoluteTolerance allowed absolute error
+     * @param scalRelativeTolerance allowed relative error
+     * @exception NumberIsTooSmallException if number of steps is smaller than 2
+     */
+    protected MultistepIntegrator(
+            final String name,
+            final int nSteps,
+            final int order,
+            final double minStep,
+            final double maxStep,
+            final double scalAbsoluteTolerance,
+            final double scalRelativeTolerance)
+            throws NumberIsTooSmallException {
+
+        super(name, minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance);
+
+        if (nSteps < 2) {
+            throw new NumberIsTooSmallException(
+                    LocalizedFormats.INTEGRATION_METHOD_NEEDS_AT_LEAST_TWO_PREVIOUS_POINTS,
+                    nSteps,
+                    2,
+                    true);
+        }
+
+        starter =
+                new DormandPrince853Integrator(
+                        minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance);
+        this.nSteps = nSteps;
+
+        exp = -1.0 / order;
+
+        // set the default values of the algorithm control parameters
+        setSafety(0.9);
+        setMinReduction(0.2);
+        setMaxGrowth(FastMath.pow(2.0, -exp));
+    }
+
+    /**
+     * Build a multistep integrator with the given stepsize bounds.
+     *
+     * <p>The default starter integrator is set to the {@link DormandPrince853Integrator
+     * Dormand-Prince 8(5,3)} integrator with some defaults settings.
+     *
+     * <p>The default max growth factor is set to a quite low value: 2<sup>1/order</sup>.
+     *
+     * @param name name of the method
+     * @param nSteps number of steps of the multistep method (excluding the one being computed)
+     * @param order order of the method
+     * @param minStep minimal step (must be positive even for backward integration), the last step
+     *     can be smaller than this
+     * @param maxStep maximal step (must be positive even for backward integration)
+     * @param vecAbsoluteTolerance allowed absolute error
+     * @param vecRelativeTolerance allowed relative error
+     */
+    protected MultistepIntegrator(
+            final String name,
+            final int nSteps,
+            final int order,
+            final double minStep,
+            final double maxStep,
+            final double[] vecAbsoluteTolerance,
+            final double[] vecRelativeTolerance) {
+        super(name, minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance);
+        starter =
+                new DormandPrince853Integrator(
+                        minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance);
+        this.nSteps = nSteps;
+
+        exp = -1.0 / order;
+
+        // set the default values of the algorithm control parameters
+        setSafety(0.9);
+        setMinReduction(0.2);
+        setMaxGrowth(FastMath.pow(2.0, -exp));
+    }
+
+    /**
+     * Get the starter integrator.
+     *
+     * @return starter integrator
+     */
+    public ODEIntegrator getStarterIntegrator() {
+        return starter;
+    }
+
+    /**
+     * Set the starter integrator.
+     *
+     * <p>The various step and event handlers for this starter integrator will be managed
+     * automatically by the multi-step integrator. Any user configuration for these elements will be
+     * cleared before use.
+     *
+     * @param starterIntegrator starter integrator
+     */
+    public void setStarterIntegrator(FirstOrderIntegrator starterIntegrator) {
+        this.starter = starterIntegrator;
+    }
+
+    /**
+     * Start the integration.
+     *
+     * <p>This method computes one step using the underlying starter integrator, and initializes the
+     * Nordsieck vector at step start. The starter integrator purpose is only to establish initial
+     * conditions, it does not really change time by itself. The top level multistep integrator
+     * remains in charge of handling time propagation and events handling as it will starts its own
+     * computation right from the beginning. In a sense, the starter integrator can be seen as a
+     * dummy one and so it will never trigger any user event nor call any user step handler.
+     *
+     * @param t0 initial time
+     * @param y0 initial value of the state vector at t0
+     * @param t target time for the integration (can be set to a value smaller than <code>t0</code>
+     *     for backward integration)
+     * @exception DimensionMismatchException if arrays dimension do not match equations settings
+     * @exception NumberIsTooSmallException if integration step is too small
+     * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+     * @exception NoBracketingException if the location of an event cannot be bracketed
+     */
+    protected void start(final double t0, final double[] y0, final double t)
+            throws DimensionMismatchException,
+                    NumberIsTooSmallException,
+                    MaxCountExceededException,
+                    NoBracketingException {
+
+        // make sure NO user event nor user step handler is triggered,
+        // this is the task of the top level integrator, not the task
+        // of the starter integrator
+        starter.clearEventHandlers();
+        starter.clearStepHandlers();
+
+        // set up one specific step handler to extract initial Nordsieck vector
+        starter.addStepHandler(new NordsieckInitializer((nSteps + 3) / 2, y0.length));
+
+        // start integration, expecting a InitializationCompletedMarkerException
+        try {
+
+            if (starter instanceof AbstractIntegrator) {
+                ((AbstractIntegrator) starter).integrate(getExpandable(), t);
+            } else {
+                starter.integrate(
+                        new FirstOrderDifferentialEquations() {
+
+                            /** {@inheritDoc} */
+                            public int getDimension() {
+                                return getExpandable().getTotalDimension();
+                            }
+
+                            /** {@inheritDoc} */
+                            public void computeDerivatives(double t, double[] y, double[] yDot) {
+                                getExpandable().computeDerivatives(t, y, yDot);
+                            }
+                        },
+                        t0,
+                        y0,
+                        t,
+                        new double[y0.length]);
+            }
+
+            // we should not reach this step
+            throw new MathIllegalStateException(LocalizedFormats.MULTISTEP_STARTER_STOPPED_EARLY);
+
+        } catch (InitializationCompletedMarkerException icme) { // NOPMD
+            // this is the expected nominal interruption of the start integrator
+
+            // count the evaluations used by the starter
+            getCounter().increment(starter.getEvaluations());
+        }
+
+        // remove the specific step handler
+        starter.clearStepHandlers();
+    }
+
+    /**
+     * Initialize the high order scaled derivatives at step start.
+     *
+     * @param h step size to use for scaling
+     * @param t first steps times
+     * @param y first steps states
+     * @param yDot first steps derivatives
+     * @return Nordieck vector at first step (h<sup>2</sup>/2 y''<sub>n</sub>, h<sup>3</sup>/6
+     *     y'''<sub>n</sub> ... h<sup>k</sup>/k! y<sup>(k)</sup><sub>n</sub>)
+     */
+    protected abstract Array2DRowRealMatrix initializeHighOrderDerivatives(
+            final double h, final double[] t, final double[][] y, final double[][] yDot);
+
+    /**
+     * Get the minimal reduction factor for stepsize control.
+     *
+     * @return minimal reduction factor
+     */
+    public double getMinReduction() {
+        return minReduction;
+    }
+
+    /**
+     * Set the minimal reduction factor for stepsize control.
+     *
+     * @param minReduction minimal reduction factor
+     */
+    public void setMinReduction(final double minReduction) {
+        this.minReduction = minReduction;
+    }
+
+    /**
+     * Get the maximal growth factor for stepsize control.
+     *
+     * @return maximal growth factor
+     */
+    public double getMaxGrowth() {
+        return maxGrowth;
+    }
+
+    /**
+     * Set the maximal growth factor for stepsize control.
+     *
+     * @param maxGrowth maximal growth factor
+     */
+    public void setMaxGrowth(final double maxGrowth) {
+        this.maxGrowth = maxGrowth;
+    }
+
+    /**
+     * Get the safety factor for stepsize control.
+     *
+     * @return safety factor
+     */
+    public double getSafety() {
+        return safety;
+    }
+
+    /**
+     * Set the safety factor for stepsize control.
+     *
+     * @param safety safety factor
+     */
+    public void setSafety(final double safety) {
+        this.safety = safety;
+    }
+
+    /**
+     * Get the number of steps of the multistep method (excluding the one being computed).
+     *
+     * @return number of steps of the multistep method (excluding the one being computed)
+     */
+    public int getNSteps() {
+        return nSteps;
+    }
+
+    /**
+     * Compute step grow/shrink factor according to normalized error.
+     *
+     * @param error normalized error of the current step
+     * @return grow/shrink factor for next step
+     */
+    protected double computeStepGrowShrinkFactor(final double error) {
+        return FastMath.min(
+                maxGrowth, FastMath.max(minReduction, safety * FastMath.pow(error, exp)));
+    }
+
+    /**
+     * Transformer used to convert the first step to Nordsieck representation.
+     *
+     * @deprecated as of 3.6 this unused interface is deprecated
+     */
+    @Deprecated
+    public interface NordsieckTransformer {
+        /**
+         * Initialize the high order scaled derivatives at step start.
+         *
+         * @param h step size to use for scaling
+         * @param t first steps times
+         * @param y first steps states
+         * @param yDot first steps derivatives
+         * @return Nordieck vector at first step (h<sup>2</sup>/2 y''<sub>n</sub>, h<sup>3</sup>/6
+         *     y'''<sub>n</sub> ... h<sup>k</sup>/k! y<sup>(k)</sup><sub>n</sub>)
+         */
+        Array2DRowRealMatrix initializeHighOrderDerivatives(
+                final double h, final double[] t, final double[][] y, final double[][] yDot);
+    }
+
+    /** Specialized step handler storing the first step. */
+    private class NordsieckInitializer implements StepHandler {
+
+        /** Steps counter. */
+        private int count;
+
+        /** First steps times. */
+        private final double[] t;
+
+        /** First steps states. */
+        private final double[][] y;
+
+        /** First steps derivatives. */
+        private final double[][] yDot;
+
+        /**
+         * Simple constructor.
+         *
+         * @param nbStartPoints number of start points (including the initial point)
+         * @param n problem dimension
+         */
+        NordsieckInitializer(final int nbStartPoints, final int n) {
+            this.count = 0;
+            this.t = new double[nbStartPoints];
+            this.y = new double[nbStartPoints][n];
+            this.yDot = new double[nbStartPoints][n];
+        }
+
+        /** {@inheritDoc} */
+        public void handleStep(StepInterpolator interpolator, boolean isLast)
+                throws MaxCountExceededException {
+
+            final double prev = interpolator.getPreviousTime();
+            final double curr = interpolator.getCurrentTime();
+
+            if (count == 0) {
+                // first step, we need to store also the point at the beginning of the step
+                interpolator.setInterpolatedTime(prev);
+                t[0] = prev;
+                final ExpandableStatefulODE expandable = getExpandable();
+                final EquationsMapper primary = expandable.getPrimaryMapper();
+                primary.insertEquationData(interpolator.getInterpolatedState(), y[count]);
+                primary.insertEquationData(interpolator.getInterpolatedDerivatives(), yDot[count]);
+                int index = 0;
+                for (final EquationsMapper secondary : expandable.getSecondaryMappers()) {
+                    secondary.insertEquationData(
+                            interpolator.getInterpolatedSecondaryState(index), y[count]);
+                    secondary.insertEquationData(
+                            interpolator.getInterpolatedSecondaryDerivatives(index), yDot[count]);
+                    ++index;
+                }
+            }
+
+            // store the point at the end of the step
+            ++count;
+            interpolator.setInterpolatedTime(curr);
+            t[count] = curr;
+
+            final ExpandableStatefulODE expandable = getExpandable();
+            final EquationsMapper primary = expandable.getPrimaryMapper();
+            primary.insertEquationData(interpolator.getInterpolatedState(), y[count]);
+            primary.insertEquationData(interpolator.getInterpolatedDerivatives(), yDot[count]);
+            int index = 0;
+            for (final EquationsMapper secondary : expandable.getSecondaryMappers()) {
+                secondary.insertEquationData(
+                        interpolator.getInterpolatedSecondaryState(index), y[count]);
+                secondary.insertEquationData(
+                        interpolator.getInterpolatedSecondaryDerivatives(index), yDot[count]);
+                ++index;
+            }
+
+            if (count == t.length - 1) {
+
+                // this was the last point we needed, we can compute the derivatives
+                stepStart = t[0];
+                stepSize = (t[t.length - 1] - t[0]) / (t.length - 1);
+
+                // first scaled derivative
+                scaled = yDot[0].clone();
+                for (int j = 0; j < scaled.length; ++j) {
+                    scaled[j] *= stepSize;
+                }
+
+                // higher order derivatives
+                nordsieck = initializeHighOrderDerivatives(stepSize, t, y, yDot);
+
+                // stop the integrator now that all needed steps have been handled
+                throw new InitializationCompletedMarkerException();
+            }
+        }
+
+        /** {@inheritDoc} */
+        public void init(double t0, double[] y0, double time) {
+            // nothing to do
+        }
+    }
+
+    /** Marker exception used ONLY to stop the starter integrator after first step. */
+    private static class InitializationCompletedMarkerException extends RuntimeException {
+
+        /** Serializable version identifier. */
+        private static final long serialVersionUID = -1914085471038046418L;
+
+        /** Simple constructor. */
+        InitializationCompletedMarkerException() {
+            super((Throwable) null);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/ODEIntegrator.java b/src/main/java/org/apache/commons/math3/ode/ODEIntegrator.java
new file mode 100644
index 0000000..f661960
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/ODEIntegrator.java
@@ -0,0 +1,185 @@
+/*
+ * 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.math3.ode;
+
+import org.apache.commons.math3.analysis.solvers.UnivariateSolver;
+import org.apache.commons.math3.ode.events.EventHandler;
+import org.apache.commons.math3.ode.sampling.StepHandler;
+
+import java.util.Collection;
+
+/**
+ * This interface defines the common parts shared by integrators for first and second order
+ * differential equations.
+ *
+ * @see FirstOrderIntegrator
+ * @see SecondOrderIntegrator
+ * @since 2.0
+ */
+public interface ODEIntegrator {
+
+    /**
+     * Get the name of the method.
+     *
+     * @return name of the method
+     */
+    String getName();
+
+    /**
+     * Add a step handler to this integrator.
+     *
+     * <p>The handler will be called by the integrator for each accepted step.
+     *
+     * @param handler handler for the accepted steps
+     * @see #getStepHandlers()
+     * @see #clearStepHandlers()
+     * @since 2.0
+     */
+    void addStepHandler(StepHandler handler);
+
+    /**
+     * Get all the step handlers that have been added to the integrator.
+     *
+     * @return an unmodifiable collection of the added events handlers
+     * @see #addStepHandler(StepHandler)
+     * @see #clearStepHandlers()
+     * @since 2.0
+     */
+    Collection<StepHandler> getStepHandlers();
+
+    /**
+     * Remove all the step handlers that have been added to the integrator.
+     *
+     * @see #addStepHandler(StepHandler)
+     * @see #getStepHandlers()
+     * @since 2.0
+     */
+    void clearStepHandlers();
+
+    /**
+     * Add an event handler to the integrator. Uses a default {@link UnivariateSolver} with an
+     * absolute accuracy equal to the given convergence threshold, as root-finding algorithm to
+     * detect the state events.
+     *
+     * @param handler event handler
+     * @param maxCheckInterval maximal time interval between switching function checks (this
+     *     interval prevents missing sign changes in case the integration steps becomes very large)
+     * @param convergence convergence threshold in the event time search
+     * @param maxIterationCount upper limit of the iteration count in the event time search
+     * @see #getEventHandlers()
+     * @see #clearEventHandlers()
+     */
+    void addEventHandler(
+            EventHandler handler,
+            double maxCheckInterval,
+            double convergence,
+            int maxIterationCount);
+
+    /**
+     * Add an event handler to the integrator.
+     *
+     * @param handler event handler
+     * @param maxCheckInterval maximal time interval between switching function checks (this
+     *     interval prevents missing sign changes in case the integration steps becomes very large)
+     * @param convergence convergence threshold in the event time search
+     * @param maxIterationCount upper limit of the iteration count in the event time search
+     * @param solver The root-finding algorithm to use to detect the state events.
+     * @see #getEventHandlers()
+     * @see #clearEventHandlers()
+     */
+    void addEventHandler(
+            EventHandler handler,
+            double maxCheckInterval,
+            double convergence,
+            int maxIterationCount,
+            UnivariateSolver solver);
+
+    /**
+     * Get all the event handlers that have been added to the integrator.
+     *
+     * @return an unmodifiable collection of the added events handlers
+     * @see #addEventHandler(EventHandler, double, double, int)
+     * @see #clearEventHandlers()
+     */
+    Collection<EventHandler> getEventHandlers();
+
+    /**
+     * Remove all the event handlers that have been added to the integrator.
+     *
+     * @see #addEventHandler(EventHandler, double, double, int)
+     * @see #getEventHandlers()
+     */
+    void clearEventHandlers();
+
+    /**
+     * Get the current value of the step start time t<sub>i</sub>.
+     *
+     * <p>This method can be called during integration (typically by the object implementing the
+     * {@link FirstOrderDifferentialEquations differential equations} problem) if the value of the
+     * current step that is attempted is needed.
+     *
+     * <p>The result is undefined if the method is called outside of calls to <code>integrate</code>
+     * .
+     *
+     * @return current value of the step start time t<sub>i</sub>
+     */
+    double getCurrentStepStart();
+
+    /**
+     * Get the current signed value of the integration stepsize.
+     *
+     * <p>This method can be called during integration (typically by the object implementing the
+     * {@link FirstOrderDifferentialEquations differential equations} problem) if the signed value
+     * of the current stepsize that is tried is needed.
+     *
+     * <p>The result is undefined if the method is called outside of calls to <code>integrate</code>
+     * .
+     *
+     * @return current signed value of the stepsize
+     */
+    double getCurrentSignedStepsize();
+
+    /**
+     * Set the maximal number of differential equations function evaluations.
+     *
+     * <p>The purpose of this method is to avoid infinite loops which can occur for example when
+     * stringent error constraints are set or when lots of discrete events are triggered, thus
+     * leading to many rejected steps.
+     *
+     * @param maxEvaluations maximal number of function evaluations (negative values are silently
+     *     converted to maximal integer value, thus representing almost unlimited evaluations)
+     */
+    void setMaxEvaluations(int maxEvaluations);
+
+    /**
+     * Get the maximal number of functions evaluations.
+     *
+     * @return maximal number of functions evaluations
+     */
+    int getMaxEvaluations();
+
+    /**
+     * Get the number of evaluations of the differential equations function.
+     *
+     * <p>The number of evaluations corresponds to the last call to the <code>integrate</code>
+     * method. It is 0 if the method has not been called yet.
+     *
+     * @return number of evaluations of the differential equations function
+     */
+    int getEvaluations();
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/ParameterConfiguration.java b/src/main/java/org/apache/commons/math3/ode/ParameterConfiguration.java
new file mode 100644
index 0000000..fe475a5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/ParameterConfiguration.java
@@ -0,0 +1,75 @@
+/*
+ * 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.math3.ode;
+
+import java.io.Serializable;
+
+/**
+ * Simple container pairing a parameter name with a step in order to compute the associated Jacobian
+ * matrix by finite difference.
+ *
+ * @since 3.0
+ */
+class ParameterConfiguration implements Serializable {
+
+    /** Serializable UID. */
+    private static final long serialVersionUID = 2247518849090889379L;
+
+    /** Parameter name. */
+    private String parameterName;
+
+    /** Parameter step for finite difference computation. */
+    private double hP;
+
+    /**
+     * Parameter name and step pair constructor.
+     *
+     * @param parameterName parameter name
+     * @param hP parameter step
+     */
+    ParameterConfiguration(final String parameterName, final double hP) {
+        this.parameterName = parameterName;
+        this.hP = hP;
+    }
+
+    /**
+     * Get parameter name.
+     *
+     * @return parameterName parameter name
+     */
+    public String getParameterName() {
+        return parameterName;
+    }
+
+    /**
+     * Get parameter step.
+     *
+     * @return hP parameter step
+     */
+    public double getHP() {
+        return hP;
+    }
+
+    /**
+     * Set parameter step.
+     *
+     * @param hParam parameter step
+     */
+    public void setHP(final double hParam) {
+        this.hP = hParam;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/ParameterJacobianProvider.java b/src/main/java/org/apache/commons/math3/ode/ParameterJacobianProvider.java
new file mode 100644
index 0000000..eac6689
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/ParameterJacobianProvider.java
@@ -0,0 +1,50 @@
+/*
+ * 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.math3.ode;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+
+/**
+ * Interface to compute exactly Jacobian matrix for some parameter when computing {@link
+ * JacobianMatrices partial derivatives equations}.
+ *
+ * @since 3.0
+ */
+public interface ParameterJacobianProvider extends Parameterizable {
+
+    /**
+     * Compute the Jacobian matrix of ODE with respect to one parameter.
+     *
+     * <p>If the parameter does not belong to the collection returned by {@link
+     * #getParametersNames()}, the Jacobian will be set to 0, but no errors will be triggered.
+     *
+     * @param t current value of the independent <I>time</I> variable
+     * @param y array containing the current value of the main state vector
+     * @param yDot array containing the current value of the time derivative of the main state
+     *     vector
+     * @param paramName name of the parameter to consider
+     * @param dFdP placeholder array where to put the Jacobian matrix of the ODE with respect to the
+     *     parameter
+     * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+     * @exception DimensionMismatchException if arrays dimensions do not match equations settings
+     * @exception UnknownParameterException if the parameter is not supported
+     */
+    void computeParameterJacobian(
+            double t, double[] y, double[] yDot, String paramName, double[] dFdP)
+            throws DimensionMismatchException, MaxCountExceededException, UnknownParameterException;
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/ParameterJacobianWrapper.java b/src/main/java/org/apache/commons/math3/ode/ParameterJacobianWrapper.java
new file mode 100644
index 0000000..351d829
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/ParameterJacobianWrapper.java
@@ -0,0 +1,103 @@
+/*
+ * 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.math3.ode;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Wrapper class to compute Jacobian matrices by finite differences for ODE which do not compute
+ * them by themselves.
+ *
+ * @since 3.0
+ */
+class ParameterJacobianWrapper implements ParameterJacobianProvider {
+
+    /** Main ODE set. */
+    private final FirstOrderDifferentialEquations fode;
+
+    /**
+     * Raw ODE without Jacobian computation skill to be wrapped into a ParameterJacobianProvider.
+     */
+    private final ParameterizedODE pode;
+
+    /** Steps for finite difference computation of the Jacobian df/dp w.r.t. parameters. */
+    private final Map<String, Double> hParam;
+
+    /**
+     * Wrap a {@link ParameterizedODE} into a {@link ParameterJacobianProvider}.
+     *
+     * @param fode main first order differential equations set
+     * @param pode secondary problem, without parameter Jacobian computation skill
+     * @param paramsAndSteps parameters and steps to compute the Jacobians df/dp
+     * @see JacobianMatrices#setParameterStep(String, double)
+     */
+    ParameterJacobianWrapper(
+            final FirstOrderDifferentialEquations fode,
+            final ParameterizedODE pode,
+            final ParameterConfiguration[] paramsAndSteps) {
+        this.fode = fode;
+        this.pode = pode;
+        this.hParam = new HashMap<String, Double>();
+
+        // set up parameters for jacobian computation
+        for (final ParameterConfiguration param : paramsAndSteps) {
+            final String name = param.getParameterName();
+            if (pode.isSupported(name)) {
+                hParam.put(name, param.getHP());
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    public Collection<String> getParametersNames() {
+        return pode.getParametersNames();
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupported(String name) {
+        return pode.isSupported(name);
+    }
+
+    /** {@inheritDoc} */
+    public void computeParameterJacobian(
+            double t, double[] y, double[] yDot, String paramName, double[] dFdP)
+            throws DimensionMismatchException, MaxCountExceededException {
+
+        final int n = fode.getDimension();
+        if (pode.isSupported(paramName)) {
+            final double[] tmpDot = new double[n];
+
+            // compute the jacobian df/dp w.r.t. parameter
+            final double p = pode.getParameter(paramName);
+            final double hP = hParam.get(paramName);
+            pode.setParameter(paramName, p + hP);
+            fode.computeDerivatives(t, y, tmpDot);
+            for (int i = 0; i < n; ++i) {
+                dFdP[i] = (tmpDot[i] - yDot[i]) / hP;
+            }
+            pode.setParameter(paramName, p);
+        } else {
+            Arrays.fill(dFdP, 0, n, 0.0);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/Parameterizable.java b/src/main/java/org/apache/commons/math3/ode/Parameterizable.java
new file mode 100644
index 0000000..40371cb
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/Parameterizable.java
@@ -0,0 +1,46 @@
+/*
+ * 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.math3.ode;
+
+import java.util.Collection;
+
+/**
+ * This interface enables to process any parameterizable object.
+ *
+ * @since 3.0
+ */
+public interface Parameterizable {
+
+    /**
+     * Get the names of the supported parameters.
+     *
+     * @return parameters names
+     * @see #isSupported(String)
+     */
+    Collection<String> getParametersNames();
+
+    /**
+     * Check if a parameter is supported.
+     *
+     * <p>Supported parameters are those listed by {@link #getParametersNames()}.
+     *
+     * @param name parameter name to check
+     * @return true if the parameter is supported
+     * @see #getParametersNames()
+     */
+    boolean isSupported(String name);
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/ParameterizedODE.java b/src/main/java/org/apache/commons/math3/ode/ParameterizedODE.java
new file mode 100644
index 0000000..ab597c6
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/ParameterizedODE.java
@@ -0,0 +1,44 @@
+/*
+ * 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.math3.ode;
+
+/**
+ * Interface to compute by finite difference Jacobian matrix for some parameter when computing
+ * {@link JacobianMatrices partial derivatives equations}.
+ *
+ * @since 3.0
+ */
+public interface ParameterizedODE extends Parameterizable {
+
+    /**
+     * Get parameter value from its name.
+     *
+     * @param name parameter name
+     * @return parameter value
+     * @exception UnknownParameterException if parameter is not supported
+     */
+    double getParameter(String name) throws UnknownParameterException;
+
+    /**
+     * Set the value for a given parameter.
+     *
+     * @param name parameter name
+     * @param value parameter value
+     * @exception UnknownParameterException if parameter is not supported
+     */
+    void setParameter(String name, double value) throws UnknownParameterException;
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/ParameterizedWrapper.java b/src/main/java/org/apache/commons/math3/ode/ParameterizedWrapper.java
new file mode 100644
index 0000000..601ce5f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/ParameterizedWrapper.java
@@ -0,0 +1,88 @@
+/*
+ * 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.math3.ode;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Wrapper class enabling {@link FirstOrderDifferentialEquations basic simple} ODE instances to be
+ * used when processing {@link JacobianMatrices}.
+ *
+ * @since 3.0
+ */
+class ParameterizedWrapper implements ParameterizedODE {
+
+    /** Basic FODE without parameter. */
+    private final FirstOrderDifferentialEquations fode;
+
+    /**
+     * Simple constructor.
+     *
+     * @param ode original first order differential equations
+     */
+    ParameterizedWrapper(final FirstOrderDifferentialEquations ode) {
+        this.fode = ode;
+    }
+
+    /**
+     * Get the dimension of the underlying FODE.
+     *
+     * @return dimension of the underlying FODE
+     */
+    public int getDimension() {
+        return fode.getDimension();
+    }
+
+    /**
+     * Get the current time derivative of the state vector of the underlying FODE.
+     *
+     * @param t current value of the independent <I>time</I> variable
+     * @param y array containing the current value of the state vector
+     * @param yDot placeholder array where to put the time derivative of the state vector
+     * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+     * @exception DimensionMismatchException if arrays dimensions do not match equations settings
+     */
+    public void computeDerivatives(double t, double[] y, double[] yDot)
+            throws MaxCountExceededException, DimensionMismatchException {
+        fode.computeDerivatives(t, y, yDot);
+    }
+
+    /** {@inheritDoc} */
+    public Collection<String> getParametersNames() {
+        return new ArrayList<String>();
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSupported(String name) {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public double getParameter(String name) throws UnknownParameterException {
+        if (!isSupported(name)) {
+            throw new UnknownParameterException(name);
+        }
+        return Double.NaN;
+    }
+
+    /** {@inheritDoc} */
+    public void setParameter(String name, double value) {}
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/SecondOrderDifferentialEquations.java b/src/main/java/org/apache/commons/math3/ode/SecondOrderDifferentialEquations.java
new file mode 100644
index 0000000..4f7f6bc
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/SecondOrderDifferentialEquations.java
@@ -0,0 +1,59 @@
+/*
+ * 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.math3.ode;
+
+/**
+ * This interface represents a second order differential equations set.
+ *
+ * <p>This interface should be implemented by all real second order differential equation problems
+ * before they can be handled by the integrators {@link SecondOrderIntegrator#integrate} method.
+ *
+ * <p>A second order differential equations problem, as seen by an integrator is the second time
+ * derivative <code>d2Y/dt^2</code> of a state vector <code>Y</code>, both being one dimensional
+ * arrays. From the integrator point of view, this derivative depends only on the current time
+ * <code>t</code>, on the state vector <code>Y</code> and on the first time derivative of the state
+ * vector.
+ *
+ * <p>For real problems, the derivative depends also on parameters that do not belong to the state
+ * vector (dynamical model constants for example). These constants are completely outside of the
+ * scope of this interface, the classes that implement it are allowed to handle them as they want.
+ *
+ * @see SecondOrderIntegrator
+ * @see FirstOrderConverter
+ * @see FirstOrderDifferentialEquations
+ * @since 1.2
+ */
+public interface SecondOrderDifferentialEquations {
+
+    /**
+     * Get the dimension of the problem.
+     *
+     * @return dimension of the problem
+     */
+    int getDimension();
+
+    /**
+     * Get the current time derivative of the state vector.
+     *
+     * @param t current value of the independent <I>time</I> variable
+     * @param y array containing the current value of the state vector
+     * @param yDot array containing the current value of the first derivative of the state vector
+     * @param yDDot placeholder array where to put the second time derivative of the state vector
+     */
+    void computeSecondDerivatives(double t, double[] y, double[] yDot, double[] yDDot);
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/SecondOrderIntegrator.java b/src/main/java/org/apache/commons/math3/ode/SecondOrderIntegrator.java
new file mode 100644
index 0000000..4df6c65
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/SecondOrderIntegrator.java
@@ -0,0 +1,61 @@
+/*
+ * 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.math3.ode;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.MathIllegalStateException;
+
+/**
+ * This interface represents a second order integrator for differential equations.
+ *
+ * <p>The classes which are devoted to solve second order differential equations should implement
+ * this interface. The problems which can be handled should implement the {@link
+ * SecondOrderDifferentialEquations} interface.
+ *
+ * @see SecondOrderDifferentialEquations
+ * @since 1.2
+ */
+public interface SecondOrderIntegrator extends ODEIntegrator {
+
+    /**
+     * Integrate the differential equations up to the given time
+     *
+     * @param equations differential equations to integrate
+     * @param t0 initial time
+     * @param y0 initial value of the state vector at t0
+     * @param yDot0 initial value of the first derivative of the state vector at t0
+     * @param t target time for the integration (can be set to a value smaller thant <code>t0</code>
+     *     for backward integration)
+     * @param y placeholder where to put the state vector at each successful step (and hence at the
+     *     end of integration), can be the same object as y0
+     * @param yDot placeholder where to put the first derivative of the state vector at time t, can
+     *     be the same object as yDot0
+     * @throws MathIllegalStateException if the integrator cannot perform integration
+     * @throws MathIllegalArgumentException if integration parameters are wrong (typically too small
+     *     integration span)
+     */
+    void integrate(
+            SecondOrderDifferentialEquations equations,
+            double t0,
+            double[] y0,
+            double[] yDot0,
+            double t,
+            double[] y,
+            double[] yDot)
+            throws MathIllegalStateException, MathIllegalArgumentException;
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/SecondaryEquations.java b/src/main/java/org/apache/commons/math3/ode/SecondaryEquations.java
new file mode 100644
index 0000000..95fed2c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/SecondaryEquations.java
@@ -0,0 +1,66 @@
+/*
+ * 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.math3.ode;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+
+/**
+ * This interface allows users to add secondary differential equations to a primary set of
+ * differential equations.
+ *
+ * <p>In some cases users may need to integrate some problem-specific equations along with a primary
+ * set of differential equations. One example is optimal control where adjoined parameters linked to
+ * the minimized hamiltonian must be integrated.
+ *
+ * <p>This interface allows users to add such equations to a primary set of {@link
+ * FirstOrderDifferentialEquations first order differential equations} thanks to the {@link
+ * ExpandableStatefulODE#addSecondaryEquations(SecondaryEquations)} method.
+ *
+ * @see ExpandableStatefulODE
+ * @since 3.0
+ */
+public interface SecondaryEquations {
+
+    /**
+     * Get the dimension of the secondary state parameters.
+     *
+     * @return dimension of the secondary state parameters
+     */
+    int getDimension();
+
+    /**
+     * Compute the derivatives related to the secondary state parameters.
+     *
+     * @param t current value of the independent <I>time</I> variable
+     * @param primary array containing the current value of the primary state vector
+     * @param primaryDot array containing the derivative of the primary state vector
+     * @param secondary array containing the current value of the secondary state vector
+     * @param secondaryDot placeholder array where to put the derivative of the secondary state
+     *     vector
+     * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+     * @exception DimensionMismatchException if arrays dimensions do not match equations settings
+     */
+    void computeDerivatives(
+            double t,
+            double[] primary,
+            double[] primaryDot,
+            double[] secondary,
+            double[] secondaryDot)
+            throws MaxCountExceededException, DimensionMismatchException;
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/UnknownParameterException.java b/src/main/java/org/apache/commons/math3/ode/UnknownParameterException.java
new file mode 100644
index 0000000..57d31bf
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/UnknownParameterException.java
@@ -0,0 +1,51 @@
+/*
+ * 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.math3.ode;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Exception to be thrown when a parameter is unknown.
+ *
+ * @since 3.1
+ */
+public class UnknownParameterException extends MathIllegalArgumentException {
+
+    /** Serializable version Id. */
+    private static final long serialVersionUID = 20120902L;
+
+    /** Parameter name. */
+    private final String name;
+
+    /**
+     * Construct an exception from the unknown parameter.
+     *
+     * @param name parameter name.
+     */
+    public UnknownParameterException(final String name) {
+        super(LocalizedFormats.UNKNOWN_PARAMETER, name);
+        this.name = name;
+    }
+
+    /**
+     * @return the name of the unknown parameter.
+     */
+    public String getName() {
+        return name;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/events/Action.java b/src/main/java/org/apache/commons/math3/ode/events/Action.java
new file mode 100644
index 0000000..ea7fa51
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/events/Action.java
@@ -0,0 +1,56 @@
+/*
+ * 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.math3.ode.events;
+
+/** Enumerate for actions to be performed when an event occurs during ODE integration.
+ * @since 3.6
+ */
+public enum Action {
+
+    /** Stop indicator.
+     * <p>This value should be used as the return value of the {@code
+     * eventOccurred} method when the integration should be
+     * stopped after the event ending the current step.</p>
+     */
+    STOP,
+
+    /** Reset state indicator.
+     * <p>This value should be used as the return value of the {@code
+     * eventOccurred}} method when the integration should
+     * go on after the event ending the current step, with a new state
+     * vector (which will be retrieved thanks to the {@code resetState}
+     * method).</p>
+     */
+    RESET_STATE,
+
+    /** Reset derivatives indicator.
+     * <p>This value should be used as the return value of the {@code
+     * eventOccurred} method when the integration should
+     * go on after the event ending the current step, with a new derivatives
+     * vector.</p>
+     */
+    RESET_DERIVATIVES,
+
+    /** Continue indicator.
+     * <p>This value should be used as the return value of the {@code
+     * eventOccurred} method when the integration should go
+     * on after the event ending the current step.</p>
+     */
+    CONTINUE;
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/events/EventFilter.java b/src/main/java/org/apache/commons/math3/ode/events/EventFilter.java
new file mode 100644
index 0000000..ffc2715
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/events/EventFilter.java
@@ -0,0 +1,204 @@
+/*
+ * 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.math3.ode.events;
+
+import java.util.Arrays;
+
+/** Wrapper used to detect only increasing or decreasing events.
+ *
+ * <p>General {@link EventHandler events} are defined implicitly
+ * by a {@link EventHandler#g(double, double[]) g function} crossing
+ * zero. This function needs to be continuous in the event neighborhood,
+ * and its sign must remain consistent between events. This implies that
+ * during an ODE integration, events triggered are alternately events
+ * for which the function increases from negative to positive values,
+ * and events for which the function decreases from positive to
+ * negative values.
+ * </p>
+ *
+ * <p>Sometimes, users are only interested in one type of event (say
+ * increasing events for example) and not in the other type. In these
+ * cases, looking precisely for all events location and triggering
+ * events that will later be ignored is a waste of computing time.</p>
+ *
+ * <p>Users can wrap a regular {@link EventHandler event handler} in
+ * an instance of this class and provide this wrapping instance to
+ * the {@link org.apache.commons.math3.ode.FirstOrderIntegrator ODE solver}
+ * in order to avoid wasting time looking for uninteresting events.
+ * The wrapper will intercept the calls to the {@link
+ * EventHandler#g(double, double[]) g function} and to the {@link
+ * EventHandler#eventOccurred(double, double[], boolean)
+ * eventOccurred} method in order to ignore uninteresting events. The
+ * wrapped regular {@link EventHandler event handler} will the see only
+ * the interesting events, i.e. either only {@code increasing} events or
+ * {@code decreasing} events. the number of calls to the {@link
+ * EventHandler#g(double, double[]) g function} will also be reduced.</p>
+ *
+ * @since 3.2
+ */
+
+public class EventFilter implements EventHandler {
+
+    /** Number of past transformers updates stored. */
+    private static final int HISTORY_SIZE = 100;
+
+    /** Wrapped event handler. */
+    private final EventHandler rawHandler;
+
+    /** Filter to use. */
+    private final FilterType filter;
+
+    /** Transformers of the g function. */
+    private final Transformer[] transformers;
+
+    /** Update time of the transformers. */
+    private final double[] updates;
+
+    /** Indicator for forward integration. */
+    private boolean forward;
+
+    /** Extreme time encountered so far. */
+    private double extremeT;
+
+    /** Wrap an {@link EventHandler event handler}.
+     * @param rawHandler event handler to wrap
+     * @param filter filter to use
+     */
+    public EventFilter(final EventHandler rawHandler, final FilterType filter) {
+        this.rawHandler   = rawHandler;
+        this.filter       = filter;
+        this.transformers = new Transformer[HISTORY_SIZE];
+        this.updates      = new double[HISTORY_SIZE];
+    }
+
+    /**  {@inheritDoc} */
+    public void init(double t0, double[] y0, double t) {
+
+        // delegate to raw handler
+        rawHandler.init(t0, y0, t);
+
+        // initialize events triggering logic
+        forward  = t >= t0;
+        extremeT = forward ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY;
+        Arrays.fill(transformers, Transformer.UNINITIALIZED);
+        Arrays.fill(updates, extremeT);
+
+    }
+
+    /**  {@inheritDoc} */
+    public double g(double t, double[] y) {
+
+        final double rawG = rawHandler.g(t, y);
+
+        // search which transformer should be applied to g
+        if (forward) {
+            final int last = transformers.length - 1;
+            if (extremeT < t) {
+                // we are at the forward end of the history
+
+                // check if a new rough root has been crossed
+                final Transformer previous = transformers[last];
+                final Transformer next     = filter.selectTransformer(previous, rawG, forward);
+                if (next != previous) {
+                    // there is a root somewhere between extremeT and t.
+                    // the new transformer is valid for t (this is how we have just computed
+                    // it above), but it is in fact valid on both sides of the root, so
+                    // it was already valid before t and even up to previous time. We store
+                    // the switch at extremeT for safety, to ensure the previous transformer
+                    // is not applied too close of the root
+                    System.arraycopy(updates,      1, updates,      0, last);
+                    System.arraycopy(transformers, 1, transformers, 0, last);
+                    updates[last]      = extremeT;
+                    transformers[last] = next;
+                }
+
+                extremeT = t;
+
+                // apply the transform
+                return next.transformed(rawG);
+
+            } else {
+                // we are in the middle of the history
+
+                // select the transformer
+                for (int i = last; i > 0; --i) {
+                    if (updates[i] <= t) {
+                        // apply the transform
+                        return transformers[i].transformed(rawG);
+                    }
+                }
+
+                return transformers[0].transformed(rawG);
+
+            }
+        } else {
+            if (t < extremeT) {
+                // we are at the backward end of the history
+
+                // check if a new rough root has been crossed
+                final Transformer previous = transformers[0];
+                final Transformer next     = filter.selectTransformer(previous, rawG, forward);
+                if (next != previous) {
+                    // there is a root somewhere between extremeT and t.
+                    // the new transformer is valid for t (this is how we have just computed
+                    // it above), but it is in fact valid on both sides of the root, so
+                    // it was already valid before t and even up to previous time. We store
+                    // the switch at extremeT for safety, to ensure the previous transformer
+                    // is not applied too close of the root
+                    System.arraycopy(updates,      0, updates,      1, updates.length - 1);
+                    System.arraycopy(transformers, 0, transformers, 1, transformers.length - 1);
+                    updates[0]      = extremeT;
+                    transformers[0] = next;
+                }
+
+                extremeT = t;
+
+                // apply the transform
+                return next.transformed(rawG);
+
+            } else {
+                // we are in the middle of the history
+
+                // select the transformer
+                for (int i = 0; i < updates.length - 1; ++i) {
+                    if (t <= updates[i]) {
+                        // apply the transform
+                        return transformers[i].transformed(rawG);
+                    }
+                }
+
+                return transformers[updates.length - 1].transformed(rawG);
+
+            }
+       }
+
+    }
+
+    /**  {@inheritDoc} */
+    public Action eventOccurred(double t, double[] y, boolean increasing) {
+        // delegate to raw handler, fixing increasing status on the fly
+        return rawHandler.eventOccurred(t, y, filter.getTriggeredIncreasing());
+    }
+
+    /**  {@inheritDoc} */
+    public void resetState(double t, double[] y) {
+        // delegate to raw handler
+        rawHandler.resetState(t, y);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/events/EventHandler.java b/src/main/java/org/apache/commons/math3/ode/events/EventHandler.java
new file mode 100644
index 0000000..58533bc
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/events/EventHandler.java
@@ -0,0 +1,219 @@
+/*
+ * 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.math3.ode.events;
+
+
+/** This interface represents a handler for discrete events triggered
+ * during ODE integration.
+ *
+ * <p>Some events can be triggered at discrete times as an ODE problem
+ * is solved. This occurs for example when the integration process
+ * should be stopped as some state is reached (G-stop facility) when the
+ * precise date is unknown a priori, or when the derivatives have
+ * discontinuities, or simply when the user wants to monitor some
+ * states boundaries crossings.
+ * </p>
+ *
+ * <p>These events are defined as occurring when a <code>g</code>
+ * switching function sign changes.</p>
+ *
+ * <p>Since events are only problem-dependent and are triggered by the
+ * independent <i>time</i> variable and the state vector, they can
+ * occur at virtually any time, unknown in advance. The integrators will
+ * take care to avoid sign changes inside the steps, they will reduce
+ * the step size when such an event is detected in order to put this
+ * event exactly at the end of the current step. This guarantees that
+ * step interpolation (which always has a one step scope) is relevant
+ * even in presence of discontinuities. This is independent from the
+ * stepsize control provided by integrators that monitor the local
+ * error (this event handling feature is available for all integrators,
+ * including fixed step ones).</p>
+ *
+ * @since 1.2
+ */
+
+public interface EventHandler  {
+
+    /** Enumerate for actions to be performed when an event occurs. */
+    enum Action {
+
+        /** Stop indicator.
+         * <p>This value should be used as the return value of the {@link
+         * #eventOccurred eventOccurred} method when the integration should be
+         * stopped after the event ending the current step.</p>
+         */
+        STOP,
+
+        /** Reset state indicator.
+         * <p>This value should be used as the return value of the {@link
+         * #eventOccurred eventOccurred} method when the integration should
+         * go on after the event ending the current step, with a new state
+         * vector (which will be retrieved thanks to the {@link #resetState
+         * resetState} method).</p>
+         */
+        RESET_STATE,
+
+        /** Reset derivatives indicator.
+         * <p>This value should be used as the return value of the {@link
+         * #eventOccurred eventOccurred} method when the integration should
+         * go on after the event ending the current step, with a new derivatives
+         * vector (which will be retrieved thanks to the {@link
+         * org.apache.commons.math3.ode.FirstOrderDifferentialEquations#computeDerivatives}
+         * method).</p>
+         */
+        RESET_DERIVATIVES,
+
+        /** Continue indicator.
+         * <p>This value should be used as the return value of the {@link
+         * #eventOccurred eventOccurred} method when the integration should go
+         * on after the event ending the current step.</p>
+         */
+        CONTINUE;
+
+    }
+
+    /** Initialize event handler at the start of an ODE integration.
+     * <p>
+     * This method is called once at the start of the integration. It
+     * may be used by the event handler to initialize some internal data
+     * if needed.
+     * </p>
+     * @param t0 start value of the independent <i>time</i> variable
+     * @param y0 array containing the start value of the state vector
+     * @param t target time for the integration
+     */
+    void init(double t0, double[] y0, double t);
+
+  /** Compute the value of the switching function.
+
+   * <p>The discrete events are generated when the sign of this
+   * switching function changes. The integrator will take care to change
+   * the stepsize in such a way these events occur exactly at step boundaries.
+   * The switching function must be continuous in its roots neighborhood
+   * (but not necessarily smooth), as the integrator will need to find its
+   * roots to locate precisely the events.</p>
+   * <p>Also note that the integrator expect that once an event has occurred,
+   * the sign of the switching function at the start of the next step (i.e.
+   * just after the event) is the opposite of the sign just before the event.
+   * This consistency between the steps <string>must</strong> be preserved,
+   * otherwise {@link org.apache.commons.math3.exception.NoBracketingException
+   * exceptions} related to root not being bracketed will occur.</p>
+   * <p>This need for consistency is sometimes tricky to achieve. A typical
+   * example is using an event to model a ball bouncing on the floor. The first
+   * idea to represent this would be to have {@code g(t) = h(t)} where h is the
+   * height above the floor at time {@code t}. When {@code g(t)} reaches 0, the
+   * ball is on the floor, so it should bounce and the typical way to do this is
+   * to reverse its vertical velocity. However, this would mean that before the
+   * event {@code g(t)} was decreasing from positive values to 0, and after the
+   * event {@code g(t)} would be increasing from 0 to positive values again.
+   * Consistency is broken here! The solution here is to have {@code g(t) = sign
+   * * h(t)}, where sign is a variable with initial value set to {@code +1}. Each
+   * time {@link #eventOccurred(double, double[], boolean) eventOccurred} is called,
+   * {@code sign} is reset to {@code -sign}. This allows the {@code g(t)}
+   * function to remain continuous (and even smooth) even across events, despite
+   * {@code h(t)} is not. Basically, the event is used to <em>fold</em> {@code h(t)}
+   * at bounce points, and {@code sign} is used to <em>unfold</em> it back, so the
+   * solvers sees a {@code g(t)} function which behaves smoothly even across events.</p>
+
+   * @param t current value of the independent <i>time</i> variable
+   * @param y array containing the current value of the state vector
+   * @return value of the g switching function
+   */
+  double g(double t, double[] y);
+
+  /** Handle an event and choose what to do next.
+
+   * <p>This method is called when the integrator has accepted a step
+   * ending exactly on a sign change of the function, just <em>before</em>
+   * the step handler itself is called (see below for scheduling). It
+   * allows the user to update his internal data to acknowledge the fact
+   * the event has been handled (for example setting a flag in the {@link
+   * org.apache.commons.math3.ode.FirstOrderDifferentialEquations
+   * differential equations} to switch the derivatives computation in
+   * case of discontinuity), or to direct the integrator to either stop
+   * or continue integration, possibly with a reset state or derivatives.</p>
+
+   * <ul>
+   *   <li>if {@link Action#STOP} is returned, the step handler will be called
+   *   with the <code>isLast</code> flag of the {@link
+   *   org.apache.commons.math3.ode.sampling.StepHandler#handleStep handleStep}
+   *   method set to true and the integration will be stopped,</li>
+   *   <li>if {@link Action#RESET_STATE} is returned, the {@link #resetState
+   *   resetState} method will be called once the step handler has
+   *   finished its task, and the integrator will also recompute the
+   *   derivatives,</li>
+   *   <li>if {@link Action#RESET_DERIVATIVES} is returned, the integrator
+   *   will recompute the derivatives,
+   *   <li>if {@link Action#CONTINUE} is returned, no specific action will
+   *   be taken (apart from having called this method) and integration
+   *   will continue.</li>
+   * </ul>
+
+   * <p>The scheduling between this method and the {@link
+   * org.apache.commons.math3.ode.sampling.StepHandler StepHandler} method {@link
+   * org.apache.commons.math3.ode.sampling.StepHandler#handleStep(
+   * org.apache.commons.math3.ode.sampling.StepInterpolator, boolean)
+   * handleStep(interpolator, isLast)} is to call this method first and
+   * <code>handleStep</code> afterwards. This scheduling allows the integrator to
+   * pass <code>true</code> as the <code>isLast</code> parameter to the step
+   * handler to make it aware the step will be the last one if this method
+   * returns {@link Action#STOP}. As the interpolator may be used to navigate back
+   * throughout the last step (as {@link
+   * org.apache.commons.math3.ode.sampling.StepNormalizer StepNormalizer}
+   * does for example), user code called by this method and user
+   * code called by step handlers may experience apparently out of order values
+   * of the independent time variable. As an example, if the same user object
+   * implements both this {@link EventHandler EventHandler} interface and the
+   * {@link org.apache.commons.math3.ode.sampling.FixedStepHandler FixedStepHandler}
+   * interface, a <em>forward</em> integration may call its
+   * <code>eventOccurred</code> method with t = 10 first and call its
+   * <code>handleStep</code> method with t = 9 afterwards. Such out of order
+   * calls are limited to the size of the integration step for {@link
+   * org.apache.commons.math3.ode.sampling.StepHandler variable step handlers} and
+   * to the size of the fixed step for {@link
+   * org.apache.commons.math3.ode.sampling.FixedStepHandler fixed step handlers}.</p>
+
+   * @param t current value of the independent <i>time</i> variable
+   * @param y array containing the current value of the state vector
+   * @param increasing if true, the value of the switching function increases
+   * when times increases around event (note that increase is measured with respect
+   * to physical time, not with respect to integration which may go backward in time)
+   * @return indication of what the integrator should do next, this
+   * value must be one of {@link Action#STOP}, {@link Action#RESET_STATE},
+   * {@link Action#RESET_DERIVATIVES} or {@link Action#CONTINUE}
+   */
+  Action eventOccurred(double t, double[] y, boolean increasing);
+
+  /** Reset the state prior to continue the integration.
+
+   * <p>This method is called after the step handler has returned and
+   * before the next step is started, but only when {@link
+   * #eventOccurred} has itself returned the {@link Action#RESET_STATE}
+   * indicator. It allows the user to reset the state vector for the
+   * next step, without perturbing the step handler of the finishing
+   * step. If the {@link #eventOccurred} never returns the {@link
+   * Action#RESET_STATE} indicator, this function will never be called, and it is
+   * safe to leave its body empty.</p>
+
+   * @param t current value of the independent <i>time</i> variable
+   * @param y array containing the current value of the state vector
+   * the new state should be put in the same array
+   */
+  void resetState(double t, double[] y);
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/events/EventState.java b/src/main/java/org/apache/commons/math3/ode/events/EventState.java
new file mode 100644
index 0000000..9e9da8d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/events/EventState.java
@@ -0,0 +1,431 @@
+/*
+ * 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.math3.ode.events;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.solvers.AllowedSolution;
+import org.apache.commons.math3.analysis.solvers.BracketedUnivariateSolver;
+import org.apache.commons.math3.analysis.solvers.PegasusSolver;
+import org.apache.commons.math3.analysis.solvers.UnivariateSolver;
+import org.apache.commons.math3.analysis.solvers.UnivariateSolverUtils;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NoBracketingException;
+import org.apache.commons.math3.ode.EquationsMapper;
+import org.apache.commons.math3.ode.ExpandableStatefulODE;
+import org.apache.commons.math3.ode.sampling.StepInterpolator;
+import org.apache.commons.math3.util.FastMath;
+
+/** This class handles the state for one {@link EventHandler
+ * event handler} during integration steps.
+ *
+ * <p>Each time the integrator proposes a step, the event handler
+ * switching function should be checked. This class handles the state
+ * of one handler during one integration step, with references to the
+ * state at the end of the preceding step. This information is used to
+ * decide if the handler should trigger an event or not during the
+ * proposed step.</p>
+ *
+ * @since 1.2
+ */
+public class EventState {
+
+    /** Event handler. */
+    private final EventHandler handler;
+
+    /** Maximal time interval between events handler checks. */
+    private final double maxCheckInterval;
+
+    /** Convergence threshold for event localization. */
+    private final double convergence;
+
+    /** Upper limit in the iteration count for event localization. */
+    private final int maxIterationCount;
+
+    /** Equation being integrated. */
+    private ExpandableStatefulODE expandable;
+
+    /** Time at the beginning of the step. */
+    private double t0;
+
+    /** Value of the events handler at the beginning of the step. */
+    private double g0;
+
+    /** Simulated sign of g0 (we cheat when crossing events). */
+    private boolean g0Positive;
+
+    /** Indicator of event expected during the step. */
+    private boolean pendingEvent;
+
+    /** Occurrence time of the pending event. */
+    private double pendingEventTime;
+
+    /** Occurrence time of the previous event. */
+    private double previousEventTime;
+
+    /** Integration direction. */
+    private boolean forward;
+
+    /** Variation direction around pending event.
+     *  (this is considered with respect to the integration direction)
+     */
+    private boolean increasing;
+
+    /** Next action indicator. */
+    private EventHandler.Action nextAction;
+
+    /** Root-finding algorithm to use to detect state events. */
+    private final UnivariateSolver solver;
+
+    /** Simple constructor.
+     * @param handler event handler
+     * @param maxCheckInterval maximal time interval between switching
+     * function checks (this interval prevents missing sign changes in
+     * case the integration steps becomes very large)
+     * @param convergence convergence threshold in the event time search
+     * @param maxIterationCount upper limit of the iteration count in
+     * the event time search
+     * @param solver Root-finding algorithm to use to detect state events
+     */
+    public EventState(final EventHandler handler, final double maxCheckInterval,
+                      final double convergence, final int maxIterationCount,
+                      final UnivariateSolver solver) {
+        this.handler           = handler;
+        this.maxCheckInterval  = maxCheckInterval;
+        this.convergence       = FastMath.abs(convergence);
+        this.maxIterationCount = maxIterationCount;
+        this.solver            = solver;
+
+        // some dummy values ...
+        expandable        = null;
+        t0                = Double.NaN;
+        g0                = Double.NaN;
+        g0Positive        = true;
+        pendingEvent      = false;
+        pendingEventTime  = Double.NaN;
+        previousEventTime = Double.NaN;
+        increasing        = true;
+        nextAction        = EventHandler.Action.CONTINUE;
+
+    }
+
+    /** Get the underlying event handler.
+     * @return underlying event handler
+     */
+    public EventHandler getEventHandler() {
+        return handler;
+    }
+
+    /** Set the equation.
+     * @param expandable equation being integrated
+     */
+    public void setExpandable(final ExpandableStatefulODE expandable) {
+        this.expandable = expandable;
+    }
+
+    /** Get the maximal time interval between events handler checks.
+     * @return maximal time interval between events handler checks
+     */
+    public double getMaxCheckInterval() {
+        return maxCheckInterval;
+    }
+
+    /** Get the convergence threshold for event localization.
+     * @return convergence threshold for event localization
+     */
+    public double getConvergence() {
+        return convergence;
+    }
+
+    /** Get the upper limit in the iteration count for event localization.
+     * @return upper limit in the iteration count for event localization
+     */
+    public int getMaxIterationCount() {
+        return maxIterationCount;
+    }
+
+    /** Reinitialize the beginning of the step.
+     * @param interpolator valid for the current step
+     * @exception MaxCountExceededException if the interpolator throws one because
+     * the number of functions evaluations is exceeded
+     */
+    public void reinitializeBegin(final StepInterpolator interpolator)
+        throws MaxCountExceededException {
+
+        t0 = interpolator.getPreviousTime();
+        interpolator.setInterpolatedTime(t0);
+        g0 = handler.g(t0, getCompleteState(interpolator));
+        if (g0 == 0) {
+            // excerpt from MATH-421 issue:
+            // If an ODE solver is setup with an EventHandler that return STOP
+            // when the even is triggered, the integrator stops (which is exactly
+            // the expected behavior). If however the user wants to restart the
+            // solver from the final state reached at the event with the same
+            // configuration (expecting the event to be triggered again at a
+            // later time), then the integrator may fail to start. It can get stuck
+            // at the previous event. The use case for the bug MATH-421 is fairly
+            // general, so events occurring exactly at start in the first step should
+            // be ignored.
+
+            // extremely rare case: there is a zero EXACTLY at interval start
+            // we will use the sign slightly after step beginning to force ignoring this zero
+            final double epsilon = FastMath.max(solver.getAbsoluteAccuracy(),
+                                                FastMath.abs(solver.getRelativeAccuracy() * t0));
+            final double tStart = t0 + 0.5 * epsilon;
+            interpolator.setInterpolatedTime(tStart);
+            g0 = handler.g(tStart, getCompleteState(interpolator));
+        }
+        g0Positive = g0 >= 0;
+
+    }
+
+    /** Get the complete state (primary and secondary).
+     * @param interpolator interpolator to use
+     * @return complete state
+     */
+    private double[] getCompleteState(final StepInterpolator interpolator) {
+
+        final double[] complete = new double[expandable.getTotalDimension()];
+
+        expandable.getPrimaryMapper().insertEquationData(interpolator.getInterpolatedState(),
+                                                         complete);
+        int index = 0;
+        for (EquationsMapper secondary : expandable.getSecondaryMappers()) {
+            secondary.insertEquationData(interpolator.getInterpolatedSecondaryState(index++),
+                                         complete);
+        }
+
+        return complete;
+
+    }
+
+    /** Evaluate the impact of the proposed step on the event handler.
+     * @param interpolator step interpolator for the proposed step
+     * @return true if the event handler triggers an event before
+     * the end of the proposed step
+     * @exception MaxCountExceededException if the interpolator throws one because
+     * the number of functions evaluations is exceeded
+     * @exception NoBracketingException if the event cannot be bracketed
+     */
+    public boolean evaluateStep(final StepInterpolator interpolator)
+        throws MaxCountExceededException, NoBracketingException {
+
+        try {
+            forward = interpolator.isForward();
+            final double t1 = interpolator.getCurrentTime();
+            final double dt = t1 - t0;
+            if (FastMath.abs(dt) < convergence) {
+                // we cannot do anything on such a small step, don't trigger any events
+                return false;
+            }
+            final int    n = FastMath.max(1, (int) FastMath.ceil(FastMath.abs(dt) / maxCheckInterval));
+            final double h = dt / n;
+
+            final UnivariateFunction f = new UnivariateFunction() {
+                /** {@inheritDoc} */
+                public double value(final double t) throws LocalMaxCountExceededException {
+                    try {
+                        interpolator.setInterpolatedTime(t);
+                        return handler.g(t, getCompleteState(interpolator));
+                    } catch (MaxCountExceededException mcee) {
+                        throw new LocalMaxCountExceededException(mcee);
+                    }
+                }
+            };
+
+            double ta = t0;
+            double ga = g0;
+            for (int i = 0; i < n; ++i) {
+
+                // evaluate handler value at the end of the substep
+                final double tb = (i == n - 1) ? t1 : t0 + (i + 1) * h;
+                interpolator.setInterpolatedTime(tb);
+                final double gb = handler.g(tb, getCompleteState(interpolator));
+
+                // check events occurrence
+                if (g0Positive ^ (gb >= 0)) {
+                    // there is a sign change: an event is expected during this step
+
+                    // variation direction, with respect to the integration direction
+                    increasing = gb >= ga;
+
+                    // find the event time making sure we select a solution just at or past the exact root
+                    final double root;
+                    if (solver instanceof BracketedUnivariateSolver<?>) {
+                        @SuppressWarnings("unchecked")
+                        BracketedUnivariateSolver<UnivariateFunction> bracketing =
+                                (BracketedUnivariateSolver<UnivariateFunction>) solver;
+                        root = forward ?
+                               bracketing.solve(maxIterationCount, f, ta, tb, AllowedSolution.RIGHT_SIDE) :
+                               bracketing.solve(maxIterationCount, f, tb, ta, AllowedSolution.LEFT_SIDE);
+                    } else {
+                        final double baseRoot = forward ?
+                                                solver.solve(maxIterationCount, f, ta, tb) :
+                                                solver.solve(maxIterationCount, f, tb, ta);
+                        final int remainingEval = maxIterationCount - solver.getEvaluations();
+                        BracketedUnivariateSolver<UnivariateFunction> bracketing =
+                                new PegasusSolver(solver.getRelativeAccuracy(), solver.getAbsoluteAccuracy());
+                        root = forward ?
+                               UnivariateSolverUtils.forceSide(remainingEval, f, bracketing,
+                                                                   baseRoot, ta, tb, AllowedSolution.RIGHT_SIDE) :
+                               UnivariateSolverUtils.forceSide(remainingEval, f, bracketing,
+                                                                   baseRoot, tb, ta, AllowedSolution.LEFT_SIDE);
+                    }
+
+                    if ((!Double.isNaN(previousEventTime)) &&
+                        (FastMath.abs(root - ta) <= convergence) &&
+                        (FastMath.abs(root - previousEventTime) <= convergence)) {
+                        // we have either found nothing or found (again ?) a past event,
+                        // retry the substep excluding this value, and taking care to have the
+                        // required sign in case the g function is noisy around its zero and
+                        // crosses the axis several times
+                        do {
+                            ta = forward ? ta + convergence : ta - convergence;
+                            ga = f.value(ta);
+                        } while ((g0Positive ^ (ga >= 0)) && (forward ^ (ta >= tb)));
+
+                        if (forward ^ (ta >= tb)) {
+                            // we were able to skip this spurious root
+                            --i;
+                        } else {
+                            // we can't avoid this root before the end of the step,
+                            // we have to handle it despite it is close to the former one
+                            // maybe we have two very close roots
+                            pendingEventTime = root;
+                            pendingEvent = true;
+                            return true;
+                        }
+                    } else if (Double.isNaN(previousEventTime) ||
+                               (FastMath.abs(previousEventTime - root) > convergence)) {
+                        pendingEventTime = root;
+                        pendingEvent = true;
+                        return true;
+                    } else {
+                        // no sign change: there is no event for now
+                        ta = tb;
+                        ga = gb;
+                    }
+
+                } else {
+                    // no sign change: there is no event for now
+                    ta = tb;
+                    ga = gb;
+                }
+
+            }
+
+            // no event during the whole step
+            pendingEvent     = false;
+            pendingEventTime = Double.NaN;
+            return false;
+
+        } catch (LocalMaxCountExceededException lmcee) {
+            throw lmcee.getException();
+        }
+
+    }
+
+    /** Get the occurrence time of the event triggered in the current step.
+     * @return occurrence time of the event triggered in the current
+     * step or infinity if no events are triggered
+     */
+    public double getEventTime() {
+        return pendingEvent ?
+               pendingEventTime :
+               (forward ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY);
+    }
+
+    /** Acknowledge the fact the step has been accepted by the integrator.
+     * @param t value of the independent <i>time</i> variable at the
+     * end of the step
+     * @param y array containing the current value of the state vector
+     * at the end of the step
+     */
+    public void stepAccepted(final double t, final double[] y) {
+
+        t0 = t;
+        g0 = handler.g(t, y);
+
+        if (pendingEvent && (FastMath.abs(pendingEventTime - t) <= convergence)) {
+            // force the sign to its value "just after the event"
+            previousEventTime = t;
+            g0Positive        = increasing;
+            nextAction        = handler.eventOccurred(t, y, !(increasing ^ forward));
+        } else {
+            g0Positive = g0 >= 0;
+            nextAction = EventHandler.Action.CONTINUE;
+        }
+    }
+
+    /** Check if the integration should be stopped at the end of the
+     * current step.
+     * @return true if the integration should be stopped
+     */
+    public boolean stop() {
+        return nextAction == EventHandler.Action.STOP;
+    }
+
+    /** Let the event handler reset the state if it wants.
+     * @param t value of the independent <i>time</i> variable at the
+     * beginning of the next step
+     * @param y array were to put the desired state vector at the beginning
+     * of the next step
+     * @return true if the integrator should reset the derivatives too
+     */
+    public boolean reset(final double t, final double[] y) {
+
+        if (!(pendingEvent && (FastMath.abs(pendingEventTime - t) <= convergence))) {
+            return false;
+        }
+
+        if (nextAction == EventHandler.Action.RESET_STATE) {
+            handler.resetState(t, y);
+        }
+        pendingEvent      = false;
+        pendingEventTime  = Double.NaN;
+
+        return (nextAction == EventHandler.Action.RESET_STATE) ||
+               (nextAction == EventHandler.Action.RESET_DERIVATIVES);
+
+    }
+
+    /** Local wrapper to propagate exceptions. */
+    private static class LocalMaxCountExceededException extends RuntimeException {
+
+        /** Serializable UID. */
+        private static final long serialVersionUID = 20120901L;
+
+        /** Wrapped exception. */
+        private final MaxCountExceededException wrapped;
+
+        /** Simple constructor.
+         * @param exception exception to wrap
+         */
+        LocalMaxCountExceededException(final MaxCountExceededException exception) {
+            wrapped = exception;
+        }
+
+        /** Get the wrapped exception.
+         * @return wrapped exception
+         */
+        public MaxCountExceededException getException() {
+            return wrapped;
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/events/FieldEventHandler.java b/src/main/java/org/apache/commons/math3/ode/events/FieldEventHandler.java
new file mode 100644
index 0000000..058f113
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/events/FieldEventHandler.java
@@ -0,0 +1,180 @@
+/*
+ * 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.math3.ode.events;
+
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.ode.FieldODEState;
+import org.apache.commons.math3.ode.FieldODEStateAndDerivative;
+
+/** This interface represents a handler for discrete events triggered
+ * during ODE integration.
+ *
+ * <p>Some events can be triggered at discrete times as an ODE problem
+ * is solved. This occurs for example when the integration process
+ * should be stopped as some state is reached (G-stop facility) when the
+ * precise date is unknown a priori, or when the derivatives have
+ * discontinuities, or simply when the user wants to monitor some
+ * states boundaries crossings.
+ * </p>
+ *
+ * <p>These events are defined as occurring when a <code>g</code>
+ * switching function sign changes.</p>
+ *
+ * <p>Since events are only problem-dependent and are triggered by the
+ * independent <i>time</i> variable and the state vector, they can
+ * occur at virtually any time, unknown in advance. The integrators will
+ * take care to avoid sign changes inside the steps, they will reduce
+ * the step size when such an event is detected in order to put this
+ * event exactly at the end of the current step. This guarantees that
+ * step interpolation (which always has a one step scope) is relevant
+ * even in presence of discontinuities. This is independent from the
+ * stepsize control provided by integrators that monitor the local
+ * error (this event handling feature is available for all integrators,
+ * including fixed step ones).</p>
+ *
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+public interface FieldEventHandler<T extends RealFieldElement<T>>  {
+
+    /** Initialize event handler at the start of an ODE integration.
+     * <p>
+     * This method is called once at the start of the integration. It
+     * may be used by the event handler to initialize some internal data
+     * if needed.
+     * </p>
+     * @param initialState initial time, state vector and derivative
+     * @param finalTime target time for the integration
+     */
+    void init(FieldODEStateAndDerivative<T> initialState, T finalTime);
+
+    /** Compute the value of the switching function.
+
+     * <p>The discrete events are generated when the sign of this
+     * switching function changes. The integrator will take care to change
+     * the stepsize in such a way these events occur exactly at step boundaries.
+     * The switching function must be continuous in its roots neighborhood
+     * (but not necessarily smooth), as the integrator will need to find its
+     * roots to locate precisely the events.</p>
+     * <p>Also note that the integrator expect that once an event has occurred,
+     * the sign of the switching function at the start of the next step (i.e.
+     * just after the event) is the opposite of the sign just before the event.
+     * This consistency between the steps <string>must</strong> be preserved,
+     * otherwise {@link org.apache.commons.math3.exception.NoBracketingException
+     * exceptions} related to root not being bracketed will occur.</p>
+     * <p>This need for consistency is sometimes tricky to achieve. A typical
+     * example is using an event to model a ball bouncing on the floor. The first
+     * idea to represent this would be to have {@code g(t) = h(t)} where h is the
+     * height above the floor at time {@code t}. When {@code g(t)} reaches 0, the
+     * ball is on the floor, so it should bounce and the typical way to do this is
+     * to reverse its vertical velocity. However, this would mean that before the
+     * event {@code g(t)} was decreasing from positive values to 0, and after the
+     * event {@code g(t)} would be increasing from 0 to positive values again.
+     * Consistency is broken here! The solution here is to have {@code g(t) = sign
+     * * h(t)}, where sign is a variable with initial value set to {@code +1}. Each
+     * time {@link #eventOccurred(FieldODEStateAndDerivative, boolean) eventOccurred}
+     * method is called, {@code sign} is reset to {@code -sign}. This allows the
+     * {@code g(t)} function to remain continuous (and even smooth) even across events,
+     * despite {@code h(t)} is not. Basically, the event is used to <em>fold</em>
+     * {@code h(t)} at bounce points, and {@code sign} is used to <em>unfold</em> it
+     * back, so the solvers sees a {@code g(t)} function which behaves smoothly even
+     * across events.</p>
+
+     * @param state current value of the independent <i>time</i> variable, state vector
+     * and derivative
+     * @return value of the g switching function
+     */
+    T g(FieldODEStateAndDerivative<T> state);
+
+    /** Handle an event and choose what to do next.
+
+     * <p>This method is called when the integrator has accepted a step
+     * ending exactly on a sign change of the function, just <em>before</em>
+     * the step handler itself is called (see below for scheduling). It
+     * allows the user to update his internal data to acknowledge the fact
+     * the event has been handled (for example setting a flag in the {@link
+     * org.apache.commons.math3.ode.FirstOrderDifferentialEquations
+     * differential equations} to switch the derivatives computation in
+     * case of discontinuity), or to direct the integrator to either stop
+     * or continue integration, possibly with a reset state or derivatives.</p>
+
+     * <ul>
+     *   <li>if {@link Action#STOP} is returned, the step handler will be called
+     *   with the <code>isLast</code> flag of the {@link
+     *   org.apache.commons.math3.ode.sampling.StepHandler#handleStep handleStep}
+     *   method set to true and the integration will be stopped,</li>
+     *   <li>if {@link Action#RESET_STATE} is returned, the {@link #resetState
+     *   resetState} method will be called once the step handler has
+     *   finished its task, and the integrator will also recompute the
+     *   derivatives,</li>
+     *   <li>if {@link Action#RESET_DERIVATIVES} is returned, the integrator
+     *   will recompute the derivatives,
+     *   <li>if {@link Action#CONTINUE} is returned, no specific action will
+     *   be taken (apart from having called this method) and integration
+     *   will continue.</li>
+     * </ul>
+
+     * <p>The scheduling between this method and the {@link
+     * org.apache.commons.math3.ode.sampling.FieldStepHandler FieldStepHandler} method {@link
+     * org.apache.commons.math3.ode.sampling.FieldStepHandler#handleStep(
+     * org.apache.commons.math3.ode.sampling.FieldStepInterpolator, boolean)
+     * handleStep(interpolator, isLast)} is to call this method first and
+     * <code>handleStep</code> afterwards. This scheduling allows the integrator to
+     * pass <code>true</code> as the <code>isLast</code> parameter to the step
+     * handler to make it aware the step will be the last one if this method
+     * returns {@link Action#STOP}. As the interpolator may be used to navigate back
+     * throughout the last step, user code called by this method and user
+     * code called by step handlers may experience apparently out of order values
+     * of the independent time variable. As an example, if the same user object
+     * implements both this {@link FieldEventHandler FieldEventHandler} interface and the
+     * {@link org.apache.commons.math3.ode.sampling.FieldStepHandler FieldStepHandler}
+     * interface, a <em>forward</em> integration may call its
+     * {code eventOccurred} method with t = 10 first and call its
+     * {code handleStep} method with t = 9 afterwards. Such out of order
+     * calls are limited to the size of the integration step for {@link
+     * org.apache.commons.math3.ode.sampling.FieldStepHandler variable step handlers}.</p>
+
+     * @param state current value of the independent <i>time</i> variable, state vector
+     * and derivative
+     * @param increasing if true, the value of the switching function increases
+     * when times increases around event (note that increase is measured with respect
+     * to physical time, not with respect to integration which may go backward in time)
+     * @return indication of what the integrator should do next, this
+     * value must be one of {@link Action#STOP}, {@link Action#RESET_STATE},
+     * {@link Action#RESET_DERIVATIVES} or {@link Action#CONTINUE}
+     */
+    Action eventOccurred(FieldODEStateAndDerivative<T> state, boolean increasing);
+
+    /** Reset the state prior to continue the integration.
+
+     * <p>This method is called after the step handler has returned and
+     * before the next step is started, but only when {@link
+     * #eventOccurred(FieldODEStateAndDerivative, boolean) eventOccurred} has itself
+     * returned the {@link Action#RESET_STATE} indicator. It allows the user to reset
+     * the state vector for the next step, without perturbing the step handler of the
+     * finishing step. If the {@link #eventOccurred(FieldODEStateAndDerivative, boolean)
+     * eventOccurred} never returns the {@link Action#RESET_STATE} indicator, this
+     * function will never be called, and it is safe to leave its body empty.</p>
+     * @param state current value of the independent <i>time</i> variable, state vector
+     * and derivative
+     * @return reset state (note that it does not include the derivatives, they will
+     * be added automatically by the integrator afterwards)
+     */
+    FieldODEState<T> resetState(FieldODEStateAndDerivative<T> state);
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/events/FieldEventState.java b/src/main/java/org/apache/commons/math3/ode/events/FieldEventState.java
new file mode 100644
index 0000000..5c22357
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/events/FieldEventState.java
@@ -0,0 +1,344 @@
+/*
+ * 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.math3.ode.events;
+
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.analysis.RealFieldUnivariateFunction;
+import org.apache.commons.math3.analysis.solvers.AllowedSolution;
+import org.apache.commons.math3.analysis.solvers.BracketedRealFieldUnivariateSolver;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NoBracketingException;
+import org.apache.commons.math3.ode.FieldODEState;
+import org.apache.commons.math3.ode.FieldODEStateAndDerivative;
+import org.apache.commons.math3.ode.sampling.FieldStepInterpolator;
+import org.apache.commons.math3.util.FastMath;
+
+/** This class handles the state for one {@link EventHandler
+ * event handler} during integration steps.
+ *
+ * <p>Each time the integrator proposes a step, the event handler
+ * switching function should be checked. This class handles the state
+ * of one handler during one integration step, with references to the
+ * state at the end of the preceding step. This information is used to
+ * decide if the handler should trigger an event or not during the
+ * proposed step.</p>
+ *
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+public class FieldEventState<T extends RealFieldElement<T>> {
+
+    /** Event handler. */
+    private final FieldEventHandler<T> handler;
+
+    /** Maximal time interval between events handler checks. */
+    private final double maxCheckInterval;
+
+    /** Convergence threshold for event localization. */
+    private final T convergence;
+
+    /** Upper limit in the iteration count for event localization. */
+    private final int maxIterationCount;
+
+    /** Time at the beginning of the step. */
+    private T t0;
+
+    /** Value of the events handler at the beginning of the step. */
+    private T g0;
+
+    /** Simulated sign of g0 (we cheat when crossing events). */
+    private boolean g0Positive;
+
+    /** Indicator of event expected during the step. */
+    private boolean pendingEvent;
+
+    /** Occurrence time of the pending event. */
+    private T pendingEventTime;
+
+    /** Occurrence time of the previous event. */
+    private T previousEventTime;
+
+    /** Integration direction. */
+    private boolean forward;
+
+    /** Variation direction around pending event.
+     *  (this is considered with respect to the integration direction)
+     */
+    private boolean increasing;
+
+    /** Next action indicator. */
+    private Action nextAction;
+
+    /** Root-finding algorithm to use to detect state events. */
+    private final BracketedRealFieldUnivariateSolver<T> solver;
+
+    /** Simple constructor.
+     * @param handler event handler
+     * @param maxCheckInterval maximal time interval between switching
+     * function checks (this interval prevents missing sign changes in
+     * case the integration steps becomes very large)
+     * @param convergence convergence threshold in the event time search
+     * @param maxIterationCount upper limit of the iteration count in
+     * the event time search
+     * @param solver Root-finding algorithm to use to detect state events
+     */
+    public FieldEventState(final FieldEventHandler<T> handler, final double maxCheckInterval,
+                           final T convergence, final int maxIterationCount,
+                           final BracketedRealFieldUnivariateSolver<T> solver) {
+        this.handler           = handler;
+        this.maxCheckInterval  = maxCheckInterval;
+        this.convergence       = convergence.abs();
+        this.maxIterationCount = maxIterationCount;
+        this.solver            = solver;
+
+        // some dummy values ...
+        t0                = null;
+        g0                = null;
+        g0Positive        = true;
+        pendingEvent      = false;
+        pendingEventTime  = null;
+        previousEventTime = null;
+        increasing        = true;
+        nextAction        = Action.CONTINUE;
+
+    }
+
+    /** Get the underlying event handler.
+     * @return underlying event handler
+     */
+    public FieldEventHandler<T> getEventHandler() {
+        return handler;
+    }
+
+    /** Get the maximal time interval between events handler checks.
+     * @return maximal time interval between events handler checks
+     */
+    public double getMaxCheckInterval() {
+        return maxCheckInterval;
+    }
+
+    /** Get the convergence threshold for event localization.
+     * @return convergence threshold for event localization
+     */
+    public T getConvergence() {
+        return convergence;
+    }
+
+    /** Get the upper limit in the iteration count for event localization.
+     * @return upper limit in the iteration count for event localization
+     */
+    public int getMaxIterationCount() {
+        return maxIterationCount;
+    }
+
+    /** Reinitialize the beginning of the step.
+     * @param interpolator valid for the current step
+     * @exception MaxCountExceededException if the interpolator throws one because
+     * the number of functions evaluations is exceeded
+     */
+    public void reinitializeBegin(final FieldStepInterpolator<T> interpolator)
+        throws MaxCountExceededException {
+
+        final FieldODEStateAndDerivative<T> s0 = interpolator.getPreviousState();
+        t0 = s0.getTime();
+        g0 = handler.g(s0);
+        if (g0.getReal() == 0) {
+            // excerpt from MATH-421 issue:
+            // If an ODE solver is setup with an EventHandler that return STOP
+            // when the even is triggered, the integrator stops (which is exactly
+            // the expected behavior). If however the user wants to restart the
+            // solver from the final state reached at the event with the same
+            // configuration (expecting the event to be triggered again at a
+            // later time), then the integrator may fail to start. It can get stuck
+            // at the previous event. The use case for the bug MATH-421 is fairly
+            // general, so events occurring exactly at start in the first step should
+            // be ignored.
+
+            // extremely rare case: there is a zero EXACTLY at interval start
+            // we will use the sign slightly after step beginning to force ignoring this zero
+            final double epsilon = FastMath.max(solver.getAbsoluteAccuracy().getReal(),
+                                                FastMath.abs(solver.getRelativeAccuracy().multiply(t0).getReal()));
+            final T tStart = t0.add(0.5 * epsilon);
+            g0 = handler.g(interpolator.getInterpolatedState(tStart));
+        }
+        g0Positive = g0.getReal() >= 0;
+
+    }
+
+    /** Evaluate the impact of the proposed step on the event handler.
+     * @param interpolator step interpolator for the proposed step
+     * @return true if the event handler triggers an event before
+     * the end of the proposed step
+     * @exception MaxCountExceededException if the interpolator throws one because
+     * the number of functions evaluations is exceeded
+     * @exception NoBracketingException if the event cannot be bracketed
+     */
+    public boolean evaluateStep(final FieldStepInterpolator<T> interpolator)
+        throws MaxCountExceededException, NoBracketingException {
+
+        forward = interpolator.isForward();
+        final FieldODEStateAndDerivative<T> s1 = interpolator.getCurrentState();
+        final T t1 = s1.getTime();
+        final T dt = t1.subtract(t0);
+        if (dt.abs().subtract(convergence).getReal() < 0) {
+            // we cannot do anything on such a small step, don't trigger any events
+            return false;
+        }
+        final int n = FastMath.max(1, (int) FastMath.ceil(FastMath.abs(dt.getReal()) / maxCheckInterval));
+        final T   h = dt.divide(n);
+
+        final RealFieldUnivariateFunction<T> f = new RealFieldUnivariateFunction<T>() {
+            /** {@inheritDoc} */
+            public T value(final T t) {
+                return handler.g(interpolator.getInterpolatedState(t));
+            }
+        };
+
+        T ta = t0;
+        T ga = g0;
+        for (int i = 0; i < n; ++i) {
+
+            // evaluate handler value at the end of the substep
+            final T tb = (i == n - 1) ? t1 : t0.add(h.multiply(i + 1));
+            final T gb = handler.g(interpolator.getInterpolatedState(tb));
+
+            // check events occurrence
+            if (g0Positive ^ (gb.getReal() >= 0)) {
+                // there is a sign change: an event is expected during this step
+
+                // variation direction, with respect to the integration direction
+                increasing = gb.subtract(ga).getReal() >= 0;
+
+                // find the event time making sure we select a solution just at or past the exact root
+                final T root = forward ?
+                               solver.solve(maxIterationCount, f, ta, tb, AllowedSolution.RIGHT_SIDE) :
+                               solver.solve(maxIterationCount, f, tb, ta, AllowedSolution.LEFT_SIDE);
+
+                if (previousEventTime != null &&
+                    root.subtract(ta).abs().subtract(convergence).getReal() <= 0 &&
+                    root.subtract(previousEventTime).abs().subtract(convergence).getReal() <= 0) {
+                    // we have either found nothing or found (again ?) a past event,
+                    // retry the substep excluding this value, and taking care to have the
+                    // required sign in case the g function is noisy around its zero and
+                    // crosses the axis several times
+                    do {
+                        ta = forward ? ta.add(convergence) : ta.subtract(convergence);
+                        ga = f.value(ta);
+                    } while ((g0Positive ^ (ga.getReal() >= 0)) && (forward ^ (ta.subtract(tb).getReal() >= 0)));
+
+                    if (forward ^ (ta.subtract(tb).getReal() >= 0)) {
+                        // we were able to skip this spurious root
+                        --i;
+                    } else {
+                        // we can't avoid this root before the end of the step,
+                        // we have to handle it despite it is close to the former one
+                        // maybe we have two very close roots
+                        pendingEventTime = root;
+                        pendingEvent     = true;
+                        return true;
+                    }
+                } else if (previousEventTime == null ||
+                           previousEventTime.subtract(root).abs().subtract(convergence).getReal() > 0) {
+                    pendingEventTime = root;
+                    pendingEvent     = true;
+                    return true;
+                } else {
+                    // no sign change: there is no event for now
+                    ta = tb;
+                    ga = gb;
+                }
+
+            } else {
+                // no sign change: there is no event for now
+                ta = tb;
+                ga = gb;
+            }
+
+        }
+
+        // no event during the whole step
+        pendingEvent     = false;
+        pendingEventTime = null;
+        return false;
+
+    }
+
+    /** Get the occurrence time of the event triggered in the current step.
+     * @return occurrence time of the event triggered in the current
+     * step or infinity if no events are triggered
+     */
+    public T getEventTime() {
+        return pendingEvent ?
+               pendingEventTime :
+               t0.getField().getZero().add(forward ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY);
+    }
+
+    /** Acknowledge the fact the step has been accepted by the integrator.
+     * @param state state at the end of the step
+     */
+    public void stepAccepted(final FieldODEStateAndDerivative<T> state) {
+
+        t0 = state.getTime();
+        g0 = handler.g(state);
+
+        if (pendingEvent && pendingEventTime.subtract(state.getTime()).abs().subtract(convergence).getReal() <= 0) {
+            // force the sign to its value "just after the event"
+            previousEventTime = state.getTime();
+            g0Positive        = increasing;
+            nextAction        = handler.eventOccurred(state, !(increasing ^ forward));
+        } else {
+            g0Positive = g0.getReal() >= 0;
+            nextAction = Action.CONTINUE;
+        }
+    }
+
+    /** Check if the integration should be stopped at the end of the
+     * current step.
+     * @return true if the integration should be stopped
+     */
+    public boolean stop() {
+        return nextAction == Action.STOP;
+    }
+
+    /** Let the event handler reset the state if it wants.
+     * @param state state at the beginning of the next step
+     * @return reset state (may by the same as initial state if only
+     * derivatives should be reset), or null if nothing is reset
+     */
+    public FieldODEState<T> reset(final FieldODEStateAndDerivative<T> state) {
+
+        if (!(pendingEvent && pendingEventTime.subtract(state.getTime()).abs().subtract(convergence).getReal() <= 0)) {
+            return null;
+        }
+
+        final FieldODEState<T> newState;
+        if (nextAction == Action.RESET_STATE) {
+            newState = handler.resetState(state);
+        } else if (nextAction == Action.RESET_DERIVATIVES) {
+            newState = state;
+        } else {
+            newState = null;
+        }
+        pendingEvent      = false;
+        pendingEventTime  = null;
+
+        return newState;
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/events/FilterType.java b/src/main/java/org/apache/commons/math3/ode/events/FilterType.java
new file mode 100644
index 0000000..2ac0df8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/events/FilterType.java
@@ -0,0 +1,400 @@
+/*
+ * 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.math3.ode.events;
+
+import org.apache.commons.math3.exception.MathInternalError;
+
+/** Enumerate for {@link EventFilter filtering events}.
+ *
+ * @since 3.2
+ */
+
+public enum FilterType {
+
+    /** Constant for triggering only decreasing events.
+     * <p>When this filter is used, the wrapped {@link EventHandler
+     * event handler} {@link EventHandler#eventOccurred(double, double[],
+     * boolean) eventOccurred} method will be called <em>only</em> with
+     * its {@code increasing} argument set to false.</p>
+     */
+    TRIGGER_ONLY_DECREASING_EVENTS {
+
+        /**  {@inheritDoc} */
+        @Override
+        protected boolean getTriggeredIncreasing() {
+            return false;
+        }
+
+        /** {@inheritDoc}
+         * <p>
+         * states scheduling for computing h(t,y) as an altered version of g(t, y)
+         * <ul>
+         * <li>0 are triggered events for which a zero is produced (here decreasing events)</li>
+         * <li>X are ignored events for which zero is masked (here increasing events)</li>
+         * </ul>
+         * </p>
+         * <pre>
+         *  g(t)
+         *             ___                     ___                     ___
+         *            /   \                   /   \                   /   \
+         *           /     \                 /     \                 /     \
+         *          /  g>0  \               /  g>0  \               /  g>0  \
+         *         /         \             /         \             /         \
+         *  ----- X --------- 0 --------- X --------- 0 --------- X --------- 0 ---
+         *       /             \         /             \         /             \
+         *      /               \ g<0   /               \  g<0  /               \ g<0
+         *     /                 \     /                 \     /                 \     /
+         * ___/                   \___/                   \___/                   \___/
+         * </pre>
+         * <pre>
+         *  h(t,y)) as an alteration of g(t,y)
+         *             ___                                 ___         ___
+         *    \       /   \                               /   \       /   \
+         *     \     /     \ h=+g                        /     \     /     \
+         *      \   /       \      h=min(-s,-g,+g)      /       \   /       \
+         *       \_/         \                         /         \_/         \
+         *  ------ ---------- 0 ----------_---------- 0 --------------------- 0 ---
+         *                     \         / \         /                         \
+         *   h=max(+s,-g,+g)    \       /   \       /       h=max(+s,-g,+g)     \
+         *                       \     /     \     / h=-g                        \     /
+         *                        \___/       \___/                               \___/
+         * </pre>
+         * <p>
+         * As shown by the figure above, several expressions are used to compute h,
+         * depending on the current state:
+         * <ul>
+         *   <li>h = max(+s,-g,+g)</li>
+         *   <li>h = +g</li>
+         *   <li>h = min(-s,-g,+g)</li>
+         *   <li>h = -g</li>
+         * </ul>
+         * where s is a tiny positive value: {@link org.apache.commons.math3.util.Precision#SAFE_MIN}.
+         * </p>
+         */
+        @Override
+        protected  Transformer selectTransformer(final Transformer previous,
+                                                 final double g, final boolean forward) {
+            if (forward) {
+                switch (previous) {
+                    case UNINITIALIZED :
+                        // we are initializing the first point
+                        if (g > 0) {
+                            // initialize as if previous root (i.e. backward one) was an ignored increasing event
+                            return Transformer.MAX;
+                        } else if (g < 0) {
+                            // initialize as if previous root (i.e. backward one) was a triggered decreasing event
+                            return Transformer.PLUS;
+                        } else {
+                            // we are exactly at a root, we don't know if it is an increasing
+                            // or a decreasing event, we remain in uninitialized state
+                            return Transformer.UNINITIALIZED;
+                        }
+                    case PLUS  :
+                        if (g >= 0) {
+                            // we have crossed the zero line on an ignored increasing event,
+                            // we must change the transformer
+                            return Transformer.MIN;
+                        } else {
+                            // we are still in the same status
+                            return previous;
+                        }
+                    case MINUS :
+                        if (g >= 0) {
+                            // we have crossed the zero line on an ignored increasing event,
+                            // we must change the transformer
+                            return Transformer.MAX;
+                        } else {
+                            // we are still in the same status
+                            return previous;
+                        }
+                    case MIN   :
+                        if (g <= 0) {
+                            // we have crossed the zero line on a triggered decreasing event,
+                            // we must change the transformer
+                            return Transformer.MINUS;
+                        } else {
+                            // we are still in the same status
+                            return previous;
+                        }
+                    case MAX   :
+                        if (g <= 0) {
+                            // we have crossed the zero line on a triggered decreasing event,
+                            // we must change the transformer
+                            return Transformer.PLUS;
+                        } else {
+                            // we are still in the same status
+                            return previous;
+                        }
+                    default    :
+                        // this should never happen
+                        throw new MathInternalError();
+                }
+            } else {
+                switch (previous) {
+                    case UNINITIALIZED :
+                        // we are initializing the first point
+                        if (g > 0) {
+                            // initialize as if previous root (i.e. forward one) was a triggered decreasing event
+                            return Transformer.MINUS;
+                        } else if (g < 0) {
+                            // initialize as if previous root (i.e. forward one) was an ignored increasing event
+                            return Transformer.MIN;
+                        } else {
+                            // we are exactly at a root, we don't know if it is an increasing
+                            // or a decreasing event, we remain in uninitialized state
+                            return Transformer.UNINITIALIZED;
+                        }
+                    case PLUS  :
+                        if (g <= 0) {
+                            // we have crossed the zero line on an ignored increasing event,
+                            // we must change the transformer
+                            return Transformer.MAX;
+                        } else {
+                            // we are still in the same status
+                            return previous;
+                        }
+                    case MINUS :
+                        if (g <= 0) {
+                            // we have crossed the zero line on an ignored increasing event,
+                            // we must change the transformer
+                            return Transformer.MIN;
+                        } else {
+                            // we are still in the same status
+                            return previous;
+                        }
+                    case MIN   :
+                        if (g >= 0) {
+                            // we have crossed the zero line on a triggered decreasing event,
+                            // we must change the transformer
+                            return Transformer.PLUS;
+                        } else {
+                            // we are still in the same status
+                            return previous;
+                        }
+                    case MAX   :
+                        if (g >= 0) {
+                            // we have crossed the zero line on a triggered decreasing event,
+                            // we must change the transformer
+                            return Transformer.MINUS;
+                        } else {
+                            // we are still in the same status
+                            return previous;
+                        }
+                    default    :
+                        // this should never happen
+                        throw new MathInternalError();
+                }
+            }
+        }
+
+    },
+
+    /** Constant for triggering only increasing events.
+     * <p>When this filter is used, the wrapped {@link EventHandler
+     * event handler} {@link EventHandler#eventOccurred(double, double[],
+     * boolean) eventOccurred} method will be called <em>only</em> with
+     * its {@code increasing} argument set to true.</p>
+     */
+    TRIGGER_ONLY_INCREASING_EVENTS {
+
+        /**  {@inheritDoc} */
+        @Override
+        protected boolean getTriggeredIncreasing() {
+            return true;
+        }
+
+        /** {@inheritDoc}
+         * <p>
+         * states scheduling for computing h(t,y) as an altered version of g(t, y)
+         * <ul>
+         * <li>0 are triggered events for which a zero is produced (here increasing events)</li>
+         * <li>X are ignored events for which zero is masked (here decreasing events)</li>
+         * </ul>
+         * </p>
+         * <pre>
+         *  g(t)
+         *             ___                     ___                     ___
+         *            /   \                   /   \                   /   \
+         *           /     \                 /     \                 /     \
+         *          /  g>0  \               /  g>0  \               /  g>0  \
+         *         /         \             /         \             /         \
+         *  ----- 0 --------- X --------- 0 --------- X --------- 0 --------- X ---
+         *       /             \         /             \         /             \
+         *      /               \ g<0   /               \  g<0  /               \ g<0
+         *     /                 \     /                 \     /                 \     /
+         * ___/                   \___/                   \___/                   \___/
+         * </pre>
+         * <pre>
+         *  h(t,y)) as an alteration of g(t,y)
+         *                                     ___         ___
+         *    \                               /   \       /   \
+         *     \ h=-g                        /     \     /     \ h=-g
+         *      \      h=min(-s,-g,+g)      /       \   /       \      h=min(-s,-g,+g)
+         *       \                         /         \_/         \
+         *  ------0 ----------_---------- 0 --------------------- 0 --------- _ ---
+         *         \         / \         /                         \         / \
+         *          \       /   \       /       h=max(+s,-g,+g)     \       /   \
+         *           \     /     \     / h=+g                        \     /     \     /
+         *            \___/       \___/                               \___/       \___/
+         * </pre>
+         * <p>
+         * As shown by the figure above, several expressions are used to compute h,
+         * depending on the current state:
+         * <ul>
+         *   <li>h = max(+s,-g,+g)</li>
+         *   <li>h = +g</li>
+         *   <li>h = min(-s,-g,+g)</li>
+         *   <li>h = -g</li>
+         * </ul>
+         * where s is a tiny positive value: {@link org.apache.commons.math3.util.Precision#SAFE_MIN}.
+         * </p>
+         */
+        @Override
+        protected  Transformer selectTransformer(final Transformer previous,
+                                                 final double g, final boolean forward) {
+            if (forward) {
+                switch (previous) {
+                    case UNINITIALIZED :
+                        // we are initializing the first point
+                        if (g > 0) {
+                            // initialize as if previous root (i.e. backward one) was a triggered increasing event
+                            return Transformer.PLUS;
+                        } else if (g < 0) {
+                            // initialize as if previous root (i.e. backward one) was an ignored decreasing event
+                            return Transformer.MIN;
+                        } else {
+                            // we are exactly at a root, we don't know if it is an increasing
+                            // or a decreasing event, we remain in uninitialized state
+                            return Transformer.UNINITIALIZED;
+                        }
+                    case PLUS  :
+                        if (g <= 0) {
+                            // we have crossed the zero line on an ignored decreasing event,
+                            // we must change the transformer
+                            return Transformer.MAX;
+                        } else {
+                            // we are still in the same status
+                            return previous;
+                        }
+                    case MINUS :
+                        if (g <= 0) {
+                            // we have crossed the zero line on an ignored decreasing event,
+                            // we must change the transformer
+                            return Transformer.MIN;
+                        } else {
+                            // we are still in the same status
+                            return previous;
+                        }
+                    case MIN   :
+                        if (g >= 0) {
+                            // we have crossed the zero line on a triggered increasing event,
+                            // we must change the transformer
+                            return Transformer.PLUS;
+                        } else {
+                            // we are still in the same status
+                            return previous;
+                        }
+                    case MAX   :
+                        if (g >= 0) {
+                            // we have crossed the zero line on a triggered increasing event,
+                            // we must change the transformer
+                            return Transformer.MINUS;
+                        } else {
+                            // we are still in the same status
+                            return previous;
+                        }
+                    default    :
+                        // this should never happen
+                        throw new MathInternalError();
+                }
+            } else {
+                switch (previous) {
+                    case UNINITIALIZED :
+                        // we are initializing the first point
+                        if (g > 0) {
+                            // initialize as if previous root (i.e. forward one) was an ignored decreasing event
+                            return Transformer.MAX;
+                        } else if (g < 0) {
+                            // initialize as if previous root (i.e. forward one) was a triggered increasing event
+                            return Transformer.MINUS;
+                        } else {
+                            // we are exactly at a root, we don't know if it is an increasing
+                            // or a decreasing event, we remain in uninitialized state
+                            return Transformer.UNINITIALIZED;
+                        }
+                    case PLUS  :
+                        if (g >= 0) {
+                            // we have crossed the zero line on an ignored decreasing event,
+                            // we must change the transformer
+                            return Transformer.MIN;
+                        } else {
+                            // we are still in the same status
+                            return previous;
+                        }
+                    case MINUS :
+                        if (g >= 0) {
+                            // we have crossed the zero line on an ignored decreasing event,
+                            // we must change the transformer
+                            return Transformer.MAX;
+                        } else {
+                            // we are still in the same status
+                            return previous;
+                        }
+                    case MIN   :
+                        if (g <= 0) {
+                            // we have crossed the zero line on a triggered increasing event,
+                            // we must change the transformer
+                            return Transformer.MINUS;
+                        } else {
+                            // we are still in the same status
+                            return previous;
+                        }
+                    case MAX   :
+                        if (g <= 0) {
+                            // we have crossed the zero line on a triggered increasing event,
+                            // we must change the transformer
+                            return Transformer.PLUS;
+                        } else {
+                            // we are still in the same status
+                            return previous;
+                        }
+                    default    :
+                        // this should never happen
+                        throw new MathInternalError();
+                }
+            }
+        }
+
+    };
+
+    /** Get the increasing status of triggered events.
+     * @return true if triggered events are increasing events
+     */
+    protected abstract boolean getTriggeredIncreasing();
+
+    /** Get next function transformer in the specified direction.
+     * @param previous transformer active on the previous point with respect
+     * to integration direction (may be null if no previous point is known)
+     * @param g current value of the g function
+     * @param forward true if integration goes forward
+     * @return next transformer transformer
+     */
+    protected abstract Transformer selectTransformer(Transformer previous,
+                                                     double g, boolean forward);
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/events/Transformer.java b/src/main/java/org/apache/commons/math3/ode/events/Transformer.java
new file mode 100644
index 0000000..5c1f97c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/events/Transformer.java
@@ -0,0 +1,107 @@
+/*
+ * 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.math3.ode.events;
+
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.Precision;
+
+
+/** Transformer for {@link EventHandler#g(double, double[]) g functions}.
+ * @see EventFilter
+ * @see FilterType
+ * @since 3.2
+ */
+enum Transformer {
+
+    /** Transformer computing transformed = 0.
+     * <p>
+     * This transformer is used when we initialize the filter, until we get at
+     * least one non-zero value to select the proper transformer.
+     * </p>
+     */
+    UNINITIALIZED {
+        /**  {@inheritDoc} */
+        @Override
+        protected double transformed(final double g) {
+            return 0;
+        }
+    },
+
+    /** Transformer computing transformed = g.
+     * <p>
+     * When this transformer is applied, the roots of the original function
+     * are preserved, with the same {@code increasing/decreasing} status.
+     * </p>
+     */
+    PLUS {
+        /**  {@inheritDoc} */
+        @Override
+        protected double transformed(final double g) {
+            return g;
+        }
+    },
+
+    /** Transformer computing transformed = -g.
+     * <p>
+     * When this transformer is applied, the roots of the original function
+     * are preserved, with reversed {@code increasing/decreasing} status.
+     * </p>
+     */
+    MINUS {
+        /**  {@inheritDoc} */
+        @Override
+        protected double transformed(final double g) {
+            return -g;
+        }
+    },
+
+    /** Transformer computing transformed = min(-{@link Precision#SAFE_MIN}, -g, +g).
+     * <p>
+     * When this transformer is applied, the transformed function is
+     * guaranteed to be always strictly negative (i.e. there are no roots).
+     * </p>
+     */
+    MIN {
+        /**  {@inheritDoc} */
+        @Override
+        protected double transformed(final double g) {
+            return FastMath.min(-Precision.SAFE_MIN, FastMath.min(-g, +g));
+        }
+    },
+
+    /** Transformer computing transformed = max(+{@link Precision#SAFE_MIN}, -g, +g).
+     * <p>
+     * When this transformer is applied, the transformed function is
+     * guaranteed to be always strictly positive (i.e. there are no roots).
+     * </p>
+     */
+    MAX {
+        /**  {@inheritDoc} */
+        @Override
+        protected double transformed(final double g) {
+            return FastMath.max(+Precision.SAFE_MIN, FastMath.max(-g, +g));
+        }
+    };
+
+    /** Transform value of function g.
+     * @param g raw value of function g
+     * @return transformed value of function g
+     */
+    protected abstract double transformed(double g);
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/events/package-info.java b/src/main/java/org/apache/commons/math3/ode/events/package-info.java
new file mode 100644
index 0000000..9d3b917
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/events/package-info.java
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * <p>
+ * This package provides classes to handle discrete events occurring during
+ * Ordinary Differential Equations integration.
+ * </p>
+ *
+ * <p>
+ * Discrete events detection is based on switching functions. The user provides
+ * a simple {@link org.apache.commons.math3.ode.events.EventHandler#g g(t, y)}
+ * function depending on the current time and state. The integrator will monitor
+ * the value of the function throughout integration range and will trigger the
+ * event when its sign changes. The magnitude of the value is almost irrelevant,
+ * it should however be continuous (but not necessarily smooth) for the sake of
+ * root finding. The steps are shortened as needed to ensure the events occur
+ * at step boundaries (even if the integrator is a fixed-step integrator).
+ * </p>
+ *
+ * <p>
+ * When an event is triggered, several different options are available:
+ * </p>
+ * <ul>
+ *  <li>integration can be stopped (this is called a G-stop facility),</li>
+ *  <li>the state vector or the derivatives can be changed,</li>
+ *  <li>or integration can simply go on.</li>
+ * </ul>
+ *
+ * <p>
+ * The first case, G-stop, is the most common one. A typical use case is when an
+ * ODE must be solved up to some target state is reached, with a known value of
+ * the state but an unknown occurrence time. As an example, if we want to monitor
+ * a chemical reaction up to some predefined concentration for the first substance,
+ * we can use the following switching function setting:
+ * <pre>
+ *  public double g(double t, double[] y) {
+ *    return y[0] - targetConcentration;
+ *  }
+ *
+ *  public int eventOccurred(double t, double[] y) {
+ *    return STOP;
+ *  }
+ * </pre>
+ * </p>
+ *
+ * <p>
+ * The second case, change state vector or derivatives is encountered when dealing
+ * with discontinuous dynamical models. A typical case would be the motion of a
+ * spacecraft when thrusters are fired for orbital maneuvers. The acceleration is
+ * smooth as long as no maneuver are performed, depending only on gravity, drag,
+ * third body attraction, radiation pressure. Firing a thruster introduces a
+ * discontinuity that must be handled appropriately by the integrator. In such a case,
+ * we would use a switching function setting similar to this:
+ * <pre>
+ *  public double g(double t, double[] y) {
+ *    return (t - tManeuverStart) &lowast; (t - tManeuverStop);
+ *  }
+ *
+ *  public int eventOccurred(double t, double[] y) {
+ *    return RESET_DERIVATIVES;
+ *  }
+ * </pre>
+ * </p>
+ *
+ * <p>
+ * The third case is useful mainly for monitoring purposes, a simple example is:
+ * <pre>
+ *  public double g(double t, double[] y) {
+ *    return y[0] - y[1];
+ *  }
+ *
+ *  public int eventOccurred(double t, double[] y) {
+ *    logger.log("y0(t) and y1(t) curves cross at t = " + t);
+ *    return CONTINUE;
+ *  }
+ * </pre>
+ * </p>
+ *
+ *
+ */
+package org.apache.commons.math3.ode.events;
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsBashforthFieldIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsBashforthFieldIntegrator.java
new file mode 100644
index 0000000..bec3343
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsBashforthFieldIntegrator.java
@@ -0,0 +1,354 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NoBracketingException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.linear.Array2DRowFieldMatrix;
+import org.apache.commons.math3.linear.FieldMatrix;
+import org.apache.commons.math3.ode.FieldExpandableODE;
+import org.apache.commons.math3.ode.FieldODEState;
+import org.apache.commons.math3.ode.FieldODEStateAndDerivative;
+import org.apache.commons.math3.util.MathArrays;
+
+
+/**
+ * This class implements explicit Adams-Bashforth integrators for Ordinary
+ * Differential Equations.
+ *
+ * <p>Adams-Bashforth methods (in fact due to Adams alone) are explicit
+ * multistep ODE solvers. This implementation is a variation of the classical
+ * one: it uses adaptive stepsize to implement error control, whereas
+ * classical implementations are fixed step size. The value of state vector
+ * at step n+1 is a simple combination of the value at step n and of the
+ * derivatives at steps n, n-1, n-2 ... Depending on the number k of previous
+ * steps one wants to use for computing the next value, different formulas
+ * are available:</p>
+ * <ul>
+ *   <li>k = 1: y<sub>n+1</sub> = y<sub>n</sub> + h y'<sub>n</sub></li>
+ *   <li>k = 2: y<sub>n+1</sub> = y<sub>n</sub> + h (3y'<sub>n</sub>-y'<sub>n-1</sub>)/2</li>
+ *   <li>k = 3: y<sub>n+1</sub> = y<sub>n</sub> + h (23y'<sub>n</sub>-16y'<sub>n-1</sub>+5y'<sub>n-2</sub>)/12</li>
+ *   <li>k = 4: y<sub>n+1</sub> = y<sub>n</sub> + h (55y'<sub>n</sub>-59y'<sub>n-1</sub>+37y'<sub>n-2</sub>-9y'<sub>n-3</sub>)/24</li>
+ *   <li>...</li>
+ * </ul>
+ *
+ * <p>A k-steps Adams-Bashforth method is of order k.</p>
+ *
+ * <h3>Implementation details</h3>
+ *
+ * <p>We define scaled derivatives s<sub>i</sub>(n) at step n as:
+ * <pre>
+ * s<sub>1</sub>(n) = h y'<sub>n</sub> for first derivative
+ * s<sub>2</sub>(n) = h<sup>2</sup>/2 y''<sub>n</sub> for second derivative
+ * s<sub>3</sub>(n) = h<sup>3</sup>/6 y'''<sub>n</sub> for third derivative
+ * ...
+ * s<sub>k</sub>(n) = h<sup>k</sup>/k! y<sup>(k)</sup><sub>n</sub> for k<sup>th</sup> derivative
+ * </pre></p>
+ *
+ * <p>The definitions above use the classical representation with several previous first
+ * derivatives. Lets define
+ * <pre>
+ *   q<sub>n</sub> = [ s<sub>1</sub>(n-1) s<sub>1</sub>(n-2) ... s<sub>1</sub>(n-(k-1)) ]<sup>T</sup>
+ * </pre>
+ * (we omit the k index in the notation for clarity). With these definitions,
+ * Adams-Bashforth methods can be written:
+ * <ul>
+ *   <li>k = 1: y<sub>n+1</sub> = y<sub>n</sub> + s<sub>1</sub>(n)</li>
+ *   <li>k = 2: y<sub>n+1</sub> = y<sub>n</sub> + 3/2 s<sub>1</sub>(n) + [ -1/2 ] q<sub>n</sub></li>
+ *   <li>k = 3: y<sub>n+1</sub> = y<sub>n</sub> + 23/12 s<sub>1</sub>(n) + [ -16/12 5/12 ] q<sub>n</sub></li>
+ *   <li>k = 4: y<sub>n+1</sub> = y<sub>n</sub> + 55/24 s<sub>1</sub>(n) + [ -59/24 37/24 -9/24 ] q<sub>n</sub></li>
+ *   <li>...</li>
+ * </ul></p>
+ *
+ * <p>Instead of using the classical representation with first derivatives only (y<sub>n</sub>,
+ * s<sub>1</sub>(n) and q<sub>n</sub>), our implementation uses the Nordsieck vector with
+ * higher degrees scaled derivatives all taken at the same step (y<sub>n</sub>, s<sub>1</sub>(n)
+ * and r<sub>n</sub>) where r<sub>n</sub> is defined as:
+ * <pre>
+ * r<sub>n</sub> = [ s<sub>2</sub>(n), s<sub>3</sub>(n) ... s<sub>k</sub>(n) ]<sup>T</sup>
+ * </pre>
+ * (here again we omit the k index in the notation for clarity)
+ * </p>
+ *
+ * <p>Taylor series formulas show that for any index offset i, s<sub>1</sub>(n-i) can be
+ * computed from s<sub>1</sub>(n), s<sub>2</sub>(n) ... s<sub>k</sub>(n), the formula being exact
+ * for degree k polynomials.
+ * <pre>
+ * s<sub>1</sub>(n-i) = s<sub>1</sub>(n) + &sum;<sub>j&gt;0</sub> (j+1) (-i)<sup>j</sup> s<sub>j+1</sub>(n)
+ * </pre>
+ * The previous formula can be used with several values for i to compute the transform between
+ * classical representation and Nordsieck vector. The transform between r<sub>n</sub>
+ * and q<sub>n</sub> resulting from the Taylor series formulas above is:
+ * <pre>
+ * q<sub>n</sub> = s<sub>1</sub>(n) u + P r<sub>n</sub>
+ * </pre>
+ * where u is the [ 1 1 ... 1 ]<sup>T</sup> vector and P is the (k-1)&times;(k-1) matrix built
+ * with the (j+1) (-i)<sup>j</sup> terms with i being the row number starting from 1 and j being
+ * the column number starting from 1:
+ * <pre>
+ *        [  -2   3   -4    5  ... ]
+ *        [  -4  12  -32   80  ... ]
+ *   P =  [  -6  27 -108  405  ... ]
+ *        [  -8  48 -256 1280  ... ]
+ *        [          ...           ]
+ * </pre></p>
+ *
+ * <p>Using the Nordsieck vector has several advantages:
+ * <ul>
+ *   <li>it greatly simplifies step interpolation as the interpolator mainly applies
+ *   Taylor series formulas,</li>
+ *   <li>it simplifies step changes that occur when discrete events that truncate
+ *   the step are triggered,</li>
+ *   <li>it allows to extend the methods in order to support adaptive stepsize.</li>
+ * </ul></p>
+ *
+ * <p>The Nordsieck vector at step n+1 is computed from the Nordsieck vector at step n as follows:
+ * <ul>
+ *   <li>y<sub>n+1</sub> = y<sub>n</sub> + s<sub>1</sub>(n) + u<sup>T</sup> r<sub>n</sub></li>
+ *   <li>s<sub>1</sub>(n+1) = h f(t<sub>n+1</sub>, y<sub>n+1</sub>)</li>
+ *   <li>r<sub>n+1</sub> = (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u + P<sup>-1</sup> A P r<sub>n</sub></li>
+ * </ul>
+ * where A is a rows shifting matrix (the lower left part is an identity matrix):
+ * <pre>
+ *        [ 0 0   ...  0 0 | 0 ]
+ *        [ ---------------+---]
+ *        [ 1 0   ...  0 0 | 0 ]
+ *    A = [ 0 1   ...  0 0 | 0 ]
+ *        [       ...      | 0 ]
+ *        [ 0 0   ...  1 0 | 0 ]
+ *        [ 0 0   ...  0 1 | 0 ]
+ * </pre></p>
+ *
+ * <p>The P<sup>-1</sup>u vector and the P<sup>-1</sup> A P matrix do not depend on the state,
+ * they only depend on k and therefore are precomputed once for all.</p>
+ *
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+public class AdamsBashforthFieldIntegrator<T extends RealFieldElement<T>> extends AdamsFieldIntegrator<T> {
+
+    /** Integrator method name. */
+    private static final String METHOD_NAME = "Adams-Bashforth";
+
+    /**
+     * Build an Adams-Bashforth integrator with the given order and step control parameters.
+     * @param field field to which the time and state vector elements belong
+     * @param nSteps number of steps of the method excluding the one being computed
+     * @param minStep minimal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param maxStep maximal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param scalAbsoluteTolerance allowed absolute error
+     * @param scalRelativeTolerance allowed relative error
+     * @exception NumberIsTooSmallException if order is 1 or less
+     */
+    public AdamsBashforthFieldIntegrator(final Field<T> field, final int nSteps,
+                                         final double minStep, final double maxStep,
+                                         final double scalAbsoluteTolerance,
+                                         final double scalRelativeTolerance)
+        throws NumberIsTooSmallException {
+        super(field, METHOD_NAME, nSteps, nSteps, minStep, maxStep,
+              scalAbsoluteTolerance, scalRelativeTolerance);
+    }
+
+    /**
+     * Build an Adams-Bashforth integrator with the given order and step control parameters.
+     * @param field field to which the time and state vector elements belong
+     * @param nSteps number of steps of the method excluding the one being computed
+     * @param minStep minimal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param maxStep maximal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param vecAbsoluteTolerance allowed absolute error
+     * @param vecRelativeTolerance allowed relative error
+     * @exception IllegalArgumentException if order is 1 or less
+     */
+    public AdamsBashforthFieldIntegrator(final Field<T> field, final int nSteps,
+                                         final double minStep, final double maxStep,
+                                         final double[] vecAbsoluteTolerance,
+                                         final double[] vecRelativeTolerance)
+        throws IllegalArgumentException {
+        super(field, METHOD_NAME, nSteps, nSteps, minStep, maxStep,
+              vecAbsoluteTolerance, vecRelativeTolerance);
+    }
+
+    /** Estimate error.
+     * <p>
+     * Error is estimated by interpolating back to previous state using
+     * the state Taylor expansion and comparing to real previous state.
+     * </p>
+     * @param previousState state vector at step start
+     * @param predictedState predicted state vector at step end
+     * @param predictedScaled predicted value of the scaled derivatives at step end
+     * @param predictedNordsieck predicted value of the Nordsieck vector at step end
+     * @return estimated normalized local discretization error
+     */
+    private T errorEstimation(final T[] previousState,
+                              final T[] predictedState,
+                              final T[] predictedScaled,
+                              final FieldMatrix<T> predictedNordsieck) {
+
+        T error = getField().getZero();
+        for (int i = 0; i < mainSetDimension; ++i) {
+            final T yScale = predictedState[i].abs();
+            final T tol = (vecAbsoluteTolerance == null) ?
+                          yScale.multiply(scalRelativeTolerance).add(scalAbsoluteTolerance) :
+                          yScale.multiply(vecRelativeTolerance[i]).add(vecAbsoluteTolerance[i]);
+
+            // apply Taylor formula from high order to low order,
+            // for the sake of numerical accuracy
+            T variation = getField().getZero();
+            int sign = predictedNordsieck.getRowDimension() % 2 == 0 ? -1 : 1;
+            for (int k = predictedNordsieck.getRowDimension() - 1; k >= 0; --k) {
+                variation = variation.add(predictedNordsieck.getEntry(k, i).multiply(sign));
+                sign      = -sign;
+            }
+            variation = variation.subtract(predictedScaled[i]);
+
+            final T ratio  = predictedState[i].subtract(previousState[i]).add(variation).divide(tol);
+            error = error.add(ratio.multiply(ratio));
+
+        }
+
+        return error.divide(mainSetDimension).sqrt();
+
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public FieldODEStateAndDerivative<T> integrate(final FieldExpandableODE<T> equations,
+                                                   final FieldODEState<T> initialState,
+                                                   final T finalTime)
+        throws NumberIsTooSmallException, DimensionMismatchException,
+               MaxCountExceededException, NoBracketingException {
+
+        sanityChecks(initialState, finalTime);
+        final T   t0 = initialState.getTime();
+        final T[] y  = equations.getMapper().mapState(initialState);
+        setStepStart(initIntegration(equations, t0, y, finalTime));
+        final boolean forward = finalTime.subtract(initialState.getTime()).getReal() > 0;
+
+        // compute the initial Nordsieck vector using the configured starter integrator
+        start(equations, getStepStart(), finalTime);
+
+        // reuse the step that was chosen by the starter integrator
+        FieldODEStateAndDerivative<T> stepStart = getStepStart();
+        FieldODEStateAndDerivative<T> stepEnd   =
+                        AdamsFieldStepInterpolator.taylor(stepStart,
+                                                          stepStart.getTime().add(getStepSize()),
+                                                          getStepSize(), scaled, nordsieck);
+
+        // main integration loop
+        setIsLastStep(false);
+        do {
+
+            T[] predictedY = null;
+            final T[] predictedScaled = MathArrays.buildArray(getField(), y.length);
+            Array2DRowFieldMatrix<T> predictedNordsieck = null;
+            T error = getField().getZero().add(10);
+            while (error.subtract(1.0).getReal() >= 0.0) {
+
+                // predict a first estimate of the state at step end
+                predictedY = stepEnd.getState();
+
+                // evaluate the derivative
+                final T[] yDot = computeDerivatives(stepEnd.getTime(), predictedY);
+
+                // predict Nordsieck vector at step end
+                for (int j = 0; j < predictedScaled.length; ++j) {
+                    predictedScaled[j] = getStepSize().multiply(yDot[j]);
+                }
+                predictedNordsieck = updateHighOrderDerivativesPhase1(nordsieck);
+                updateHighOrderDerivativesPhase2(scaled, predictedScaled, predictedNordsieck);
+
+                // evaluate error
+                error = errorEstimation(y, predictedY, predictedScaled, predictedNordsieck);
+
+                if (error.subtract(1.0).getReal() >= 0.0) {
+                    // reject the step and attempt to reduce error by stepsize control
+                    final T factor = computeStepGrowShrinkFactor(error);
+                    rescale(filterStep(getStepSize().multiply(factor), forward, false));
+                    stepEnd = AdamsFieldStepInterpolator.taylor(getStepStart(),
+                                                                getStepStart().getTime().add(getStepSize()),
+                                                                getStepSize(),
+                                                                scaled,
+                                                                nordsieck);
+
+                }
+            }
+
+            // discrete events handling
+            setStepStart(acceptStep(new AdamsFieldStepInterpolator<T>(getStepSize(), stepEnd,
+                                                                      predictedScaled, predictedNordsieck, forward,
+                                                                      getStepStart(), stepEnd,
+                                                                      equations.getMapper()),
+                                    finalTime));
+            scaled    = predictedScaled;
+            nordsieck = predictedNordsieck;
+
+            if (!isLastStep()) {
+
+                System.arraycopy(predictedY, 0, y, 0, y.length);
+
+                if (resetOccurred()) {
+                    // some events handler has triggered changes that
+                    // invalidate the derivatives, we need to restart from scratch
+                    start(equations, getStepStart(), finalTime);
+                }
+
+                // stepsize control for next step
+                final T       factor     = computeStepGrowShrinkFactor(error);
+                final T       scaledH    = getStepSize().multiply(factor);
+                final T       nextT      = getStepStart().getTime().add(scaledH);
+                final boolean nextIsLast = forward ?
+                                           nextT.subtract(finalTime).getReal() >= 0 :
+                                           nextT.subtract(finalTime).getReal() <= 0;
+                T hNew = filterStep(scaledH, forward, nextIsLast);
+
+                final T       filteredNextT      = getStepStart().getTime().add(hNew);
+                final boolean filteredNextIsLast = forward ?
+                                                   filteredNextT.subtract(finalTime).getReal() >= 0 :
+                                                   filteredNextT.subtract(finalTime).getReal() <= 0;
+                if (filteredNextIsLast) {
+                    hNew = finalTime.subtract(getStepStart().getTime());
+                }
+
+                rescale(hNew);
+                stepEnd = AdamsFieldStepInterpolator.taylor(getStepStart(), getStepStart().getTime().add(getStepSize()),
+                                                            getStepSize(), scaled, nordsieck);
+
+            }
+
+        } while (!isLastStep());
+
+        final FieldODEStateAndDerivative<T> finalState = getStepStart();
+        setStepStart(null);
+        setStepSize(null);
+        return finalState;
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsBashforthIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsBashforthIntegrator.java
new file mode 100644
index 0000000..af4a435
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsBashforthIntegrator.java
@@ -0,0 +1,362 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NoBracketingException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.linear.Array2DRowRealMatrix;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.ode.EquationsMapper;
+import org.apache.commons.math3.ode.ExpandableStatefulODE;
+import org.apache.commons.math3.ode.sampling.NordsieckStepInterpolator;
+import org.apache.commons.math3.util.FastMath;
+
+
+/**
+ * This class implements explicit Adams-Bashforth integrators for Ordinary
+ * Differential Equations.
+ *
+ * <p>Adams-Bashforth methods (in fact due to Adams alone) are explicit
+ * multistep ODE solvers. This implementation is a variation of the classical
+ * one: it uses adaptive stepsize to implement error control, whereas
+ * classical implementations are fixed step size. The value of state vector
+ * at step n+1 is a simple combination of the value at step n and of the
+ * derivatives at steps n, n-1, n-2 ... Depending on the number k of previous
+ * steps one wants to use for computing the next value, different formulas
+ * are available:</p>
+ * <ul>
+ *   <li>k = 1: y<sub>n+1</sub> = y<sub>n</sub> + h y'<sub>n</sub></li>
+ *   <li>k = 2: y<sub>n+1</sub> = y<sub>n</sub> + h (3y'<sub>n</sub>-y'<sub>n-1</sub>)/2</li>
+ *   <li>k = 3: y<sub>n+1</sub> = y<sub>n</sub> + h (23y'<sub>n</sub>-16y'<sub>n-1</sub>+5y'<sub>n-2</sub>)/12</li>
+ *   <li>k = 4: y<sub>n+1</sub> = y<sub>n</sub> + h (55y'<sub>n</sub>-59y'<sub>n-1</sub>+37y'<sub>n-2</sub>-9y'<sub>n-3</sub>)/24</li>
+ *   <li>...</li>
+ * </ul>
+ *
+ * <p>A k-steps Adams-Bashforth method is of order k.</p>
+ *
+ * <h3>Implementation details</h3>
+ *
+ * <p>We define scaled derivatives s<sub>i</sub>(n) at step n as:
+ * <pre>
+ * s<sub>1</sub>(n) = h y'<sub>n</sub> for first derivative
+ * s<sub>2</sub>(n) = h<sup>2</sup>/2 y''<sub>n</sub> for second derivative
+ * s<sub>3</sub>(n) = h<sup>3</sup>/6 y'''<sub>n</sub> for third derivative
+ * ...
+ * s<sub>k</sub>(n) = h<sup>k</sup>/k! y<sup>(k)</sup><sub>n</sub> for k<sup>th</sup> derivative
+ * </pre></p>
+ *
+ * <p>The definitions above use the classical representation with several previous first
+ * derivatives. Lets define
+ * <pre>
+ *   q<sub>n</sub> = [ s<sub>1</sub>(n-1) s<sub>1</sub>(n-2) ... s<sub>1</sub>(n-(k-1)) ]<sup>T</sup>
+ * </pre>
+ * (we omit the k index in the notation for clarity). With these definitions,
+ * Adams-Bashforth methods can be written:
+ * <ul>
+ *   <li>k = 1: y<sub>n+1</sub> = y<sub>n</sub> + s<sub>1</sub>(n)</li>
+ *   <li>k = 2: y<sub>n+1</sub> = y<sub>n</sub> + 3/2 s<sub>1</sub>(n) + [ -1/2 ] q<sub>n</sub></li>
+ *   <li>k = 3: y<sub>n+1</sub> = y<sub>n</sub> + 23/12 s<sub>1</sub>(n) + [ -16/12 5/12 ] q<sub>n</sub></li>
+ *   <li>k = 4: y<sub>n+1</sub> = y<sub>n</sub> + 55/24 s<sub>1</sub>(n) + [ -59/24 37/24 -9/24 ] q<sub>n</sub></li>
+ *   <li>...</li>
+ * </ul></p>
+ *
+ * <p>Instead of using the classical representation with first derivatives only (y<sub>n</sub>,
+ * s<sub>1</sub>(n) and q<sub>n</sub>), our implementation uses the Nordsieck vector with
+ * higher degrees scaled derivatives all taken at the same step (y<sub>n</sub>, s<sub>1</sub>(n)
+ * and r<sub>n</sub>) where r<sub>n</sub> is defined as:
+ * <pre>
+ * r<sub>n</sub> = [ s<sub>2</sub>(n), s<sub>3</sub>(n) ... s<sub>k</sub>(n) ]<sup>T</sup>
+ * </pre>
+ * (here again we omit the k index in the notation for clarity)
+ * </p>
+ *
+ * <p>Taylor series formulas show that for any index offset i, s<sub>1</sub>(n-i) can be
+ * computed from s<sub>1</sub>(n), s<sub>2</sub>(n) ... s<sub>k</sub>(n), the formula being exact
+ * for degree k polynomials.
+ * <pre>
+ * s<sub>1</sub>(n-i) = s<sub>1</sub>(n) + &sum;<sub>j&gt;0</sub> (j+1) (-i)<sup>j</sup> s<sub>j+1</sub>(n)
+ * </pre>
+ * The previous formula can be used with several values for i to compute the transform between
+ * classical representation and Nordsieck vector. The transform between r<sub>n</sub>
+ * and q<sub>n</sub> resulting from the Taylor series formulas above is:
+ * <pre>
+ * q<sub>n</sub> = s<sub>1</sub>(n) u + P r<sub>n</sub>
+ * </pre>
+ * where u is the [ 1 1 ... 1 ]<sup>T</sup> vector and P is the (k-1)&times;(k-1) matrix built
+ * with the (j+1) (-i)<sup>j</sup> terms with i being the row number starting from 1 and j being
+ * the column number starting from 1:
+ * <pre>
+ *        [  -2   3   -4    5  ... ]
+ *        [  -4  12  -32   80  ... ]
+ *   P =  [  -6  27 -108  405  ... ]
+ *        [  -8  48 -256 1280  ... ]
+ *        [          ...           ]
+ * </pre></p>
+ *
+ * <p>Using the Nordsieck vector has several advantages:
+ * <ul>
+ *   <li>it greatly simplifies step interpolation as the interpolator mainly applies
+ *   Taylor series formulas,</li>
+ *   <li>it simplifies step changes that occur when discrete events that truncate
+ *   the step are triggered,</li>
+ *   <li>it allows to extend the methods in order to support adaptive stepsize.</li>
+ * </ul></p>
+ *
+ * <p>The Nordsieck vector at step n+1 is computed from the Nordsieck vector at step n as follows:
+ * <ul>
+ *   <li>y<sub>n+1</sub> = y<sub>n</sub> + s<sub>1</sub>(n) + u<sup>T</sup> r<sub>n</sub></li>
+ *   <li>s<sub>1</sub>(n+1) = h f(t<sub>n+1</sub>, y<sub>n+1</sub>)</li>
+ *   <li>r<sub>n+1</sub> = (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u + P<sup>-1</sup> A P r<sub>n</sub></li>
+ * </ul>
+ * where A is a rows shifting matrix (the lower left part is an identity matrix):
+ * <pre>
+ *        [ 0 0   ...  0 0 | 0 ]
+ *        [ ---------------+---]
+ *        [ 1 0   ...  0 0 | 0 ]
+ *    A = [ 0 1   ...  0 0 | 0 ]
+ *        [       ...      | 0 ]
+ *        [ 0 0   ...  1 0 | 0 ]
+ *        [ 0 0   ...  0 1 | 0 ]
+ * </pre></p>
+ *
+ * <p>The P<sup>-1</sup>u vector and the P<sup>-1</sup> A P matrix do not depend on the state,
+ * they only depend on k and therefore are precomputed once for all.</p>
+ *
+ * @since 2.0
+ */
+public class AdamsBashforthIntegrator extends AdamsIntegrator {
+
+    /** Integrator method name. */
+    private static final String METHOD_NAME = "Adams-Bashforth";
+
+    /**
+     * Build an Adams-Bashforth integrator with the given order and step control parameters.
+     * @param nSteps number of steps of the method excluding the one being computed
+     * @param minStep minimal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param maxStep maximal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param scalAbsoluteTolerance allowed absolute error
+     * @param scalRelativeTolerance allowed relative error
+     * @exception NumberIsTooSmallException if order is 1 or less
+     */
+    public AdamsBashforthIntegrator(final int nSteps,
+                                    final double minStep, final double maxStep,
+                                    final double scalAbsoluteTolerance,
+                                    final double scalRelativeTolerance)
+        throws NumberIsTooSmallException {
+        super(METHOD_NAME, nSteps, nSteps, minStep, maxStep,
+              scalAbsoluteTolerance, scalRelativeTolerance);
+    }
+
+    /**
+     * Build an Adams-Bashforth integrator with the given order and step control parameters.
+     * @param nSteps number of steps of the method excluding the one being computed
+     * @param minStep minimal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param maxStep maximal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param vecAbsoluteTolerance allowed absolute error
+     * @param vecRelativeTolerance allowed relative error
+     * @exception IllegalArgumentException if order is 1 or less
+     */
+    public AdamsBashforthIntegrator(final int nSteps,
+                                    final double minStep, final double maxStep,
+                                    final double[] vecAbsoluteTolerance,
+                                    final double[] vecRelativeTolerance)
+        throws IllegalArgumentException {
+        super(METHOD_NAME, nSteps, nSteps, minStep, maxStep,
+              vecAbsoluteTolerance, vecRelativeTolerance);
+    }
+
+    /** Estimate error.
+     * <p>
+     * Error is estimated by interpolating back to previous state using
+     * the state Taylor expansion and comparing to real previous state.
+     * </p>
+     * @param previousState state vector at step start
+     * @param predictedState predicted state vector at step end
+     * @param predictedScaled predicted value of the scaled derivatives at step end
+     * @param predictedNordsieck predicted value of the Nordsieck vector at step end
+     * @return estimated normalized local discretization error
+     */
+    private double errorEstimation(final double[] previousState,
+                                   final double[] predictedState,
+                                   final double[] predictedScaled,
+                                   final RealMatrix predictedNordsieck) {
+
+        double error = 0;
+        for (int i = 0; i < mainSetDimension; ++i) {
+            final double yScale = FastMath.abs(predictedState[i]);
+            final double tol = (vecAbsoluteTolerance == null) ?
+                               (scalAbsoluteTolerance + scalRelativeTolerance * yScale) :
+                               (vecAbsoluteTolerance[i] + vecRelativeTolerance[i] * yScale);
+
+            // apply Taylor formula from high order to low order,
+            // for the sake of numerical accuracy
+            double variation = 0;
+            int sign = predictedNordsieck.getRowDimension() % 2 == 0 ? -1 : 1;
+            for (int k = predictedNordsieck.getRowDimension() - 1; k >= 0; --k) {
+                variation += sign * predictedNordsieck.getEntry(k, i);
+                sign       = -sign;
+            }
+            variation -= predictedScaled[i];
+
+            final double ratio  = (predictedState[i] - previousState[i] + variation) / tol;
+            error              += ratio * ratio;
+
+        }
+
+        return FastMath.sqrt(error / mainSetDimension);
+
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void integrate(final ExpandableStatefulODE equations, final double t)
+        throws NumberIsTooSmallException, DimensionMismatchException,
+               MaxCountExceededException, NoBracketingException {
+
+        sanityChecks(equations, t);
+        setEquations(equations);
+        final boolean forward = t > equations.getTime();
+
+        // initialize working arrays
+        final double[] y    = equations.getCompleteState();
+        final double[] yDot = new double[y.length];
+
+        // set up an interpolator sharing the integrator arrays
+        final NordsieckStepInterpolator interpolator = new NordsieckStepInterpolator();
+        interpolator.reinitialize(y, forward,
+                                  equations.getPrimaryMapper(), equations.getSecondaryMappers());
+
+        // set up integration control objects
+        initIntegration(equations.getTime(), y, t);
+
+        // compute the initial Nordsieck vector using the configured starter integrator
+        start(equations.getTime(), y, t);
+        interpolator.reinitialize(stepStart, stepSize, scaled, nordsieck);
+        interpolator.storeTime(stepStart);
+
+        // reuse the step that was chosen by the starter integrator
+        double hNew = stepSize;
+        interpolator.rescale(hNew);
+
+        // main integration loop
+        isLastStep = false;
+        do {
+
+            interpolator.shift();
+            final double[] predictedY      = new double[y.length];
+            final double[] predictedScaled = new double[y.length];
+            Array2DRowRealMatrix predictedNordsieck = null;
+            double error = 10;
+            while (error >= 1.0) {
+
+                // predict a first estimate of the state at step end
+                final double stepEnd = stepStart + hNew;
+                interpolator.storeTime(stepEnd);
+                final ExpandableStatefulODE expandable = getExpandable();
+                final EquationsMapper primary = expandable.getPrimaryMapper();
+                primary.insertEquationData(interpolator.getInterpolatedState(), predictedY);
+                int index = 0;
+                for (final EquationsMapper secondary : expandable.getSecondaryMappers()) {
+                    secondary.insertEquationData(interpolator.getInterpolatedSecondaryState(index), predictedY);
+                    ++index;
+                }
+
+                // evaluate the derivative
+                computeDerivatives(stepEnd, predictedY, yDot);
+
+                // predict Nordsieck vector at step end
+                for (int j = 0; j < predictedScaled.length; ++j) {
+                    predictedScaled[j] = hNew * yDot[j];
+                }
+                predictedNordsieck = updateHighOrderDerivativesPhase1(nordsieck);
+                updateHighOrderDerivativesPhase2(scaled, predictedScaled, predictedNordsieck);
+
+                // evaluate error
+                error = errorEstimation(y, predictedY, predictedScaled, predictedNordsieck);
+
+                if (error >= 1.0) {
+                    // reject the step and attempt to reduce error by stepsize control
+                    final double factor = computeStepGrowShrinkFactor(error);
+                    hNew = filterStep(hNew * factor, forward, false);
+                    interpolator.rescale(hNew);
+
+                }
+            }
+
+            stepSize = hNew;
+            final double stepEnd = stepStart + stepSize;
+            interpolator.reinitialize(stepEnd, stepSize, predictedScaled, predictedNordsieck);
+
+            // discrete events handling
+            interpolator.storeTime(stepEnd);
+            System.arraycopy(predictedY, 0, y, 0, y.length);
+            stepStart = acceptStep(interpolator, y, yDot, t);
+            scaled    = predictedScaled;
+            nordsieck = predictedNordsieck;
+            interpolator.reinitialize(stepEnd, stepSize, scaled, nordsieck);
+
+            if (!isLastStep) {
+
+                // prepare next step
+                interpolator.storeTime(stepStart);
+
+                if (resetOccurred) {
+                    // some events handler has triggered changes that
+                    // invalidate the derivatives, we need to restart from scratch
+                    start(stepStart, y, t);
+                    interpolator.reinitialize(stepStart, stepSize, scaled, nordsieck);
+                }
+
+                // stepsize control for next step
+                final double  factor     = computeStepGrowShrinkFactor(error);
+                final double  scaledH    = stepSize * factor;
+                final double  nextT      = stepStart + scaledH;
+                final boolean nextIsLast = forward ? (nextT >= t) : (nextT <= t);
+                hNew = filterStep(scaledH, forward, nextIsLast);
+
+                final double  filteredNextT      = stepStart + hNew;
+                final boolean filteredNextIsLast = forward ? (filteredNextT >= t) : (filteredNextT <= t);
+                if (filteredNextIsLast) {
+                    hNew = t - stepStart;
+                }
+
+                interpolator.rescale(hNew);
+
+            }
+
+        } while (!isLastStep);
+
+        // dispatch results
+        equations.setTime(stepStart);
+        equations.setCompleteState(y);
+
+        resetInternalState();
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsFieldIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsFieldIntegrator.java
new file mode 100644
index 0000000..fcd9397
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsFieldIntegrator.java
@@ -0,0 +1,145 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NoBracketingException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.linear.Array2DRowFieldMatrix;
+import org.apache.commons.math3.ode.FieldExpandableODE;
+import org.apache.commons.math3.ode.FieldODEState;
+import org.apache.commons.math3.ode.FieldODEStateAndDerivative;
+import org.apache.commons.math3.ode.MultistepFieldIntegrator;
+
+
+/** Base class for {@link AdamsBashforthFieldIntegrator Adams-Bashforth} and
+ * {@link AdamsMoultonFieldIntegrator Adams-Moulton} integrators.
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+public abstract class AdamsFieldIntegrator<T extends RealFieldElement<T>> extends MultistepFieldIntegrator<T> {
+
+    /** Transformer. */
+    private final AdamsNordsieckFieldTransformer<T> transformer;
+
+    /**
+     * Build an Adams integrator with the given order and step control parameters.
+     * @param field field to which the time and state vector elements belong
+     * @param name name of the method
+     * @param nSteps number of steps of the method excluding the one being computed
+     * @param order order of the method
+     * @param minStep minimal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param maxStep maximal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param scalAbsoluteTolerance allowed absolute error
+     * @param scalRelativeTolerance allowed relative error
+     * @exception NumberIsTooSmallException if order is 1 or less
+     */
+    public AdamsFieldIntegrator(final Field<T> field, final String name,
+                                final int nSteps, final int order,
+                                final double minStep, final double maxStep,
+                                final double scalAbsoluteTolerance,
+                                final double scalRelativeTolerance)
+        throws NumberIsTooSmallException {
+        super(field, name, nSteps, order, minStep, maxStep,
+              scalAbsoluteTolerance, scalRelativeTolerance);
+        transformer = AdamsNordsieckFieldTransformer.getInstance(field, nSteps);
+    }
+
+    /**
+     * Build an Adams integrator with the given order and step control parameters.
+     * @param field field to which the time and state vector elements belong
+     * @param name name of the method
+     * @param nSteps number of steps of the method excluding the one being computed
+     * @param order order of the method
+     * @param minStep minimal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param maxStep maximal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param vecAbsoluteTolerance allowed absolute error
+     * @param vecRelativeTolerance allowed relative error
+     * @exception IllegalArgumentException if order is 1 or less
+     */
+    public AdamsFieldIntegrator(final Field<T> field, final String name,
+                                final int nSteps, final int order,
+                                final double minStep, final double maxStep,
+                                final double[] vecAbsoluteTolerance,
+                                final double[] vecRelativeTolerance)
+        throws IllegalArgumentException {
+        super(field, name, nSteps, order, minStep, maxStep,
+              vecAbsoluteTolerance, vecRelativeTolerance);
+        transformer = AdamsNordsieckFieldTransformer.getInstance(field, nSteps);
+    }
+
+    /** {@inheritDoc} */
+    public abstract FieldODEStateAndDerivative<T> integrate(final FieldExpandableODE<T> equations,
+                                                            final FieldODEState<T> initialState,
+                                                            final T finalTime)
+        throws NumberIsTooSmallException, DimensionMismatchException,
+               MaxCountExceededException, NoBracketingException;
+
+    /** {@inheritDoc} */
+    @Override
+    protected Array2DRowFieldMatrix<T> initializeHighOrderDerivatives(final T h, final T[] t,
+                                                                      final T[][] y,
+                                                                      final T[][] yDot) {
+        return transformer.initializeHighOrderDerivatives(h, t, y, yDot);
+    }
+
+    /** Update the high order scaled derivatives for Adams integrators (phase 1).
+     * <p>The complete update of high order derivatives has a form similar to:
+     * <pre>
+     * r<sub>n+1</sub> = (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u + P<sup>-1</sup> A P r<sub>n</sub>
+     * </pre>
+     * this method computes the P<sup>-1</sup> A P r<sub>n</sub> part.</p>
+     * @param highOrder high order scaled derivatives
+     * (h<sup>2</sup>/2 y'', ... h<sup>k</sup>/k! y(k))
+     * @return updated high order derivatives
+     * @see #updateHighOrderDerivativesPhase2(RealFieldElement[], RealFieldElement[], Array2DRowFieldMatrix)
+     */
+    public Array2DRowFieldMatrix<T> updateHighOrderDerivativesPhase1(final Array2DRowFieldMatrix<T> highOrder) {
+        return transformer.updateHighOrderDerivativesPhase1(highOrder);
+    }
+
+    /** Update the high order scaled derivatives Adams integrators (phase 2).
+     * <p>The complete update of high order derivatives has a form similar to:
+     * <pre>
+     * r<sub>n+1</sub> = (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u + P<sup>-1</sup> A P r<sub>n</sub>
+     * </pre>
+     * this method computes the (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u part.</p>
+     * <p>Phase 1 of the update must already have been performed.</p>
+     * @param start first order scaled derivatives at step start
+     * @param end first order scaled derivatives at step end
+     * @param highOrder high order scaled derivatives, will be modified
+     * (h<sup>2</sup>/2 y'', ... h<sup>k</sup>/k! y(k))
+     * @see #updateHighOrderDerivativesPhase1(Array2DRowFieldMatrix)
+     */
+    public void updateHighOrderDerivativesPhase2(final T[] start, final T[] end,
+                                                 final Array2DRowFieldMatrix<T> highOrder) {
+        transformer.updateHighOrderDerivativesPhase2(start, end, highOrder);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsFieldStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsFieldStepInterpolator.java
new file mode 100644
index 0000000..5de61cc
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsFieldStepInterpolator.java
@@ -0,0 +1,189 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import java.util.Arrays;
+
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.linear.Array2DRowFieldMatrix;
+import org.apache.commons.math3.ode.FieldEquationsMapper;
+import org.apache.commons.math3.ode.FieldODEStateAndDerivative;
+import org.apache.commons.math3.ode.sampling.AbstractFieldStepInterpolator;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * This class implements an interpolator for Adams integrators using Nordsieck representation.
+ *
+ * <p>This interpolator computes dense output around the current point.
+ * The interpolation equation is based on Taylor series formulas.
+ *
+ * @see AdamsBashforthFieldIntegrator
+ * @see AdamsMoultonFieldIntegrator
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+
+class AdamsFieldStepInterpolator<T extends RealFieldElement<T>> extends AbstractFieldStepInterpolator<T> {
+
+    /** Step size used in the first scaled derivative and Nordsieck vector. */
+    private T scalingH;
+
+    /** Reference state.
+     * <p>Sometimes, the reference state is the same as globalPreviousState,
+     * sometimes it is the same as globalCurrentState, so we use a separate
+     * field to avoid any confusion.
+     * </p>
+     */
+    private final FieldODEStateAndDerivative<T> reference;
+
+    /** First scaled derivative. */
+    private final T[] scaled;
+
+    /** Nordsieck vector. */
+    private final Array2DRowFieldMatrix<T> nordsieck;
+
+    /** Simple constructor.
+     * @param stepSize step size used in the scaled and Nordsieck arrays
+     * @param reference reference state from which Taylor expansion are estimated
+     * @param scaled first scaled derivative
+     * @param nordsieck Nordsieck vector
+     * @param isForward integration direction indicator
+     * @param globalPreviousState start of the global step
+     * @param globalCurrentState end of the global step
+     * @param equationsMapper mapper for ODE equations primary and secondary components
+     */
+    AdamsFieldStepInterpolator(final T stepSize, final FieldODEStateAndDerivative<T> reference,
+                               final T[] scaled, final Array2DRowFieldMatrix<T> nordsieck,
+                               final boolean isForward,
+                               final FieldODEStateAndDerivative<T> globalPreviousState,
+                               final FieldODEStateAndDerivative<T> globalCurrentState,
+                               final FieldEquationsMapper<T> equationsMapper) {
+        this(stepSize, reference, scaled, nordsieck,
+             isForward, globalPreviousState, globalCurrentState,
+             globalPreviousState, globalCurrentState, equationsMapper);
+    }
+
+    /** Simple constructor.
+     * @param stepSize step size used in the scaled and Nordsieck arrays
+     * @param reference reference state from which Taylor expansion are estimated
+     * @param scaled first scaled derivative
+     * @param nordsieck Nordsieck vector
+     * @param isForward integration direction indicator
+     * @param globalPreviousState start of the global step
+     * @param globalCurrentState end of the global step
+     * @param softPreviousState start of the restricted step
+     * @param softCurrentState end of the restricted step
+     * @param equationsMapper mapper for ODE equations primary and secondary components
+     */
+    private AdamsFieldStepInterpolator(final T stepSize, final FieldODEStateAndDerivative<T> reference,
+                                       final T[] scaled, final Array2DRowFieldMatrix<T> nordsieck,
+                                       final boolean isForward,
+                                       final FieldODEStateAndDerivative<T> globalPreviousState,
+                                       final FieldODEStateAndDerivative<T> globalCurrentState,
+                                       final FieldODEStateAndDerivative<T> softPreviousState,
+                                       final FieldODEStateAndDerivative<T> softCurrentState,
+                                       final FieldEquationsMapper<T> equationsMapper) {
+        super(isForward, globalPreviousState, globalCurrentState,
+              softPreviousState, softCurrentState, equationsMapper);
+        this.scalingH  = stepSize;
+        this.reference = reference;
+        this.scaled    = scaled.clone();
+        this.nordsieck = new Array2DRowFieldMatrix<T>(nordsieck.getData(), false);
+    }
+
+    /** Create a new instance.
+     * @param newForward integration direction indicator
+     * @param newGlobalPreviousState start of the global step
+     * @param newGlobalCurrentState end of the global step
+     * @param newSoftPreviousState start of the restricted step
+     * @param newSoftCurrentState end of the restricted step
+     * @param newMapper equations mapper for the all equations
+     * @return a new instance
+     */
+    @Override
+    protected AdamsFieldStepInterpolator<T> create(boolean newForward,
+                                                   FieldODEStateAndDerivative<T> newGlobalPreviousState,
+                                                   FieldODEStateAndDerivative<T> newGlobalCurrentState,
+                                                   FieldODEStateAndDerivative<T> newSoftPreviousState,
+                                                   FieldODEStateAndDerivative<T> newSoftCurrentState,
+                                                   FieldEquationsMapper<T> newMapper) {
+        return new AdamsFieldStepInterpolator<T>(scalingH, reference, scaled, nordsieck,
+                                                 newForward,
+                                                 newGlobalPreviousState, newGlobalCurrentState,
+                                                 newSoftPreviousState, newSoftCurrentState,
+                                                 newMapper);
+
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected FieldODEStateAndDerivative<T> computeInterpolatedStateAndDerivatives(final FieldEquationsMapper<T> equationsMapper,
+                                                                                   final T time, final T theta,
+                                                                                   final T thetaH, final T oneMinusThetaH) {
+        return taylor(reference, time, scalingH, scaled, nordsieck);
+    }
+
+    /** Estimate state by applying Taylor formula.
+     * @param reference reference state
+     * @param time time at which state must be estimated
+     * @param stepSize step size used in the scaled and Nordsieck arrays
+     * @param scaled first scaled derivative
+     * @param nordsieck Nordsieck vector
+     * @return estimated state
+     * @param <S> the type of the field elements
+     */
+    public static <S extends RealFieldElement<S>> FieldODEStateAndDerivative<S> taylor(final FieldODEStateAndDerivative<S> reference,
+                                                                                       final S time, final S stepSize,
+                                                                                       final S[] scaled,
+                                                                                       final Array2DRowFieldMatrix<S> nordsieck) {
+
+        final S x = time.subtract(reference.getTime());
+        final S normalizedAbscissa = x.divide(stepSize);
+
+        S[] stateVariation = MathArrays.buildArray(time.getField(), scaled.length);
+        Arrays.fill(stateVariation, time.getField().getZero());
+        S[] estimatedDerivatives = MathArrays.buildArray(time.getField(), scaled.length);
+        Arrays.fill(estimatedDerivatives, time.getField().getZero());
+
+        // apply Taylor formula from high order to low order,
+        // for the sake of numerical accuracy
+        final S[][] nData = nordsieck.getDataRef();
+        for (int i = nData.length - 1; i >= 0; --i) {
+            final int order = i + 2;
+            final S[] nDataI = nData[i];
+            final S power = normalizedAbscissa.pow(order);
+            for (int j = 0; j < nDataI.length; ++j) {
+                final S d = nDataI[j].multiply(power);
+                stateVariation[j]          = stateVariation[j].add(d);
+                estimatedDerivatives[j] = estimatedDerivatives[j].add(d.multiply(order));
+            }
+        }
+
+        S[] estimatedState = reference.getState();
+        for (int j = 0; j < stateVariation.length; ++j) {
+            stateVariation[j]    = stateVariation[j].add(scaled[j].multiply(normalizedAbscissa));
+            estimatedState[j] = estimatedState[j].add(stateVariation[j]);
+            estimatedDerivatives[j] =
+                estimatedDerivatives[j].add(scaled[j].multiply(normalizedAbscissa)).divide(x);
+        }
+
+        return new FieldODEStateAndDerivative<S>(time, estimatedState, estimatedDerivatives);
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsIntegrator.java
new file mode 100644
index 0000000..dd6f2a1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsIntegrator.java
@@ -0,0 +1,136 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NoBracketingException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.linear.Array2DRowRealMatrix;
+import org.apache.commons.math3.ode.ExpandableStatefulODE;
+import org.apache.commons.math3.ode.MultistepIntegrator;
+
+
+/** Base class for {@link AdamsBashforthIntegrator Adams-Bashforth} and
+ * {@link AdamsMoultonIntegrator Adams-Moulton} integrators.
+ * @since 2.0
+ */
+public abstract class AdamsIntegrator extends MultistepIntegrator {
+
+    /** Transformer. */
+    private final AdamsNordsieckTransformer transformer;
+
+    /**
+     * Build an Adams integrator with the given order and step control parameters.
+     * @param name name of the method
+     * @param nSteps number of steps of the method excluding the one being computed
+     * @param order order of the method
+     * @param minStep minimal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param maxStep maximal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param scalAbsoluteTolerance allowed absolute error
+     * @param scalRelativeTolerance allowed relative error
+     * @exception NumberIsTooSmallException if order is 1 or less
+     */
+    public AdamsIntegrator(final String name, final int nSteps, final int order,
+                           final double minStep, final double maxStep,
+                           final double scalAbsoluteTolerance,
+                           final double scalRelativeTolerance)
+        throws NumberIsTooSmallException {
+        super(name, nSteps, order, minStep, maxStep,
+              scalAbsoluteTolerance, scalRelativeTolerance);
+        transformer = AdamsNordsieckTransformer.getInstance(nSteps);
+    }
+
+    /**
+     * Build an Adams integrator with the given order and step control parameters.
+     * @param name name of the method
+     * @param nSteps number of steps of the method excluding the one being computed
+     * @param order order of the method
+     * @param minStep minimal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param maxStep maximal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param vecAbsoluteTolerance allowed absolute error
+     * @param vecRelativeTolerance allowed relative error
+     * @exception IllegalArgumentException if order is 1 or less
+     */
+    public AdamsIntegrator(final String name, final int nSteps, final int order,
+                           final double minStep, final double maxStep,
+                           final double[] vecAbsoluteTolerance,
+                           final double[] vecRelativeTolerance)
+        throws IllegalArgumentException {
+        super(name, nSteps, order, minStep, maxStep,
+              vecAbsoluteTolerance, vecRelativeTolerance);
+        transformer = AdamsNordsieckTransformer.getInstance(nSteps);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public abstract void integrate(final ExpandableStatefulODE equations, final double t)
+        throws NumberIsTooSmallException, DimensionMismatchException,
+               MaxCountExceededException, NoBracketingException;
+
+    /** {@inheritDoc} */
+    @Override
+    protected Array2DRowRealMatrix initializeHighOrderDerivatives(final double h, final double[] t,
+                                                                  final double[][] y,
+                                                                  final double[][] yDot) {
+        return transformer.initializeHighOrderDerivatives(h, t, y, yDot);
+    }
+
+    /** Update the high order scaled derivatives for Adams integrators (phase 1).
+     * <p>The complete update of high order derivatives has a form similar to:
+     * <pre>
+     * r<sub>n+1</sub> = (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u + P<sup>-1</sup> A P r<sub>n</sub>
+     * </pre>
+     * this method computes the P<sup>-1</sup> A P r<sub>n</sub> part.</p>
+     * @param highOrder high order scaled derivatives
+     * (h<sup>2</sup>/2 y'', ... h<sup>k</sup>/k! y(k))
+     * @return updated high order derivatives
+     * @see #updateHighOrderDerivativesPhase2(double[], double[], Array2DRowRealMatrix)
+     */
+    public Array2DRowRealMatrix updateHighOrderDerivativesPhase1(final Array2DRowRealMatrix highOrder) {
+        return transformer.updateHighOrderDerivativesPhase1(highOrder);
+    }
+
+    /** Update the high order scaled derivatives Adams integrators (phase 2).
+     * <p>The complete update of high order derivatives has a form similar to:
+     * <pre>
+     * r<sub>n+1</sub> = (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u + P<sup>-1</sup> A P r<sub>n</sub>
+     * </pre>
+     * this method computes the (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u part.</p>
+     * <p>Phase 1 of the update must already have been performed.</p>
+     * @param start first order scaled derivatives at step start
+     * @param end first order scaled derivatives at step end
+     * @param highOrder high order scaled derivatives, will be modified
+     * (h<sup>2</sup>/2 y'', ... h<sup>k</sup>/k! y(k))
+     * @see #updateHighOrderDerivativesPhase1(Array2DRowRealMatrix)
+     */
+    public void updateHighOrderDerivativesPhase2(final double[] start,
+                                                 final double[] end,
+                                                 final Array2DRowRealMatrix highOrder) {
+        transformer.updateHighOrderDerivativesPhase2(start, end, highOrder);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsMoultonFieldIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsMoultonFieldIntegrator.java
new file mode 100644
index 0000000..2594321
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsMoultonFieldIntegrator.java
@@ -0,0 +1,416 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import java.util.Arrays;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NoBracketingException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.linear.Array2DRowFieldMatrix;
+import org.apache.commons.math3.linear.FieldMatrixPreservingVisitor;
+import org.apache.commons.math3.ode.FieldExpandableODE;
+import org.apache.commons.math3.ode.FieldODEState;
+import org.apache.commons.math3.ode.FieldODEStateAndDerivative;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.util.MathUtils;
+
+
+/**
+ * This class implements implicit Adams-Moulton integrators for Ordinary
+ * Differential Equations.
+ *
+ * <p>Adams-Moulton methods (in fact due to Adams alone) are implicit
+ * multistep ODE solvers. This implementation is a variation of the classical
+ * one: it uses adaptive stepsize to implement error control, whereas
+ * classical implementations are fixed step size. The value of state vector
+ * at step n+1 is a simple combination of the value at step n and of the
+ * derivatives at steps n+1, n, n-1 ... Since y'<sub>n+1</sub> is needed to
+ * compute y<sub>n+1</sub>, another method must be used to compute a first
+ * estimate of y<sub>n+1</sub>, then compute y'<sub>n+1</sub>, then compute
+ * a final estimate of y<sub>n+1</sub> using the following formulas. Depending
+ * on the number k of previous steps one wants to use for computing the next
+ * value, different formulas are available for the final estimate:</p>
+ * <ul>
+ *   <li>k = 1: y<sub>n+1</sub> = y<sub>n</sub> + h y'<sub>n+1</sub></li>
+ *   <li>k = 2: y<sub>n+1</sub> = y<sub>n</sub> + h (y'<sub>n+1</sub>+y'<sub>n</sub>)/2</li>
+ *   <li>k = 3: y<sub>n+1</sub> = y<sub>n</sub> + h (5y'<sub>n+1</sub>+8y'<sub>n</sub>-y'<sub>n-1</sub>)/12</li>
+ *   <li>k = 4: y<sub>n+1</sub> = y<sub>n</sub> + h (9y'<sub>n+1</sub>+19y'<sub>n</sub>-5y'<sub>n-1</sub>+y'<sub>n-2</sub>)/24</li>
+ *   <li>...</li>
+ * </ul>
+ *
+ * <p>A k-steps Adams-Moulton method is of order k+1.</p>
+ *
+ * <h3>Implementation details</h3>
+ *
+ * <p>We define scaled derivatives s<sub>i</sub>(n) at step n as:
+ * <pre>
+ * s<sub>1</sub>(n) = h y'<sub>n</sub> for first derivative
+ * s<sub>2</sub>(n) = h<sup>2</sup>/2 y''<sub>n</sub> for second derivative
+ * s<sub>3</sub>(n) = h<sup>3</sup>/6 y'''<sub>n</sub> for third derivative
+ * ...
+ * s<sub>k</sub>(n) = h<sup>k</sup>/k! y<sup>(k)</sup><sub>n</sub> for k<sup>th</sup> derivative
+ * </pre></p>
+ *
+ * <p>The definitions above use the classical representation with several previous first
+ * derivatives. Lets define
+ * <pre>
+ *   q<sub>n</sub> = [ s<sub>1</sub>(n-1) s<sub>1</sub>(n-2) ... s<sub>1</sub>(n-(k-1)) ]<sup>T</sup>
+ * </pre>
+ * (we omit the k index in the notation for clarity). With these definitions,
+ * Adams-Moulton methods can be written:
+ * <ul>
+ *   <li>k = 1: y<sub>n+1</sub> = y<sub>n</sub> + s<sub>1</sub>(n+1)</li>
+ *   <li>k = 2: y<sub>n+1</sub> = y<sub>n</sub> + 1/2 s<sub>1</sub>(n+1) + [ 1/2 ] q<sub>n+1</sub></li>
+ *   <li>k = 3: y<sub>n+1</sub> = y<sub>n</sub> + 5/12 s<sub>1</sub>(n+1) + [ 8/12 -1/12 ] q<sub>n+1</sub></li>
+ *   <li>k = 4: y<sub>n+1</sub> = y<sub>n</sub> + 9/24 s<sub>1</sub>(n+1) + [ 19/24 -5/24 1/24 ] q<sub>n+1</sub></li>
+ *   <li>...</li>
+ * </ul></p>
+ *
+ * <p>Instead of using the classical representation with first derivatives only (y<sub>n</sub>,
+ * s<sub>1</sub>(n+1) and q<sub>n+1</sub>), our implementation uses the Nordsieck vector with
+ * higher degrees scaled derivatives all taken at the same step (y<sub>n</sub>, s<sub>1</sub>(n)
+ * and r<sub>n</sub>) where r<sub>n</sub> is defined as:
+ * <pre>
+ * r<sub>n</sub> = [ s<sub>2</sub>(n), s<sub>3</sub>(n) ... s<sub>k</sub>(n) ]<sup>T</sup>
+ * </pre>
+ * (here again we omit the k index in the notation for clarity)
+ * </p>
+ *
+ * <p>Taylor series formulas show that for any index offset i, s<sub>1</sub>(n-i) can be
+ * computed from s<sub>1</sub>(n), s<sub>2</sub>(n) ... s<sub>k</sub>(n), the formula being exact
+ * for degree k polynomials.
+ * <pre>
+ * s<sub>1</sub>(n-i) = s<sub>1</sub>(n) + &sum;<sub>j&gt;0</sub> (j+1) (-i)<sup>j</sup> s<sub>j+1</sub>(n)
+ * </pre>
+ * The previous formula can be used with several values for i to compute the transform between
+ * classical representation and Nordsieck vector. The transform between r<sub>n</sub>
+ * and q<sub>n</sub> resulting from the Taylor series formulas above is:
+ * <pre>
+ * q<sub>n</sub> = s<sub>1</sub>(n) u + P r<sub>n</sub>
+ * </pre>
+ * where u is the [ 1 1 ... 1 ]<sup>T</sup> vector and P is the (k-1)&times;(k-1) matrix built
+ * with the (j+1) (-i)<sup>j</sup> terms with i being the row number starting from 1 and j being
+ * the column number starting from 1:
+ * <pre>
+ *        [  -2   3   -4    5  ... ]
+ *        [  -4  12  -32   80  ... ]
+ *   P =  [  -6  27 -108  405  ... ]
+ *        [  -8  48 -256 1280  ... ]
+ *        [          ...           ]
+ * </pre></p>
+ *
+ * <p>Using the Nordsieck vector has several advantages:
+ * <ul>
+ *   <li>it greatly simplifies step interpolation as the interpolator mainly applies
+ *   Taylor series formulas,</li>
+ *   <li>it simplifies step changes that occur when discrete events that truncate
+ *   the step are triggered,</li>
+ *   <li>it allows to extend the methods in order to support adaptive stepsize.</li>
+ * </ul></p>
+ *
+ * <p>The predicted Nordsieck vector at step n+1 is computed from the Nordsieck vector at step
+ * n as follows:
+ * <ul>
+ *   <li>Y<sub>n+1</sub> = y<sub>n</sub> + s<sub>1</sub>(n) + u<sup>T</sup> r<sub>n</sub></li>
+ *   <li>S<sub>1</sub>(n+1) = h f(t<sub>n+1</sub>, Y<sub>n+1</sub>)</li>
+ *   <li>R<sub>n+1</sub> = (s<sub>1</sub>(n) - S<sub>1</sub>(n+1)) P<sup>-1</sup> u + P<sup>-1</sup> A P r<sub>n</sub></li>
+ * </ul>
+ * where A is a rows shifting matrix (the lower left part is an identity matrix):
+ * <pre>
+ *        [ 0 0   ...  0 0 | 0 ]
+ *        [ ---------------+---]
+ *        [ 1 0   ...  0 0 | 0 ]
+ *    A = [ 0 1   ...  0 0 | 0 ]
+ *        [       ...      | 0 ]
+ *        [ 0 0   ...  1 0 | 0 ]
+ *        [ 0 0   ...  0 1 | 0 ]
+ * </pre>
+ * From this predicted vector, the corrected vector is computed as follows:
+ * <ul>
+ *   <li>y<sub>n+1</sub> = y<sub>n</sub> + S<sub>1</sub>(n+1) + [ -1 +1 -1 +1 ... &plusmn;1 ] r<sub>n+1</sub></li>
+ *   <li>s<sub>1</sub>(n+1) = h f(t<sub>n+1</sub>, y<sub>n+1</sub>)</li>
+ *   <li>r<sub>n+1</sub> = R<sub>n+1</sub> + (s<sub>1</sub>(n+1) - S<sub>1</sub>(n+1)) P<sup>-1</sup> u</li>
+ * </ul>
+ * where the upper case Y<sub>n+1</sub>, S<sub>1</sub>(n+1) and R<sub>n+1</sub> represent the
+ * predicted states whereas the lower case y<sub>n+1</sub>, s<sub>n+1</sub> and r<sub>n+1</sub>
+ * represent the corrected states.</p>
+ *
+ * <p>The P<sup>-1</sup>u vector and the P<sup>-1</sup> A P matrix do not depend on the state,
+ * they only depend on k and therefore are precomputed once for all.</p>
+ *
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+public class AdamsMoultonFieldIntegrator<T extends RealFieldElement<T>> extends AdamsFieldIntegrator<T> {
+
+    /** Integrator method name. */
+    private static final String METHOD_NAME = "Adams-Moulton";
+
+    /**
+     * Build an Adams-Moulton integrator with the given order and error control parameters.
+     * @param field field to which the time and state vector elements belong
+     * @param nSteps number of steps of the method excluding the one being computed
+     * @param minStep minimal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param maxStep maximal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param scalAbsoluteTolerance allowed absolute error
+     * @param scalRelativeTolerance allowed relative error
+     * @exception NumberIsTooSmallException if order is 1 or less
+     */
+    public AdamsMoultonFieldIntegrator(final Field<T> field, final int nSteps,
+                                       final double minStep, final double maxStep,
+                                       final double scalAbsoluteTolerance,
+                                       final double scalRelativeTolerance)
+        throws NumberIsTooSmallException {
+        super(field, METHOD_NAME, nSteps, nSteps + 1, minStep, maxStep,
+              scalAbsoluteTolerance, scalRelativeTolerance);
+    }
+
+    /**
+     * Build an Adams-Moulton integrator with the given order and error control parameters.
+     * @param field field to which the time and state vector elements belong
+     * @param nSteps number of steps of the method excluding the one being computed
+     * @param minStep minimal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param maxStep maximal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param vecAbsoluteTolerance allowed absolute error
+     * @param vecRelativeTolerance allowed relative error
+     * @exception IllegalArgumentException if order is 1 or less
+     */
+    public AdamsMoultonFieldIntegrator(final Field<T> field, final int nSteps,
+                                       final double minStep, final double maxStep,
+                                       final double[] vecAbsoluteTolerance,
+                                       final double[] vecRelativeTolerance)
+        throws IllegalArgumentException {
+        super(field, METHOD_NAME, nSteps, nSteps + 1, minStep, maxStep,
+              vecAbsoluteTolerance, vecRelativeTolerance);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public FieldODEStateAndDerivative<T> integrate(final FieldExpandableODE<T> equations,
+                                                   final FieldODEState<T> initialState,
+                                                   final T finalTime)
+        throws NumberIsTooSmallException, DimensionMismatchException,
+               MaxCountExceededException, NoBracketingException {
+
+        sanityChecks(initialState, finalTime);
+        final T   t0 = initialState.getTime();
+        final T[] y  = equations.getMapper().mapState(initialState);
+        setStepStart(initIntegration(equations, t0, y, finalTime));
+        final boolean forward = finalTime.subtract(initialState.getTime()).getReal() > 0;
+
+        // compute the initial Nordsieck vector using the configured starter integrator
+        start(equations, getStepStart(), finalTime);
+
+        // reuse the step that was chosen by the starter integrator
+        FieldODEStateAndDerivative<T> stepStart = getStepStart();
+        FieldODEStateAndDerivative<T> stepEnd   =
+                        AdamsFieldStepInterpolator.taylor(stepStart,
+                                                          stepStart.getTime().add(getStepSize()),
+                                                          getStepSize(), scaled, nordsieck);
+
+        // main integration loop
+        setIsLastStep(false);
+        do {
+
+            T[] predictedY = null;
+            final T[] predictedScaled = MathArrays.buildArray(getField(), y.length);
+            Array2DRowFieldMatrix<T> predictedNordsieck = null;
+            T error = getField().getZero().add(10);
+            while (error.subtract(1.0).getReal() >= 0.0) {
+
+                // predict a first estimate of the state at step end (P in the PECE sequence)
+                predictedY = stepEnd.getState();
+
+                // evaluate a first estimate of the derivative (first E in the PECE sequence)
+                final T[] yDot = computeDerivatives(stepEnd.getTime(), predictedY);
+
+                // update Nordsieck vector
+                for (int j = 0; j < predictedScaled.length; ++j) {
+                    predictedScaled[j] = getStepSize().multiply(yDot[j]);
+                }
+                predictedNordsieck = updateHighOrderDerivativesPhase1(nordsieck);
+                updateHighOrderDerivativesPhase2(scaled, predictedScaled, predictedNordsieck);
+
+                // apply correction (C in the PECE sequence)
+                error = predictedNordsieck.walkInOptimizedOrder(new Corrector(y, predictedScaled, predictedY));
+
+                if (error.subtract(1.0).getReal() >= 0.0) {
+                    // reject the step and attempt to reduce error by stepsize control
+                    final T factor = computeStepGrowShrinkFactor(error);
+                    rescale(filterStep(getStepSize().multiply(factor), forward, false));
+                    stepEnd = AdamsFieldStepInterpolator.taylor(getStepStart(),
+                                                                getStepStart().getTime().add(getStepSize()),
+                                                                getStepSize(),
+                                                                scaled,
+                                                                nordsieck);
+                }
+            }
+
+            // evaluate a final estimate of the derivative (second E in the PECE sequence)
+            final T[] correctedYDot = computeDerivatives(stepEnd.getTime(), predictedY);
+
+            // update Nordsieck vector
+            final T[] correctedScaled = MathArrays.buildArray(getField(), y.length);
+            for (int j = 0; j < correctedScaled.length; ++j) {
+                correctedScaled[j] = getStepSize().multiply(correctedYDot[j]);
+            }
+            updateHighOrderDerivativesPhase2(predictedScaled, correctedScaled, predictedNordsieck);
+
+            // discrete events handling
+            stepEnd = new FieldODEStateAndDerivative<T>(stepEnd.getTime(), predictedY, correctedYDot);
+            setStepStart(acceptStep(new AdamsFieldStepInterpolator<T>(getStepSize(), stepEnd,
+                                                                      correctedScaled, predictedNordsieck, forward,
+                                                                      getStepStart(), stepEnd,
+                                                                      equations.getMapper()),
+                                    finalTime));
+            scaled    = correctedScaled;
+            nordsieck = predictedNordsieck;
+
+            if (!isLastStep()) {
+
+                System.arraycopy(predictedY, 0, y, 0, y.length);
+
+                if (resetOccurred()) {
+                    // some events handler has triggered changes that
+                    // invalidate the derivatives, we need to restart from scratch
+                    start(equations, getStepStart(), finalTime);
+                }
+
+                // stepsize control for next step
+                final T  factor     = computeStepGrowShrinkFactor(error);
+                final T  scaledH    = getStepSize().multiply(factor);
+                final T  nextT      = getStepStart().getTime().add(scaledH);
+                final boolean nextIsLast = forward ?
+                                           nextT.subtract(finalTime).getReal() >= 0 :
+                                           nextT.subtract(finalTime).getReal() <= 0;
+                T hNew = filterStep(scaledH, forward, nextIsLast);
+
+                final T  filteredNextT      = getStepStart().getTime().add(hNew);
+                final boolean filteredNextIsLast = forward ?
+                                                   filteredNextT.subtract(finalTime).getReal() >= 0 :
+                                                   filteredNextT.subtract(finalTime).getReal() <= 0;
+                if (filteredNextIsLast) {
+                    hNew = finalTime.subtract(getStepStart().getTime());
+                }
+
+                rescale(hNew);
+                stepEnd = AdamsFieldStepInterpolator.taylor(getStepStart(), getStepStart().getTime().add(getStepSize()),
+                                                            getStepSize(), scaled, nordsieck);
+
+            }
+
+        } while (!isLastStep());
+
+        final FieldODEStateAndDerivative<T> finalState = getStepStart();
+        setStepStart(null);
+        setStepSize(null);
+        return finalState;
+
+    }
+
+    /** Corrector for current state in Adams-Moulton method.
+     * <p>
+     * This visitor implements the Taylor series formula:
+     * <pre>
+     * Y<sub>n+1</sub> = y<sub>n</sub> + s<sub>1</sub>(n+1) + [ -1 +1 -1 +1 ... &plusmn;1 ] r<sub>n+1</sub>
+     * </pre>
+     * </p>
+     */
+    private class Corrector implements FieldMatrixPreservingVisitor<T> {
+
+        /** Previous state. */
+        private final T[] previous;
+
+        /** Current scaled first derivative. */
+        private final T[] scaled;
+
+        /** Current state before correction. */
+        private final T[] before;
+
+        /** Current state after correction. */
+        private final T[] after;
+
+        /** Simple constructor.
+         * @param previous previous state
+         * @param scaled current scaled first derivative
+         * @param state state to correct (will be overwritten after visit)
+         */
+        Corrector(final T[] previous, final T[] scaled, final T[] state) {
+            this.previous = previous;
+            this.scaled   = scaled;
+            this.after    = state;
+            this.before   = state.clone();
+        }
+
+        /** {@inheritDoc} */
+        public void start(int rows, int columns,
+                          int startRow, int endRow, int startColumn, int endColumn) {
+            Arrays.fill(after, getField().getZero());
+        }
+
+        /** {@inheritDoc} */
+        public void visit(int row, int column, T value) {
+            if ((row & 0x1) == 0) {
+                after[column] = after[column].subtract(value);
+            } else {
+                after[column] = after[column].add(value);
+            }
+        }
+
+        /**
+         * End visiting the Nordsieck vector.
+         * <p>The correction is used to control stepsize. So its amplitude is
+         * considered to be an error, which must be normalized according to
+         * error control settings. If the normalized value is greater than 1,
+         * the correction was too large and the step must be rejected.</p>
+         * @return the normalized correction, if greater than 1, the step
+         * must be rejected
+         */
+        public T end() {
+
+            T error = getField().getZero();
+            for (int i = 0; i < after.length; ++i) {
+                after[i] = after[i].add(previous[i].add(scaled[i]));
+                if (i < mainSetDimension) {
+                    final T yScale = MathUtils.max(previous[i].abs(), after[i].abs());
+                    final T tol = (vecAbsoluteTolerance == null) ?
+                                  yScale.multiply(scalRelativeTolerance).add(scalAbsoluteTolerance) :
+                                  yScale.multiply(vecRelativeTolerance[i]).add(vecAbsoluteTolerance[i]);
+                    final T ratio  = after[i].subtract(before[i]).divide(tol); // (corrected-predicted)/tol
+                    error = error.add(ratio.multiply(ratio));
+                }
+            }
+
+            return error.divide(mainSetDimension).sqrt();
+
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsMoultonIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsMoultonIntegrator.java
new file mode 100644
index 0000000..69d2469
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsMoultonIntegrator.java
@@ -0,0 +1,421 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import java.util.Arrays;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NoBracketingException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.linear.Array2DRowRealMatrix;
+import org.apache.commons.math3.linear.RealMatrixPreservingVisitor;
+import org.apache.commons.math3.ode.EquationsMapper;
+import org.apache.commons.math3.ode.ExpandableStatefulODE;
+import org.apache.commons.math3.ode.sampling.NordsieckStepInterpolator;
+import org.apache.commons.math3.util.FastMath;
+
+
+/**
+ * This class implements implicit Adams-Moulton integrators for Ordinary
+ * Differential Equations.
+ *
+ * <p>Adams-Moulton methods (in fact due to Adams alone) are implicit
+ * multistep ODE solvers. This implementation is a variation of the classical
+ * one: it uses adaptive stepsize to implement error control, whereas
+ * classical implementations are fixed step size. The value of state vector
+ * at step n+1 is a simple combination of the value at step n and of the
+ * derivatives at steps n+1, n, n-1 ... Since y'<sub>n+1</sub> is needed to
+ * compute y<sub>n+1</sub>, another method must be used to compute a first
+ * estimate of y<sub>n+1</sub>, then compute y'<sub>n+1</sub>, then compute
+ * a final estimate of y<sub>n+1</sub> using the following formulas. Depending
+ * on the number k of previous steps one wants to use for computing the next
+ * value, different formulas are available for the final estimate:</p>
+ * <ul>
+ *   <li>k = 1: y<sub>n+1</sub> = y<sub>n</sub> + h y'<sub>n+1</sub></li>
+ *   <li>k = 2: y<sub>n+1</sub> = y<sub>n</sub> + h (y'<sub>n+1</sub>+y'<sub>n</sub>)/2</li>
+ *   <li>k = 3: y<sub>n+1</sub> = y<sub>n</sub> + h (5y'<sub>n+1</sub>+8y'<sub>n</sub>-y'<sub>n-1</sub>)/12</li>
+ *   <li>k = 4: y<sub>n+1</sub> = y<sub>n</sub> + h (9y'<sub>n+1</sub>+19y'<sub>n</sub>-5y'<sub>n-1</sub>+y'<sub>n-2</sub>)/24</li>
+ *   <li>...</li>
+ * </ul>
+ *
+ * <p>A k-steps Adams-Moulton method is of order k+1.</p>
+ *
+ * <h3>Implementation details</h3>
+ *
+ * <p>We define scaled derivatives s<sub>i</sub>(n) at step n as:
+ * <pre>
+ * s<sub>1</sub>(n) = h y'<sub>n</sub> for first derivative
+ * s<sub>2</sub>(n) = h<sup>2</sup>/2 y''<sub>n</sub> for second derivative
+ * s<sub>3</sub>(n) = h<sup>3</sup>/6 y'''<sub>n</sub> for third derivative
+ * ...
+ * s<sub>k</sub>(n) = h<sup>k</sup>/k! y<sup>(k)</sup><sub>n</sub> for k<sup>th</sup> derivative
+ * </pre></p>
+ *
+ * <p>The definitions above use the classical representation with several previous first
+ * derivatives. Lets define
+ * <pre>
+ *   q<sub>n</sub> = [ s<sub>1</sub>(n-1) s<sub>1</sub>(n-2) ... s<sub>1</sub>(n-(k-1)) ]<sup>T</sup>
+ * </pre>
+ * (we omit the k index in the notation for clarity). With these definitions,
+ * Adams-Moulton methods can be written:
+ * <ul>
+ *   <li>k = 1: y<sub>n+1</sub> = y<sub>n</sub> + s<sub>1</sub>(n+1)</li>
+ *   <li>k = 2: y<sub>n+1</sub> = y<sub>n</sub> + 1/2 s<sub>1</sub>(n+1) + [ 1/2 ] q<sub>n+1</sub></li>
+ *   <li>k = 3: y<sub>n+1</sub> = y<sub>n</sub> + 5/12 s<sub>1</sub>(n+1) + [ 8/12 -1/12 ] q<sub>n+1</sub></li>
+ *   <li>k = 4: y<sub>n+1</sub> = y<sub>n</sub> + 9/24 s<sub>1</sub>(n+1) + [ 19/24 -5/24 1/24 ] q<sub>n+1</sub></li>
+ *   <li>...</li>
+ * </ul></p>
+ *
+ * <p>Instead of using the classical representation with first derivatives only (y<sub>n</sub>,
+ * s<sub>1</sub>(n+1) and q<sub>n+1</sub>), our implementation uses the Nordsieck vector with
+ * higher degrees scaled derivatives all taken at the same step (y<sub>n</sub>, s<sub>1</sub>(n)
+ * and r<sub>n</sub>) where r<sub>n</sub> is defined as:
+ * <pre>
+ * r<sub>n</sub> = [ s<sub>2</sub>(n), s<sub>3</sub>(n) ... s<sub>k</sub>(n) ]<sup>T</sup>
+ * </pre>
+ * (here again we omit the k index in the notation for clarity)
+ * </p>
+ *
+ * <p>Taylor series formulas show that for any index offset i, s<sub>1</sub>(n-i) can be
+ * computed from s<sub>1</sub>(n), s<sub>2</sub>(n) ... s<sub>k</sub>(n), the formula being exact
+ * for degree k polynomials.
+ * <pre>
+ * s<sub>1</sub>(n-i) = s<sub>1</sub>(n) + &sum;<sub>j&gt;0</sub> (j+1) (-i)<sup>j</sup> s<sub>j+1</sub>(n)
+ * </pre>
+ * The previous formula can be used with several values for i to compute the transform between
+ * classical representation and Nordsieck vector. The transform between r<sub>n</sub>
+ * and q<sub>n</sub> resulting from the Taylor series formulas above is:
+ * <pre>
+ * q<sub>n</sub> = s<sub>1</sub>(n) u + P r<sub>n</sub>
+ * </pre>
+ * where u is the [ 1 1 ... 1 ]<sup>T</sup> vector and P is the (k-1)&times;(k-1) matrix built
+ * with the (j+1) (-i)<sup>j</sup> terms with i being the row number starting from 1 and j being
+ * the column number starting from 1:
+ * <pre>
+ *        [  -2   3   -4    5  ... ]
+ *        [  -4  12  -32   80  ... ]
+ *   P =  [  -6  27 -108  405  ... ]
+ *        [  -8  48 -256 1280  ... ]
+ *        [          ...           ]
+ * </pre></p>
+ *
+ * <p>Using the Nordsieck vector has several advantages:
+ * <ul>
+ *   <li>it greatly simplifies step interpolation as the interpolator mainly applies
+ *   Taylor series formulas,</li>
+ *   <li>it simplifies step changes that occur when discrete events that truncate
+ *   the step are triggered,</li>
+ *   <li>it allows to extend the methods in order to support adaptive stepsize.</li>
+ * </ul></p>
+ *
+ * <p>The predicted Nordsieck vector at step n+1 is computed from the Nordsieck vector at step
+ * n as follows:
+ * <ul>
+ *   <li>Y<sub>n+1</sub> = y<sub>n</sub> + s<sub>1</sub>(n) + u<sup>T</sup> r<sub>n</sub></li>
+ *   <li>S<sub>1</sub>(n+1) = h f(t<sub>n+1</sub>, Y<sub>n+1</sub>)</li>
+ *   <li>R<sub>n+1</sub> = (s<sub>1</sub>(n) - S<sub>1</sub>(n+1)) P<sup>-1</sup> u + P<sup>-1</sup> A P r<sub>n</sub></li>
+ * </ul>
+ * where A is a rows shifting matrix (the lower left part is an identity matrix):
+ * <pre>
+ *        [ 0 0   ...  0 0 | 0 ]
+ *        [ ---------------+---]
+ *        [ 1 0   ...  0 0 | 0 ]
+ *    A = [ 0 1   ...  0 0 | 0 ]
+ *        [       ...      | 0 ]
+ *        [ 0 0   ...  1 0 | 0 ]
+ *        [ 0 0   ...  0 1 | 0 ]
+ * </pre>
+ * From this predicted vector, the corrected vector is computed as follows:
+ * <ul>
+ *   <li>y<sub>n+1</sub> = y<sub>n</sub> + S<sub>1</sub>(n+1) + [ -1 +1 -1 +1 ... &plusmn;1 ] r<sub>n+1</sub></li>
+ *   <li>s<sub>1</sub>(n+1) = h f(t<sub>n+1</sub>, y<sub>n+1</sub>)</li>
+ *   <li>r<sub>n+1</sub> = R<sub>n+1</sub> + (s<sub>1</sub>(n+1) - S<sub>1</sub>(n+1)) P<sup>-1</sup> u</li>
+ * </ul>
+ * where the upper case Y<sub>n+1</sub>, S<sub>1</sub>(n+1) and R<sub>n+1</sub> represent the
+ * predicted states whereas the lower case y<sub>n+1</sub>, s<sub>n+1</sub> and r<sub>n+1</sub>
+ * represent the corrected states.</p>
+ *
+ * <p>The P<sup>-1</sup>u vector and the P<sup>-1</sup> A P matrix do not depend on the state,
+ * they only depend on k and therefore are precomputed once for all.</p>
+ *
+ * @since 2.0
+ */
+public class AdamsMoultonIntegrator extends AdamsIntegrator {
+
+    /** Integrator method name. */
+    private static final String METHOD_NAME = "Adams-Moulton";
+
+    /**
+     * Build an Adams-Moulton integrator with the given order and error control parameters.
+     * @param nSteps number of steps of the method excluding the one being computed
+     * @param minStep minimal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param maxStep maximal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param scalAbsoluteTolerance allowed absolute error
+     * @param scalRelativeTolerance allowed relative error
+     * @exception NumberIsTooSmallException if order is 1 or less
+     */
+    public AdamsMoultonIntegrator(final int nSteps,
+                                  final double minStep, final double maxStep,
+                                  final double scalAbsoluteTolerance,
+                                  final double scalRelativeTolerance)
+        throws NumberIsTooSmallException {
+        super(METHOD_NAME, nSteps, nSteps + 1, minStep, maxStep,
+              scalAbsoluteTolerance, scalRelativeTolerance);
+    }
+
+    /**
+     * Build an Adams-Moulton integrator with the given order and error control parameters.
+     * @param nSteps number of steps of the method excluding the one being computed
+     * @param minStep minimal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param maxStep maximal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param vecAbsoluteTolerance allowed absolute error
+     * @param vecRelativeTolerance allowed relative error
+     * @exception IllegalArgumentException if order is 1 or less
+     */
+    public AdamsMoultonIntegrator(final int nSteps,
+                                  final double minStep, final double maxStep,
+                                  final double[] vecAbsoluteTolerance,
+                                  final double[] vecRelativeTolerance)
+        throws IllegalArgumentException {
+        super(METHOD_NAME, nSteps, nSteps + 1, minStep, maxStep,
+              vecAbsoluteTolerance, vecRelativeTolerance);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void integrate(final ExpandableStatefulODE equations,final double t)
+        throws NumberIsTooSmallException, DimensionMismatchException,
+               MaxCountExceededException, NoBracketingException {
+
+        sanityChecks(equations, t);
+        setEquations(equations);
+        final boolean forward = t > equations.getTime();
+
+        // initialize working arrays
+        final double[] y0   = equations.getCompleteState();
+        final double[] y    = y0.clone();
+        final double[] yDot = new double[y.length];
+        final double[] yTmp = new double[y.length];
+        final double[] predictedScaled = new double[y.length];
+        Array2DRowRealMatrix nordsieckTmp = null;
+
+        // set up two interpolators sharing the integrator arrays
+        final NordsieckStepInterpolator interpolator = new NordsieckStepInterpolator();
+        interpolator.reinitialize(y, forward,
+                                  equations.getPrimaryMapper(), equations.getSecondaryMappers());
+
+        // set up integration control objects
+        initIntegration(equations.getTime(), y0, t);
+
+        // compute the initial Nordsieck vector using the configured starter integrator
+        start(equations.getTime(), y, t);
+        interpolator.reinitialize(stepStart, stepSize, scaled, nordsieck);
+        interpolator.storeTime(stepStart);
+
+        double hNew = stepSize;
+        interpolator.rescale(hNew);
+
+        isLastStep = false;
+        do {
+
+            double error = 10;
+            while (error >= 1.0) {
+
+                stepSize = hNew;
+
+                // predict a first estimate of the state at step end (P in the PECE sequence)
+                final double stepEnd = stepStart + stepSize;
+                interpolator.setInterpolatedTime(stepEnd);
+                final ExpandableStatefulODE expandable = getExpandable();
+                final EquationsMapper primary = expandable.getPrimaryMapper();
+                primary.insertEquationData(interpolator.getInterpolatedState(), yTmp);
+                int index = 0;
+                for (final EquationsMapper secondary : expandable.getSecondaryMappers()) {
+                    secondary.insertEquationData(interpolator.getInterpolatedSecondaryState(index), yTmp);
+                    ++index;
+                }
+
+                // evaluate a first estimate of the derivative (first E in the PECE sequence)
+                computeDerivatives(stepEnd, yTmp, yDot);
+
+                // update Nordsieck vector
+                for (int j = 0; j < y0.length; ++j) {
+                    predictedScaled[j] = stepSize * yDot[j];
+                }
+                nordsieckTmp = updateHighOrderDerivativesPhase1(nordsieck);
+                updateHighOrderDerivativesPhase2(scaled, predictedScaled, nordsieckTmp);
+
+                // apply correction (C in the PECE sequence)
+                error = nordsieckTmp.walkInOptimizedOrder(new Corrector(y, predictedScaled, yTmp));
+
+                if (error >= 1.0) {
+                    // reject the step and attempt to reduce error by stepsize control
+                    final double factor = computeStepGrowShrinkFactor(error);
+                    hNew = filterStep(stepSize * factor, forward, false);
+                    interpolator.rescale(hNew);
+                }
+            }
+
+            // evaluate a final estimate of the derivative (second E in the PECE sequence)
+            final double stepEnd = stepStart + stepSize;
+            computeDerivatives(stepEnd, yTmp, yDot);
+
+            // update Nordsieck vector
+            final double[] correctedScaled = new double[y0.length];
+            for (int j = 0; j < y0.length; ++j) {
+                correctedScaled[j] = stepSize * yDot[j];
+            }
+            updateHighOrderDerivativesPhase2(predictedScaled, correctedScaled, nordsieckTmp);
+
+            // discrete events handling
+            System.arraycopy(yTmp, 0, y, 0, y.length);
+            interpolator.reinitialize(stepEnd, stepSize, correctedScaled, nordsieckTmp);
+            interpolator.storeTime(stepStart);
+            interpolator.shift();
+            interpolator.storeTime(stepEnd);
+            stepStart = acceptStep(interpolator, y, yDot, t);
+            scaled    = correctedScaled;
+            nordsieck = nordsieckTmp;
+
+            if (!isLastStep) {
+
+                // prepare next step
+                interpolator.storeTime(stepStart);
+
+                if (resetOccurred) {
+                    // some events handler has triggered changes that
+                    // invalidate the derivatives, we need to restart from scratch
+                    start(stepStart, y, t);
+                    interpolator.reinitialize(stepStart, stepSize, scaled, nordsieck);
+
+                }
+
+                // stepsize control for next step
+                final double  factor     = computeStepGrowShrinkFactor(error);
+                final double  scaledH    = stepSize * factor;
+                final double  nextT      = stepStart + scaledH;
+                final boolean nextIsLast = forward ? (nextT >= t) : (nextT <= t);
+                hNew = filterStep(scaledH, forward, nextIsLast);
+
+                final double  filteredNextT      = stepStart + hNew;
+                final boolean filteredNextIsLast = forward ? (filteredNextT >= t) : (filteredNextT <= t);
+                if (filteredNextIsLast) {
+                    hNew = t - stepStart;
+                }
+
+                interpolator.rescale(hNew);
+            }
+
+        } while (!isLastStep);
+
+        // dispatch results
+        equations.setTime(stepStart);
+        equations.setCompleteState(y);
+
+        resetInternalState();
+
+    }
+
+    /** Corrector for current state in Adams-Moulton method.
+     * <p>
+     * This visitor implements the Taylor series formula:
+     * <pre>
+     * Y<sub>n+1</sub> = y<sub>n</sub> + s<sub>1</sub>(n+1) + [ -1 +1 -1 +1 ... &plusmn;1 ] r<sub>n+1</sub>
+     * </pre>
+     * </p>
+     */
+    private class Corrector implements RealMatrixPreservingVisitor {
+
+        /** Previous state. */
+        private final double[] previous;
+
+        /** Current scaled first derivative. */
+        private final double[] scaled;
+
+        /** Current state before correction. */
+        private final double[] before;
+
+        /** Current state after correction. */
+        private final double[] after;
+
+        /** Simple constructor.
+         * @param previous previous state
+         * @param scaled current scaled first derivative
+         * @param state state to correct (will be overwritten after visit)
+         */
+        Corrector(final double[] previous, final double[] scaled, final double[] state) {
+            this.previous = previous;
+            this.scaled   = scaled;
+            this.after    = state;
+            this.before   = state.clone();
+        }
+
+        /** {@inheritDoc} */
+        public void start(int rows, int columns,
+                          int startRow, int endRow, int startColumn, int endColumn) {
+            Arrays.fill(after, 0.0);
+        }
+
+        /** {@inheritDoc} */
+        public void visit(int row, int column, double value) {
+            if ((row & 0x1) == 0) {
+                after[column] -= value;
+            } else {
+                after[column] += value;
+            }
+        }
+
+        /**
+         * End visiting the Nordsieck vector.
+         * <p>The correction is used to control stepsize. So its amplitude is
+         * considered to be an error, which must be normalized according to
+         * error control settings. If the normalized value is greater than 1,
+         * the correction was too large and the step must be rejected.</p>
+         * @return the normalized correction, if greater than 1, the step
+         * must be rejected
+         */
+        public double end() {
+
+            double error = 0;
+            for (int i = 0; i < after.length; ++i) {
+                after[i] += previous[i] + scaled[i];
+                if (i < mainSetDimension) {
+                    final double yScale = FastMath.max(FastMath.abs(previous[i]), FastMath.abs(after[i]));
+                    final double tol    = (vecAbsoluteTolerance == null) ?
+                                          (scalAbsoluteTolerance + scalRelativeTolerance * yScale) :
+                                          (vecAbsoluteTolerance[i] + vecRelativeTolerance[i] * yScale);
+                    final double ratio  = (after[i] - before[i]) / tol; // (corrected-predicted)/tol
+                    error += ratio * ratio;
+                }
+            }
+
+            return FastMath.sqrt(error / mainSetDimension);
+
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsNordsieckFieldTransformer.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsNordsieckFieldTransformer.java
new file mode 100644
index 0000000..b8f872b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsNordsieckFieldTransformer.java
@@ -0,0 +1,363 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.linear.Array2DRowFieldMatrix;
+import org.apache.commons.math3.linear.ArrayFieldVector;
+import org.apache.commons.math3.linear.FieldDecompositionSolver;
+import org.apache.commons.math3.linear.FieldLUDecomposition;
+import org.apache.commons.math3.linear.FieldMatrix;
+import org.apache.commons.math3.util.MathArrays;
+
+/** Transformer to Nordsieck vectors for Adams integrators.
+ * <p>This class is used by {@link AdamsBashforthIntegrator Adams-Bashforth} and
+ * {@link AdamsMoultonIntegrator Adams-Moulton} integrators to convert between
+ * classical representation with several previous first derivatives and Nordsieck
+ * representation with higher order scaled derivatives.</p>
+ *
+ * <p>We define scaled derivatives s<sub>i</sub>(n) at step n as:
+ * <pre>
+ * s<sub>1</sub>(n) = h y'<sub>n</sub> for first derivative
+ * s<sub>2</sub>(n) = h<sup>2</sup>/2 y''<sub>n</sub> for second derivative
+ * s<sub>3</sub>(n) = h<sup>3</sup>/6 y'''<sub>n</sub> for third derivative
+ * ...
+ * s<sub>k</sub>(n) = h<sup>k</sup>/k! y<sup>(k)</sup><sub>n</sub> for k<sup>th</sup> derivative
+ * </pre></p>
+ *
+ * <p>With the previous definition, the classical representation of multistep methods
+ * uses first derivatives only, i.e. it handles y<sub>n</sub>, s<sub>1</sub>(n) and
+ * q<sub>n</sub> where q<sub>n</sub> is defined as:
+ * <pre>
+ *   q<sub>n</sub> = [ s<sub>1</sub>(n-1) s<sub>1</sub>(n-2) ... s<sub>1</sub>(n-(k-1)) ]<sup>T</sup>
+ * </pre>
+ * (we omit the k index in the notation for clarity).</p>
+ *
+ * <p>Another possible representation uses the Nordsieck vector with
+ * higher degrees scaled derivatives all taken at the same step, i.e it handles y<sub>n</sub>,
+ * s<sub>1</sub>(n) and r<sub>n</sub>) where r<sub>n</sub> is defined as:
+ * <pre>
+ * r<sub>n</sub> = [ s<sub>2</sub>(n), s<sub>3</sub>(n) ... s<sub>k</sub>(n) ]<sup>T</sup>
+ * </pre>
+ * (here again we omit the k index in the notation for clarity)
+ * </p>
+ *
+ * <p>Taylor series formulas show that for any index offset i, s<sub>1</sub>(n-i) can be
+ * computed from s<sub>1</sub>(n), s<sub>2</sub>(n) ... s<sub>k</sub>(n), the formula being exact
+ * for degree k polynomials.
+ * <pre>
+ * s<sub>1</sub>(n-i) = s<sub>1</sub>(n) + &sum;<sub>j&gt;0</sub> (j+1) (-i)<sup>j</sup> s<sub>j+1</sub>(n)
+ * </pre>
+ * The previous formula can be used with several values for i to compute the transform between
+ * classical representation and Nordsieck vector at step end. The transform between r<sub>n</sub>
+ * and q<sub>n</sub> resulting from the Taylor series formulas above is:
+ * <pre>
+ * q<sub>n</sub> = s<sub>1</sub>(n) u + P r<sub>n</sub>
+ * </pre>
+ * where u is the [ 1 1 ... 1 ]<sup>T</sup> vector and P is the (k-1)&times;(k-1) matrix built
+ * with the (j+1) (-i)<sup>j</sup> terms with i being the row number starting from 1 and j being
+ * the column number starting from 1:
+ * <pre>
+ *        [  -2   3   -4    5  ... ]
+ *        [  -4  12  -32   80  ... ]
+ *   P =  [  -6  27 -108  405  ... ]
+ *        [  -8  48 -256 1280  ... ]
+ *        [          ...           ]
+ * </pre></p>
+ *
+ * <p>Changing -i into +i in the formula above can be used to compute a similar transform between
+ * classical representation and Nordsieck vector at step start. The resulting matrix is simply
+ * the absolute value of matrix P.</p>
+ *
+ * <p>For {@link AdamsBashforthIntegrator Adams-Bashforth} method, the Nordsieck vector
+ * at step n+1 is computed from the Nordsieck vector at step n as follows:
+ * <ul>
+ *   <li>y<sub>n+1</sub> = y<sub>n</sub> + s<sub>1</sub>(n) + u<sup>T</sup> r<sub>n</sub></li>
+ *   <li>s<sub>1</sub>(n+1) = h f(t<sub>n+1</sub>, y<sub>n+1</sub>)</li>
+ *   <li>r<sub>n+1</sub> = (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u + P<sup>-1</sup> A P r<sub>n</sub></li>
+ * </ul>
+ * where A is a rows shifting matrix (the lower left part is an identity matrix):
+ * <pre>
+ *        [ 0 0   ...  0 0 | 0 ]
+ *        [ ---------------+---]
+ *        [ 1 0   ...  0 0 | 0 ]
+ *    A = [ 0 1   ...  0 0 | 0 ]
+ *        [       ...      | 0 ]
+ *        [ 0 0   ...  1 0 | 0 ]
+ *        [ 0 0   ...  0 1 | 0 ]
+ * </pre></p>
+ *
+ * <p>For {@link AdamsMoultonIntegrator Adams-Moulton} method, the predicted Nordsieck vector
+ * at step n+1 is computed from the Nordsieck vector at step n as follows:
+ * <ul>
+ *   <li>Y<sub>n+1</sub> = y<sub>n</sub> + s<sub>1</sub>(n) + u<sup>T</sup> r<sub>n</sub></li>
+ *   <li>S<sub>1</sub>(n+1) = h f(t<sub>n+1</sub>, Y<sub>n+1</sub>)</li>
+ *   <li>R<sub>n+1</sub> = (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u + P<sup>-1</sup> A P r<sub>n</sub></li>
+ * </ul>
+ * From this predicted vector, the corrected vector is computed as follows:
+ * <ul>
+ *   <li>y<sub>n+1</sub> = y<sub>n</sub> + S<sub>1</sub>(n+1) + [ -1 +1 -1 +1 ... &plusmn;1 ] r<sub>n+1</sub></li>
+ *   <li>s<sub>1</sub>(n+1) = h f(t<sub>n+1</sub>, y<sub>n+1</sub>)</li>
+ *   <li>r<sub>n+1</sub> = R<sub>n+1</sub> + (s<sub>1</sub>(n+1) - S<sub>1</sub>(n+1)) P<sup>-1</sup> u</li>
+ * </ul>
+ * where the upper case Y<sub>n+1</sub>, S<sub>1</sub>(n+1) and R<sub>n+1</sub> represent the
+ * predicted states whereas the lower case y<sub>n+1</sub>, s<sub>n+1</sub> and r<sub>n+1</sub>
+ * represent the corrected states.</p>
+ *
+ * <p>We observe that both methods use similar update formulas. In both cases a P<sup>-1</sup>u
+ * vector and a P<sup>-1</sup> A P matrix are used that do not depend on the state,
+ * they only depend on k. This class handles these transformations.</p>
+ *
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+public class AdamsNordsieckFieldTransformer<T extends RealFieldElement<T>> {
+
+    /** Cache for already computed coefficients. */
+    private static final Map<Integer,
+                         Map<Field<? extends RealFieldElement<?>>,
+                                   AdamsNordsieckFieldTransformer<? extends RealFieldElement<?>>>> CACHE =
+        new HashMap<Integer, Map<Field<? extends RealFieldElement<?>>,
+                                 AdamsNordsieckFieldTransformer<? extends RealFieldElement<?>>>>();
+
+    /** Field to which the time and state vector elements belong. */
+    private final Field<T> field;
+
+    /** Update matrix for the higher order derivatives h<sup>2</sup>/2 y'', h<sup>3</sup>/6 y''' ... */
+    private final Array2DRowFieldMatrix<T> update;
+
+    /** Update coefficients of the higher order derivatives wrt y'. */
+    private final T[] c1;
+
+    /** Simple constructor.
+     * @param field field to which the time and state vector elements belong
+     * @param n number of steps of the multistep method
+     * (excluding the one being computed)
+     */
+    private AdamsNordsieckFieldTransformer(final Field<T> field, final int n) {
+
+        this.field = field;
+        final int rows = n - 1;
+
+        // compute coefficients
+        FieldMatrix<T> bigP = buildP(rows);
+        FieldDecompositionSolver<T> pSolver =
+            new FieldLUDecomposition<T>(bigP).getSolver();
+
+        T[] u = MathArrays.buildArray(field, rows);
+        Arrays.fill(u, field.getOne());
+        c1 = pSolver.solve(new ArrayFieldVector<T>(u, false)).toArray();
+
+        // update coefficients are computed by combining transform from
+        // Nordsieck to multistep, then shifting rows to represent step advance
+        // then applying inverse transform
+        T[][] shiftedP = bigP.getData();
+        for (int i = shiftedP.length - 1; i > 0; --i) {
+            // shift rows
+            shiftedP[i] = shiftedP[i - 1];
+        }
+        shiftedP[0] = MathArrays.buildArray(field, rows);
+        Arrays.fill(shiftedP[0], field.getZero());
+        update = new Array2DRowFieldMatrix<T>(pSolver.solve(new Array2DRowFieldMatrix<T>(shiftedP, false)).getData());
+
+    }
+
+    /** Get the Nordsieck transformer for a given field and number of steps.
+     * @param field field to which the time and state vector elements belong
+     * @param nSteps number of steps of the multistep method
+     * (excluding the one being computed)
+     * @return Nordsieck transformer for the specified field and number of steps
+     * @param <T> the type of the field elements
+     */
+    @SuppressWarnings("unchecked")
+    public static <T extends RealFieldElement<T>> AdamsNordsieckFieldTransformer<T>
+    getInstance(final Field<T> field, final int nSteps) {
+        synchronized(CACHE) {
+            Map<Field<? extends RealFieldElement<?>>,
+                      AdamsNordsieckFieldTransformer<? extends RealFieldElement<?>>> map = CACHE.get(nSteps);
+            if (map == null) {
+                map = new HashMap<Field<? extends RealFieldElement<?>>,
+                                        AdamsNordsieckFieldTransformer<? extends RealFieldElement<?>>>();
+                CACHE.put(nSteps, map);
+            }
+            @SuppressWarnings("rawtypes") // use rawtype to avoid compilation problems with java 1.5
+            AdamsNordsieckFieldTransformer t = map.get(field);
+            if (t == null) {
+                t = new AdamsNordsieckFieldTransformer<T>(field, nSteps);
+                map.put(field, (AdamsNordsieckFieldTransformer<T>) t);
+            }
+            return (AdamsNordsieckFieldTransformer<T>) t;
+
+        }
+    }
+
+    /** Build the P matrix.
+     * <p>The P matrix general terms are shifted (j+1) (-i)<sup>j</sup> terms
+     * with i being the row number starting from 1 and j being the column
+     * number starting from 1:
+     * <pre>
+     *        [  -2   3   -4    5  ... ]
+     *        [  -4  12  -32   80  ... ]
+     *   P =  [  -6  27 -108  405  ... ]
+     *        [  -8  48 -256 1280  ... ]
+     *        [          ...           ]
+     * </pre></p>
+     * @param rows number of rows of the matrix
+     * @return P matrix
+     */
+    private FieldMatrix<T> buildP(final int rows) {
+
+        final T[][] pData = MathArrays.buildArray(field, rows, rows);
+
+        for (int i = 1; i <= pData.length; ++i) {
+            // build the P matrix elements from Taylor series formulas
+            final T[] pI = pData[i - 1];
+            final int factor = -i;
+            T aj = field.getZero().add(factor);
+            for (int j = 1; j <= pI.length; ++j) {
+                pI[j - 1] = aj.multiply(j + 1);
+                aj = aj.multiply(factor);
+            }
+        }
+
+        return new Array2DRowFieldMatrix<T>(pData, false);
+
+    }
+
+    /** Initialize the high order scaled derivatives at step start.
+     * @param h step size to use for scaling
+     * @param t first steps times
+     * @param y first steps states
+     * @param yDot first steps derivatives
+     * @return Nordieck vector at start of first step (h<sup>2</sup>/2 y''<sub>n</sub>,
+     * h<sup>3</sup>/6 y'''<sub>n</sub> ... h<sup>k</sup>/k! y<sup>(k)</sup><sub>n</sub>)
+     */
+
+    public Array2DRowFieldMatrix<T> initializeHighOrderDerivatives(final T h, final T[] t,
+                                                                   final T[][] y,
+                                                                   final T[][] yDot) {
+
+        // using Taylor series with di = ti - t0, we get:
+        //  y(ti)  - y(t0)  - di y'(t0) =   di^2 / h^2 s2 + ... +   di^k     / h^k sk + O(h^k)
+        //  y'(ti) - y'(t0)             = 2 di   / h^2 s2 + ... + k di^(k-1) / h^k sk + O(h^(k-1))
+        // we write these relations for i = 1 to i= 1+n/2 as a set of n + 2 linear
+        // equations depending on the Nordsieck vector [s2 ... sk rk], so s2 to sk correspond
+        // to the appropriately truncated Taylor expansion, and rk is the Taylor remainder.
+        // The goal is to have s2 to sk as accurate as possible considering the fact the sum is
+        // truncated and we don't want the error terms to be included in s2 ... sk, so we need
+        // to solve also for the remainder
+        final T[][] a     = MathArrays.buildArray(field, c1.length + 1, c1.length + 1);
+        final T[][] b     = MathArrays.buildArray(field, c1.length + 1, y[0].length);
+        final T[]   y0    = y[0];
+        final T[]   yDot0 = yDot[0];
+        for (int i = 1; i < y.length; ++i) {
+
+            final T di    = t[i].subtract(t[0]);
+            final T ratio = di.divide(h);
+            T dikM1Ohk    = h.reciprocal();
+
+            // linear coefficients of equations
+            // y(ti) - y(t0) - di y'(t0) and y'(ti) - y'(t0)
+            final T[] aI    = a[2 * i - 2];
+            final T[] aDotI = (2 * i - 1) < a.length ? a[2 * i - 1] : null;
+            for (int j = 0; j < aI.length; ++j) {
+                dikM1Ohk = dikM1Ohk.multiply(ratio);
+                aI[j]    = di.multiply(dikM1Ohk);
+                if (aDotI != null) {
+                    aDotI[j]  = dikM1Ohk.multiply(j + 2);
+                }
+            }
+
+            // expected value of the previous equations
+            final T[] yI    = y[i];
+            final T[] yDotI = yDot[i];
+            final T[] bI    = b[2 * i - 2];
+            final T[] bDotI = (2 * i - 1) < b.length ? b[2 * i - 1] : null;
+            for (int j = 0; j < yI.length; ++j) {
+                bI[j]    = yI[j].subtract(y0[j]).subtract(di.multiply(yDot0[j]));
+                if (bDotI != null) {
+                    bDotI[j] = yDotI[j].subtract(yDot0[j]);
+                }
+            }
+
+        }
+
+        // solve the linear system to get the best estimate of the Nordsieck vector [s2 ... sk],
+        // with the additional terms s(k+1) and c grabbing the parts after the truncated Taylor expansion
+        final FieldLUDecomposition<T> decomposition = new FieldLUDecomposition<T>(new Array2DRowFieldMatrix<T>(a, false));
+        final FieldMatrix<T> x = decomposition.getSolver().solve(new Array2DRowFieldMatrix<T>(b, false));
+
+        // extract just the Nordsieck vector [s2 ... sk]
+        final Array2DRowFieldMatrix<T> truncatedX =
+                        new Array2DRowFieldMatrix<T>(field, x.getRowDimension() - 1, x.getColumnDimension());
+        for (int i = 0; i < truncatedX.getRowDimension(); ++i) {
+            for (int j = 0; j < truncatedX.getColumnDimension(); ++j) {
+                truncatedX.setEntry(i, j, x.getEntry(i, j));
+            }
+        }
+        return truncatedX;
+
+    }
+
+    /** Update the high order scaled derivatives for Adams integrators (phase 1).
+     * <p>The complete update of high order derivatives has a form similar to:
+     * <pre>
+     * r<sub>n+1</sub> = (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u + P<sup>-1</sup> A P r<sub>n</sub>
+     * </pre>
+     * this method computes the P<sup>-1</sup> A P r<sub>n</sub> part.</p>
+     * @param highOrder high order scaled derivatives
+     * (h<sup>2</sup>/2 y'', ... h<sup>k</sup>/k! y(k))
+     * @return updated high order derivatives
+     * @see #updateHighOrderDerivativesPhase2(RealFieldElement[], RealFieldElement[], Array2DRowFieldMatrix)
+     */
+    public Array2DRowFieldMatrix<T> updateHighOrderDerivativesPhase1(final Array2DRowFieldMatrix<T> highOrder) {
+        return update.multiply(highOrder);
+    }
+
+    /** Update the high order scaled derivatives Adams integrators (phase 2).
+     * <p>The complete update of high order derivatives has a form similar to:
+     * <pre>
+     * r<sub>n+1</sub> = (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u + P<sup>-1</sup> A P r<sub>n</sub>
+     * </pre>
+     * this method computes the (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u part.</p>
+     * <p>Phase 1 of the update must already have been performed.</p>
+     * @param start first order scaled derivatives at step start
+     * @param end first order scaled derivatives at step end
+     * @param highOrder high order scaled derivatives, will be modified
+     * (h<sup>2</sup>/2 y'', ... h<sup>k</sup>/k! y(k))
+     * @see #updateHighOrderDerivativesPhase1(Array2DRowFieldMatrix)
+     */
+    public void updateHighOrderDerivativesPhase2(final T[] start,
+                                                 final T[] end,
+                                                 final Array2DRowFieldMatrix<T> highOrder) {
+        final T[][] data = highOrder.getDataRef();
+        for (int i = 0; i < data.length; ++i) {
+            final T[] dataI = data[i];
+            final T c1I = c1[i];
+            for (int j = 0; j < dataI.length; ++j) {
+                dataI[j] = dataI[j].add(c1I.multiply(start[j].subtract(end[j])));
+            }
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsNordsieckTransformer.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsNordsieckTransformer.java
new file mode 100644
index 0000000..1c54b17
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdamsNordsieckTransformer.java
@@ -0,0 +1,361 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.math3.fraction.BigFraction;
+import org.apache.commons.math3.linear.Array2DRowFieldMatrix;
+import org.apache.commons.math3.linear.Array2DRowRealMatrix;
+import org.apache.commons.math3.linear.ArrayFieldVector;
+import org.apache.commons.math3.linear.FieldDecompositionSolver;
+import org.apache.commons.math3.linear.FieldLUDecomposition;
+import org.apache.commons.math3.linear.FieldMatrix;
+import org.apache.commons.math3.linear.MatrixUtils;
+import org.apache.commons.math3.linear.QRDecomposition;
+import org.apache.commons.math3.linear.RealMatrix;
+
+/** Transformer to Nordsieck vectors for Adams integrators.
+ * <p>This class is used by {@link AdamsBashforthIntegrator Adams-Bashforth} and
+ * {@link AdamsMoultonIntegrator Adams-Moulton} integrators to convert between
+ * classical representation with several previous first derivatives and Nordsieck
+ * representation with higher order scaled derivatives.</p>
+ *
+ * <p>We define scaled derivatives s<sub>i</sub>(n) at step n as:
+ * <pre>
+ * s<sub>1</sub>(n) = h y'<sub>n</sub> for first derivative
+ * s<sub>2</sub>(n) = h<sup>2</sup>/2 y''<sub>n</sub> for second derivative
+ * s<sub>3</sub>(n) = h<sup>3</sup>/6 y'''<sub>n</sub> for third derivative
+ * ...
+ * s<sub>k</sub>(n) = h<sup>k</sup>/k! y<sup>(k)</sup><sub>n</sub> for k<sup>th</sup> derivative
+ * </pre></p>
+ *
+ * <p>With the previous definition, the classical representation of multistep methods
+ * uses first derivatives only, i.e. it handles y<sub>n</sub>, s<sub>1</sub>(n) and
+ * q<sub>n</sub> where q<sub>n</sub> is defined as:
+ * <pre>
+ *   q<sub>n</sub> = [ s<sub>1</sub>(n-1) s<sub>1</sub>(n-2) ... s<sub>1</sub>(n-(k-1)) ]<sup>T</sup>
+ * </pre>
+ * (we omit the k index in the notation for clarity).</p>
+ *
+ * <p>Another possible representation uses the Nordsieck vector with
+ * higher degrees scaled derivatives all taken at the same step, i.e it handles y<sub>n</sub>,
+ * s<sub>1</sub>(n) and r<sub>n</sub>) where r<sub>n</sub> is defined as:
+ * <pre>
+ * r<sub>n</sub> = [ s<sub>2</sub>(n), s<sub>3</sub>(n) ... s<sub>k</sub>(n) ]<sup>T</sup>
+ * </pre>
+ * (here again we omit the k index in the notation for clarity)
+ * </p>
+ *
+ * <p>Taylor series formulas show that for any index offset i, s<sub>1</sub>(n-i) can be
+ * computed from s<sub>1</sub>(n), s<sub>2</sub>(n) ... s<sub>k</sub>(n), the formula being exact
+ * for degree k polynomials.
+ * <pre>
+ * s<sub>1</sub>(n-i) = s<sub>1</sub>(n) + &sum;<sub>j&gt;0</sub> (j+1) (-i)<sup>j</sup> s<sub>j+1</sub>(n)
+ * </pre>
+ * The previous formula can be used with several values for i to compute the transform between
+ * classical representation and Nordsieck vector at step end. The transform between r<sub>n</sub>
+ * and q<sub>n</sub> resulting from the Taylor series formulas above is:
+ * <pre>
+ * q<sub>n</sub> = s<sub>1</sub>(n) u + P r<sub>n</sub>
+ * </pre>
+ * where u is the [ 1 1 ... 1 ]<sup>T</sup> vector and P is the (k-1)&times;(k-1) matrix built
+ * with the (j+1) (-i)<sup>j</sup> terms with i being the row number starting from 1 and j being
+ * the column number starting from 1:
+ * <pre>
+ *        [  -2   3   -4    5  ... ]
+ *        [  -4  12  -32   80  ... ]
+ *   P =  [  -6  27 -108  405  ... ]
+ *        [  -8  48 -256 1280  ... ]
+ *        [          ...           ]
+ * </pre></p>
+ *
+ * <p>Changing -i into +i in the formula above can be used to compute a similar transform between
+ * classical representation and Nordsieck vector at step start. The resulting matrix is simply
+ * the absolute value of matrix P.</p>
+ *
+ * <p>For {@link AdamsBashforthIntegrator Adams-Bashforth} method, the Nordsieck vector
+ * at step n+1 is computed from the Nordsieck vector at step n as follows:
+ * <ul>
+ *   <li>y<sub>n+1</sub> = y<sub>n</sub> + s<sub>1</sub>(n) + u<sup>T</sup> r<sub>n</sub></li>
+ *   <li>s<sub>1</sub>(n+1) = h f(t<sub>n+1</sub>, y<sub>n+1</sub>)</li>
+ *   <li>r<sub>n+1</sub> = (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u + P<sup>-1</sup> A P r<sub>n</sub></li>
+ * </ul>
+ * where A is a rows shifting matrix (the lower left part is an identity matrix):
+ * <pre>
+ *        [ 0 0   ...  0 0 | 0 ]
+ *        [ ---------------+---]
+ *        [ 1 0   ...  0 0 | 0 ]
+ *    A = [ 0 1   ...  0 0 | 0 ]
+ *        [       ...      | 0 ]
+ *        [ 0 0   ...  1 0 | 0 ]
+ *        [ 0 0   ...  0 1 | 0 ]
+ * </pre></p>
+ *
+ * <p>For {@link AdamsMoultonIntegrator Adams-Moulton} method, the predicted Nordsieck vector
+ * at step n+1 is computed from the Nordsieck vector at step n as follows:
+ * <ul>
+ *   <li>Y<sub>n+1</sub> = y<sub>n</sub> + s<sub>1</sub>(n) + u<sup>T</sup> r<sub>n</sub></li>
+ *   <li>S<sub>1</sub>(n+1) = h f(t<sub>n+1</sub>, Y<sub>n+1</sub>)</li>
+ *   <li>R<sub>n+1</sub> = (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u + P<sup>-1</sup> A P r<sub>n</sub></li>
+ * </ul>
+ * From this predicted vector, the corrected vector is computed as follows:
+ * <ul>
+ *   <li>y<sub>n+1</sub> = y<sub>n</sub> + S<sub>1</sub>(n+1) + [ -1 +1 -1 +1 ... &plusmn;1 ] r<sub>n+1</sub></li>
+ *   <li>s<sub>1</sub>(n+1) = h f(t<sub>n+1</sub>, y<sub>n+1</sub>)</li>
+ *   <li>r<sub>n+1</sub> = R<sub>n+1</sub> + (s<sub>1</sub>(n+1) - S<sub>1</sub>(n+1)) P<sup>-1</sup> u</li>
+ * </ul>
+ * where the upper case Y<sub>n+1</sub>, S<sub>1</sub>(n+1) and R<sub>n+1</sub> represent the
+ * predicted states whereas the lower case y<sub>n+1</sub>, s<sub>n+1</sub> and r<sub>n+1</sub>
+ * represent the corrected states.</p>
+ *
+ * <p>We observe that both methods use similar update formulas. In both cases a P<sup>-1</sup>u
+ * vector and a P<sup>-1</sup> A P matrix are used that do not depend on the state,
+ * they only depend on k. This class handles these transformations.</p>
+ *
+ * @since 2.0
+ */
+public class AdamsNordsieckTransformer {
+
+    /** Cache for already computed coefficients. */
+    private static final Map<Integer, AdamsNordsieckTransformer> CACHE =
+        new HashMap<Integer, AdamsNordsieckTransformer>();
+
+    /** Update matrix for the higher order derivatives h<sup>2</sup>/2 y'', h<sup>3</sup>/6 y''' ... */
+    private final Array2DRowRealMatrix update;
+
+    /** Update coefficients of the higher order derivatives wrt y'. */
+    private final double[] c1;
+
+    /** Simple constructor.
+     * @param n number of steps of the multistep method
+     * (excluding the one being computed)
+     */
+    private AdamsNordsieckTransformer(final int n) {
+
+        final int rows = n - 1;
+
+        // compute exact coefficients
+        FieldMatrix<BigFraction> bigP = buildP(rows);
+        FieldDecompositionSolver<BigFraction> pSolver =
+            new FieldLUDecomposition<BigFraction>(bigP).getSolver();
+
+        BigFraction[] u = new BigFraction[rows];
+        Arrays.fill(u, BigFraction.ONE);
+        BigFraction[] bigC1 = pSolver.solve(new ArrayFieldVector<BigFraction>(u, false)).toArray();
+
+        // update coefficients are computed by combining transform from
+        // Nordsieck to multistep, then shifting rows to represent step advance
+        // then applying inverse transform
+        BigFraction[][] shiftedP = bigP.getData();
+        for (int i = shiftedP.length - 1; i > 0; --i) {
+            // shift rows
+            shiftedP[i] = shiftedP[i - 1];
+        }
+        shiftedP[0] = new BigFraction[rows];
+        Arrays.fill(shiftedP[0], BigFraction.ZERO);
+        FieldMatrix<BigFraction> bigMSupdate =
+            pSolver.solve(new Array2DRowFieldMatrix<BigFraction>(shiftedP, false));
+
+        // convert coefficients to double
+        update         = MatrixUtils.bigFractionMatrixToRealMatrix(bigMSupdate);
+        c1             = new double[rows];
+        for (int i = 0; i < rows; ++i) {
+            c1[i] = bigC1[i].doubleValue();
+        }
+
+    }
+
+    /** Get the Nordsieck transformer for a given number of steps.
+     * @param nSteps number of steps of the multistep method
+     * (excluding the one being computed)
+     * @return Nordsieck transformer for the specified number of steps
+     */
+    public static AdamsNordsieckTransformer getInstance(final int nSteps) {
+        synchronized(CACHE) {
+            AdamsNordsieckTransformer t = CACHE.get(nSteps);
+            if (t == null) {
+                t = new AdamsNordsieckTransformer(nSteps);
+                CACHE.put(nSteps, t);
+            }
+            return t;
+        }
+    }
+
+    /** Get the number of steps of the method
+     * (excluding the one being computed).
+     * @return number of steps of the method
+     * (excluding the one being computed)
+     * @deprecated as of 3.6, this method is not used anymore
+     */
+    @Deprecated
+    public int getNSteps() {
+        return c1.length;
+    }
+
+    /** Build the P matrix.
+     * <p>The P matrix general terms are shifted (j+1) (-i)<sup>j</sup> terms
+     * with i being the row number starting from 1 and j being the column
+     * number starting from 1:
+     * <pre>
+     *        [  -2   3   -4    5  ... ]
+     *        [  -4  12  -32   80  ... ]
+     *   P =  [  -6  27 -108  405  ... ]
+     *        [  -8  48 -256 1280  ... ]
+     *        [          ...           ]
+     * </pre></p>
+     * @param rows number of rows of the matrix
+     * @return P matrix
+     */
+    private FieldMatrix<BigFraction> buildP(final int rows) {
+
+        final BigFraction[][] pData = new BigFraction[rows][rows];
+
+        for (int i = 1; i <= pData.length; ++i) {
+            // build the P matrix elements from Taylor series formulas
+            final BigFraction[] pI = pData[i - 1];
+            final int factor = -i;
+            int aj = factor;
+            for (int j = 1; j <= pI.length; ++j) {
+                pI[j - 1] = new BigFraction(aj * (j + 1));
+                aj *= factor;
+            }
+        }
+
+        return new Array2DRowFieldMatrix<BigFraction>(pData, false);
+
+    }
+
+    /** Initialize the high order scaled derivatives at step start.
+     * @param h step size to use for scaling
+     * @param t first steps times
+     * @param y first steps states
+     * @param yDot first steps derivatives
+     * @return Nordieck vector at start of first step (h<sup>2</sup>/2 y''<sub>n</sub>,
+     * h<sup>3</sup>/6 y'''<sub>n</sub> ... h<sup>k</sup>/k! y<sup>(k)</sup><sub>n</sub>)
+     */
+
+    public Array2DRowRealMatrix initializeHighOrderDerivatives(final double h, final double[] t,
+                                                               final double[][] y,
+                                                               final double[][] yDot) {
+
+        // using Taylor series with di = ti - t0, we get:
+        //  y(ti)  - y(t0)  - di y'(t0) =   di^2 / h^2 s2 + ... +   di^k     / h^k sk + O(h^k)
+        //  y'(ti) - y'(t0)             = 2 di   / h^2 s2 + ... + k di^(k-1) / h^k sk + O(h^(k-1))
+        // we write these relations for i = 1 to i= 1+n/2 as a set of n + 2 linear
+        // equations depending on the Nordsieck vector [s2 ... sk rk], so s2 to sk correspond
+        // to the appropriately truncated Taylor expansion, and rk is the Taylor remainder.
+        // The goal is to have s2 to sk as accurate as possible considering the fact the sum is
+        // truncated and we don't want the error terms to be included in s2 ... sk, so we need
+        // to solve also for the remainder
+        final double[][] a     = new double[c1.length + 1][c1.length + 1];
+        final double[][] b     = new double[c1.length + 1][y[0].length];
+        final double[]   y0    = y[0];
+        final double[]   yDot0 = yDot[0];
+        for (int i = 1; i < y.length; ++i) {
+
+            final double di    = t[i] - t[0];
+            final double ratio = di / h;
+            double dikM1Ohk    =  1 / h;
+
+            // linear coefficients of equations
+            // y(ti) - y(t0) - di y'(t0) and y'(ti) - y'(t0)
+            final double[] aI    = a[2 * i - 2];
+            final double[] aDotI = (2 * i - 1) < a.length ? a[2 * i - 1] : null;
+            for (int j = 0; j < aI.length; ++j) {
+                dikM1Ohk *= ratio;
+                aI[j]     = di      * dikM1Ohk;
+                if (aDotI != null) {
+                    aDotI[j]  = (j + 2) * dikM1Ohk;
+                }
+            }
+
+            // expected value of the previous equations
+            final double[] yI    = y[i];
+            final double[] yDotI = yDot[i];
+            final double[] bI    = b[2 * i - 2];
+            final double[] bDotI = (2 * i - 1) < b.length ? b[2 * i - 1] : null;
+            for (int j = 0; j < yI.length; ++j) {
+                bI[j]    = yI[j] - y0[j] - di * yDot0[j];
+                if (bDotI != null) {
+                    bDotI[j] = yDotI[j] - yDot0[j];
+                }
+            }
+
+        }
+
+        // solve the linear system to get the best estimate of the Nordsieck vector [s2 ... sk],
+        // with the additional terms s(k+1) and c grabbing the parts after the truncated Taylor expansion
+        final QRDecomposition decomposition = new QRDecomposition(new Array2DRowRealMatrix(a, false));
+        final RealMatrix x = decomposition.getSolver().solve(new Array2DRowRealMatrix(b, false));
+
+        // extract just the Nordsieck vector [s2 ... sk]
+        final Array2DRowRealMatrix truncatedX = new Array2DRowRealMatrix(x.getRowDimension() - 1, x.getColumnDimension());
+        for (int i = 0; i < truncatedX.getRowDimension(); ++i) {
+            for (int j = 0; j < truncatedX.getColumnDimension(); ++j) {
+                truncatedX.setEntry(i, j, x.getEntry(i, j));
+            }
+        }
+        return truncatedX;
+
+    }
+
+    /** Update the high order scaled derivatives for Adams integrators (phase 1).
+     * <p>The complete update of high order derivatives has a form similar to:
+     * <pre>
+     * r<sub>n+1</sub> = (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u + P<sup>-1</sup> A P r<sub>n</sub>
+     * </pre>
+     * this method computes the P<sup>-1</sup> A P r<sub>n</sub> part.</p>
+     * @param highOrder high order scaled derivatives
+     * (h<sup>2</sup>/2 y'', ... h<sup>k</sup>/k! y(k))
+     * @return updated high order derivatives
+     * @see #updateHighOrderDerivativesPhase2(double[], double[], Array2DRowRealMatrix)
+     */
+    public Array2DRowRealMatrix updateHighOrderDerivativesPhase1(final Array2DRowRealMatrix highOrder) {
+        return update.multiply(highOrder);
+    }
+
+    /** Update the high order scaled derivatives Adams integrators (phase 2).
+     * <p>The complete update of high order derivatives has a form similar to:
+     * <pre>
+     * r<sub>n+1</sub> = (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u + P<sup>-1</sup> A P r<sub>n</sub>
+     * </pre>
+     * this method computes the (s<sub>1</sub>(n) - s<sub>1</sub>(n+1)) P<sup>-1</sup> u part.</p>
+     * <p>Phase 1 of the update must already have been performed.</p>
+     * @param start first order scaled derivatives at step start
+     * @param end first order scaled derivatives at step end
+     * @param highOrder high order scaled derivatives, will be modified
+     * (h<sup>2</sup>/2 y'', ... h<sup>k</sup>/k! y(k))
+     * @see #updateHighOrderDerivativesPhase1(Array2DRowRealMatrix)
+     */
+    public void updateHighOrderDerivativesPhase2(final double[] start,
+                                                 final double[] end,
+                                                 final Array2DRowRealMatrix highOrder) {
+        final double[][] data = highOrder.getDataRef();
+        for (int i = 0; i < data.length; ++i) {
+            final double[] dataI = data[i];
+            final double c1I = c1[i];
+            for (int j = 0; j < dataI.length; ++j) {
+                dataI[j] += c1I * (start[j] - end[j]);
+            }
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/AdaptiveStepsizeFieldIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdaptiveStepsizeFieldIntegrator.java
new file mode 100644
index 0000000..c8e592b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdaptiveStepsizeFieldIntegrator.java
@@ -0,0 +1,366 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.ode.AbstractFieldIntegrator;
+import org.apache.commons.math3.ode.FieldEquationsMapper;
+import org.apache.commons.math3.ode.FieldODEState;
+import org.apache.commons.math3.ode.FieldODEStateAndDerivative;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * This abstract class holds the common part of all adaptive
+ * stepsize integrators for Ordinary Differential Equations.
+ *
+ * <p>These algorithms perform integration with stepsize control, which
+ * means the user does not specify the integration step but rather a
+ * tolerance on error. The error threshold is computed as
+ * <pre>
+ * threshold_i = absTol_i + relTol_i * max (abs (ym), abs (ym+1))
+ * </pre>
+ * where absTol_i is the absolute tolerance for component i of the
+ * state vector and relTol_i is the relative tolerance for the same
+ * component. The user can also use only two scalar values absTol and
+ * relTol which will be used for all components.
+ * </p>
+ * <p>
+ * Note that <em>only</em> the {@link FieldODEState#getState() main part}
+ * of the state vector is used for stepsize control. The {@link
+ * FieldODEState#getSecondaryState(int) secondary parts} of the state
+ * vector are explicitly ignored for stepsize control.
+ * </p>
+ *
+ * <p>If the estimated error for ym+1 is such that
+ * <pre>
+ * sqrt((sum (errEst_i / threshold_i)^2 ) / n) < 1
+ * </pre>
+ *
+ * (where n is the main set dimension) then the step is accepted,
+ * otherwise the step is rejected and a new attempt is made with a new
+ * stepsize.</p>
+ *
+ * @param <T> the type of the field elements
+ * @since 3.6
+ *
+ */
+
+public abstract class AdaptiveStepsizeFieldIntegrator<T extends RealFieldElement<T>>
+    extends AbstractFieldIntegrator<T> {
+
+    /** Allowed absolute scalar error. */
+    protected double scalAbsoluteTolerance;
+
+    /** Allowed relative scalar error. */
+    protected double scalRelativeTolerance;
+
+    /** Allowed absolute vectorial error. */
+    protected double[] vecAbsoluteTolerance;
+
+    /** Allowed relative vectorial error. */
+    protected double[] vecRelativeTolerance;
+
+    /** Main set dimension. */
+    protected int mainSetDimension;
+
+    /** User supplied initial step. */
+    private T initialStep;
+
+    /** Minimal step. */
+    private T minStep;
+
+    /** Maximal step. */
+    private T maxStep;
+
+    /** Build an integrator with the given stepsize bounds.
+     * The default step handler does nothing.
+     * @param field field to which the time and state vector elements belong
+     * @param name name of the method
+     * @param minStep minimal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param maxStep maximal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param scalAbsoluteTolerance allowed absolute error
+     * @param scalRelativeTolerance allowed relative error
+     */
+    public AdaptiveStepsizeFieldIntegrator(final Field<T> field, final String name,
+                                           final double minStep, final double maxStep,
+                                           final double scalAbsoluteTolerance,
+                                           final double scalRelativeTolerance) {
+
+        super(field, name);
+        setStepSizeControl(minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance);
+        resetInternalState();
+
+    }
+
+    /** Build an integrator with the given stepsize bounds.
+     * The default step handler does nothing.
+     * @param field field to which the time and state vector elements belong
+     * @param name name of the method
+     * @param minStep minimal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param maxStep maximal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param vecAbsoluteTolerance allowed absolute error
+     * @param vecRelativeTolerance allowed relative error
+     */
+    public AdaptiveStepsizeFieldIntegrator(final Field<T> field, final String name,
+                                           final double minStep, final double maxStep,
+                                           final double[] vecAbsoluteTolerance,
+                                           final double[] vecRelativeTolerance) {
+
+        super(field, name);
+        setStepSizeControl(minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance);
+        resetInternalState();
+
+    }
+
+    /** Set the adaptive step size control parameters.
+     * <p>
+     * A side effect of this method is to also reset the initial
+     * step so it will be automatically computed by the integrator
+     * if {@link #setInitialStepSize(RealFieldElement) setInitialStepSize}
+     * is not called by the user.
+     * </p>
+     * @param minimalStep minimal step (must be positive even for backward
+     * integration), the last step can be smaller than this
+     * @param maximalStep maximal step (must be positive even for backward
+     * integration)
+     * @param absoluteTolerance allowed absolute error
+     * @param relativeTolerance allowed relative error
+     */
+    public void setStepSizeControl(final double minimalStep, final double maximalStep,
+                                   final double absoluteTolerance,
+                                   final double relativeTolerance) {
+
+        minStep     = getField().getZero().add(FastMath.abs(minimalStep));
+        maxStep     = getField().getZero().add(FastMath.abs(maximalStep));
+        initialStep = getField().getOne().negate();
+
+        scalAbsoluteTolerance = absoluteTolerance;
+        scalRelativeTolerance = relativeTolerance;
+        vecAbsoluteTolerance  = null;
+        vecRelativeTolerance  = null;
+
+    }
+
+    /** Set the adaptive step size control parameters.
+     * <p>
+     * A side effect of this method is to also reset the initial
+     * step so it will be automatically computed by the integrator
+     * if {@link #setInitialStepSize(RealFieldElement) setInitialStepSize}
+     * is not called by the user.
+     * </p>
+     * @param minimalStep minimal step (must be positive even for backward
+     * integration), the last step can be smaller than this
+     * @param maximalStep maximal step (must be positive even for backward
+     * integration)
+     * @param absoluteTolerance allowed absolute error
+     * @param relativeTolerance allowed relative error
+     */
+    public void setStepSizeControl(final double minimalStep, final double maximalStep,
+                                   final double[] absoluteTolerance,
+                                   final double[] relativeTolerance) {
+
+        minStep     = getField().getZero().add(FastMath.abs(minimalStep));
+        maxStep     = getField().getZero().add(FastMath.abs(maximalStep));
+        initialStep = getField().getOne().negate();
+
+        scalAbsoluteTolerance = 0;
+        scalRelativeTolerance = 0;
+        vecAbsoluteTolerance  = absoluteTolerance.clone();
+        vecRelativeTolerance  = relativeTolerance.clone();
+
+    }
+
+    /** Set the initial step size.
+     * <p>This method allows the user to specify an initial positive
+     * step size instead of letting the integrator guess it by
+     * itself. If this method is not called before integration is
+     * started, the initial step size will be estimated by the
+     * integrator.</p>
+     * @param initialStepSize initial step size to use (must be positive even
+     * for backward integration ; providing a negative value or a value
+     * outside of the min/max step interval will lead the integrator to
+     * ignore the value and compute the initial step size by itself)
+     */
+    public void setInitialStepSize(final T initialStepSize) {
+        if (initialStepSize.subtract(minStep).getReal() < 0 ||
+            initialStepSize.subtract(maxStep).getReal() > 0) {
+            initialStep = getField().getOne().negate();
+        } else {
+            initialStep = initialStepSize;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void sanityChecks(final FieldODEState<T> eqn, final T t)
+        throws DimensionMismatchException, NumberIsTooSmallException {
+
+        super.sanityChecks(eqn, t);
+
+        mainSetDimension = eqn.getStateDimension();
+
+        if (vecAbsoluteTolerance != null && vecAbsoluteTolerance.length != mainSetDimension) {
+            throw new DimensionMismatchException(mainSetDimension, vecAbsoluteTolerance.length);
+        }
+
+        if (vecRelativeTolerance != null && vecRelativeTolerance.length != mainSetDimension) {
+            throw new DimensionMismatchException(mainSetDimension, vecRelativeTolerance.length);
+        }
+
+    }
+
+    /** Initialize the integration step.
+     * @param forward forward integration indicator
+     * @param order order of the method
+     * @param scale scaling vector for the state vector (can be shorter than state vector)
+     * @param state0 state at integration start time
+     * @param mapper mapper for all the equations
+     * @return first integration step
+     * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+     * @exception DimensionMismatchException if arrays dimensions do not match equations settings
+     */
+    public T initializeStep(final boolean forward, final int order, final T[] scale,
+                            final FieldODEStateAndDerivative<T> state0,
+                            final FieldEquationsMapper<T> mapper)
+        throws MaxCountExceededException, DimensionMismatchException {
+
+        if (initialStep.getReal() > 0) {
+            // use the user provided value
+            return forward ? initialStep : initialStep.negate();
+        }
+
+        // very rough first guess : h = 0.01 * ||y/scale|| / ||y'/scale||
+        // this guess will be used to perform an Euler step
+        final T[] y0    = mapper.mapState(state0);
+        final T[] yDot0 = mapper.mapDerivative(state0);
+        T yOnScale2    = getField().getZero();
+        T yDotOnScale2 = getField().getZero();
+        for (int j = 0; j < scale.length; ++j) {
+            final T ratio    = y0[j].divide(scale[j]);
+            yOnScale2        = yOnScale2.add(ratio.multiply(ratio));
+            final T ratioDot = yDot0[j].divide(scale[j]);
+            yDotOnScale2     = yDotOnScale2.add(ratioDot.multiply(ratioDot));
+        }
+
+        T h = (yOnScale2.getReal() < 1.0e-10 || yDotOnScale2.getReal() < 1.0e-10) ?
+              getField().getZero().add(1.0e-6) :
+              yOnScale2.divide(yDotOnScale2).sqrt().multiply(0.01);
+        if (! forward) {
+            h = h.negate();
+        }
+
+        // perform an Euler step using the preceding rough guess
+        final T[] y1 = MathArrays.buildArray(getField(), y0.length);
+        for (int j = 0; j < y0.length; ++j) {
+            y1[j] = y0[j].add(yDot0[j].multiply(h));
+        }
+        final T[] yDot1 = computeDerivatives(state0.getTime().add(h), y1);
+
+        // estimate the second derivative of the solution
+        T yDDotOnScale = getField().getZero();
+        for (int j = 0; j < scale.length; ++j) {
+            final T ratioDotDot = yDot1[j].subtract(yDot0[j]).divide(scale[j]);
+            yDDotOnScale = yDDotOnScale.add(ratioDotDot.multiply(ratioDotDot));
+        }
+        yDDotOnScale = yDDotOnScale.sqrt().divide(h);
+
+        // step size is computed such that
+        // h^order * max (||y'/tol||, ||y''/tol||) = 0.01
+        final T maxInv2 = MathUtils.max(yDotOnScale2.sqrt(), yDDotOnScale);
+        final T h1 = maxInv2.getReal() < 1.0e-15 ?
+                     MathUtils.max(getField().getZero().add(1.0e-6), h.abs().multiply(0.001)) :
+                     maxInv2.multiply(100).reciprocal().pow(1.0 / order);
+        h = MathUtils.min(h.abs().multiply(100), h1);
+        h = MathUtils.max(h, state0.getTime().abs().multiply(1.0e-12));  // avoids cancellation when computing t1 - t0
+        h = MathUtils.max(minStep, MathUtils.min(maxStep, h));
+        if (! forward) {
+            h = h.negate();
+        }
+
+        return h;
+
+    }
+
+    /** Filter the integration step.
+     * @param h signed step
+     * @param forward forward integration indicator
+     * @param acceptSmall if true, steps smaller than the minimal value
+     * are silently increased up to this value, if false such small
+     * steps generate an exception
+     * @return a bounded integration step (h if no bound is reach, or a bounded value)
+     * @exception NumberIsTooSmallException if the step is too small and acceptSmall is false
+     */
+    protected T filterStep(final T h, final boolean forward, final boolean acceptSmall)
+        throws NumberIsTooSmallException {
+
+        T filteredH = h;
+        if (h.abs().subtract(minStep).getReal() < 0) {
+            if (acceptSmall) {
+                filteredH = forward ? minStep : minStep.negate();
+            } else {
+                throw new NumberIsTooSmallException(LocalizedFormats.MINIMAL_STEPSIZE_REACHED_DURING_INTEGRATION,
+                                                    h.abs().getReal(), minStep.getReal(), true);
+            }
+        }
+
+        if (filteredH.subtract(maxStep).getReal() > 0) {
+            filteredH = maxStep;
+        } else if (filteredH.add(maxStep).getReal() < 0) {
+            filteredH = maxStep.negate();
+        }
+
+        return filteredH;
+
+    }
+
+    /** Reset internal state to dummy values. */
+    protected void resetInternalState() {
+        setStepStart(null);
+        setStepSize(minStep.multiply(maxStep).sqrt());
+    }
+
+    /** Get the minimal step.
+     * @return minimal step
+     */
+    public T getMinStep() {
+        return minStep;
+    }
+
+    /** Get the maximal step.
+     * @return maximal step
+     */
+    public T getMaxStep() {
+        return maxStep;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/AdaptiveStepsizeIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdaptiveStepsizeIntegrator.java
new file mode 100644
index 0000000..ab79bbf
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/AdaptiveStepsizeIntegrator.java
@@ -0,0 +1,375 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NoBracketingException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.ode.AbstractIntegrator;
+import org.apache.commons.math3.ode.ExpandableStatefulODE;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * This abstract class holds the common part of all adaptive
+ * stepsize integrators for Ordinary Differential Equations.
+ *
+ * <p>These algorithms perform integration with stepsize control, which
+ * means the user does not specify the integration step but rather a
+ * tolerance on error. The error threshold is computed as
+ * <pre>
+ * threshold_i = absTol_i + relTol_i * max (abs (ym), abs (ym+1))
+ * </pre>
+ * where absTol_i is the absolute tolerance for component i of the
+ * state vector and relTol_i is the relative tolerance for the same
+ * component. The user can also use only two scalar values absTol and
+ * relTol which will be used for all components.
+ * </p>
+ * <p>
+ * If the Ordinary Differential Equations is an {@link ExpandableStatefulODE
+ * extended ODE} rather than a {@link
+ * org.apache.commons.math3.ode.FirstOrderDifferentialEquations basic ODE}, then
+ * <em>only</em> the {@link ExpandableStatefulODE#getPrimaryState() primary part}
+ * of the state vector is used for stepsize control, not the complete state vector.
+ * </p>
+ *
+ * <p>If the estimated error for ym+1 is such that
+ * <pre>
+ * sqrt((sum (errEst_i / threshold_i)^2 ) / n) < 1
+ * </pre>
+ *
+ * (where n is the main set dimension) then the step is accepted,
+ * otherwise the step is rejected and a new attempt is made with a new
+ * stepsize.</p>
+ *
+ * @since 1.2
+ *
+ */
+
+public abstract class AdaptiveStepsizeIntegrator
+  extends AbstractIntegrator {
+
+    /** Allowed absolute scalar error. */
+    protected double scalAbsoluteTolerance;
+
+    /** Allowed relative scalar error. */
+    protected double scalRelativeTolerance;
+
+    /** Allowed absolute vectorial error. */
+    protected double[] vecAbsoluteTolerance;
+
+    /** Allowed relative vectorial error. */
+    protected double[] vecRelativeTolerance;
+
+    /** Main set dimension. */
+    protected int mainSetDimension;
+
+    /** User supplied initial step. */
+    private double initialStep;
+
+    /** Minimal step. */
+    private double minStep;
+
+    /** Maximal step. */
+    private double maxStep;
+
+  /** Build an integrator with the given stepsize bounds.
+   * The default step handler does nothing.
+   * @param name name of the method
+   * @param minStep minimal step (sign is irrelevant, regardless of
+   * integration direction, forward or backward), the last step can
+   * be smaller than this
+   * @param maxStep maximal step (sign is irrelevant, regardless of
+   * integration direction, forward or backward), the last step can
+   * be smaller than this
+   * @param scalAbsoluteTolerance allowed absolute error
+   * @param scalRelativeTolerance allowed relative error
+   */
+  public AdaptiveStepsizeIntegrator(final String name,
+                                    final double minStep, final double maxStep,
+                                    final double scalAbsoluteTolerance,
+                                    final double scalRelativeTolerance) {
+
+    super(name);
+    setStepSizeControl(minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance);
+    resetInternalState();
+
+  }
+
+  /** Build an integrator with the given stepsize bounds.
+   * The default step handler does nothing.
+   * @param name name of the method
+   * @param minStep minimal step (sign is irrelevant, regardless of
+   * integration direction, forward or backward), the last step can
+   * be smaller than this
+   * @param maxStep maximal step (sign is irrelevant, regardless of
+   * integration direction, forward or backward), the last step can
+   * be smaller than this
+   * @param vecAbsoluteTolerance allowed absolute error
+   * @param vecRelativeTolerance allowed relative error
+   */
+  public AdaptiveStepsizeIntegrator(final String name,
+                                    final double minStep, final double maxStep,
+                                    final double[] vecAbsoluteTolerance,
+                                    final double[] vecRelativeTolerance) {
+
+    super(name);
+    setStepSizeControl(minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance);
+    resetInternalState();
+
+  }
+
+  /** Set the adaptive step size control parameters.
+   * <p>
+   * A side effect of this method is to also reset the initial
+   * step so it will be automatically computed by the integrator
+   * if {@link #setInitialStepSize(double) setInitialStepSize}
+   * is not called by the user.
+   * </p>
+   * @param minimalStep minimal step (must be positive even for backward
+   * integration), the last step can be smaller than this
+   * @param maximalStep maximal step (must be positive even for backward
+   * integration)
+   * @param absoluteTolerance allowed absolute error
+   * @param relativeTolerance allowed relative error
+   */
+  public void setStepSizeControl(final double minimalStep, final double maximalStep,
+                                 final double absoluteTolerance,
+                                 final double relativeTolerance) {
+
+      minStep     = FastMath.abs(minimalStep);
+      maxStep     = FastMath.abs(maximalStep);
+      initialStep = -1;
+
+      scalAbsoluteTolerance = absoluteTolerance;
+      scalRelativeTolerance = relativeTolerance;
+      vecAbsoluteTolerance  = null;
+      vecRelativeTolerance  = null;
+
+  }
+
+  /** Set the adaptive step size control parameters.
+   * <p>
+   * A side effect of this method is to also reset the initial
+   * step so it will be automatically computed by the integrator
+   * if {@link #setInitialStepSize(double) setInitialStepSize}
+   * is not called by the user.
+   * </p>
+   * @param minimalStep minimal step (must be positive even for backward
+   * integration), the last step can be smaller than this
+   * @param maximalStep maximal step (must be positive even for backward
+   * integration)
+   * @param absoluteTolerance allowed absolute error
+   * @param relativeTolerance allowed relative error
+   */
+  public void setStepSizeControl(final double minimalStep, final double maximalStep,
+                                 final double[] absoluteTolerance,
+                                 final double[] relativeTolerance) {
+
+      minStep     = FastMath.abs(minimalStep);
+      maxStep     = FastMath.abs(maximalStep);
+      initialStep = -1;
+
+      scalAbsoluteTolerance = 0;
+      scalRelativeTolerance = 0;
+      vecAbsoluteTolerance  = absoluteTolerance.clone();
+      vecRelativeTolerance  = relativeTolerance.clone();
+
+  }
+
+  /** Set the initial step size.
+   * <p>This method allows the user to specify an initial positive
+   * step size instead of letting the integrator guess it by
+   * itself. If this method is not called before integration is
+   * started, the initial step size will be estimated by the
+   * integrator.</p>
+   * @param initialStepSize initial step size to use (must be positive even
+   * for backward integration ; providing a negative value or a value
+   * outside of the min/max step interval will lead the integrator to
+   * ignore the value and compute the initial step size by itself)
+   */
+  public void setInitialStepSize(final double initialStepSize) {
+    if ((initialStepSize < minStep) || (initialStepSize > maxStep)) {
+      initialStep = -1.0;
+    } else {
+      initialStep = initialStepSize;
+    }
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  protected void sanityChecks(final ExpandableStatefulODE equations, final double t)
+      throws DimensionMismatchException, NumberIsTooSmallException {
+
+      super.sanityChecks(equations, t);
+
+      mainSetDimension = equations.getPrimaryMapper().getDimension();
+
+      if ((vecAbsoluteTolerance != null) && (vecAbsoluteTolerance.length != mainSetDimension)) {
+          throw new DimensionMismatchException(mainSetDimension, vecAbsoluteTolerance.length);
+      }
+
+      if ((vecRelativeTolerance != null) && (vecRelativeTolerance.length != mainSetDimension)) {
+          throw new DimensionMismatchException(mainSetDimension, vecRelativeTolerance.length);
+      }
+
+  }
+
+  /** Initialize the integration step.
+   * @param forward forward integration indicator
+   * @param order order of the method
+   * @param scale scaling vector for the state vector (can be shorter than state vector)
+   * @param t0 start time
+   * @param y0 state vector at t0
+   * @param yDot0 first time derivative of y0
+   * @param y1 work array for a state vector
+   * @param yDot1 work array for the first time derivative of y1
+   * @return first integration step
+   * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+   * @exception DimensionMismatchException if arrays dimensions do not match equations settings
+   */
+  public double initializeStep(final boolean forward, final int order, final double[] scale,
+                               final double t0, final double[] y0, final double[] yDot0,
+                               final double[] y1, final double[] yDot1)
+      throws MaxCountExceededException, DimensionMismatchException {
+
+    if (initialStep > 0) {
+      // use the user provided value
+      return forward ? initialStep : -initialStep;
+    }
+
+    // very rough first guess : h = 0.01 * ||y/scale|| / ||y'/scale||
+    // this guess will be used to perform an Euler step
+    double ratio;
+    double yOnScale2 = 0;
+    double yDotOnScale2 = 0;
+    for (int j = 0; j < scale.length; ++j) {
+      ratio         = y0[j] / scale[j];
+      yOnScale2    += ratio * ratio;
+      ratio         = yDot0[j] / scale[j];
+      yDotOnScale2 += ratio * ratio;
+    }
+
+    double h = ((yOnScale2 < 1.0e-10) || (yDotOnScale2 < 1.0e-10)) ?
+               1.0e-6 : (0.01 * FastMath.sqrt(yOnScale2 / yDotOnScale2));
+    if (! forward) {
+      h = -h;
+    }
+
+    // perform an Euler step using the preceding rough guess
+    for (int j = 0; j < y0.length; ++j) {
+      y1[j] = y0[j] + h * yDot0[j];
+    }
+    computeDerivatives(t0 + h, y1, yDot1);
+
+    // estimate the second derivative of the solution
+    double yDDotOnScale = 0;
+    for (int j = 0; j < scale.length; ++j) {
+      ratio         = (yDot1[j] - yDot0[j]) / scale[j];
+      yDDotOnScale += ratio * ratio;
+    }
+    yDDotOnScale = FastMath.sqrt(yDDotOnScale) / h;
+
+    // step size is computed such that
+    // h^order * max (||y'/tol||, ||y''/tol||) = 0.01
+    final double maxInv2 = FastMath.max(FastMath.sqrt(yDotOnScale2), yDDotOnScale);
+    final double h1 = (maxInv2 < 1.0e-15) ?
+                      FastMath.max(1.0e-6, 0.001 * FastMath.abs(h)) :
+                      FastMath.pow(0.01 / maxInv2, 1.0 / order);
+    h = FastMath.min(100.0 * FastMath.abs(h), h1);
+    h = FastMath.max(h, 1.0e-12 * FastMath.abs(t0));  // avoids cancellation when computing t1 - t0
+    if (h < getMinStep()) {
+      h = getMinStep();
+    }
+    if (h > getMaxStep()) {
+      h = getMaxStep();
+    }
+    if (! forward) {
+      h = -h;
+    }
+
+    return h;
+
+  }
+
+  /** Filter the integration step.
+   * @param h signed step
+   * @param forward forward integration indicator
+   * @param acceptSmall if true, steps smaller than the minimal value
+   * are silently increased up to this value, if false such small
+   * steps generate an exception
+   * @return a bounded integration step (h if no bound is reach, or a bounded value)
+   * @exception NumberIsTooSmallException if the step is too small and acceptSmall is false
+   */
+  protected double filterStep(final double h, final boolean forward, final boolean acceptSmall)
+    throws NumberIsTooSmallException {
+
+      double filteredH = h;
+      if (FastMath.abs(h) < minStep) {
+          if (acceptSmall) {
+              filteredH = forward ? minStep : -minStep;
+          } else {
+              throw new NumberIsTooSmallException(LocalizedFormats.MINIMAL_STEPSIZE_REACHED_DURING_INTEGRATION,
+                                                  FastMath.abs(h), minStep, true);
+          }
+      }
+
+      if (filteredH > maxStep) {
+          filteredH = maxStep;
+      } else if (filteredH < -maxStep) {
+          filteredH = -maxStep;
+      }
+
+      return filteredH;
+
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public abstract void integrate (ExpandableStatefulODE equations, double t)
+      throws NumberIsTooSmallException, DimensionMismatchException,
+             MaxCountExceededException, NoBracketingException;
+
+  /** {@inheritDoc} */
+  @Override
+  public double getCurrentStepStart() {
+    return stepStart;
+  }
+
+  /** Reset internal state to dummy values. */
+  protected void resetInternalState() {
+    stepStart = Double.NaN;
+    stepSize  = FastMath.sqrt(minStep * maxStep);
+  }
+
+  /** Get the minimal step.
+   * @return minimal step
+   */
+  public double getMinStep() {
+    return minStep;
+  }
+
+  /** Get the maximal step.
+   * @return maximal step
+   */
+  public double getMaxStep() {
+    return maxStep;
+  }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaFieldIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaFieldIntegrator.java
new file mode 100644
index 0000000..81e6d69
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaFieldIntegrator.java
@@ -0,0 +1,111 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.ode.FieldEquationsMapper;
+import org.apache.commons.math3.ode.FieldODEStateAndDerivative;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * This class implements the classical fourth order Runge-Kutta
+ * integrator for Ordinary Differential Equations (it is the most
+ * often used Runge-Kutta method).
+ *
+ * <p>This method is an explicit Runge-Kutta method, its Butcher-array
+ * is the following one :
+ * <pre>
+ *    0  |  0    0    0    0
+ *   1/2 | 1/2   0    0    0
+ *   1/2 |  0   1/2   0    0
+ *    1  |  0    0    1    0
+ *       |--------------------
+ *       | 1/6  1/3  1/3  1/6
+ * </pre>
+ * </p>
+ *
+ * @see EulerFieldIntegrator
+ * @see GillFieldIntegrator
+ * @see MidpointFieldIntegrator
+ * @see ThreeEighthesFieldIntegrator
+ * @see LutherFieldIntegrator
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+
+public class ClassicalRungeKuttaFieldIntegrator<T extends RealFieldElement<T>>
+    extends RungeKuttaFieldIntegrator<T> {
+
+    /** Simple constructor.
+     * Build a fourth-order Runge-Kutta integrator with the given step.
+     * @param field field to which the time and state vector elements belong
+     * @param step integration step
+     */
+    public ClassicalRungeKuttaFieldIntegrator(final Field<T> field, final T step) {
+        super(field, "classical Runge-Kutta", step);
+    }
+
+    /** {@inheritDoc} */
+    public T[] getC() {
+        final T[] c = MathArrays.buildArray(getField(), 3);
+        c[0] = getField().getOne().multiply(0.5);
+        c[1] = c[0];
+        c[2] = getField().getOne();
+        return c;
+    }
+
+    /** {@inheritDoc} */
+    public T[][] getA() {
+        final T[][] a = MathArrays.buildArray(getField(), 3, -1);
+        for (int i = 0; i < a.length; ++i) {
+            a[i] = MathArrays.buildArray(getField(), i + 1);
+        }
+        a[0][0] = fraction(1, 2);
+        a[1][0] = getField().getZero();
+        a[1][1] = a[0][0];
+        a[2][0] = getField().getZero();
+        a[2][1] = getField().getZero();
+        a[2][2] = getField().getOne();
+        return a;
+    }
+
+    /** {@inheritDoc} */
+    public T[] getB() {
+        final T[] b = MathArrays.buildArray(getField(), 4);
+        b[0] = fraction(1, 6);
+        b[1] = fraction(1, 3);
+        b[2] = b[1];
+        b[3] = b[0];
+        return b;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected ClassicalRungeKuttaFieldStepInterpolator<T>
+        createInterpolator(final boolean forward, T[][] yDotK,
+                           final FieldODEStateAndDerivative<T> globalPreviousState,
+                           final FieldODEStateAndDerivative<T> globalCurrentState,
+                           final FieldEquationsMapper<T> mapper) {
+        return new ClassicalRungeKuttaFieldStepInterpolator<T>(getField(), forward, yDotK,
+                                                               globalPreviousState, globalCurrentState,
+                                                               globalPreviousState, globalCurrentState,
+                                                               mapper);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaFieldStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaFieldStepInterpolator.java
new file mode 100644
index 0000000..4ad8f4e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaFieldStepInterpolator.java
@@ -0,0 +1,136 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.ode.FieldEquationsMapper;
+import org.apache.commons.math3.ode.FieldODEStateAndDerivative;
+
+/**
+ * This class implements a step interpolator for the classical fourth
+ * order Runge-Kutta integrator.
+ *
+ * <p>This interpolator allows to compute dense output inside the last
+ * step computed. The interpolation equation is consistent with the
+ * integration scheme :
+ * <ul>
+ *   <li>Using reference point at step start:<br>
+ *   y(t<sub>n</sub> + &theta; h) = y (t<sub>n</sub>)
+ *                    + &theta; (h/6) [  (6 - 9 &theta; + 4 &theta;<sup>2</sup>) y'<sub>1</sub>
+ *                                     + (    6 &theta; - 4 &theta;<sup>2</sup>) (y'<sub>2</sub> + y'<sub>3</sub>)
+ *                                     + (   -3 &theta; + 4 &theta;<sup>2</sup>) y'<sub>4</sub>
+ *                                    ]
+ *   </li>
+ *   <li>Using reference point at step end:<br>
+ *   y(t<sub>n</sub> + &theta; h) = y (t<sub>n</sub> + h)
+ *                    + (1 - &theta;) (h/6) [ (-4 &theta;^2 + 5 &theta; - 1) y'<sub>1</sub>
+ *                                          +(4 &theta;^2 - 2 &theta; - 2) (y'<sub>2</sub> + y'<sub>3</sub>)
+ *                                          -(4 &theta;^2 +   &theta; + 1) y'<sub>4</sub>
+ *                                        ]
+ *   </li>
+ * </ul>
+ * </p>
+ *
+ * where &theta; belongs to [0 ; 1] and where y'<sub>1</sub> to y'<sub>4</sub> are the four
+ * evaluations of the derivatives already computed during the
+ * step.</p>
+ *
+ * @see ClassicalRungeKuttaFieldIntegrator
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+
+class ClassicalRungeKuttaFieldStepInterpolator<T extends RealFieldElement<T>>
+    extends RungeKuttaFieldStepInterpolator<T> {
+
+    /** Simple constructor.
+     * @param field field to which the time and state vector elements belong
+     * @param forward integration direction indicator
+     * @param yDotK slopes at the intermediate points
+     * @param globalPreviousState start of the global step
+     * @param globalCurrentState end of the global step
+     * @param softPreviousState start of the restricted step
+     * @param softCurrentState end of the restricted step
+     * @param mapper equations mapper for the all equations
+     */
+    ClassicalRungeKuttaFieldStepInterpolator(final Field<T> field, final boolean forward,
+                                             final T[][] yDotK,
+                                             final FieldODEStateAndDerivative<T> globalPreviousState,
+                                             final FieldODEStateAndDerivative<T> globalCurrentState,
+                                             final FieldODEStateAndDerivative<T> softPreviousState,
+                                             final FieldODEStateAndDerivative<T> softCurrentState,
+                                             final FieldEquationsMapper<T> mapper) {
+        super(field, forward, yDotK,
+              globalPreviousState, globalCurrentState, softPreviousState, softCurrentState,
+              mapper);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected ClassicalRungeKuttaFieldStepInterpolator<T> create(final Field<T> newField, final boolean newForward, final T[][] newYDotK,
+                                                                 final FieldODEStateAndDerivative<T> newGlobalPreviousState,
+                                                                 final FieldODEStateAndDerivative<T> newGlobalCurrentState,
+                                                                 final FieldODEStateAndDerivative<T> newSoftPreviousState,
+                                                                 final FieldODEStateAndDerivative<T> newSoftCurrentState,
+                                                                 final FieldEquationsMapper<T> newMapper) {
+        return new ClassicalRungeKuttaFieldStepInterpolator<T>(newField, newForward, newYDotK,
+                                                               newGlobalPreviousState, newGlobalCurrentState,
+                                                               newSoftPreviousState, newSoftCurrentState,
+                                                               newMapper);
+    }
+
+    /** {@inheritDoc} */
+    @SuppressWarnings("unchecked")
+    @Override
+    protected FieldODEStateAndDerivative<T> computeInterpolatedStateAndDerivatives(final FieldEquationsMapper<T> mapper,
+                                                                                   final T time, final T theta,
+                                                                                   final T thetaH, final T oneMinusThetaH) {
+
+        final T one                       = time.getField().getOne();
+        final T oneMinusTheta             = one.subtract(theta);
+        final T oneMinus2Theta            = one.subtract(theta.multiply(2));
+        final T coeffDot1                 = oneMinusTheta.multiply(oneMinus2Theta);
+        final T coeffDot23                = theta.multiply(oneMinusTheta).multiply(2);
+        final T coeffDot4                 = theta.multiply(oneMinus2Theta).negate();
+        final T[] interpolatedState;
+        final T[] interpolatedDerivatives;
+
+        if (getGlobalPreviousState() != null && theta.getReal() <= 0.5) {
+            final T fourTheta2      = theta.multiply(theta).multiply(4);
+            final T s               = thetaH.divide(6.0);
+            final T coeff1          = s.multiply(fourTheta2.subtract(theta.multiply(9)).add(6));
+            final T coeff23         = s.multiply(theta.multiply(6).subtract(fourTheta2));
+            final T coeff4          = s.multiply(fourTheta2.subtract(theta.multiply(3)));
+            interpolatedState       = previousStateLinearCombination(coeff1, coeff23, coeff23, coeff4);
+            interpolatedDerivatives = derivativeLinearCombination(coeffDot1, coeffDot23, coeffDot23, coeffDot4);
+        } else {
+            final T fourTheta       = theta.multiply(4);
+            final T s               = oneMinusThetaH.divide(6);
+            final T coeff1          = s.multiply(theta.multiply(fourTheta.negate().add(5)).subtract(1));
+            final T coeff23         = s.multiply(theta.multiply(fourTheta.subtract(2)).subtract(2));
+            final T coeff4          = s.multiply(theta.multiply(fourTheta.negate().subtract(1)).subtract(1));
+            interpolatedState       = currentStateLinearCombination(coeff1, coeff23, coeff23, coeff4);
+            interpolatedDerivatives = derivativeLinearCombination(coeffDot1, coeffDot23, coeffDot23, coeffDot4);
+        }
+
+        return new FieldODEStateAndDerivative<T>(time, interpolatedState, interpolatedDerivatives);
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaIntegrator.java
new file mode 100644
index 0000000..ca915f1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaIntegrator.java
@@ -0,0 +1,75 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+
+/**
+ * This class implements the classical fourth order Runge-Kutta
+ * integrator for Ordinary Differential Equations (it is the most
+ * often used Runge-Kutta method).
+ *
+ * <p>This method is an explicit Runge-Kutta method, its Butcher-array
+ * is the following one :
+ * <pre>
+ *    0  |  0    0    0    0
+ *   1/2 | 1/2   0    0    0
+ *   1/2 |  0   1/2   0    0
+ *    1  |  0    0    1    0
+ *       |--------------------
+ *       | 1/6  1/3  1/3  1/6
+ * </pre>
+ * </p>
+ *
+ * @see EulerIntegrator
+ * @see GillIntegrator
+ * @see MidpointIntegrator
+ * @see ThreeEighthesIntegrator
+ * @see LutherIntegrator
+ * @since 1.2
+ */
+
+public class ClassicalRungeKuttaIntegrator extends RungeKuttaIntegrator {
+
+  /** Time steps Butcher array. */
+  private static final double[] STATIC_C = {
+    1.0 / 2.0, 1.0 / 2.0, 1.0
+  };
+
+  /** Internal weights Butcher array. */
+  private static final double[][] STATIC_A = {
+    { 1.0 / 2.0 },
+    { 0.0, 1.0 / 2.0 },
+    { 0.0, 0.0, 1.0 }
+  };
+
+  /** Propagation weights Butcher array. */
+  private static final double[] STATIC_B = {
+    1.0 / 6.0, 1.0 / 3.0, 1.0 / 3.0, 1.0 / 6.0
+  };
+
+  /** Simple constructor.
+   * Build a fourth-order Runge-Kutta integrator with the given
+   * step.
+   * @param step integration step
+   */
+  public ClassicalRungeKuttaIntegrator(final double step) {
+    super("classical Runge-Kutta", STATIC_C, STATIC_A, STATIC_B,
+          new ClassicalRungeKuttaStepInterpolator(), step);
+  }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaStepInterpolator.java
new file mode 100644
index 0000000..296f5b5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/ClassicalRungeKuttaStepInterpolator.java
@@ -0,0 +1,136 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.ode.sampling.StepInterpolator;
+
+/**
+ * This class implements a step interpolator for the classical fourth
+ * order Runge-Kutta integrator.
+ *
+ * <p>This interpolator allows to compute dense output inside the last
+ * step computed. The interpolation equation is consistent with the
+ * integration scheme :
+ * <ul>
+ *   <li>Using reference point at step start:<br>
+ *   y(t<sub>n</sub> + &theta; h) = y (t<sub>n</sub>)
+ *                    + &theta; (h/6) [  (6 - 9 &theta; + 4 &theta;<sup>2</sup>) y'<sub>1</sub>
+ *                                     + (    6 &theta; - 4 &theta;<sup>2</sup>) (y'<sub>2</sub> + y'<sub>3</sub>)
+ *                                     + (   -3 &theta; + 4 &theta;<sup>2</sup>) y'<sub>4</sub>
+ *                                    ]
+ *   </li>
+ *   <li>Using reference point at step end:<br>
+ *   y(t<sub>n</sub> + &theta; h) = y (t<sub>n</sub> + h)
+ *                    + (1 - &theta;) (h/6) [ (-4 &theta;^2 + 5 &theta; - 1) y'<sub>1</sub>
+ *                                          +(4 &theta;^2 - 2 &theta; - 2) (y'<sub>2</sub> + y'<sub>3</sub>)
+ *                                          -(4 &theta;^2 +   &theta; + 1) y'<sub>4</sub>
+ *                                        ]
+ *   </li>
+ * </ul>
+ * </p>
+ *
+ * where &theta; belongs to [0 ; 1] and where y'<sub>1</sub> to y'<sub>4</sub> are the four
+ * evaluations of the derivatives already computed during the
+ * step.</p>
+ *
+ * @see ClassicalRungeKuttaIntegrator
+ * @since 1.2
+ */
+
+class ClassicalRungeKuttaStepInterpolator
+    extends RungeKuttaStepInterpolator {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 20111120L;
+
+    /** Simple constructor.
+     * This constructor builds an instance that is not usable yet, the
+     * {@link RungeKuttaStepInterpolator#reinitialize} method should be
+     * called before using the instance in order to initialize the
+     * internal arrays. This constructor is used only in order to delay
+     * the initialization in some cases. The {@link RungeKuttaIntegrator}
+     * class uses the prototyping design pattern to create the step
+     * interpolators by cloning an uninitialized model and latter initializing
+     * the copy.
+     */
+    // CHECKSTYLE: stop RedundantModifier
+    // the public modifier here is needed for serialization
+    public ClassicalRungeKuttaStepInterpolator() {
+    }
+    // CHECKSTYLE: resume RedundantModifier
+
+    /** Copy constructor.
+     * @param interpolator interpolator to copy from. The copy is a deep
+     * copy: its arrays are separated from the original arrays of the
+     * instance
+     */
+    ClassicalRungeKuttaStepInterpolator(final ClassicalRungeKuttaStepInterpolator interpolator) {
+        super(interpolator);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected StepInterpolator doCopy() {
+        return new ClassicalRungeKuttaStepInterpolator(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void computeInterpolatedStateAndDerivatives(final double theta,
+                                            final double oneMinusThetaH) {
+
+        final double oneMinusTheta  = 1 - theta;
+        final double oneMinus2Theta = 1 - 2 * theta;
+        final double coeffDot1     = oneMinusTheta * oneMinus2Theta;
+        final double coeffDot23    = 2 * theta * oneMinusTheta;
+        final double coeffDot4     = -theta * oneMinus2Theta;
+        if ((previousState != null) && (theta <= 0.5)) {
+            final double fourTheta2     = 4 * theta * theta;
+            final double s             = theta * h / 6.0;
+            final double coeff1        = s * ( 6 - 9 * theta + fourTheta2);
+            final double coeff23       = s * ( 6 * theta - fourTheta2);
+            final double coeff4        = s * (-3 * theta + fourTheta2);
+            for (int i = 0; i < interpolatedState.length; ++i) {
+                final double yDot1  = yDotK[0][i];
+                final double yDot23 = yDotK[1][i] + yDotK[2][i];
+                final double yDot4  = yDotK[3][i];
+                interpolatedState[i] =
+                        previousState[i] + coeff1  * yDot1 + coeff23 * yDot23 + coeff4  * yDot4;
+                interpolatedDerivatives[i] =
+                        coeffDot1 * yDot1 + coeffDot23 * yDot23 + coeffDot4 * yDot4;
+            }
+        } else {
+            final double fourTheta      = 4 * theta;
+            final double s             = oneMinusThetaH / 6.0;
+            final double coeff1        = s * ((-fourTheta + 5) * theta - 1);
+            final double coeff23       = s * (( fourTheta - 2) * theta - 2);
+            final double coeff4        = s * ((-fourTheta - 1) * theta - 1);
+            for (int i = 0; i < interpolatedState.length; ++i) {
+                final double yDot1  = yDotK[0][i];
+                final double yDot23 = yDotK[1][i] + yDotK[2][i];
+                final double yDot4  = yDotK[3][i];
+                interpolatedState[i] =
+                        currentState[i] + coeff1  * yDot1 + coeff23 * yDot23 + coeff4  * yDot4;
+                interpolatedDerivatives[i] =
+                        coeffDot1 * yDot1 + coeffDot23 * yDot23 + coeffDot4 * yDot4;
+            }
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince54FieldIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince54FieldIntegrator.java
new file mode 100644
index 0000000..5cdd828
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince54FieldIntegrator.java
@@ -0,0 +1,232 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.ode.FieldEquationsMapper;
+import org.apache.commons.math3.ode.FieldODEStateAndDerivative;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.util.MathUtils;
+
+
+/**
+ * This class implements the 5(4) Dormand-Prince integrator for Ordinary
+ * Differential Equations.
+
+ * <p>This integrator is an embedded Runge-Kutta integrator
+ * of order 5(4) used in local extrapolation mode (i.e. the solution
+ * is computed using the high order formula) with stepsize control
+ * (and automatic step initialization) and continuous output. This
+ * method uses 7 functions evaluations per step. However, since this
+ * is an <i>fsal</i>, the last evaluation of one step is the same as
+ * the first evaluation of the next step and hence can be avoided. So
+ * the cost is really 6 functions evaluations per step.</p>
+ *
+ * <p>This method has been published (whithout the continuous output
+ * that was added by Shampine in 1986) in the following article :
+ * <pre>
+ *  A family of embedded Runge-Kutta formulae
+ *  J. R. Dormand and P. J. Prince
+ *  Journal of Computational and Applied Mathematics
+ *  volume 6, no 1, 1980, pp. 19-26
+ * </pre></p>
+ *
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+
+public class DormandPrince54FieldIntegrator<T extends RealFieldElement<T>>
+    extends EmbeddedRungeKuttaFieldIntegrator<T> {
+
+    /** Integrator method name. */
+    private static final String METHOD_NAME = "Dormand-Prince 5(4)";
+
+    /** Error array, element 1. */
+    private final T e1;
+
+    // element 2 is zero, so it is neither stored nor used
+
+    /** Error array, element 3. */
+    private final T e3;
+
+    /** Error array, element 4. */
+    private final T e4;
+
+    /** Error array, element 5. */
+    private final T e5;
+
+    /** Error array, element 6. */
+    private final T e6;
+
+    /** Error array, element 7. */
+    private final T e7;
+
+    /** Simple constructor.
+     * Build a fifth order Dormand-Prince integrator with the given step bounds
+     * @param field field to which the time and state vector elements belong
+     * @param minStep minimal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param maxStep maximal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param scalAbsoluteTolerance allowed absolute error
+     * @param scalRelativeTolerance allowed relative error
+     */
+    public DormandPrince54FieldIntegrator(final Field<T> field,
+                                          final double minStep, final double maxStep,
+                                          final double scalAbsoluteTolerance,
+                                          final double scalRelativeTolerance) {
+        super(field, METHOD_NAME, 6,
+              minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance);
+        e1 = fraction(    71,  57600);
+        e3 = fraction(   -71,  16695);
+        e4 = fraction(    71,   1920);
+        e5 = fraction(-17253, 339200);
+        e6 = fraction(    22,    525);
+        e7 = fraction(    -1,     40);
+    }
+
+    /** Simple constructor.
+     * Build a fifth order Dormand-Prince integrator with the given step bounds
+     * @param field field to which the time and state vector elements belong
+     * @param minStep minimal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param maxStep maximal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param vecAbsoluteTolerance allowed absolute error
+     * @param vecRelativeTolerance allowed relative error
+     */
+    public DormandPrince54FieldIntegrator(final Field<T> field,
+                                          final double minStep, final double maxStep,
+                                          final double[] vecAbsoluteTolerance,
+                                          final double[] vecRelativeTolerance) {
+        super(field, METHOD_NAME, 6,
+              minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance);
+        e1 = fraction(    71,  57600);
+        e3 = fraction(   -71,  16695);
+        e4 = fraction(    71,   1920);
+        e5 = fraction(-17253, 339200);
+        e6 = fraction(    22,    525);
+        e7 = fraction(    -1,     40);
+    }
+
+    /** {@inheritDoc} */
+    public T[] getC() {
+        final T[] c = MathArrays.buildArray(getField(), 6);
+        c[0] = fraction(1,  5);
+        c[1] = fraction(3, 10);
+        c[2] = fraction(4,  5);
+        c[3] = fraction(8,  9);
+        c[4] = getField().getOne();
+        c[5] = getField().getOne();
+        return c;
+    }
+
+    /** {@inheritDoc} */
+    public T[][] getA() {
+        final T[][] a = MathArrays.buildArray(getField(), 6, -1);
+        for (int i = 0; i < a.length; ++i) {
+            a[i] = MathArrays.buildArray(getField(), i + 1);
+        }
+        a[0][0] = fraction(     1,     5);
+        a[1][0] = fraction(     3,    40);
+        a[1][1] = fraction(     9,    40);
+        a[2][0] = fraction(    44,    45);
+        a[2][1] = fraction(   -56,    15);
+        a[2][2] = fraction(    32,     9);
+        a[3][0] = fraction( 19372,  6561);
+        a[3][1] = fraction(-25360,  2187);
+        a[3][2] = fraction( 64448,  6561);
+        a[3][3] = fraction(  -212,   729);
+        a[4][0] = fraction(  9017,  3168);
+        a[4][1] = fraction(  -355,    33);
+        a[4][2] = fraction( 46732,  5247);
+        a[4][3] = fraction(    49,   176);
+        a[4][4] = fraction( -5103, 18656);
+        a[5][0] = fraction(    35,   384);
+        a[5][1] = getField().getZero();
+        a[5][2] = fraction(   500,  1113);
+        a[5][3] = fraction(   125,   192);
+        a[5][4] = fraction( -2187,  6784);
+        a[5][5] = fraction(    11,    84);
+        return a;
+    }
+
+    /** {@inheritDoc} */
+    public T[] getB() {
+        final T[] b = MathArrays.buildArray(getField(), 7);
+        b[0] = fraction(   35,   384);
+        b[1] = getField().getZero();
+        b[2] = fraction(  500, 1113);
+        b[3] = fraction(  125,  192);
+        b[4] = fraction(-2187, 6784);
+        b[5] = fraction(   11,   84);
+        b[6] = getField().getZero();
+        return b;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected DormandPrince54FieldStepInterpolator<T>
+        createInterpolator(final boolean forward, T[][] yDotK,
+                           final FieldODEStateAndDerivative<T> globalPreviousState,
+                           final FieldODEStateAndDerivative<T> globalCurrentState, final FieldEquationsMapper<T> mapper) {
+        return new DormandPrince54FieldStepInterpolator<T>(getField(), forward, yDotK,
+                                                           globalPreviousState, globalCurrentState,
+                                                           globalPreviousState, globalCurrentState,
+                                                           mapper);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getOrder() {
+        return 5;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected T estimateError(final T[][] yDotK, final T[] y0, final T[] y1, final T h) {
+
+        T error = getField().getZero();
+
+        for (int j = 0; j < mainSetDimension; ++j) {
+            final T errSum =     yDotK[0][j].multiply(e1).
+                             add(yDotK[2][j].multiply(e3)).
+                             add(yDotK[3][j].multiply(e4)).
+                             add(yDotK[4][j].multiply(e5)).
+                             add(yDotK[5][j].multiply(e6)).
+                             add(yDotK[6][j].multiply(e7));
+
+            final T yScale = MathUtils.max(y0[j].abs(), y1[j].abs());
+            final T tol    = (vecAbsoluteTolerance == null) ?
+                             yScale.multiply(scalRelativeTolerance).add(scalAbsoluteTolerance) :
+                             yScale.multiply(vecRelativeTolerance[j]).add(vecAbsoluteTolerance[j]);
+            final T ratio  = h.multiply(errSum).divide(tol);
+            error = error.add(ratio.multiply(ratio));
+
+        }
+
+        return error.divide(mainSetDimension).sqrt();
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince54FieldStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince54FieldStepInterpolator.java
new file mode 100644
index 0000000..62a6fa3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince54FieldStepInterpolator.java
@@ -0,0 +1,249 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.ode.FieldEquationsMapper;
+import org.apache.commons.math3.ode.FieldODEStateAndDerivative;
+
+/**
+ * This class represents an interpolator over the last step during an
+ * ODE integration for the 5(4) Dormand-Prince integrator.
+ *
+ * @see DormandPrince54Integrator
+ *
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+
+class DormandPrince54FieldStepInterpolator<T extends RealFieldElement<T>>
+      extends RungeKuttaFieldStepInterpolator<T> {
+
+    /** Last row of the Butcher-array internal weights, element 0. */
+    private final T a70;
+
+    // element 1 is zero, so it is neither stored nor used
+
+    /** Last row of the Butcher-array internal weights, element 2. */
+    private final T a72;
+
+    /** Last row of the Butcher-array internal weights, element 3. */
+    private final T a73;
+
+    /** Last row of the Butcher-array internal weights, element 4. */
+    private final T a74;
+
+    /** Last row of the Butcher-array internal weights, element 5. */
+    private final T a75;
+
+    /** Shampine (1986) Dense output, element 0. */
+    private final T d0;
+
+    // element 1 is zero, so it is neither stored nor used
+
+    /** Shampine (1986) Dense output, element 2. */
+    private final T d2;
+
+    /** Shampine (1986) Dense output, element 3. */
+    private final T d3;
+
+    /** Shampine (1986) Dense output, element 4. */
+    private final T d4;
+
+    /** Shampine (1986) Dense output, element 5. */
+    private final T d5;
+
+    /** Shampine (1986) Dense output, element 6. */
+    private final T d6;
+
+    /** Simple constructor.
+     * @param field field to which the time and state vector elements belong
+     * @param forward integration direction indicator
+     * @param yDotK slopes at the intermediate points
+     * @param globalPreviousState start of the global step
+     * @param globalCurrentState end of the global step
+     * @param softPreviousState start of the restricted step
+     * @param softCurrentState end of the restricted step
+     * @param mapper equations mapper for the all equations
+     */
+    DormandPrince54FieldStepInterpolator(final Field<T> field, final boolean forward,
+                                         final T[][] yDotK,
+                                         final FieldODEStateAndDerivative<T> globalPreviousState,
+                                         final FieldODEStateAndDerivative<T> globalCurrentState,
+                                         final FieldODEStateAndDerivative<T> softPreviousState,
+                                         final FieldODEStateAndDerivative<T> softCurrentState,
+                                         final FieldEquationsMapper<T> mapper) {
+        super(field, forward, yDotK,
+              globalPreviousState, globalCurrentState, softPreviousState, softCurrentState,
+              mapper);
+        final T one = field.getOne();
+        a70 = one.multiply(   35.0).divide( 384.0);
+        a72 = one.multiply(  500.0).divide(1113.0);
+        a73 = one.multiply(  125.0).divide( 192.0);
+        a74 = one.multiply(-2187.0).divide(6784.0);
+        a75 = one.multiply(   11.0).divide(  84.0);
+        d0  = one.multiply(-12715105075.0).divide( 11282082432.0);
+        d2  = one.multiply( 87487479700.0).divide( 32700410799.0);
+        d3  = one.multiply(-10690763975.0).divide(  1880347072.0);
+        d4  = one.multiply(701980252875.0).divide(199316789632.0);
+        d5  = one.multiply( -1453857185.0).divide(   822651844.0);
+        d6  = one.multiply(    69997945.0).divide(    29380423.0);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected DormandPrince54FieldStepInterpolator<T> create(final Field<T> newField, final boolean newForward, final T[][] newYDotK,
+                                                                 final FieldODEStateAndDerivative<T> newGlobalPreviousState,
+                                                                 final FieldODEStateAndDerivative<T> newGlobalCurrentState,
+                                                                 final FieldODEStateAndDerivative<T> newSoftPreviousState,
+                                                                 final FieldODEStateAndDerivative<T> newSoftCurrentState,
+                                                                 final FieldEquationsMapper<T> newMapper) {
+        return new DormandPrince54FieldStepInterpolator<T>(newField, newForward, newYDotK,
+                                                           newGlobalPreviousState, newGlobalCurrentState,
+                                                           newSoftPreviousState, newSoftCurrentState,
+                                                           newMapper);
+    }
+    /** {@inheritDoc} */
+    @SuppressWarnings("unchecked")
+    @Override
+    protected FieldODEStateAndDerivative<T> computeInterpolatedStateAndDerivatives(final FieldEquationsMapper<T> mapper,
+                                                                                   final T time, final T theta,
+                                                                                   final T thetaH, final T oneMinusThetaH) {
+
+        // interpolate
+        final T one      = time.getField().getOne();
+        final T eta      = one.subtract(theta);
+        final T twoTheta = theta.multiply(2);
+        final T dot2     = one.subtract(twoTheta);
+        final T dot3     = theta.multiply(theta.multiply(-3).add(2));
+        final T dot4     = twoTheta.multiply(theta.multiply(twoTheta.subtract(3)).add(1));
+        final T[] interpolatedState;
+        final T[] interpolatedDerivatives;
+        if (getGlobalPreviousState() != null && theta.getReal() <= 0.5) {
+            final T f1        = thetaH;
+            final T f2        = f1.multiply(eta);
+            final T f3        = f2.multiply(theta);
+            final T f4        = f3.multiply(eta);
+            final T coeff0    = f1.multiply(a70).
+                                subtract(f2.multiply(a70.subtract(1))).
+                                add(f3.multiply(a70.multiply(2).subtract(1))).
+                                add(f4.multiply(d0));
+            final T coeff1    = time.getField().getZero();
+            final T coeff2    = f1.multiply(a72).
+                                subtract(f2.multiply(a72)).
+                                add(f3.multiply(a72.multiply(2))).
+                                add(f4.multiply(d2));
+            final T coeff3    = f1.multiply(a73).
+                                subtract(f2.multiply(a73)).
+                                add(f3.multiply(a73.multiply(2))).
+                                add(f4.multiply(d3));
+            final T coeff4    = f1.multiply(a74).
+                                subtract(f2.multiply(a74)).
+                                add(f3.multiply(a74.multiply(2))).
+                                add(f4.multiply(d4));
+            final T coeff5    = f1.multiply(a75).
+                                subtract(f2.multiply(a75)).
+                                add(f3.multiply(a75.multiply(2))).
+                                add(f4.multiply(d5));
+            final T coeff6    = f4.multiply(d6).subtract(f3);
+            final T coeffDot0 = a70.
+                                subtract(dot2.multiply(a70.subtract(1))).
+                                add(dot3.multiply(a70.multiply(2).subtract(1))).
+                                add(dot4.multiply(d0));
+            final T coeffDot1 = time.getField().getZero();
+            final T coeffDot2 = a72.
+                                subtract(dot2.multiply(a72)).
+                                add(dot3.multiply(a72.multiply(2))).
+                                add(dot4.multiply(d2));
+            final T coeffDot3 = a73.
+                                subtract(dot2.multiply(a73)).
+                                add(dot3.multiply(a73.multiply(2))).
+                                add(dot4.multiply(d3));
+            final T coeffDot4 = a74.
+                                subtract(dot2.multiply(a74)).
+                                add(dot3.multiply(a74.multiply(2))).
+                                add(dot4.multiply(d4));
+            final T coeffDot5 = a75.
+                                subtract(dot2.multiply(a75)).
+                                add(dot3.multiply(a75.multiply(2))).
+                                add(dot4.multiply(d5));
+            final T coeffDot6 = dot4.multiply(d6).subtract(dot3);
+            interpolatedState       = previousStateLinearCombination(coeff0, coeff1, coeff2, coeff3,
+                                                                     coeff4, coeff5, coeff6);
+            interpolatedDerivatives = derivativeLinearCombination(coeffDot0, coeffDot1, coeffDot2, coeffDot3,
+                                                                  coeffDot4, coeffDot5, coeffDot6);
+        } else {
+            final T f1        = oneMinusThetaH.negate();
+            final T f2        = oneMinusThetaH.multiply(theta);
+            final T f3        = f2.multiply(theta);
+            final T f4        = f3.multiply(eta);
+            final T coeff0    = f1.multiply(a70).
+                                subtract(f2.multiply(a70.subtract(1))).
+                                add(f3.multiply(a70.multiply(2).subtract(1))).
+                                add(f4.multiply(d0));
+            final T coeff1    = time.getField().getZero();
+            final T coeff2    = f1.multiply(a72).
+                                subtract(f2.multiply(a72)).
+                                add(f3.multiply(a72.multiply(2))).
+                                add(f4.multiply(d2));
+            final T coeff3    = f1.multiply(a73).
+                                subtract(f2.multiply(a73)).
+                                add(f3.multiply(a73.multiply(2))).
+                                add(f4.multiply(d3));
+            final T coeff4    = f1.multiply(a74).
+                                subtract(f2.multiply(a74)).
+                                add(f3.multiply(a74.multiply(2))).
+                                add(f4.multiply(d4));
+            final T coeff5    = f1.multiply(a75).
+                                subtract(f2.multiply(a75)).
+                                add(f3.multiply(a75.multiply(2))).
+                                add(f4.multiply(d5));
+            final T coeff6    = f4.multiply(d6).subtract(f3);
+            final T coeffDot0 = a70.
+                                subtract(dot2.multiply(a70.subtract(1))).
+                                add(dot3.multiply(a70.multiply(2).subtract(1))).
+                                add(dot4.multiply(d0));
+            final T coeffDot1 = time.getField().getZero();
+            final T coeffDot2 = a72.
+                                subtract(dot2.multiply(a72)).
+                                add(dot3.multiply(a72.multiply(2))).
+                                add(dot4.multiply(d2));
+            final T coeffDot3 = a73.
+                                subtract(dot2.multiply(a73)).
+                                add(dot3.multiply(a73.multiply(2))).
+                                add(dot4.multiply(d3));
+            final T coeffDot4 = a74.
+                                subtract(dot2.multiply(a74)).
+                                add(dot3.multiply(a74.multiply(2))).
+                                add(dot4.multiply(d4));
+            final T coeffDot5 = a75.
+                                subtract(dot2.multiply(a75)).
+                                add(dot3.multiply(a75.multiply(2))).
+                                add(dot4.multiply(d5));
+            final T coeffDot6 = dot4.multiply(d6).subtract(dot3);
+            interpolatedState       = currentStateLinearCombination(coeff0, coeff1, coeff2, coeff3,
+                                                                    coeff4, coeff5, coeff6);
+            interpolatedDerivatives = derivativeLinearCombination(coeffDot0, coeffDot1, coeffDot2, coeffDot3,
+                                                                  coeffDot4, coeffDot5, coeffDot6);
+        }
+        return new FieldODEStateAndDerivative<T>(time, interpolatedState, interpolatedDerivatives);
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince54Integrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince54Integrator.java
new file mode 100644
index 0000000..45229b2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince54Integrator.java
@@ -0,0 +1,161 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.util.FastMath;
+
+
+/**
+ * This class implements the 5(4) Dormand-Prince integrator for Ordinary
+ * Differential Equations.
+
+ * <p>This integrator is an embedded Runge-Kutta integrator
+ * of order 5(4) used in local extrapolation mode (i.e. the solution
+ * is computed using the high order formula) with stepsize control
+ * (and automatic step initialization) and continuous output. This
+ * method uses 7 functions evaluations per step. However, since this
+ * is an <i>fsal</i>, the last evaluation of one step is the same as
+ * the first evaluation of the next step and hence can be avoided. So
+ * the cost is really 6 functions evaluations per step.</p>
+ *
+ * <p>This method has been published (whithout the continuous output
+ * that was added by Shampine in 1986) in the following article :
+ * <pre>
+ *  A family of embedded Runge-Kutta formulae
+ *  J. R. Dormand and P. J. Prince
+ *  Journal of Computational and Applied Mathematics
+ *  volume 6, no 1, 1980, pp. 19-26
+ * </pre></p>
+ *
+ * @since 1.2
+ */
+
+public class DormandPrince54Integrator extends EmbeddedRungeKuttaIntegrator {
+
+  /** Integrator method name. */
+  private static final String METHOD_NAME = "Dormand-Prince 5(4)";
+
+  /** Time steps Butcher array. */
+  private static final double[] STATIC_C = {
+    1.0/5.0, 3.0/10.0, 4.0/5.0, 8.0/9.0, 1.0, 1.0
+  };
+
+  /** Internal weights Butcher array. */
+  private static final double[][] STATIC_A = {
+    {1.0/5.0},
+    {3.0/40.0, 9.0/40.0},
+    {44.0/45.0, -56.0/15.0, 32.0/9.0},
+    {19372.0/6561.0, -25360.0/2187.0, 64448.0/6561.0,  -212.0/729.0},
+    {9017.0/3168.0, -355.0/33.0, 46732.0/5247.0, 49.0/176.0, -5103.0/18656.0},
+    {35.0/384.0, 0.0, 500.0/1113.0, 125.0/192.0, -2187.0/6784.0, 11.0/84.0}
+  };
+
+  /** Propagation weights Butcher array. */
+  private static final double[] STATIC_B = {
+    35.0/384.0, 0.0, 500.0/1113.0, 125.0/192.0, -2187.0/6784.0, 11.0/84.0, 0.0
+  };
+
+  /** Error array, element 1. */
+  private static final double E1 =     71.0 / 57600.0;
+
+  // element 2 is zero, so it is neither stored nor used
+
+  /** Error array, element 3. */
+  private static final double E3 =    -71.0 / 16695.0;
+
+  /** Error array, element 4. */
+  private static final double E4 =     71.0 / 1920.0;
+
+  /** Error array, element 5. */
+  private static final double E5 = -17253.0 / 339200.0;
+
+  /** Error array, element 6. */
+  private static final double E6 =     22.0 / 525.0;
+
+  /** Error array, element 7. */
+  private static final double E7 =     -1.0 / 40.0;
+
+  /** Simple constructor.
+   * Build a fifth order Dormand-Prince integrator with the given step bounds
+   * @param minStep minimal step (sign is irrelevant, regardless of
+   * integration direction, forward or backward), the last step can
+   * be smaller than this
+   * @param maxStep maximal step (sign is irrelevant, regardless of
+   * integration direction, forward or backward), the last step can
+   * be smaller than this
+   * @param scalAbsoluteTolerance allowed absolute error
+   * @param scalRelativeTolerance allowed relative error
+   */
+  public DormandPrince54Integrator(final double minStep, final double maxStep,
+                                   final double scalAbsoluteTolerance,
+                                   final double scalRelativeTolerance) {
+    super(METHOD_NAME, true, STATIC_C, STATIC_A, STATIC_B, new DormandPrince54StepInterpolator(),
+          minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance);
+  }
+
+  /** Simple constructor.
+   * Build a fifth order Dormand-Prince integrator with the given step bounds
+   * @param minStep minimal step (sign is irrelevant, regardless of
+   * integration direction, forward or backward), the last step can
+   * be smaller than this
+   * @param maxStep maximal step (sign is irrelevant, regardless of
+   * integration direction, forward or backward), the last step can
+   * be smaller than this
+   * @param vecAbsoluteTolerance allowed absolute error
+   * @param vecRelativeTolerance allowed relative error
+   */
+  public DormandPrince54Integrator(final double minStep, final double maxStep,
+                                   final double[] vecAbsoluteTolerance,
+                                   final double[] vecRelativeTolerance) {
+    super(METHOD_NAME, true, STATIC_C, STATIC_A, STATIC_B, new DormandPrince54StepInterpolator(),
+          minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance);
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public int getOrder() {
+    return 5;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  protected double estimateError(final double[][] yDotK,
+                                 final double[] y0, final double[] y1,
+                                 final double h) {
+
+    double error = 0;
+
+    for (int j = 0; j < mainSetDimension; ++j) {
+        final double errSum = E1 * yDotK[0][j] +  E3 * yDotK[2][j] +
+                              E4 * yDotK[3][j] +  E5 * yDotK[4][j] +
+                              E6 * yDotK[5][j] +  E7 * yDotK[6][j];
+
+        final double yScale = FastMath.max(FastMath.abs(y0[j]), FastMath.abs(y1[j]));
+        final double tol = (vecAbsoluteTolerance == null) ?
+                           (scalAbsoluteTolerance + scalRelativeTolerance * yScale) :
+                               (vecAbsoluteTolerance[j] + vecRelativeTolerance[j] * yScale);
+        final double ratio  = h * errSum / tol;
+        error += ratio * ratio;
+
+    }
+
+    return FastMath.sqrt(error / mainSetDimension);
+
+  }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince54StepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince54StepInterpolator.java
new file mode 100644
index 0000000..04aa970
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince54StepInterpolator.java
@@ -0,0 +1,225 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.ode.AbstractIntegrator;
+import org.apache.commons.math3.ode.EquationsMapper;
+import org.apache.commons.math3.ode.sampling.StepInterpolator;
+
+/**
+ * This class represents an interpolator over the last step during an
+ * ODE integration for the 5(4) Dormand-Prince integrator.
+ *
+ * @see DormandPrince54Integrator
+ *
+ * @since 1.2
+ */
+
+class DormandPrince54StepInterpolator
+  extends RungeKuttaStepInterpolator {
+
+    /** Last row of the Butcher-array internal weights, element 0. */
+    private static final double A70 =    35.0 /  384.0;
+
+    // element 1 is zero, so it is neither stored nor used
+
+    /** Last row of the Butcher-array internal weights, element 2. */
+    private static final double A72 =   500.0 / 1113.0;
+
+    /** Last row of the Butcher-array internal weights, element 3. */
+    private static final double A73 =   125.0 /  192.0;
+
+    /** Last row of the Butcher-array internal weights, element 4. */
+    private static final double A74 = -2187.0 / 6784.0;
+
+    /** Last row of the Butcher-array internal weights, element 5. */
+    private static final double A75 =    11.0 /   84.0;
+
+    /** Shampine (1986) Dense output, element 0. */
+    private static final double D0 =  -12715105075.0 /  11282082432.0;
+
+    // element 1 is zero, so it is neither stored nor used
+
+    /** Shampine (1986) Dense output, element 2. */
+    private static final double D2 =   87487479700.0 /  32700410799.0;
+
+    /** Shampine (1986) Dense output, element 3. */
+    private static final double D3 =  -10690763975.0 /   1880347072.0;
+
+    /** Shampine (1986) Dense output, element 4. */
+    private static final double D4 =  701980252875.0 / 199316789632.0;
+
+    /** Shampine (1986) Dense output, element 5. */
+    private static final double D5 =   -1453857185.0 /    822651844.0;
+
+    /** Shampine (1986) Dense output, element 6. */
+    private static final double D6 =      69997945.0 /     29380423.0;
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 20111120L;
+
+    /** First vector for interpolation. */
+    private double[] v1;
+
+    /** Second vector for interpolation. */
+    private double[] v2;
+
+    /** Third vector for interpolation. */
+    private double[] v3;
+
+    /** Fourth vector for interpolation. */
+    private double[] v4;
+
+    /** Initialization indicator for the interpolation vectors. */
+    private boolean vectorsInitialized;
+
+  /** Simple constructor.
+   * This constructor builds an instance that is not usable yet, the
+   * {@link #reinitialize} method should be called before using the
+   * instance in order to initialize the internal arrays. This
+   * constructor is used only in order to delay the initialization in
+   * some cases. The {@link EmbeddedRungeKuttaIntegrator} uses the
+   * prototyping design pattern to create the step interpolators by
+   * cloning an uninitialized model and latter initializing the copy.
+   */
+  // CHECKSTYLE: stop RedundantModifier
+  // the public modifier here is needed for serialization
+  public DormandPrince54StepInterpolator() {
+    super();
+    v1 = null;
+    v2 = null;
+    v3 = null;
+    v4 = null;
+    vectorsInitialized = false;
+  }
+  // CHECKSTYLE: resume RedundantModifier
+
+  /** Copy constructor.
+   * @param interpolator interpolator to copy from. The copy is a deep
+   * copy: its arrays are separated from the original arrays of the
+   * instance
+   */
+  DormandPrince54StepInterpolator(final DormandPrince54StepInterpolator interpolator) {
+
+    super(interpolator);
+
+    if (interpolator.v1 == null) {
+
+      v1 = null;
+      v2 = null;
+      v3 = null;
+      v4 = null;
+      vectorsInitialized = false;
+
+    } else {
+
+      v1 = interpolator.v1.clone();
+      v2 = interpolator.v2.clone();
+      v3 = interpolator.v3.clone();
+      v4 = interpolator.v4.clone();
+      vectorsInitialized = interpolator.vectorsInitialized;
+
+    }
+
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  protected StepInterpolator doCopy() {
+    return new DormandPrince54StepInterpolator(this);
+  }
+
+
+  /** {@inheritDoc} */
+  @Override
+  public void reinitialize(final AbstractIntegrator integrator,
+                           final double[] y, final double[][] yDotK, final boolean forward,
+                           final EquationsMapper primaryMapper,
+                           final EquationsMapper[] secondaryMappers) {
+    super.reinitialize(integrator, y, yDotK, forward, primaryMapper, secondaryMappers);
+    v1 = null;
+    v2 = null;
+    v3 = null;
+    v4 = null;
+    vectorsInitialized = false;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public void storeTime(final double t) {
+    super.storeTime(t);
+    vectorsInitialized = false;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  protected void computeInterpolatedStateAndDerivatives(final double theta,
+                                          final double oneMinusThetaH) {
+
+    if (! vectorsInitialized) {
+
+      if (v1 == null) {
+        v1 = new double[interpolatedState.length];
+        v2 = new double[interpolatedState.length];
+        v3 = new double[interpolatedState.length];
+        v4 = new double[interpolatedState.length];
+      }
+
+      // no step finalization is needed for this interpolator
+
+      // we need to compute the interpolation vectors for this time step
+      for (int i = 0; i < interpolatedState.length; ++i) {
+          final double yDot0 = yDotK[0][i];
+          final double yDot2 = yDotK[2][i];
+          final double yDot3 = yDotK[3][i];
+          final double yDot4 = yDotK[4][i];
+          final double yDot5 = yDotK[5][i];
+          final double yDot6 = yDotK[6][i];
+          v1[i] = A70 * yDot0 + A72 * yDot2 + A73 * yDot3 + A74 * yDot4 + A75 * yDot5;
+          v2[i] = yDot0 - v1[i];
+          v3[i] = v1[i] - v2[i] - yDot6;
+          v4[i] = D0 * yDot0 + D2 * yDot2 + D3 * yDot3 + D4 * yDot4 + D5 * yDot5 + D6 * yDot6;
+      }
+
+      vectorsInitialized = true;
+
+    }
+
+    // interpolate
+    final double eta = 1 - theta;
+    final double twoTheta = 2 * theta;
+    final double dot2 = 1 - twoTheta;
+    final double dot3 = theta * (2 - 3 * theta);
+    final double dot4 = twoTheta * (1 + theta * (twoTheta - 3));
+    if ((previousState != null) && (theta <= 0.5)) {
+        for (int i = 0; i < interpolatedState.length; ++i) {
+            interpolatedState[i] =
+                    previousState[i] + theta * h * (v1[i] + eta * (v2[i] + theta * (v3[i] + eta * v4[i])));
+            interpolatedDerivatives[i] = v1[i] + dot2 * v2[i] + dot3 * v3[i] + dot4 * v4[i];
+        }
+    } else {
+        for (int i = 0; i < interpolatedState.length; ++i) {
+            interpolatedState[i] =
+                    currentState[i] - oneMinusThetaH * (v1[i] - theta * (v2[i] + theta * (v3[i] + eta * v4[i])));
+            interpolatedDerivatives[i] = v1[i] + dot2 * v2[i] + dot3 * v3[i] + dot4 * v4[i];
+        }
+    }
+
+  }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince853FieldIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince853FieldIntegrator.java
new file mode 100644
index 0000000..3111756
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince853FieldIntegrator.java
@@ -0,0 +1,454 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.ode.FieldEquationsMapper;
+import org.apache.commons.math3.ode.FieldODEStateAndDerivative;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.util.MathUtils;
+
+
+/**
+ * This class implements the 8(5,3) Dormand-Prince integrator for Ordinary
+ * Differential Equations.
+ *
+ * <p>This integrator is an embedded Runge-Kutta integrator
+ * of order 8(5,3) used in local extrapolation mode (i.e. the solution
+ * is computed using the high order formula) with stepsize control
+ * (and automatic step initialization) and continuous output. This
+ * method uses 12 functions evaluations per step for integration and 4
+ * evaluations for interpolation. However, since the first
+ * interpolation evaluation is the same as the first integration
+ * evaluation of the next step, we have included it in the integrator
+ * rather than in the interpolator and specified the method was an
+ * <i>fsal</i>. Hence, despite we have 13 stages here, the cost is
+ * really 12 evaluations per step even if no interpolation is done,
+ * and the overcost of interpolation is only 3 evaluations.</p>
+ *
+ * <p>This method is based on an 8(6) method by Dormand and Prince
+ * (i.e. order 8 for the integration and order 6 for error estimation)
+ * modified by Hairer and Wanner to use a 5th order error estimator
+ * with 3rd order correction. This modification was introduced because
+ * the original method failed in some cases (wrong steps can be
+ * accepted when step size is too large, for example in the
+ * Brusselator problem) and also had <i>severe difficulties when
+ * applied to problems with discontinuities</i>. This modification is
+ * explained in the second edition of the first volume (Nonstiff
+ * Problems) of the reference book by Hairer, Norsett and Wanner:
+ * <i>Solving Ordinary Differential Equations</i> (Springer-Verlag,
+ * ISBN 3-540-56670-8).</p>
+ *
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+
+public class DormandPrince853FieldIntegrator<T extends RealFieldElement<T>>
+    extends EmbeddedRungeKuttaFieldIntegrator<T> {
+
+    /** Integrator method name. */
+    private static final String METHOD_NAME = "Dormand-Prince 8 (5, 3)";
+
+    /** First error weights array, element 1. */
+    private final T e1_01;
+
+    // elements 2 to 5 are zero, so they are neither stored nor used
+
+    /** First error weights array, element 6. */
+    private final T e1_06;
+
+    /** First error weights array, element 7. */
+    private final T e1_07;
+
+    /** First error weights array, element 8. */
+    private final T e1_08;
+
+    /** First error weights array, element 9. */
+    private final T e1_09;
+
+    /** First error weights array, element 10. */
+    private final T e1_10;
+
+    /** First error weights array, element 11. */
+    private final T e1_11;
+
+    /** First error weights array, element 12. */
+    private final T e1_12;
+
+
+    /** Second error weights array, element 1. */
+    private final T e2_01;
+
+    // elements 2 to 5 are zero, so they are neither stored nor used
+
+    /** Second error weights array, element 6. */
+    private final T e2_06;
+
+    /** Second error weights array, element 7. */
+    private final T e2_07;
+
+    /** Second error weights array, element 8. */
+    private final T e2_08;
+
+    /** Second error weights array, element 9. */
+    private final T e2_09;
+
+    /** Second error weights array, element 10. */
+    private final T e2_10;
+
+    /** Second error weights array, element 11. */
+    private final T e2_11;
+
+    /** Second error weights array, element 12. */
+    private final T e2_12;
+
+    /** Simple constructor.
+     * Build an eighth order Dormand-Prince integrator with the given step bounds
+     * @param field field to which the time and state vector elements belong
+     * @param minStep minimal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param maxStep maximal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param scalAbsoluteTolerance allowed absolute error
+     * @param scalRelativeTolerance allowed relative error
+     */
+    public DormandPrince853FieldIntegrator(final Field<T> field,
+                                           final double minStep, final double maxStep,
+                                           final double scalAbsoluteTolerance,
+                                           final double scalRelativeTolerance) {
+        super(field, METHOD_NAME, 12,
+              minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance);
+        e1_01 = fraction(        116092271.0,       8848465920.0);
+        e1_06 = fraction(         -1871647.0,          1527680.0);
+        e1_07 = fraction(        -69799717.0,        140793660.0);
+        e1_08 = fraction(    1230164450203.0,     739113984000.0);
+        e1_09 = fraction(-1980813971228885.0, 5654156025964544.0);
+        e1_10 = fraction(        464500805.0,       1389975552.0);
+        e1_11 = fraction(    1606764981773.0,   19613062656000.0);
+        e1_12 = fraction(          -137909.0,          6168960.0);
+        e2_01 = fraction(          -364463.0,          1920240.0);
+        e2_06 = fraction(          3399327.0,           763840.0);
+        e2_07 = fraction(         66578432.0,         35198415.0);
+        e2_08 = fraction(      -1674902723.0,        288716400.0);
+        e2_09 = fraction(  -74684743568175.0,  176692375811392.0);
+        e2_10 = fraction(          -734375.0,          4826304.0);
+        e2_11 = fraction(        171414593.0,        851261400.0);
+        e2_12 = fraction(            69869.0,          3084480.0);
+    }
+
+    /** Simple constructor.
+     * Build an eighth order Dormand-Prince integrator with the given step bounds
+     * @param field field to which the time and state vector elements belong
+     * @param minStep minimal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param maxStep maximal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param vecAbsoluteTolerance allowed absolute error
+     * @param vecRelativeTolerance allowed relative error
+     */
+    public DormandPrince853FieldIntegrator(final Field<T> field,
+                                           final double minStep, final double maxStep,
+                                           final double[] vecAbsoluteTolerance,
+                                           final double[] vecRelativeTolerance) {
+        super(field, METHOD_NAME, 12,
+              minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance);
+        e1_01 = fraction(        116092271.0,       8848465920.0);
+        e1_06 = fraction(         -1871647.0,          1527680.0);
+        e1_07 = fraction(        -69799717.0,        140793660.0);
+        e1_08 = fraction(    1230164450203.0,     739113984000.0);
+        e1_09 = fraction(-1980813971228885.0, 5654156025964544.0);
+        e1_10 = fraction(        464500805.0,       1389975552.0);
+        e1_11 = fraction(    1606764981773.0,   19613062656000.0);
+        e1_12 = fraction(          -137909.0,          6168960.0);
+        e2_01 = fraction(          -364463.0,          1920240.0);
+        e2_06 = fraction(          3399327.0,           763840.0);
+        e2_07 = fraction(         66578432.0,         35198415.0);
+        e2_08 = fraction(      -1674902723.0,        288716400.0);
+        e2_09 = fraction(  -74684743568175.0,  176692375811392.0);
+        e2_10 = fraction(          -734375.0,          4826304.0);
+        e2_11 = fraction(        171414593.0,        851261400.0);
+        e2_12 = fraction(            69869.0,          3084480.0);
+    }
+
+    /** {@inheritDoc} */
+    public T[] getC() {
+
+        final T sqrt6 = getField().getOne().multiply(6).sqrt();
+
+        final T[] c = MathArrays.buildArray(getField(), 15);
+        c[ 0] = sqrt6.add(-6).divide(-67.5);
+        c[ 1] = sqrt6.add(-6).divide(-45.0);
+        c[ 2] = sqrt6.add(-6).divide(-30.0);
+        c[ 3] = sqrt6.add( 6).divide( 30.0);
+        c[ 4] = fraction(1, 3);
+        c[ 5] = fraction(1, 4);
+        c[ 6] = fraction(4, 13);
+        c[ 7] = fraction(127, 195);
+        c[ 8] = fraction(3, 5);
+        c[ 9] = fraction(6, 7);
+        c[10] = getField().getOne();
+        c[11] = getField().getOne();
+        c[12] = fraction(1.0, 10.0);
+        c[13] = fraction(1.0, 5.0);
+        c[14] = fraction(7.0, 9.0);
+
+        return c;
+
+    }
+
+    /** {@inheritDoc} */
+    public T[][] getA() {
+
+        final T sqrt6 = getField().getOne().multiply(6).sqrt();
+
+        final T[][] a = MathArrays.buildArray(getField(), 15, -1);
+        for (int i = 0; i < a.length; ++i) {
+            a[i] = MathArrays.buildArray(getField(), i + 1);
+        }
+
+        a[ 0][ 0] = sqrt6.add(-6).divide(-67.5);
+
+        a[ 1][ 0] = sqrt6.add(-6).divide(-180);
+        a[ 1][ 1] = sqrt6.add(-6).divide( -60);
+
+        a[ 2][ 0] = sqrt6.add(-6).divide(-120);
+        a[ 2][ 1] = getField().getZero();
+        a[ 2][ 2] = sqrt6.add(-6).divide( -40);
+
+        a[ 3][ 0] = sqrt6.multiply(107).add(462).divide( 3000);
+        a[ 3][ 1] = getField().getZero();
+        a[ 3][ 2] = sqrt6.multiply(197).add(402).divide(-1000);
+        a[ 3][ 3] = sqrt6.multiply( 73).add(168).divide(  375);
+
+        a[ 4][ 0] = fraction(1, 27);
+        a[ 4][ 1] = getField().getZero();
+        a[ 4][ 2] = getField().getZero();
+        a[ 4][ 3] = sqrt6.add( 16).divide( 108);
+        a[ 4][ 4] = sqrt6.add(-16).divide(-108);
+
+        a[ 5][ 0] = fraction(19, 512);
+        a[ 5][ 1] = getField().getZero();
+        a[ 5][ 2] = getField().getZero();
+        a[ 5][ 3] = sqrt6.multiply( 23).add(118).divide(1024);
+        a[ 5][ 4] = sqrt6.multiply(-23).add(118).divide(1024);
+        a[ 5][ 5] = fraction(-9, 512);
+
+        a[ 6][ 0] = fraction(13772, 371293);
+        a[ 6][ 1] = getField().getZero();
+        a[ 6][ 2] = getField().getZero();
+        a[ 6][ 3] = sqrt6.multiply( 4784).add(51544).divide(371293);
+        a[ 6][ 4] = sqrt6.multiply(-4784).add(51544).divide(371293);
+        a[ 6][ 5] = fraction(-5688, 371293);
+        a[ 6][ 6] = fraction( 3072, 371293);
+
+        a[ 7][ 0] = fraction(58656157643.0, 93983540625.0);
+        a[ 7][ 1] = getField().getZero();
+        a[ 7][ 2] = getField().getZero();
+        a[ 7][ 3] = sqrt6.multiply(-318801444819.0).add(-1324889724104.0).divide(626556937500.0);
+        a[ 7][ 4] = sqrt6.multiply( 318801444819.0).add(-1324889724104.0).divide(626556937500.0);
+        a[ 7][ 5] = fraction(96044563816.0, 3480871875.0);
+        a[ 7][ 6] = fraction(5682451879168.0, 281950621875.0);
+        a[ 7][ 7] = fraction(-165125654.0, 3796875.0);
+
+        a[ 8][ 0] = fraction(8909899.0, 18653125.0);
+        a[ 8][ 1] = getField().getZero();
+        a[ 8][ 2] = getField().getZero();
+        a[ 8][ 3] = sqrt6.multiply(-1137963.0).add(-4521408.0).divide(2937500.0);
+        a[ 8][ 4] = sqrt6.multiply( 1137963.0).add(-4521408.0).divide(2937500.0);
+        a[ 8][ 5] = fraction(96663078.0, 4553125.0);
+        a[ 8][ 6] = fraction(2107245056.0, 137915625.0);
+        a[ 8][ 7] = fraction(-4913652016.0, 147609375.0);
+        a[ 8][ 8] = fraction(-78894270.0, 3880452869.0);
+
+        a[ 9][ 0] = fraction(-20401265806.0, 21769653311.0);
+        a[ 9][ 1] = getField().getZero();
+        a[ 9][ 2] = getField().getZero();
+        a[ 9][ 3] = sqrt6.multiply( 94326.0).add(354216.0).divide(112847.0);
+        a[ 9][ 4] = sqrt6.multiply(-94326.0).add(354216.0).divide(112847.0);
+        a[ 9][ 5] = fraction(-43306765128.0, 5313852383.0);
+        a[ 9][ 6] = fraction(-20866708358144.0, 1126708119789.0);
+        a[ 9][ 7] = fraction(14886003438020.0, 654632330667.0);
+        a[ 9][ 8] = fraction(35290686222309375.0, 14152473387134411.0);
+        a[ 9][ 9] = fraction(-1477884375.0, 485066827.0);
+
+        a[10][ 0] = fraction(39815761.0, 17514443.0);
+        a[10][ 1] = getField().getZero();
+        a[10][ 2] = getField().getZero();
+        a[10][ 3] = sqrt6.multiply(-960905.0).add(-3457480.0).divide(551636.0);
+        a[10][ 4] = sqrt6.multiply( 960905.0).add(-3457480.0).divide(551636.0);
+        a[10][ 5] = fraction(-844554132.0, 47026969.0);
+        a[10][ 6] = fraction(8444996352.0, 302158619.0);
+        a[10][ 7] = fraction(-2509602342.0, 877790785.0);
+        a[10][ 8] = fraction(-28388795297996250.0, 3199510091356783.0);
+        a[10][ 9] = fraction(226716250.0, 18341897.0);
+        a[10][10] = fraction(1371316744.0, 2131383595.0);
+
+        // the following stage is both for interpolation and the first stage in next step
+        // (the coefficients are identical to the B array)
+        a[11][ 0] = fraction(104257.0, 1920240.0);
+        a[11][ 1] = getField().getZero();
+        a[11][ 2] = getField().getZero();
+        a[11][ 3] = getField().getZero();
+        a[11][ 4] = getField().getZero();
+        a[11][ 5] = fraction(3399327.0, 763840.0);
+        a[11][ 6] = fraction(66578432.0, 35198415.0);
+        a[11][ 7] = fraction(-1674902723.0, 288716400.0);
+        a[11][ 8] = fraction(54980371265625.0, 176692375811392.0);
+        a[11][ 9] = fraction(-734375.0, 4826304.0);
+        a[11][10] = fraction(171414593.0, 851261400.0);
+        a[11][11] = fraction(137909.0, 3084480.0);
+
+        // the following stages are for interpolation only
+        a[12][ 0] = fraction(      13481885573.0, 240030000000.0);
+        a[12][ 1] = getField().getZero();
+        a[12][ 2] = getField().getZero();
+        a[12][ 3] = getField().getZero();
+        a[12][ 4] = getField().getZero();
+        a[12][ 5] = getField().getZero();
+        a[12][ 6] = fraction(     139418837528.0, 549975234375.0);
+        a[12][ 7] = fraction(  -11108320068443.0, 45111937500000.0);
+        a[12][ 8] = fraction(-1769651421925959.0, 14249385146080000.0);
+        a[12][ 9] = fraction(         57799439.0, 377055000.0);
+        a[12][10] = fraction(     793322643029.0, 96734250000000.0);
+        a[12][11] = fraction(       1458939311.0, 192780000000.0);
+        a[12][12]  = fraction(            -4149.0, 500000.0);
+
+        a[13][ 0] = fraction(    1595561272731.0, 50120273500000.0);
+        a[13][ 1] = getField().getZero();
+        a[13][ 2] = getField().getZero();
+        a[13][ 3] = getField().getZero();
+        a[13][ 4] = getField().getZero();
+        a[13][ 5] = fraction(     975183916491.0, 34457688031250.0);
+        a[13][ 6] = fraction(   38492013932672.0, 718912673015625.0);
+        a[13][ 7] = fraction(-1114881286517557.0, 20298710767500000.0);
+        a[13][ 8] = getField().getZero();
+        a[13][ 9] = getField().getZero();
+        a[13][10] = fraction(   -2538710946863.0, 23431227861250000.0);
+        a[13][11] = fraction(       8824659001.0, 23066716781250.0);
+        a[13][12] = fraction(     -11518334563.0, 33831184612500.0);
+        a[13][13] = fraction(       1912306948.0, 13532473845.0);
+
+        a[14][ 0] = fraction(     -13613986967.0, 31741908048.0);
+        a[14][ 1] = getField().getZero();
+        a[14][ 2] = getField().getZero();
+        a[14][ 3] = getField().getZero();
+        a[14][ 4] = getField().getZero();
+        a[14][ 5] = fraction(      -4755612631.0, 1012344804.0);
+        a[14][ 6] = fraction(   42939257944576.0, 5588559685701.0);
+        a[14][ 7] = fraction(   77881972900277.0, 19140370552944.0);
+        a[14][ 8] = fraction(   22719829234375.0, 63689648654052.0);
+        a[14][ 9] = getField().getZero();
+        a[14][10] = getField().getZero();
+        a[14][11] = getField().getZero();
+        a[14][12] = fraction(      -1199007803.0, 857031517296.0);
+        a[14][13] = fraction(     157882067000.0, 53564469831.0);
+        a[14][14] = fraction(    -290468882375.0, 31741908048.0);
+
+        return a;
+
+    }
+
+    /** {@inheritDoc} */
+    public T[] getB() {
+        final T[] b = MathArrays.buildArray(getField(), 16);
+        b[ 0] = fraction(104257, 1920240);
+        b[ 1] = getField().getZero();
+        b[ 2] = getField().getZero();
+        b[ 3] = getField().getZero();
+        b[ 4] = getField().getZero();
+        b[ 5] = fraction(        3399327.0,          763840.0);
+        b[ 6] = fraction(       66578432.0,        35198415.0);
+        b[ 7] = fraction(    -1674902723.0,       288716400.0);
+        b[ 8] = fraction( 54980371265625.0, 176692375811392.0);
+        b[ 9] = fraction(        -734375.0,         4826304.0);
+        b[10] = fraction(      171414593.0,       851261400.0);
+        b[11] = fraction(         137909.0,         3084480.0);
+        b[12] = getField().getZero();
+        b[13] = getField().getZero();
+        b[14] = getField().getZero();
+        b[15] = getField().getZero();
+        return b;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected DormandPrince853FieldStepInterpolator<T>
+        createInterpolator(final boolean forward, T[][] yDotK,
+                           final FieldODEStateAndDerivative<T> globalPreviousState,
+                           final FieldODEStateAndDerivative<T> globalCurrentState, final FieldEquationsMapper<T> mapper) {
+        return new DormandPrince853FieldStepInterpolator<T>(getField(), forward, yDotK,
+                                                            globalPreviousState, globalCurrentState,
+                                                            globalPreviousState, globalCurrentState,
+                                                            mapper);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getOrder() {
+        return 8;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected T estimateError(final T[][] yDotK, final T[] y0, final T[] y1, final T h) {
+        T error1 = h.getField().getZero();
+        T error2 = h.getField().getZero();
+
+        for (int j = 0; j < mainSetDimension; ++j) {
+            final T errSum1 =      yDotK[ 0][j].multiply(e1_01).
+                               add(yDotK[ 5][j].multiply(e1_06)).
+                               add(yDotK[ 6][j].multiply(e1_07)).
+                               add(yDotK[ 7][j].multiply(e1_08)).
+                               add(yDotK[ 8][j].multiply(e1_09)).
+                               add(yDotK[ 9][j].multiply(e1_10)).
+                               add(yDotK[10][j].multiply(e1_11)).
+                               add(yDotK[11][j].multiply(e1_12));
+            final T errSum2 =      yDotK[ 0][j].multiply(e2_01).
+                               add(yDotK[ 5][j].multiply(e2_06)).
+                               add(yDotK[ 6][j].multiply(e2_07)).
+                               add(yDotK[ 7][j].multiply(e2_08)).
+                               add(yDotK[ 8][j].multiply(e2_09)).
+                               add(yDotK[ 9][j].multiply(e2_10)).
+                               add(yDotK[10][j].multiply(e2_11)).
+                               add(yDotK[11][j].multiply(e2_12));
+
+            final T yScale = MathUtils.max(y0[j].abs(), y1[j].abs());
+            final T tol = vecAbsoluteTolerance == null ?
+                          yScale.multiply(scalRelativeTolerance).add(scalAbsoluteTolerance) :
+                          yScale.multiply(vecRelativeTolerance[j]).add(vecAbsoluteTolerance[j]);
+            final T ratio1  = errSum1.divide(tol);
+            error1        = error1.add(ratio1.multiply(ratio1));
+            final T ratio2  = errSum2.divide(tol);
+            error2        = error2.add(ratio2.multiply(ratio2));
+        }
+
+        T den = error1.add(error2.multiply(0.01));
+        if (den.getReal() <= 0.0) {
+            den = h.getField().getOne();
+        }
+
+        return h.abs().multiply(error1).divide(den.multiply(mainSetDimension).sqrt());
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince853FieldStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince853FieldStepInterpolator.java
new file mode 100644
index 0000000..c706e2a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince853FieldStepInterpolator.java
@@ -0,0 +1,302 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.ode.FieldEquationsMapper;
+import org.apache.commons.math3.ode.FieldODEStateAndDerivative;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * This class represents an interpolator over the last step during an
+ * ODE integration for the 8(5,3) Dormand-Prince integrator.
+ *
+ * @see DormandPrince853FieldIntegrator
+ *
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+
+class DormandPrince853FieldStepInterpolator<T extends RealFieldElement<T>>
+    extends RungeKuttaFieldStepInterpolator<T> {
+
+    /** Interpolation weights.
+     * (beware that only the non-null values are in the table)
+     */
+    private final T[][] d;
+
+    /** Simple constructor.
+     * @param field field to which the time and state vector elements belong
+     * @param forward integration direction indicator
+     * @param yDotK slopes at the intermediate points
+     * @param globalPreviousState start of the global step
+     * @param globalCurrentState end of the global step
+     * @param softPreviousState start of the restricted step
+     * @param softCurrentState end of the restricted step
+     * @param mapper equations mapper for the all equations
+     */
+    DormandPrince853FieldStepInterpolator(final Field<T> field, final boolean forward,
+                                          final T[][] yDotK,
+                                          final FieldODEStateAndDerivative<T> globalPreviousState,
+                                          final FieldODEStateAndDerivative<T> globalCurrentState,
+                                          final FieldODEStateAndDerivative<T> softPreviousState,
+                                          final FieldODEStateAndDerivative<T> softCurrentState,
+                                          final FieldEquationsMapper<T> mapper) {
+        super(field, forward, yDotK,
+              globalPreviousState, globalCurrentState, softPreviousState, softCurrentState,
+              mapper);
+        // interpolation weights
+        d = MathArrays.buildArray(field, 7, 16);
+
+        // this row is the same as the b array
+        d[0][ 0] = fraction(field, 104257, 1920240);
+        d[0][ 1] = field.getZero();
+        d[0][ 2] = field.getZero();
+        d[0][ 3] = field.getZero();
+        d[0][ 4] = field.getZero();
+        d[0][ 5] = fraction(field,         3399327.0,          763840.0);
+        d[0][ 6] = fraction(field,        66578432.0,        35198415.0);
+        d[0][ 7] = fraction(field,     -1674902723.0,       288716400.0);
+        d[0][ 8] = fraction(field,  54980371265625.0, 176692375811392.0);
+        d[0][ 9] = fraction(field,         -734375.0,         4826304.0);
+        d[0][10] = fraction(field,       171414593.0,       851261400.0);
+        d[0][11] = fraction(field,          137909.0,         3084480.0);
+        d[0][12] = field.getZero();
+        d[0][13] = field.getZero();
+        d[0][14] = field.getZero();
+        d[0][15] = field.getZero();
+
+        d[1][ 0] = d[0][ 0].negate().add(1);
+        d[1][ 1] = d[0][ 1].negate();
+        d[1][ 2] = d[0][ 2].negate();
+        d[1][ 3] = d[0][ 3].negate();
+        d[1][ 4] = d[0][ 4].negate();
+        d[1][ 5] = d[0][ 5].negate();
+        d[1][ 6] = d[0][ 6].negate();
+        d[1][ 7] = d[0][ 7].negate();
+        d[1][ 8] = d[0][ 8].negate();
+        d[1][ 9] = d[0][ 9].negate();
+        d[1][10] = d[0][10].negate();
+        d[1][11] = d[0][11].negate();
+        d[1][12] = d[0][12].negate(); // really 0
+        d[1][13] = d[0][13].negate(); // really 0
+        d[1][14] = d[0][14].negate(); // really 0
+        d[1][15] = d[0][15].negate(); // really 0
+
+        d[2][ 0] = d[0][ 0].multiply(2).subtract(1);
+        d[2][ 1] = d[0][ 1].multiply(2);
+        d[2][ 2] = d[0][ 2].multiply(2);
+        d[2][ 3] = d[0][ 3].multiply(2);
+        d[2][ 4] = d[0][ 4].multiply(2);
+        d[2][ 5] = d[0][ 5].multiply(2);
+        d[2][ 6] = d[0][ 6].multiply(2);
+        d[2][ 7] = d[0][ 7].multiply(2);
+        d[2][ 8] = d[0][ 8].multiply(2);
+        d[2][ 9] = d[0][ 9].multiply(2);
+        d[2][10] = d[0][10].multiply(2);
+        d[2][11] = d[0][11].multiply(2);
+        d[2][12] = d[0][12].multiply(2).subtract(1); // really -1
+        d[2][13] = d[0][13].multiply(2);             // really  0
+        d[2][14] = d[0][14].multiply(2);             // really  0
+        d[2][15] = d[0][15].multiply(2);             // really  0
+
+        d[3][ 0] = fraction(field,         -17751989329.0, 2106076560.0);
+        d[3][ 1] = field.getZero();
+        d[3][ 2] = field.getZero();
+        d[3][ 3] = field.getZero();
+        d[3][ 4] = field.getZero();
+        d[3][ 5] = fraction(field,           4272954039.0, 7539864640.0);
+        d[3][ 6] = fraction(field,        -118476319744.0, 38604839385.0);
+        d[3][ 7] = fraction(field,         755123450731.0, 316657731600.0);
+        d[3][ 8] = fraction(field,  3692384461234828125.0, 1744130441634250432.0);
+        d[3][ 9] = fraction(field,          -4612609375.0, 5293382976.0);
+        d[3][10] = fraction(field,        2091772278379.0, 933644586600.0);
+        d[3][11] = fraction(field,           2136624137.0, 3382989120.0);
+        d[3][12] = fraction(field,              -126493.0, 1421424.0);
+        d[3][13] = fraction(field,             98350000.0, 5419179.0);
+        d[3][14] = fraction(field,            -18878125.0, 2053168.0);
+        d[3][15] = fraction(field,          -1944542619.0, 438351368.0);
+
+        d[4][ 0] = fraction(field,          32941697297.0, 3159114840.0);
+        d[4][ 1] = field.getZero();
+        d[4][ 2] = field.getZero();
+        d[4][ 3] = field.getZero();
+        d[4][ 4] = field.getZero();
+        d[4][ 5] = fraction(field,         456696183123.0, 1884966160.0);
+        d[4][ 6] = fraction(field,       19132610714624.0, 115814518155.0);
+        d[4][ 7] = fraction(field,     -177904688592943.0, 474986597400.0);
+        d[4][ 8] = fraction(field, -4821139941836765625.0, 218016305204281304.0);
+        d[4][ 9] = fraction(field,          30702015625.0, 3970037232.0);
+        d[4][10] = fraction(field,      -85916079474274.0, 2800933759800.0);
+        d[4][11] = fraction(field,          -5919468007.0, 634310460.0);
+        d[4][12] = fraction(field,              2479159.0, 157936.0);
+        d[4][13] = fraction(field,            -18750000.0, 602131.0);
+        d[4][14] = fraction(field,            -19203125.0, 2053168.0);
+        d[4][15] = fraction(field,          15700361463.0, 438351368.0);
+
+        d[5][ 0] = fraction(field,          12627015655.0, 631822968.0);
+        d[5][ 1] = field.getZero();
+        d[5][ 2] = field.getZero();
+        d[5][ 3] = field.getZero();
+        d[5][ 4] = field.getZero();
+        d[5][ 5] = fraction(field,         -72955222965.0, 188496616.0);
+        d[5][ 6] = fraction(field,      -13145744952320.0, 69488710893.0);
+        d[5][ 7] = fraction(field,       30084216194513.0, 56998391688.0);
+        d[5][ 8] = fraction(field,  -296858761006640625.0, 25648977082856624.0);
+        d[5][ 9] = fraction(field,            569140625.0, 82709109.0);
+        d[5][10] = fraction(field,         -18684190637.0, 18672891732.0);
+        d[5][11] = fraction(field,             69644045.0, 89549712.0);
+        d[5][12] = fraction(field,            -11847025.0, 4264272.0);
+        d[5][13] = fraction(field,           -978650000.0, 16257537.0);
+        d[5][14] = fraction(field,            519371875.0, 6159504.0);
+        d[5][15] = fraction(field,           5256837225.0, 438351368.0);
+
+        d[6][ 0] = fraction(field,           -450944925.0, 17550638.0);
+        d[6][ 1] = field.getZero();
+        d[6][ 2] = field.getZero();
+        d[6][ 3] = field.getZero();
+        d[6][ 4] = field.getZero();
+        d[6][ 5] = fraction(field,         -14532122925.0, 94248308.0);
+        d[6][ 6] = fraction(field,        -595876966400.0, 2573655959.0);
+        d[6][ 7] = fraction(field,         188748653015.0, 527762886.0);
+        d[6][ 8] = fraction(field,  2545485458115234375.0, 27252038150535163.0);
+        d[6][ 9] = fraction(field,          -1376953125.0, 36759604.0);
+        d[6][10] = fraction(field,          53995596795.0, 518691437.0);
+        d[6][11] = fraction(field,            210311225.0, 7047894.0);
+        d[6][12] = fraction(field,             -1718875.0, 39484.0);
+        d[6][13] = fraction(field,             58000000.0, 602131.0);
+        d[6][14] = fraction(field,             -1546875.0, 39484.0);
+        d[6][15] = fraction(field,          -1262172375.0, 8429834.0);
+
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected DormandPrince853FieldStepInterpolator<T> create(final Field<T> newField, final boolean newForward, final T[][] newYDotK,
+                                                               final FieldODEStateAndDerivative<T> newGlobalPreviousState,
+                                                               final FieldODEStateAndDerivative<T> newGlobalCurrentState,
+                                                               final FieldODEStateAndDerivative<T> newSoftPreviousState,
+                                                               final FieldODEStateAndDerivative<T> newSoftCurrentState,
+                                                               final FieldEquationsMapper<T> newMapper) {
+        return new DormandPrince853FieldStepInterpolator<T>(newField, newForward, newYDotK,
+                                                            newGlobalPreviousState, newGlobalCurrentState,
+                                                            newSoftPreviousState, newSoftCurrentState,
+                                                            newMapper);
+    }
+
+    /** Create a fraction.
+     * @param field field to which the elements belong
+     * @param p numerator
+     * @param q denominator
+     * @return p/q computed in the instance field
+     */
+    private T fraction(final Field<T> field, final double p, final double q) {
+        return field.getZero().add(p).divide(q);
+    }
+
+    /** {@inheritDoc} */
+    @SuppressWarnings("unchecked")
+    @Override
+    protected FieldODEStateAndDerivative<T> computeInterpolatedStateAndDerivatives(final FieldEquationsMapper<T> mapper,
+                                                                                   final T time, final T theta,
+                                                                                   final T thetaH, final T oneMinusThetaH)
+        throws MaxCountExceededException {
+
+        final T one      = time.getField().getOne();
+        final T eta      = one.subtract(theta);
+        final T twoTheta = theta.multiply(2);
+        final T theta2   = theta.multiply(theta);
+        final T dot1     = one.subtract(twoTheta);
+        final T dot2     = theta.multiply(theta.multiply(-3).add(2));
+        final T dot3     = twoTheta.multiply(theta.multiply(twoTheta.subtract(3)).add(1));
+        final T dot4     = theta2.multiply(theta.multiply(theta.multiply(5).subtract(8)).add(3));
+        final T dot5     = theta2.multiply(theta.multiply(theta.multiply(theta.multiply(-6).add(15)).subtract(12)).add(3));
+        final T dot6     = theta2.multiply(theta.multiply(theta.multiply(theta.multiply(theta.multiply(-7).add(18)).subtract(15)).add(4)));
+        final T[] interpolatedState;
+        final T[] interpolatedDerivatives;
+
+
+        if (getGlobalPreviousState() != null && theta.getReal() <= 0.5) {
+            final T f0 = thetaH;
+            final T f1 = f0.multiply(eta);
+            final T f2 = f1.multiply(theta);
+            final T f3 = f2.multiply(eta);
+            final T f4 = f3.multiply(theta);
+            final T f5 = f4.multiply(eta);
+            final T f6 = f5.multiply(theta);
+            final T[] p = MathArrays.buildArray(time.getField(), 16);
+            final T[] q = MathArrays.buildArray(time.getField(), 16);
+            for (int i = 0; i < p.length; ++i) {
+                p[i] =     f0.multiply(d[0][i]).
+                       add(f1.multiply(d[1][i])).
+                       add(f2.multiply(d[2][i])).
+                       add(f3.multiply(d[3][i])).
+                       add(f4.multiply(d[4][i])).
+                       add(f5.multiply(d[5][i])).
+                       add(f6.multiply(d[6][i]));
+                q[i] =                    d[0][i].
+                        add(dot1.multiply(d[1][i])).
+                        add(dot2.multiply(d[2][i])).
+                        add(dot3.multiply(d[3][i])).
+                        add(dot4.multiply(d[4][i])).
+                        add(dot5.multiply(d[5][i])).
+                        add(dot6.multiply(d[6][i]));
+            }
+            interpolatedState       = previousStateLinearCombination(p[0], p[1], p[ 2], p[ 3], p[ 4], p[ 5], p[ 6], p[ 7],
+                                                                     p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]);
+            interpolatedDerivatives = derivativeLinearCombination(q[0], q[1], q[ 2], q[ 3], q[ 4], q[ 5], q[ 6], q[ 7],
+                                                                  q[8], q[9], q[10], q[11], q[12], q[13], q[14], q[15]);
+        } else {
+            final T f0 = oneMinusThetaH.negate();
+            final T f1 = f0.multiply(theta).negate();
+            final T f2 = f1.multiply(theta);
+            final T f3 = f2.multiply(eta);
+            final T f4 = f3.multiply(theta);
+            final T f5 = f4.multiply(eta);
+            final T f6 = f5.multiply(theta);
+            final T[] p = MathArrays.buildArray(time.getField(), 16);
+            final T[] q = MathArrays.buildArray(time.getField(), 16);
+            for (int i = 0; i < p.length; ++i) {
+                p[i] =     f0.multiply(d[0][i]).
+                       add(f1.multiply(d[1][i])).
+                       add(f2.multiply(d[2][i])).
+                       add(f3.multiply(d[3][i])).
+                       add(f4.multiply(d[4][i])).
+                       add(f5.multiply(d[5][i])).
+                       add(f6.multiply(d[6][i]));
+                q[i] =                    d[0][i].
+                        add(dot1.multiply(d[1][i])).
+                        add(dot2.multiply(d[2][i])).
+                        add(dot3.multiply(d[3][i])).
+                        add(dot4.multiply(d[4][i])).
+                        add(dot5.multiply(d[5][i])).
+                        add(dot6.multiply(d[6][i]));
+            }
+            interpolatedState       = currentStateLinearCombination(p[0], p[1], p[ 2], p[ 3], p[ 4], p[ 5], p[ 6], p[ 7],
+                                                                    p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]);
+            interpolatedDerivatives = derivativeLinearCombination(q[0], q[1], q[ 2], q[ 3], q[ 4], q[ 5], q[ 6], q[ 7],
+                                                                  q[8], q[9], q[10], q[11], q[12], q[13], q[14], q[15]);
+        }
+
+        return new FieldODEStateAndDerivative<T>(time, interpolatedState, interpolatedDerivatives);
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince853Integrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince853Integrator.java
new file mode 100644
index 0000000..895cb88
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince853Integrator.java
@@ -0,0 +1,286 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.util.FastMath;
+
+
+/**
+ * This class implements the 8(5,3) Dormand-Prince integrator for Ordinary
+ * Differential Equations.
+ *
+ * <p>This integrator is an embedded Runge-Kutta integrator
+ * of order 8(5,3) used in local extrapolation mode (i.e. the solution
+ * is computed using the high order formula) with stepsize control
+ * (and automatic step initialization) and continuous output. This
+ * method uses 12 functions evaluations per step for integration and 4
+ * evaluations for interpolation. However, since the first
+ * interpolation evaluation is the same as the first integration
+ * evaluation of the next step, we have included it in the integrator
+ * rather than in the interpolator and specified the method was an
+ * <i>fsal</i>. Hence, despite we have 13 stages here, the cost is
+ * really 12 evaluations per step even if no interpolation is done,
+ * and the overcost of interpolation is only 3 evaluations.</p>
+ *
+ * <p>This method is based on an 8(6) method by Dormand and Prince
+ * (i.e. order 8 for the integration and order 6 for error estimation)
+ * modified by Hairer and Wanner to use a 5th order error estimator
+ * with 3rd order correction. This modification was introduced because
+ * the original method failed in some cases (wrong steps can be
+ * accepted when step size is too large, for example in the
+ * Brusselator problem) and also had <i>severe difficulties when
+ * applied to problems with discontinuities</i>. This modification is
+ * explained in the second edition of the first volume (Nonstiff
+ * Problems) of the reference book by Hairer, Norsett and Wanner:
+ * <i>Solving Ordinary Differential Equations</i> (Springer-Verlag,
+ * ISBN 3-540-56670-8).</p>
+ *
+ * @since 1.2
+ */
+
+public class DormandPrince853Integrator extends EmbeddedRungeKuttaIntegrator {
+
+  /** Integrator method name. */
+  private static final String METHOD_NAME = "Dormand-Prince 8 (5, 3)";
+
+  /** Time steps Butcher array. */
+  private static final double[] STATIC_C = {
+    (12.0 - 2.0 * FastMath.sqrt(6.0)) / 135.0, (6.0 - FastMath.sqrt(6.0)) / 45.0, (6.0 - FastMath.sqrt(6.0)) / 30.0,
+    (6.0 + FastMath.sqrt(6.0)) / 30.0, 1.0/3.0, 1.0/4.0, 4.0/13.0, 127.0/195.0, 3.0/5.0,
+    6.0/7.0, 1.0, 1.0
+  };
+
+  /** Internal weights Butcher array. */
+  private static final double[][] STATIC_A = {
+
+    // k2
+    {(12.0 - 2.0 * FastMath.sqrt(6.0)) / 135.0},
+
+    // k3
+    {(6.0 - FastMath.sqrt(6.0)) / 180.0, (6.0 - FastMath.sqrt(6.0)) / 60.0},
+
+    // k4
+    {(6.0 - FastMath.sqrt(6.0)) / 120.0, 0.0, (6.0 - FastMath.sqrt(6.0)) / 40.0},
+
+    // k5
+    {(462.0 + 107.0 * FastMath.sqrt(6.0)) / 3000.0, 0.0,
+     (-402.0 - 197.0 * FastMath.sqrt(6.0)) / 1000.0, (168.0 + 73.0 * FastMath.sqrt(6.0)) / 375.0},
+
+    // k6
+    {1.0 / 27.0, 0.0, 0.0, (16.0 + FastMath.sqrt(6.0)) / 108.0, (16.0 - FastMath.sqrt(6.0)) / 108.0},
+
+    // k7
+    {19.0 / 512.0, 0.0, 0.0, (118.0 + 23.0 * FastMath.sqrt(6.0)) / 1024.0,
+     (118.0 - 23.0 * FastMath.sqrt(6.0)) / 1024.0, -9.0 / 512.0},
+
+    // k8
+    {13772.0 / 371293.0, 0.0, 0.0, (51544.0 + 4784.0 * FastMath.sqrt(6.0)) / 371293.0,
+     (51544.0 - 4784.0 * FastMath.sqrt(6.0)) / 371293.0, -5688.0 / 371293.0, 3072.0 / 371293.0},
+
+    // k9
+    {58656157643.0 / 93983540625.0, 0.0, 0.0,
+     (-1324889724104.0 - 318801444819.0 * FastMath.sqrt(6.0)) / 626556937500.0,
+     (-1324889724104.0 + 318801444819.0 * FastMath.sqrt(6.0)) / 626556937500.0,
+     96044563816.0 / 3480871875.0, 5682451879168.0 / 281950621875.0,
+     -165125654.0 / 3796875.0},
+
+    // k10
+    {8909899.0 / 18653125.0, 0.0, 0.0,
+     (-4521408.0 - 1137963.0 * FastMath.sqrt(6.0)) / 2937500.0,
+     (-4521408.0 + 1137963.0 * FastMath.sqrt(6.0)) / 2937500.0,
+     96663078.0 / 4553125.0, 2107245056.0 / 137915625.0,
+     -4913652016.0 / 147609375.0, -78894270.0 / 3880452869.0},
+
+    // k11
+    {-20401265806.0 / 21769653311.0, 0.0, 0.0,
+     (354216.0 + 94326.0 * FastMath.sqrt(6.0)) / 112847.0,
+     (354216.0 - 94326.0 * FastMath.sqrt(6.0)) / 112847.0,
+     -43306765128.0 / 5313852383.0, -20866708358144.0 / 1126708119789.0,
+     14886003438020.0 / 654632330667.0, 35290686222309375.0 / 14152473387134411.0,
+     -1477884375.0 / 485066827.0},
+
+    // k12
+    {39815761.0 / 17514443.0, 0.0, 0.0,
+     (-3457480.0 - 960905.0 * FastMath.sqrt(6.0)) / 551636.0,
+     (-3457480.0 + 960905.0 * FastMath.sqrt(6.0)) / 551636.0,
+     -844554132.0 / 47026969.0, 8444996352.0 / 302158619.0,
+     -2509602342.0 / 877790785.0, -28388795297996250.0 / 3199510091356783.0,
+     226716250.0 / 18341897.0, 1371316744.0 / 2131383595.0},
+
+    // k13 should be for interpolation only, but since it is the same
+    // stage as the first evaluation of the next step, we perform it
+    // here at no cost by specifying this is an fsal method
+    {104257.0/1920240.0, 0.0, 0.0, 0.0, 0.0, 3399327.0/763840.0,
+     66578432.0/35198415.0, -1674902723.0/288716400.0,
+     54980371265625.0/176692375811392.0, -734375.0/4826304.0,
+     171414593.0/851261400.0, 137909.0/3084480.0}
+
+  };
+
+  /** Propagation weights Butcher array. */
+  private static final double[] STATIC_B = {
+      104257.0/1920240.0,
+      0.0,
+      0.0,
+      0.0,
+      0.0,
+      3399327.0/763840.0,
+      66578432.0/35198415.0,
+      -1674902723.0/288716400.0,
+      54980371265625.0/176692375811392.0,
+      -734375.0/4826304.0,
+      171414593.0/851261400.0,
+      137909.0/3084480.0,
+      0.0
+  };
+
+  /** First error weights array, element 1. */
+  private static final double E1_01 =         116092271.0 / 8848465920.0;
+
+  // elements 2 to 5 are zero, so they are neither stored nor used
+
+  /** First error weights array, element 6. */
+  private static final double E1_06 =          -1871647.0 / 1527680.0;
+
+  /** First error weights array, element 7. */
+  private static final double E1_07 =         -69799717.0 / 140793660.0;
+
+  /** First error weights array, element 8. */
+  private static final double E1_08 =     1230164450203.0 / 739113984000.0;
+
+  /** First error weights array, element 9. */
+  private static final double E1_09 = -1980813971228885.0 / 5654156025964544.0;
+
+  /** First error weights array, element 10. */
+  private static final double E1_10 =         464500805.0 / 1389975552.0;
+
+  /** First error weights array, element 11. */
+  private static final double E1_11 =     1606764981773.0 / 19613062656000.0;
+
+  /** First error weights array, element 12. */
+  private static final double E1_12 =           -137909.0 / 6168960.0;
+
+
+  /** Second error weights array, element 1. */
+  private static final double E2_01 =           -364463.0 / 1920240.0;
+
+  // elements 2 to 5 are zero, so they are neither stored nor used
+
+  /** Second error weights array, element 6. */
+  private static final double E2_06 =           3399327.0 / 763840.0;
+
+  /** Second error weights array, element 7. */
+  private static final double E2_07 =          66578432.0 / 35198415.0;
+
+  /** Second error weights array, element 8. */
+  private static final double E2_08 =       -1674902723.0 / 288716400.0;
+
+  /** Second error weights array, element 9. */
+  private static final double E2_09 =   -74684743568175.0 / 176692375811392.0;
+
+  /** Second error weights array, element 10. */
+  private static final double E2_10 =           -734375.0 / 4826304.0;
+
+  /** Second error weights array, element 11. */
+  private static final double E2_11 =         171414593.0 / 851261400.0;
+
+  /** Second error weights array, element 12. */
+  private static final double E2_12 =             69869.0 / 3084480.0;
+
+  /** Simple constructor.
+   * Build an eighth order Dormand-Prince integrator with the given step bounds
+   * @param minStep minimal step (sign is irrelevant, regardless of
+   * integration direction, forward or backward), the last step can
+   * be smaller than this
+   * @param maxStep maximal step (sign is irrelevant, regardless of
+   * integration direction, forward or backward), the last step can
+   * be smaller than this
+   * @param scalAbsoluteTolerance allowed absolute error
+   * @param scalRelativeTolerance allowed relative error
+   */
+  public DormandPrince853Integrator(final double minStep, final double maxStep,
+                                    final double scalAbsoluteTolerance,
+                                    final double scalRelativeTolerance) {
+    super(METHOD_NAME, true, STATIC_C, STATIC_A, STATIC_B,
+          new DormandPrince853StepInterpolator(),
+          minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance);
+  }
+
+  /** Simple constructor.
+   * Build an eighth order Dormand-Prince integrator with the given step bounds
+   * @param minStep minimal step (sign is irrelevant, regardless of
+   * integration direction, forward or backward), the last step can
+   * be smaller than this
+   * @param maxStep maximal step (sign is irrelevant, regardless of
+   * integration direction, forward or backward), the last step can
+   * be smaller than this
+   * @param vecAbsoluteTolerance allowed absolute error
+   * @param vecRelativeTolerance allowed relative error
+   */
+  public DormandPrince853Integrator(final double minStep, final double maxStep,
+                                    final double[] vecAbsoluteTolerance,
+                                    final double[] vecRelativeTolerance) {
+    super(METHOD_NAME, true, STATIC_C, STATIC_A, STATIC_B,
+          new DormandPrince853StepInterpolator(),
+          minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance);
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public int getOrder() {
+    return 8;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  protected double estimateError(final double[][] yDotK,
+                                 final double[] y0, final double[] y1,
+                                 final double h) {
+    double error1 = 0;
+    double error2 = 0;
+
+    for (int j = 0; j < mainSetDimension; ++j) {
+      final double errSum1 = E1_01 * yDotK[0][j]  + E1_06 * yDotK[5][j] +
+                             E1_07 * yDotK[6][j]  + E1_08 * yDotK[7][j] +
+                             E1_09 * yDotK[8][j]  + E1_10 * yDotK[9][j] +
+                             E1_11 * yDotK[10][j] + E1_12 * yDotK[11][j];
+      final double errSum2 = E2_01 * yDotK[0][j]  + E2_06 * yDotK[5][j] +
+                             E2_07 * yDotK[6][j]  + E2_08 * yDotK[7][j] +
+                             E2_09 * yDotK[8][j]  + E2_10 * yDotK[9][j] +
+                             E2_11 * yDotK[10][j] + E2_12 * yDotK[11][j];
+
+      final double yScale = FastMath.max(FastMath.abs(y0[j]), FastMath.abs(y1[j]));
+      final double tol = (vecAbsoluteTolerance == null) ?
+                         (scalAbsoluteTolerance + scalRelativeTolerance * yScale) :
+                         (vecAbsoluteTolerance[j] + vecRelativeTolerance[j] * yScale);
+      final double ratio1  = errSum1 / tol;
+      error1        += ratio1 * ratio1;
+      final double ratio2  = errSum2 / tol;
+      error2        += ratio2 * ratio2;
+    }
+
+    double den = error1 + 0.01 * error2;
+    if (den <= 0.0) {
+      den = 1.0;
+    }
+
+    return FastMath.abs(h) * error1 / FastMath.sqrt(mainSetDimension * den);
+
+  }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince853StepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince853StepInterpolator.java
new file mode 100644
index 0000000..6d6ff6c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/DormandPrince853StepInterpolator.java
@@ -0,0 +1,501 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.ode.AbstractIntegrator;
+import org.apache.commons.math3.ode.EquationsMapper;
+import org.apache.commons.math3.ode.sampling.StepInterpolator;
+
+/**
+ * This class represents an interpolator over the last step during an
+ * ODE integration for the 8(5,3) Dormand-Prince integrator.
+ *
+ * @see DormandPrince853Integrator
+ *
+ * @since 1.2
+ */
+
+class DormandPrince853StepInterpolator
+  extends RungeKuttaStepInterpolator {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 20111120L;
+
+    /** Propagation weights, element 1. */
+    private static final double B_01 =         104257.0 / 1920240.0;
+
+    // elements 2 to 5 are zero, so they are neither stored nor used
+
+    /** Propagation weights, element 6. */
+    private static final double B_06 =        3399327.0 / 763840.0;
+
+    /** Propagation weights, element 7. */
+    private static final double B_07 =       66578432.0 / 35198415.0;
+
+    /** Propagation weights, element 8. */
+    private static final double B_08 =    -1674902723.0 / 288716400.0;
+
+    /** Propagation weights, element 9. */
+    private static final double B_09 = 54980371265625.0 / 176692375811392.0;
+
+    /** Propagation weights, element 10. */
+    private static final double B_10 =        -734375.0 / 4826304.0;
+
+    /** Propagation weights, element 11. */
+    private static final double B_11 =      171414593.0 / 851261400.0;
+
+    /** Propagation weights, element 12. */
+    private static final double B_12 =         137909.0 / 3084480.0;
+
+    /** Time step for stage 14 (interpolation only). */
+    private static final double C14    = 1.0 / 10.0;
+
+    /** Internal weights for stage 14, element 1. */
+    private static final double K14_01 =       13481885573.0 / 240030000000.0      - B_01;
+
+    // elements 2 to 5 are zero, so they are neither stored nor used
+
+    /** Internal weights for stage 14, element 6. */
+    private static final double K14_06 =                 0.0                       - B_06;
+
+    /** Internal weights for stage 14, element 7. */
+    private static final double K14_07 =      139418837528.0 / 549975234375.0      - B_07;
+
+    /** Internal weights for stage 14, element 8. */
+    private static final double K14_08 =   -11108320068443.0 / 45111937500000.0    - B_08;
+
+    /** Internal weights for stage 14, element 9. */
+    private static final double K14_09 = -1769651421925959.0 / 14249385146080000.0 - B_09;
+
+    /** Internal weights for stage 14, element 10. */
+    private static final double K14_10 =          57799439.0 / 377055000.0         - B_10;
+
+    /** Internal weights for stage 14, element 11. */
+    private static final double K14_11 =      793322643029.0 / 96734250000000.0    - B_11;
+
+    /** Internal weights for stage 14, element 12. */
+    private static final double K14_12 =        1458939311.0 / 192780000000.0      - B_12;
+
+    /** Internal weights for stage 14, element 13. */
+    private static final double K14_13 =             -4149.0 / 500000.0;
+
+    /** Time step for stage 15 (interpolation only). */
+    private static final double C15    = 1.0 / 5.0;
+
+
+    /** Internal weights for stage 15, element 1. */
+    private static final double K15_01 =     1595561272731.0 / 50120273500000.0    - B_01;
+
+    // elements 2 to 5 are zero, so they are neither stored nor used
+
+    /** Internal weights for stage 15, element 6. */
+    private static final double K15_06 =      975183916491.0 / 34457688031250.0    - B_06;
+
+    /** Internal weights for stage 15, element 7. */
+    private static final double K15_07 =    38492013932672.0 / 718912673015625.0   - B_07;
+
+    /** Internal weights for stage 15, element 8. */
+    private static final double K15_08 = -1114881286517557.0 / 20298710767500000.0 - B_08;
+
+    /** Internal weights for stage 15, element 9. */
+    private static final double K15_09 =                 0.0                       - B_09;
+
+    /** Internal weights for stage 15, element 10. */
+    private static final double K15_10 =                 0.0                       - B_10;
+
+    /** Internal weights for stage 15, element 11. */
+    private static final double K15_11 =    -2538710946863.0 / 23431227861250000.0 - B_11;
+
+    /** Internal weights for stage 15, element 12. */
+    private static final double K15_12 =        8824659001.0 / 23066716781250.0    - B_12;
+
+    /** Internal weights for stage 15, element 13. */
+    private static final double K15_13 =      -11518334563.0 / 33831184612500.0;
+
+    /** Internal weights for stage 15, element 14. */
+    private static final double K15_14 =        1912306948.0 / 13532473845.0;
+
+    /** Time step for stage 16 (interpolation only). */
+    private static final double C16    = 7.0 / 9.0;
+
+
+    /** Internal weights for stage 16, element 1. */
+    private static final double K16_01 =      -13613986967.0 / 31741908048.0       - B_01;
+
+    // elements 2 to 5 are zero, so they are neither stored nor used
+
+    /** Internal weights for stage 16, element 6. */
+    private static final double K16_06 =       -4755612631.0 / 1012344804.0        - B_06;
+
+    /** Internal weights for stage 16, element 7. */
+    private static final double K16_07 =    42939257944576.0 / 5588559685701.0     - B_07;
+
+    /** Internal weights for stage 16, element 8. */
+    private static final double K16_08 =    77881972900277.0 / 19140370552944.0    - B_08;
+
+    /** Internal weights for stage 16, element 9. */
+    private static final double K16_09 =    22719829234375.0 / 63689648654052.0    - B_09;
+
+    /** Internal weights for stage 16, element 10. */
+    private static final double K16_10 =                 0.0                       - B_10;
+
+    /** Internal weights for stage 16, element 11. */
+    private static final double K16_11 =                 0.0                       - B_11;
+
+    /** Internal weights for stage 16, element 12. */
+    private static final double K16_12 =                 0.0                       - B_12;
+
+    /** Internal weights for stage 16, element 13. */
+    private static final double K16_13 =       -1199007803.0 / 857031517296.0;
+
+    /** Internal weights for stage 16, element 14. */
+    private static final double K16_14 =      157882067000.0 / 53564469831.0;
+
+    /** Internal weights for stage 16, element 15. */
+    private static final double K16_15 =     -290468882375.0 / 31741908048.0;
+
+    /** Interpolation weights.
+     * (beware that only the non-null values are in the table)
+     */
+    private static final double[][] D = {
+
+      {        -17751989329.0 / 2106076560.0,               4272954039.0 / 7539864640.0,
+              -118476319744.0 / 38604839385.0,            755123450731.0 / 316657731600.0,
+        3692384461234828125.0 / 1744130441634250432.0,     -4612609375.0 / 5293382976.0,
+              2091772278379.0 / 933644586600.0,             2136624137.0 / 3382989120.0,
+                    -126493.0 / 1421424.0,                    98350000.0 / 5419179.0,
+                  -18878125.0 / 2053168.0,                 -1944542619.0 / 438351368.0},
+
+      {         32941697297.0 / 3159114840.0,             456696183123.0 / 1884966160.0,
+             19132610714624.0 / 115814518155.0,       -177904688592943.0 / 474986597400.0,
+       -4821139941836765625.0 / 218016305204281304.0,      30702015625.0 / 3970037232.0,
+            -85916079474274.0 / 2800933759800.0,           -5919468007.0 / 634310460.0,
+                    2479159.0 / 157936.0,                    -18750000.0 / 602131.0,
+                  -19203125.0 / 2053168.0,                 15700361463.0 / 438351368.0},
+
+      {         12627015655.0 / 631822968.0,              -72955222965.0 / 188496616.0,
+            -13145744952320.0 / 69488710893.0,          30084216194513.0 / 56998391688.0,
+        -296858761006640625.0 / 25648977082856624.0,         569140625.0 / 82709109.0,
+               -18684190637.0 / 18672891732.0,                69644045.0 / 89549712.0,
+                  -11847025.0 / 4264272.0,                  -978650000.0 / 16257537.0,
+                  519371875.0 / 6159504.0,                  5256837225.0 / 438351368.0},
+
+      {          -450944925.0 / 17550638.0,               -14532122925.0 / 94248308.0,
+              -595876966400.0 / 2573655959.0,             188748653015.0 / 527762886.0,
+        2545485458115234375.0 / 27252038150535163.0,       -1376953125.0 / 36759604.0,
+                53995596795.0 / 518691437.0,                 210311225.0 / 7047894.0,
+                   -1718875.0 / 39484.0,                      58000000.0 / 602131.0,
+                   -1546875.0 / 39484.0,                   -1262172375.0 / 8429834.0}
+
+    };
+
+    /** Last evaluations. */
+    private double[][] yDotKLast;
+
+    /** Vectors for interpolation. */
+    private double[][] v;
+
+    /** Initialization indicator for the interpolation vectors. */
+    private boolean vectorsInitialized;
+
+  /** Simple constructor.
+   * This constructor builds an instance that is not usable yet, the
+   * {@link #reinitialize} method should be called before using the
+   * instance in order to initialize the internal arrays. This
+   * constructor is used only in order to delay the initialization in
+   * some cases. The {@link EmbeddedRungeKuttaIntegrator} uses the
+   * prototyping design pattern to create the step interpolators by
+   * cloning an uninitialized model and latter initializing the copy.
+   */
+  // CHECKSTYLE: stop RedundantModifier
+  // the public modifier here is needed for serialization
+  public DormandPrince853StepInterpolator() {
+    super();
+    yDotKLast = null;
+    v         = null;
+    vectorsInitialized = false;
+  }
+  // CHECKSTYLE: resume RedundantModifier
+
+  /** Copy constructor.
+   * @param interpolator interpolator to copy from. The copy is a deep
+   * copy: its arrays are separated from the original arrays of the
+   * instance
+   */
+  DormandPrince853StepInterpolator(final DormandPrince853StepInterpolator interpolator) {
+
+    super(interpolator);
+
+    if (interpolator.currentState == null) {
+
+      yDotKLast = null;
+      v         = null;
+      vectorsInitialized = false;
+
+    } else {
+
+      final int dimension = interpolator.currentState.length;
+
+      yDotKLast    = new double[3][];
+      for (int k = 0; k < yDotKLast.length; ++k) {
+        yDotKLast[k] = new double[dimension];
+        System.arraycopy(interpolator.yDotKLast[k], 0, yDotKLast[k], 0,
+                         dimension);
+      }
+
+      v = new double[7][];
+      for (int k = 0; k < v.length; ++k) {
+        v[k] = new double[dimension];
+        System.arraycopy(interpolator.v[k], 0, v[k], 0, dimension);
+      }
+
+      vectorsInitialized = interpolator.vectorsInitialized;
+
+    }
+
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  protected StepInterpolator doCopy() {
+    return new DormandPrince853StepInterpolator(this);
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public void reinitialize(final AbstractIntegrator integrator,
+                           final double[] y, final double[][] yDotK, final boolean forward,
+                           final EquationsMapper primaryMapper,
+                           final EquationsMapper[] secondaryMappers) {
+
+    super.reinitialize(integrator, y, yDotK, forward, primaryMapper, secondaryMappers);
+
+    final int dimension = currentState.length;
+
+    yDotKLast = new double[3][];
+    for (int k = 0; k < yDotKLast.length; ++k) {
+      yDotKLast[k] = new double[dimension];
+    }
+
+    v = new double[7][];
+    for (int k = 0; k < v.length; ++k) {
+      v[k]  = new double[dimension];
+    }
+
+    vectorsInitialized = false;
+
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public void storeTime(final double t) {
+    super.storeTime(t);
+    vectorsInitialized = false;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  protected void computeInterpolatedStateAndDerivatives(final double theta,
+                                          final double oneMinusThetaH)
+      throws MaxCountExceededException {
+
+    if (! vectorsInitialized) {
+
+      if (v == null) {
+        v = new double[7][];
+        for (int k = 0; k < 7; ++k) {
+          v[k] = new double[interpolatedState.length];
+        }
+      }
+
+      // perform the last evaluations if they have not been done yet
+      finalizeStep();
+
+      // compute the interpolation vectors for this time step
+      for (int i = 0; i < interpolatedState.length; ++i) {
+          final double yDot1  = yDotK[0][i];
+          final double yDot6  = yDotK[5][i];
+          final double yDot7  = yDotK[6][i];
+          final double yDot8  = yDotK[7][i];
+          final double yDot9  = yDotK[8][i];
+          final double yDot10 = yDotK[9][i];
+          final double yDot11 = yDotK[10][i];
+          final double yDot12 = yDotK[11][i];
+          final double yDot13 = yDotK[12][i];
+          final double yDot14 = yDotKLast[0][i];
+          final double yDot15 = yDotKLast[1][i];
+          final double yDot16 = yDotKLast[2][i];
+          v[0][i] = B_01 * yDot1  + B_06 * yDot6 + B_07 * yDot7 +
+                    B_08 * yDot8  + B_09 * yDot9 + B_10 * yDot10 +
+                    B_11 * yDot11 + B_12 * yDot12;
+          v[1][i] = yDot1 - v[0][i];
+          v[2][i] = v[0][i] - v[1][i] - yDotK[12][i];
+          for (int k = 0; k < D.length; ++k) {
+              v[k+3][i] = D[k][0] * yDot1  + D[k][1]  * yDot6  + D[k][2]  * yDot7  +
+                          D[k][3] * yDot8  + D[k][4]  * yDot9  + D[k][5]  * yDot10 +
+                          D[k][6] * yDot11 + D[k][7]  * yDot12 + D[k][8]  * yDot13 +
+                          D[k][9] * yDot14 + D[k][10] * yDot15 + D[k][11] * yDot16;
+          }
+      }
+
+      vectorsInitialized = true;
+
+    }
+
+    final double eta      = 1 - theta;
+    final double twoTheta = 2 * theta;
+    final double theta2   = theta * theta;
+    final double dot1 = 1 - twoTheta;
+    final double dot2 = theta * (2 - 3 * theta);
+    final double dot3 = twoTheta * (1 + theta * (twoTheta -3));
+    final double dot4 = theta2 * (3 + theta * (5 * theta - 8));
+    final double dot5 = theta2 * (3 + theta * (-12 + theta * (15 - 6 * theta)));
+    final double dot6 = theta2 * theta * (4 + theta * (-15 + theta * (18 - 7 * theta)));
+
+    if ((previousState != null) && (theta <= 0.5)) {
+        for (int i = 0; i < interpolatedState.length; ++i) {
+            interpolatedState[i] = previousState[i] +
+                    theta * h * (v[0][i] +
+                            eta * (v[1][i] +
+                                    theta * (v[2][i] +
+                                            eta * (v[3][i] +
+                                                    theta * (v[4][i] +
+                                                            eta * (v[5][i] +
+                                                                    theta * (v[6][i])))))));
+            interpolatedDerivatives[i] =  v[0][i] + dot1 * v[1][i] + dot2 * v[2][i] +
+                    dot3 * v[3][i] + dot4 * v[4][i] +
+                    dot5 * v[5][i] + dot6 * v[6][i];
+        }
+    } else {
+        for (int i = 0; i < interpolatedState.length; ++i) {
+            interpolatedState[i] = currentState[i] -
+                    oneMinusThetaH * (v[0][i] -
+                            theta * (v[1][i] +
+                                    theta * (v[2][i] +
+                                            eta * (v[3][i] +
+                                                    theta * (v[4][i] +
+                                                            eta * (v[5][i] +
+                                                                    theta * (v[6][i])))))));
+            interpolatedDerivatives[i] =  v[0][i] + dot1 * v[1][i] + dot2 * v[2][i] +
+                    dot3 * v[3][i] + dot4 * v[4][i] +
+                    dot5 * v[5][i] + dot6 * v[6][i];
+        }
+    }
+
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  protected void doFinalize() throws MaxCountExceededException {
+
+      if (currentState == null) {
+          // we are finalizing an uninitialized instance
+          return;
+      }
+
+      double s;
+      final double[] yTmp = new double[currentState.length];
+      final double pT = getGlobalPreviousTime();
+
+      // k14
+      for (int j = 0; j < currentState.length; ++j) {
+          s = K14_01 * yDotK[0][j]  + K14_06 * yDotK[5][j]  + K14_07 * yDotK[6][j] +
+                  K14_08 * yDotK[7][j]  + K14_09 * yDotK[8][j]  + K14_10 * yDotK[9][j] +
+                  K14_11 * yDotK[10][j] + K14_12 * yDotK[11][j] + K14_13 * yDotK[12][j];
+          yTmp[j] = currentState[j] + h * s;
+      }
+      integrator.computeDerivatives(pT + C14 * h, yTmp, yDotKLast[0]);
+
+      // k15
+      for (int j = 0; j < currentState.length; ++j) {
+          s = K15_01 * yDotK[0][j]  + K15_06 * yDotK[5][j]  + K15_07 * yDotK[6][j] +
+                  K15_08 * yDotK[7][j]  + K15_09 * yDotK[8][j]  + K15_10 * yDotK[9][j] +
+                  K15_11 * yDotK[10][j] + K15_12 * yDotK[11][j] + K15_13 * yDotK[12][j] +
+                  K15_14 * yDotKLast[0][j];
+          yTmp[j] = currentState[j] + h * s;
+      }
+      integrator.computeDerivatives(pT + C15 * h, yTmp, yDotKLast[1]);
+
+      // k16
+      for (int j = 0; j < currentState.length; ++j) {
+          s = K16_01 * yDotK[0][j]  + K16_06 * yDotK[5][j]  + K16_07 * yDotK[6][j] +
+                  K16_08 * yDotK[7][j]  + K16_09 * yDotK[8][j]  + K16_10 * yDotK[9][j] +
+                  K16_11 * yDotK[10][j] + K16_12 * yDotK[11][j] + K16_13 * yDotK[12][j] +
+                  K16_14 * yDotKLast[0][j] +  K16_15 * yDotKLast[1][j];
+          yTmp[j] = currentState[j] + h * s;
+      }
+      integrator.computeDerivatives(pT + C16 * h, yTmp, yDotKLast[2]);
+
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public void writeExternal(final ObjectOutput out)
+    throws IOException {
+
+    try {
+        // save the local attributes
+        finalizeStep();
+    } catch (MaxCountExceededException mcee) {
+        final IOException ioe = new IOException(mcee.getLocalizedMessage());
+        ioe.initCause(mcee);
+        throw ioe;
+    }
+
+    final int dimension = (currentState == null) ? -1 : currentState.length;
+    out.writeInt(dimension);
+    for (int i = 0; i < dimension; ++i) {
+      out.writeDouble(yDotKLast[0][i]);
+      out.writeDouble(yDotKLast[1][i]);
+      out.writeDouble(yDotKLast[2][i]);
+    }
+
+    // save the state of the base class
+    super.writeExternal(out);
+
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public void readExternal(final ObjectInput in)
+    throws IOException, ClassNotFoundException {
+
+    // read the local attributes
+    yDotKLast = new double[3][];
+    final int dimension = in.readInt();
+    yDotKLast[0] = (dimension < 0) ? null : new double[dimension];
+    yDotKLast[1] = (dimension < 0) ? null : new double[dimension];
+    yDotKLast[2] = (dimension < 0) ? null : new double[dimension];
+
+    for (int i = 0; i < dimension; ++i) {
+      yDotKLast[0][i] = in.readDouble();
+      yDotKLast[1][i] = in.readDouble();
+      yDotKLast[2][i] = in.readDouble();
+    }
+
+    // read the base state
+    super.readExternal(in);
+
+  }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/EmbeddedRungeKuttaFieldIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/EmbeddedRungeKuttaFieldIntegrator.java
new file mode 100644
index 0000000..036cf01
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/EmbeddedRungeKuttaFieldIntegrator.java
@@ -0,0 +1,385 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NoBracketingException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.ode.FieldEquationsMapper;
+import org.apache.commons.math3.ode.FieldExpandableODE;
+import org.apache.commons.math3.ode.FieldODEState;
+import org.apache.commons.math3.ode.FieldODEStateAndDerivative;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * This class implements the common part of all embedded Runge-Kutta
+ * integrators for Ordinary Differential Equations.
+ *
+ * <p>These methods are embedded explicit Runge-Kutta methods with two
+ * sets of coefficients allowing to estimate the error, their Butcher
+ * arrays are as follows :
+ * <pre>
+ *    0  |
+ *   c2  | a21
+ *   c3  | a31  a32
+ *   ... |        ...
+ *   cs  | as1  as2  ...  ass-1
+ *       |--------------------------
+ *       |  b1   b2  ...   bs-1  bs
+ *       |  b'1  b'2 ...   b's-1 b's
+ * </pre>
+ * </p>
+ *
+ * <p>In fact, we rather use the array defined by ej = bj - b'j to
+ * compute directly the error rather than computing two estimates and
+ * then comparing them.</p>
+ *
+ * <p>Some methods are qualified as <i>fsal</i> (first same as last)
+ * methods. This means the last evaluation of the derivatives in one
+ * step is the same as the first in the next step. Then, this
+ * evaluation can be reused from one step to the next one and the cost
+ * of such a method is really s-1 evaluations despite the method still
+ * has s stages. This behaviour is true only for successful steps, if
+ * the step is rejected after the error estimation phase, no
+ * evaluation is saved. For an <i>fsal</i> method, we have cs = 1 and
+ * asi = bi for all i.</p>
+ *
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+
+public abstract class EmbeddedRungeKuttaFieldIntegrator<T extends RealFieldElement<T>>
+    extends AdaptiveStepsizeFieldIntegrator<T>
+    implements FieldButcherArrayProvider<T> {
+
+    /** Index of the pre-computed derivative for <i>fsal</i> methods. */
+    private final int fsal;
+
+    /** Time steps from Butcher array (without the first zero). */
+    private final T[] c;
+
+    /** Internal weights from Butcher array (without the first empty row). */
+    private final T[][] a;
+
+    /** External weights for the high order method from Butcher array. */
+    private final T[] b;
+
+    /** Stepsize control exponent. */
+    private final T exp;
+
+    /** Safety factor for stepsize control. */
+    private T safety;
+
+    /** Minimal reduction factor for stepsize control. */
+    private T minReduction;
+
+    /** Maximal growth factor for stepsize control. */
+    private T maxGrowth;
+
+    /** Build a Runge-Kutta integrator with the given Butcher array.
+     * @param field field to which the time and state vector elements belong
+     * @param name name of the method
+     * @param fsal index of the pre-computed derivative for <i>fsal</i> methods
+     * or -1 if method is not <i>fsal</i>
+     * @param minStep minimal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param maxStep maximal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param scalAbsoluteTolerance allowed absolute error
+     * @param scalRelativeTolerance allowed relative error
+     */
+    protected EmbeddedRungeKuttaFieldIntegrator(final Field<T> field, final String name, final int fsal,
+                                                final double minStep, final double maxStep,
+                                                final double scalAbsoluteTolerance,
+                                                final double scalRelativeTolerance) {
+
+        super(field, name, minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance);
+
+        this.fsal = fsal;
+        this.c    = getC();
+        this.a    = getA();
+        this.b    = getB();
+
+        exp = field.getOne().divide(-getOrder());
+
+        // set the default values of the algorithm control parameters
+        setSafety(field.getZero().add(0.9));
+        setMinReduction(field.getZero().add(0.2));
+        setMaxGrowth(field.getZero().add(10.0));
+
+    }
+
+    /** Build a Runge-Kutta integrator with the given Butcher array.
+     * @param field field to which the time and state vector elements belong
+     * @param name name of the method
+     * @param fsal index of the pre-computed derivative for <i>fsal</i> methods
+     * or -1 if method is not <i>fsal</i>
+     * @param minStep minimal step (must be positive even for backward
+     * integration), the last step can be smaller than this
+     * @param maxStep maximal step (must be positive even for backward
+     * integration)
+     * @param vecAbsoluteTolerance allowed absolute error
+     * @param vecRelativeTolerance allowed relative error
+     */
+    protected EmbeddedRungeKuttaFieldIntegrator(final Field<T> field, final String name, final int fsal,
+                                                final double   minStep, final double maxStep,
+                                                final double[] vecAbsoluteTolerance,
+                                                final double[] vecRelativeTolerance) {
+
+        super(field, name, minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance);
+
+        this.fsal = fsal;
+        this.c    = getC();
+        this.a    = getA();
+        this.b    = getB();
+
+        exp = field.getOne().divide(-getOrder());
+
+        // set the default values of the algorithm control parameters
+        setSafety(field.getZero().add(0.9));
+        setMinReduction(field.getZero().add(0.2));
+        setMaxGrowth(field.getZero().add(10.0));
+
+    }
+
+    /** Create a fraction.
+     * @param p numerator
+     * @param q denominator
+     * @return p/q computed in the instance field
+     */
+    protected T fraction(final int p, final int q) {
+        return getField().getOne().multiply(p).divide(q);
+    }
+
+    /** Create a fraction.
+     * @param p numerator
+     * @param q denominator
+     * @return p/q computed in the instance field
+     */
+    protected T fraction(final double p, final double q) {
+        return getField().getOne().multiply(p).divide(q);
+    }
+
+    /** Create an interpolator.
+     * @param forward integration direction indicator
+     * @param yDotK slopes at the intermediate points
+     * @param globalPreviousState start of the global step
+     * @param globalCurrentState end of the global step
+     * @param mapper equations mapper for the all equations
+     * @return external weights for the high order method from Butcher array
+     */
+    protected abstract RungeKuttaFieldStepInterpolator<T> createInterpolator(boolean forward, T[][] yDotK,
+                                                                             final FieldODEStateAndDerivative<T> globalPreviousState,
+                                                                             final FieldODEStateAndDerivative<T> globalCurrentState,
+                                                                             FieldEquationsMapper<T> mapper);
+    /** Get the order of the method.
+     * @return order of the method
+     */
+    public abstract int getOrder();
+
+    /** Get the safety factor for stepsize control.
+     * @return safety factor
+     */
+    public T getSafety() {
+        return safety;
+    }
+
+    /** Set the safety factor for stepsize control.
+     * @param safety safety factor
+     */
+    public void setSafety(final T safety) {
+        this.safety = safety;
+    }
+
+    /** {@inheritDoc} */
+    public FieldODEStateAndDerivative<T> integrate(final FieldExpandableODE<T> equations,
+                                                   final FieldODEState<T> initialState, final T finalTime)
+        throws NumberIsTooSmallException, DimensionMismatchException,
+        MaxCountExceededException, NoBracketingException {
+
+        sanityChecks(initialState, finalTime);
+        final T   t0 = initialState.getTime();
+        final T[] y0 = equations.getMapper().mapState(initialState);
+        setStepStart(initIntegration(equations, t0, y0, finalTime));
+        final boolean forward = finalTime.subtract(initialState.getTime()).getReal() > 0;
+
+        // create some internal working arrays
+        final int   stages = c.length + 1;
+        T[]         y      = y0;
+        final T[][] yDotK  = MathArrays.buildArray(getField(), stages, -1);
+        final T[]   yTmp   = MathArrays.buildArray(getField(), y0.length);
+
+        // set up integration control objects
+        T  hNew           = getField().getZero();
+        boolean firstTime = true;
+
+        // main integration loop
+        setIsLastStep(false);
+        do {
+
+            // iterate over step size, ensuring local normalized error is smaller than 1
+            T error = getField().getZero().add(10);
+            while (error.subtract(1.0).getReal() >= 0) {
+
+                // first stage
+                y        = equations.getMapper().mapState(getStepStart());
+                yDotK[0] = equations.getMapper().mapDerivative(getStepStart());
+
+                if (firstTime) {
+                    final T[] scale = MathArrays.buildArray(getField(), mainSetDimension);
+                    if (vecAbsoluteTolerance == null) {
+                        for (int i = 0; i < scale.length; ++i) {
+                            scale[i] = y[i].abs().multiply(scalRelativeTolerance).add(scalAbsoluteTolerance);
+                        }
+                    } else {
+                        for (int i = 0; i < scale.length; ++i) {
+                            scale[i] = y[i].abs().multiply(vecRelativeTolerance[i]).add(vecAbsoluteTolerance[i]);
+                        }
+                    }
+                    hNew = initializeStep(forward, getOrder(), scale, getStepStart(), equations.getMapper());
+                    firstTime = false;
+                }
+
+                setStepSize(hNew);
+                if (forward) {
+                    if (getStepStart().getTime().add(getStepSize()).subtract(finalTime).getReal() >= 0) {
+                        setStepSize(finalTime.subtract(getStepStart().getTime()));
+                    }
+                } else {
+                    if (getStepStart().getTime().add(getStepSize()).subtract(finalTime).getReal() <= 0) {
+                        setStepSize(finalTime.subtract(getStepStart().getTime()));
+                    }
+                }
+
+                // next stages
+                for (int k = 1; k < stages; ++k) {
+
+                    for (int j = 0; j < y0.length; ++j) {
+                        T sum = yDotK[0][j].multiply(a[k-1][0]);
+                        for (int l = 1; l < k; ++l) {
+                            sum = sum.add(yDotK[l][j].multiply(a[k-1][l]));
+                        }
+                        yTmp[j] = y[j].add(getStepSize().multiply(sum));
+                    }
+
+                    yDotK[k] = computeDerivatives(getStepStart().getTime().add(getStepSize().multiply(c[k-1])), yTmp);
+
+                }
+
+                // estimate the state at the end of the step
+                for (int j = 0; j < y0.length; ++j) {
+                    T sum    = yDotK[0][j].multiply(b[0]);
+                    for (int l = 1; l < stages; ++l) {
+                        sum = sum.add(yDotK[l][j].multiply(b[l]));
+                    }
+                    yTmp[j] = y[j].add(getStepSize().multiply(sum));
+                }
+
+                // estimate the error at the end of the step
+                error = estimateError(yDotK, y, yTmp, getStepSize());
+                if (error.subtract(1.0).getReal() >= 0) {
+                    // reject the step and attempt to reduce error by stepsize control
+                    final T factor = MathUtils.min(maxGrowth,
+                                                   MathUtils.max(minReduction, safety.multiply(error.pow(exp))));
+                    hNew = filterStep(getStepSize().multiply(factor), forward, false);
+                }
+
+            }
+            final T   stepEnd = getStepStart().getTime().add(getStepSize());
+            final T[] yDotTmp = (fsal >= 0) ? yDotK[fsal] : computeDerivatives(stepEnd, yTmp);
+            final FieldODEStateAndDerivative<T> stateTmp = new FieldODEStateAndDerivative<T>(stepEnd, yTmp, yDotTmp);
+
+            // local error is small enough: accept the step, trigger events and step handlers
+            System.arraycopy(yTmp, 0, y, 0, y0.length);
+            setStepStart(acceptStep(createInterpolator(forward, yDotK, getStepStart(), stateTmp, equations.getMapper()),
+                                    finalTime));
+
+            if (!isLastStep()) {
+
+                // stepsize control for next step
+                final T factor = MathUtils.min(maxGrowth,
+                                               MathUtils.max(minReduction, safety.multiply(error.pow(exp))));
+                final T  scaledH    = getStepSize().multiply(factor);
+                final T  nextT      = getStepStart().getTime().add(scaledH);
+                final boolean nextIsLast = forward ?
+                                           nextT.subtract(finalTime).getReal() >= 0 :
+                                           nextT.subtract(finalTime).getReal() <= 0;
+                hNew = filterStep(scaledH, forward, nextIsLast);
+
+                final T  filteredNextT      = getStepStart().getTime().add(hNew);
+                final boolean filteredNextIsLast = forward ?
+                                                   filteredNextT.subtract(finalTime).getReal() >= 0 :
+                                                   filteredNextT.subtract(finalTime).getReal() <= 0;
+                if (filteredNextIsLast) {
+                    hNew = finalTime.subtract(getStepStart().getTime());
+                }
+
+            }
+
+        } while (!isLastStep());
+
+        final FieldODEStateAndDerivative<T> finalState = getStepStart();
+        resetInternalState();
+        return finalState;
+
+    }
+
+    /** Get the minimal reduction factor for stepsize control.
+     * @return minimal reduction factor
+     */
+    public T getMinReduction() {
+        return minReduction;
+    }
+
+    /** Set the minimal reduction factor for stepsize control.
+     * @param minReduction minimal reduction factor
+     */
+    public void setMinReduction(final T minReduction) {
+        this.minReduction = minReduction;
+    }
+
+    /** Get the maximal growth factor for stepsize control.
+     * @return maximal growth factor
+     */
+    public T getMaxGrowth() {
+        return maxGrowth;
+    }
+
+    /** Set the maximal growth factor for stepsize control.
+     * @param maxGrowth maximal growth factor
+     */
+    public void setMaxGrowth(final T maxGrowth) {
+        this.maxGrowth = maxGrowth;
+    }
+
+    /** Compute the error ratio.
+     * @param yDotK derivatives computed during the first stages
+     * @param y0 estimate of the step at the start of the step
+     * @param y1 estimate of the step at the end of the step
+     * @param h  current step
+     * @return error ratio, greater than 1 if step should be rejected
+     */
+    protected abstract T estimateError(T[][] yDotK, T[] y0, T[] y1, T h);
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/EmbeddedRungeKuttaIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/EmbeddedRungeKuttaIntegrator.java
new file mode 100644
index 0000000..098d2e5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/EmbeddedRungeKuttaIntegrator.java
@@ -0,0 +1,380 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NoBracketingException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.ode.ExpandableStatefulODE;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * This class implements the common part of all embedded Runge-Kutta
+ * integrators for Ordinary Differential Equations.
+ *
+ * <p>These methods are embedded explicit Runge-Kutta methods with two
+ * sets of coefficients allowing to estimate the error, their Butcher
+ * arrays are as follows :
+ * <pre>
+ *    0  |
+ *   c2  | a21
+ *   c3  | a31  a32
+ *   ... |        ...
+ *   cs  | as1  as2  ...  ass-1
+ *       |--------------------------
+ *       |  b1   b2  ...   bs-1  bs
+ *       |  b'1  b'2 ...   b's-1 b's
+ * </pre>
+ * </p>
+ *
+ * <p>In fact, we rather use the array defined by ej = bj - b'j to
+ * compute directly the error rather than computing two estimates and
+ * then comparing them.</p>
+ *
+ * <p>Some methods are qualified as <i>fsal</i> (first same as last)
+ * methods. This means the last evaluation of the derivatives in one
+ * step is the same as the first in the next step. Then, this
+ * evaluation can be reused from one step to the next one and the cost
+ * of such a method is really s-1 evaluations despite the method still
+ * has s stages. This behaviour is true only for successful steps, if
+ * the step is rejected after the error estimation phase, no
+ * evaluation is saved. For an <i>fsal</i> method, we have cs = 1 and
+ * asi = bi for all i.</p>
+ *
+ * @since 1.2
+ */
+
+public abstract class EmbeddedRungeKuttaIntegrator
+  extends AdaptiveStepsizeIntegrator {
+
+    /** Indicator for <i>fsal</i> methods. */
+    private final boolean fsal;
+
+    /** Time steps from Butcher array (without the first zero). */
+    private final double[] c;
+
+    /** Internal weights from Butcher array (without the first empty row). */
+    private final double[][] a;
+
+    /** External weights for the high order method from Butcher array. */
+    private final double[] b;
+
+    /** Prototype of the step interpolator. */
+    private final RungeKuttaStepInterpolator prototype;
+
+    /** Stepsize control exponent. */
+    private final double exp;
+
+    /** Safety factor for stepsize control. */
+    private double safety;
+
+    /** Minimal reduction factor for stepsize control. */
+    private double minReduction;
+
+    /** Maximal growth factor for stepsize control. */
+    private double maxGrowth;
+
+  /** Build a Runge-Kutta integrator with the given Butcher array.
+   * @param name name of the method
+   * @param fsal indicate that the method is an <i>fsal</i>
+   * @param c time steps from Butcher array (without the first zero)
+   * @param a internal weights from Butcher array (without the first empty row)
+   * @param b propagation weights for the high order method from Butcher array
+   * @param prototype prototype of the step interpolator to use
+   * @param minStep minimal step (sign is irrelevant, regardless of
+   * integration direction, forward or backward), the last step can
+   * be smaller than this
+   * @param maxStep maximal step (sign is irrelevant, regardless of
+   * integration direction, forward or backward), the last step can
+   * be smaller than this
+   * @param scalAbsoluteTolerance allowed absolute error
+   * @param scalRelativeTolerance allowed relative error
+   */
+  protected EmbeddedRungeKuttaIntegrator(final String name, final boolean fsal,
+                                         final double[] c, final double[][] a, final double[] b,
+                                         final RungeKuttaStepInterpolator prototype,
+                                         final double minStep, final double maxStep,
+                                         final double scalAbsoluteTolerance,
+                                         final double scalRelativeTolerance) {
+
+    super(name, minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance);
+
+    this.fsal      = fsal;
+    this.c         = c;
+    this.a         = a;
+    this.b         = b;
+    this.prototype = prototype;
+
+    exp = -1.0 / getOrder();
+
+    // set the default values of the algorithm control parameters
+    setSafety(0.9);
+    setMinReduction(0.2);
+    setMaxGrowth(10.0);
+
+  }
+
+  /** Build a Runge-Kutta integrator with the given Butcher array.
+   * @param name name of the method
+   * @param fsal indicate that the method is an <i>fsal</i>
+   * @param c time steps from Butcher array (without the first zero)
+   * @param a internal weights from Butcher array (without the first empty row)
+   * @param b propagation weights for the high order method from Butcher array
+   * @param prototype prototype of the step interpolator to use
+   * @param minStep minimal step (must be positive even for backward
+   * integration), the last step can be smaller than this
+   * @param maxStep maximal step (must be positive even for backward
+   * integration)
+   * @param vecAbsoluteTolerance allowed absolute error
+   * @param vecRelativeTolerance allowed relative error
+   */
+  protected EmbeddedRungeKuttaIntegrator(final String name, final boolean fsal,
+                                         final double[] c, final double[][] a, final double[] b,
+                                         final RungeKuttaStepInterpolator prototype,
+                                         final double   minStep, final double maxStep,
+                                         final double[] vecAbsoluteTolerance,
+                                         final double[] vecRelativeTolerance) {
+
+    super(name, minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance);
+
+    this.fsal      = fsal;
+    this.c         = c;
+    this.a         = a;
+    this.b         = b;
+    this.prototype = prototype;
+
+    exp = -1.0 / getOrder();
+
+    // set the default values of the algorithm control parameters
+    setSafety(0.9);
+    setMinReduction(0.2);
+    setMaxGrowth(10.0);
+
+  }
+
+  /** Get the order of the method.
+   * @return order of the method
+   */
+  public abstract int getOrder();
+
+  /** Get the safety factor for stepsize control.
+   * @return safety factor
+   */
+  public double getSafety() {
+    return safety;
+  }
+
+  /** Set the safety factor for stepsize control.
+   * @param safety safety factor
+   */
+  public void setSafety(final double safety) {
+    this.safety = safety;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public void integrate(final ExpandableStatefulODE equations, final double t)
+      throws NumberIsTooSmallException, DimensionMismatchException,
+             MaxCountExceededException, NoBracketingException {
+
+    sanityChecks(equations, t);
+    setEquations(equations);
+    final boolean forward = t > equations.getTime();
+
+    // create some internal working arrays
+    final double[] y0  = equations.getCompleteState();
+    final double[] y = y0.clone();
+    final int stages = c.length + 1;
+    final double[][] yDotK = new double[stages][y.length];
+    final double[] yTmp    = y0.clone();
+    final double[] yDotTmp = new double[y.length];
+
+    // set up an interpolator sharing the integrator arrays
+    final RungeKuttaStepInterpolator interpolator = (RungeKuttaStepInterpolator) prototype.copy();
+    interpolator.reinitialize(this, yTmp, yDotK, forward,
+                              equations.getPrimaryMapper(), equations.getSecondaryMappers());
+    interpolator.storeTime(equations.getTime());
+
+    // set up integration control objects
+    stepStart         = equations.getTime();
+    double  hNew      = 0;
+    boolean firstTime = true;
+    initIntegration(equations.getTime(), y0, t);
+
+    // main integration loop
+    isLastStep = false;
+    do {
+
+      interpolator.shift();
+
+      // iterate over step size, ensuring local normalized error is smaller than 1
+      double error = 10;
+      while (error >= 1.0) {
+
+        if (firstTime || !fsal) {
+          // first stage
+          computeDerivatives(stepStart, y, yDotK[0]);
+        }
+
+        if (firstTime) {
+          final double[] scale = new double[mainSetDimension];
+          if (vecAbsoluteTolerance == null) {
+              for (int i = 0; i < scale.length; ++i) {
+                scale[i] = scalAbsoluteTolerance + scalRelativeTolerance * FastMath.abs(y[i]);
+              }
+          } else {
+              for (int i = 0; i < scale.length; ++i) {
+                scale[i] = vecAbsoluteTolerance[i] + vecRelativeTolerance[i] * FastMath.abs(y[i]);
+              }
+          }
+          hNew = initializeStep(forward, getOrder(), scale,
+                                stepStart, y, yDotK[0], yTmp, yDotK[1]);
+          firstTime = false;
+        }
+
+        stepSize = hNew;
+        if (forward) {
+            if (stepStart + stepSize >= t) {
+                stepSize = t - stepStart;
+            }
+        } else {
+            if (stepStart + stepSize <= t) {
+                stepSize = t - stepStart;
+            }
+        }
+
+        // next stages
+        for (int k = 1; k < stages; ++k) {
+
+          for (int j = 0; j < y0.length; ++j) {
+            double sum = a[k-1][0] * yDotK[0][j];
+            for (int l = 1; l < k; ++l) {
+              sum += a[k-1][l] * yDotK[l][j];
+            }
+            yTmp[j] = y[j] + stepSize * sum;
+          }
+
+          computeDerivatives(stepStart + c[k-1] * stepSize, yTmp, yDotK[k]);
+
+        }
+
+        // estimate the state at the end of the step
+        for (int j = 0; j < y0.length; ++j) {
+          double sum    = b[0] * yDotK[0][j];
+          for (int l = 1; l < stages; ++l) {
+            sum    += b[l] * yDotK[l][j];
+          }
+          yTmp[j] = y[j] + stepSize * sum;
+        }
+
+        // estimate the error at the end of the step
+        error = estimateError(yDotK, y, yTmp, stepSize);
+        if (error >= 1.0) {
+          // reject the step and attempt to reduce error by stepsize control
+          final double factor =
+              FastMath.min(maxGrowth,
+                           FastMath.max(minReduction, safety * FastMath.pow(error, exp)));
+          hNew = filterStep(stepSize * factor, forward, false);
+        }
+
+      }
+
+      // local error is small enough: accept the step, trigger events and step handlers
+      interpolator.storeTime(stepStart + stepSize);
+      System.arraycopy(yTmp, 0, y, 0, y0.length);
+      System.arraycopy(yDotK[stages - 1], 0, yDotTmp, 0, y0.length);
+      stepStart = acceptStep(interpolator, y, yDotTmp, t);
+      System.arraycopy(y, 0, yTmp, 0, y.length);
+
+      if (!isLastStep) {
+
+          // prepare next step
+          interpolator.storeTime(stepStart);
+
+          if (fsal) {
+              // save the last evaluation for the next step
+              System.arraycopy(yDotTmp, 0, yDotK[0], 0, y0.length);
+          }
+
+          // stepsize control for next step
+          final double factor =
+              FastMath.min(maxGrowth, FastMath.max(minReduction, safety * FastMath.pow(error, exp)));
+          final double  scaledH    = stepSize * factor;
+          final double  nextT      = stepStart + scaledH;
+          final boolean nextIsLast = forward ? (nextT >= t) : (nextT <= t);
+          hNew = filterStep(scaledH, forward, nextIsLast);
+
+          final double  filteredNextT      = stepStart + hNew;
+          final boolean filteredNextIsLast = forward ? (filteredNextT >= t) : (filteredNextT <= t);
+          if (filteredNextIsLast) {
+              hNew = t - stepStart;
+          }
+
+      }
+
+    } while (!isLastStep);
+
+    // dispatch results
+    equations.setTime(stepStart);
+    equations.setCompleteState(y);
+
+    resetInternalState();
+
+  }
+
+  /** Get the minimal reduction factor for stepsize control.
+   * @return minimal reduction factor
+   */
+  public double getMinReduction() {
+    return minReduction;
+  }
+
+  /** Set the minimal reduction factor for stepsize control.
+   * @param minReduction minimal reduction factor
+   */
+  public void setMinReduction(final double minReduction) {
+    this.minReduction = minReduction;
+  }
+
+  /** Get the maximal growth factor for stepsize control.
+   * @return maximal growth factor
+   */
+  public double getMaxGrowth() {
+    return maxGrowth;
+  }
+
+  /** Set the maximal growth factor for stepsize control.
+   * @param maxGrowth maximal growth factor
+   */
+  public void setMaxGrowth(final double maxGrowth) {
+    this.maxGrowth = maxGrowth;
+  }
+
+  /** Compute the error ratio.
+   * @param yDotK derivatives computed during the first stages
+   * @param y0 estimate of the step at the start of the step
+   * @param y1 estimate of the step at the end of the step
+   * @param h  current step
+   * @return error ratio, greater than 1 if step should be rejected
+   */
+  protected abstract double estimateError(double[][] yDotK,
+                                          double[] y0, double[] y1,
+                                          double h);
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/EulerFieldIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/EulerFieldIntegrator.java
new file mode 100644
index 0000000..38516cf
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/EulerFieldIntegrator.java
@@ -0,0 +1,96 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.ode.FieldEquationsMapper;
+import org.apache.commons.math3.ode.FieldODEStateAndDerivative;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * This class implements a simple Euler integrator for Ordinary
+ * Differential Equations.
+ *
+ * <p>The Euler algorithm is the simplest one that can be used to
+ * integrate ordinary differential equations. It is a simple inversion
+ * of the forward difference expression :
+ * <code>f'=(f(t+h)-f(t))/h</code> which leads to
+ * <code>f(t+h)=f(t)+hf'</code>. The interpolation scheme used for
+ * dense output is the linear scheme already used for integration.</p>
+ *
+ * <p>This algorithm looks cheap because it needs only one function
+ * evaluation per step. However, as it uses linear estimates, it needs
+ * very small steps to achieve high accuracy, and small steps lead to
+ * numerical errors and instabilities.</p>
+ *
+ * <p>This algorithm is almost never used and has been included in
+ * this package only as a comparison reference for more useful
+ * integrators.</p>
+ *
+ * @see MidpointFieldIntegrator
+ * @see ClassicalRungeKuttaFieldIntegrator
+ * @see GillFieldIntegrator
+ * @see ThreeEighthesFieldIntegrator
+ * @see LutherFieldIntegrator
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+
+public class EulerFieldIntegrator<T extends RealFieldElement<T>> extends RungeKuttaFieldIntegrator<T> {
+
+    /** Simple constructor.
+     * Build an Euler integrator with the given step.
+     * @param field field to which the time and state vector elements belong
+     * @param step integration step
+     */
+    public EulerFieldIntegrator(final Field<T> field, final T step) {
+        super(field, "Euler", step);
+    }
+
+    /** {@inheritDoc} */
+    public T[] getC() {
+        return MathArrays.buildArray(getField(), 0);
+    }
+
+    /** {@inheritDoc} */
+    public T[][] getA() {
+        return MathArrays.buildArray(getField(), 0, 0);
+    }
+
+    /** {@inheritDoc} */
+    public T[] getB() {
+        final T[] b = MathArrays.buildArray(getField(), 1);
+        b[0] = getField().getOne();
+        return b;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected EulerFieldStepInterpolator<T>
+        createInterpolator(final boolean forward, T[][] yDotK,
+                           final FieldODEStateAndDerivative<T> globalPreviousState,
+                           final FieldODEStateAndDerivative<T> globalCurrentState,
+                           final FieldEquationsMapper<T> mapper) {
+        return new EulerFieldStepInterpolator<T>(getField(), forward, yDotK,
+                                                 globalPreviousState, globalCurrentState,
+                                                 globalPreviousState, globalCurrentState,
+                                                 mapper);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/EulerFieldStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/EulerFieldStepInterpolator.java
new file mode 100644
index 0000000..0907b8e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/EulerFieldStepInterpolator.java
@@ -0,0 +1,108 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.ode.FieldEquationsMapper;
+import org.apache.commons.math3.ode.FieldODEStateAndDerivative;
+
+/**
+ * This class implements a linear interpolator for step.
+ *
+ * <p>This interpolator computes dense output inside the last
+ * step computed. The interpolation equation is consistent with the
+ * integration scheme :
+ * <ul>
+ *   <li>Using reference point at step start:<br>
+ *     y(t<sub>n</sub> + &theta; h) = y (t<sub>n</sub>) + &theta; h y'
+ *   </li>
+ *   <li>Using reference point at step end:<br>
+ *     y(t<sub>n</sub> + &theta; h) = y (t<sub>n</sub> + h) - (1-&theta;) h y'
+ *   </li>
+ * </ul>
+ * </p>
+ *
+ * where &theta; belongs to [0 ; 1] and where y' is the evaluation of
+ * the derivatives already computed during the step.</p>
+ *
+ * @see EulerFieldIntegrator
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+
+class EulerFieldStepInterpolator<T extends RealFieldElement<T>>
+    extends RungeKuttaFieldStepInterpolator<T> {
+
+    /** Simple constructor.
+     * @param field field to which the time and state vector elements belong
+     * @param forward integration direction indicator
+     * @param yDotK slopes at the intermediate points
+     * @param globalPreviousState start of the global step
+     * @param globalCurrentState end of the global step
+     * @param softPreviousState start of the restricted step
+     * @param softCurrentState end of the restricted step
+     * @param mapper equations mapper for the all equations
+     */
+    EulerFieldStepInterpolator(final Field<T> field, final boolean forward,
+                                             final T[][] yDotK,
+                                             final FieldODEStateAndDerivative<T> globalPreviousState,
+                                             final FieldODEStateAndDerivative<T> globalCurrentState,
+                                             final FieldODEStateAndDerivative<T> softPreviousState,
+                                             final FieldODEStateAndDerivative<T> softCurrentState,
+                                             final FieldEquationsMapper<T> mapper) {
+        super(field, forward, yDotK,
+              globalPreviousState, globalCurrentState, softPreviousState, softCurrentState,
+              mapper);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected EulerFieldStepInterpolator<T> create(final Field<T> newField, final boolean newForward, final T[][] newYDotK,
+                                                                 final FieldODEStateAndDerivative<T> newGlobalPreviousState,
+                                                                 final FieldODEStateAndDerivative<T> newGlobalCurrentState,
+                                                                 final FieldODEStateAndDerivative<T> newSoftPreviousState,
+                                                                 final FieldODEStateAndDerivative<T> newSoftCurrentState,
+                                                                 final FieldEquationsMapper<T> newMapper) {
+        return new EulerFieldStepInterpolator<T>(newField, newForward, newYDotK,
+                                                 newGlobalPreviousState, newGlobalCurrentState,
+                                                 newSoftPreviousState, newSoftCurrentState,
+                                                 newMapper);
+    }
+
+    /** {@inheritDoc} */
+    @SuppressWarnings("unchecked")
+    @Override
+    protected FieldODEStateAndDerivative<T> computeInterpolatedStateAndDerivatives(final FieldEquationsMapper<T> mapper,
+                                                                                   final T time, final T theta,
+                                                                                   final T thetaH, final T oneMinusThetaH) {
+        final T[] interpolatedState;
+        final T[] interpolatedDerivatives;
+        if (getGlobalPreviousState() != null && theta.getReal() <= 0.5) {
+            interpolatedState       = previousStateLinearCombination(thetaH);
+            interpolatedDerivatives = derivativeLinearCombination(time.getField().getOne());
+        } else {
+            interpolatedState       = currentStateLinearCombination(oneMinusThetaH.negate());
+            interpolatedDerivatives = derivativeLinearCombination(time.getField().getOne());
+        }
+
+        return new FieldODEStateAndDerivative<T>(time, interpolatedState, interpolatedDerivatives);
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/EulerIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/EulerIntegrator.java
new file mode 100644
index 0000000..22c15c5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/EulerIntegrator.java
@@ -0,0 +1,72 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+
+/**
+ * This class implements a simple Euler integrator for Ordinary
+ * Differential Equations.
+ *
+ * <p>The Euler algorithm is the simplest one that can be used to
+ * integrate ordinary differential equations. It is a simple inversion
+ * of the forward difference expression :
+ * <code>f'=(f(t+h)-f(t))/h</code> which leads to
+ * <code>f(t+h)=f(t)+hf'</code>. The interpolation scheme used for
+ * dense output is the linear scheme already used for integration.</p>
+ *
+ * <p>This algorithm looks cheap because it needs only one function
+ * evaluation per step. However, as it uses linear estimates, it needs
+ * very small steps to achieve high accuracy, and small steps lead to
+ * numerical errors and instabilities.</p>
+ *
+ * <p>This algorithm is almost never used and has been included in
+ * this package only as a comparison reference for more useful
+ * integrators.</p>
+ *
+ * @see MidpointIntegrator
+ * @see ClassicalRungeKuttaIntegrator
+ * @see GillIntegrator
+ * @see ThreeEighthesIntegrator
+ * @see LutherIntegrator
+ * @since 1.2
+ */
+
+public class EulerIntegrator extends RungeKuttaIntegrator {
+
+  /** Time steps Butcher array. */
+  private static final double[] STATIC_C = {
+  };
+
+  /** Internal weights Butcher array. */
+  private static final double[][] STATIC_A = {
+  };
+
+  /** Propagation weights Butcher array. */
+  private static final double[] STATIC_B = {
+    1.0
+  };
+
+  /** Simple constructor.
+   * Build an Euler integrator with the given step.
+   * @param step integration step
+   */
+  public EulerIntegrator(final double step) {
+    super("Euler", STATIC_C, STATIC_A, STATIC_B, new EulerStepInterpolator(), step);
+  }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/EulerStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/EulerStepInterpolator.java
new file mode 100644
index 0000000..331cb14
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/EulerStepInterpolator.java
@@ -0,0 +1,102 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.ode.sampling.StepInterpolator;
+
+/**
+ * This class implements a linear interpolator for step.
+ *
+ * <p>This interpolator computes dense output inside the last
+ * step computed. The interpolation equation is consistent with the
+ * integration scheme :
+ * <ul>
+ *   <li>Using reference point at step start:<br>
+ *     y(t<sub>n</sub> + &theta; h) = y (t<sub>n</sub>) + &theta; h y'
+ *   </li>
+ *   <li>Using reference point at step end:<br>
+ *     y(t<sub>n</sub> + &theta; h) = y (t<sub>n</sub> + h) - (1-&theta;) h y'
+ *   </li>
+ * </ul>
+ * </p>
+ *
+ * where &theta; belongs to [0 ; 1] and where y' is the evaluation of
+ * the derivatives already computed during the step.</p>
+ *
+ * @see EulerIntegrator
+ * @since 1.2
+ */
+
+class EulerStepInterpolator
+  extends RungeKuttaStepInterpolator {
+
+  /** Serializable version identifier. */
+  private static final long serialVersionUID = 20111120L;
+
+  /** Simple constructor.
+   * This constructor builds an instance that is not usable yet, the
+   * {@link
+   * org.apache.commons.math3.ode.sampling.AbstractStepInterpolator#reinitialize}
+   * method should be called before using the instance in order to
+   * initialize the internal arrays. This constructor is used only
+   * in order to delay the initialization in some cases. The {@link
+   * RungeKuttaIntegrator} class uses the prototyping design pattern
+   * to create the step interpolators by cloning an uninitialized model
+   * and later initializing the copy.
+   */
+  // CHECKSTYLE: stop RedundantModifier
+  // the public modifier here is needed for serialization
+  public EulerStepInterpolator() {
+  }
+  // CHECKSTYLE: resume RedundantModifier
+
+  /** Copy constructor.
+   * @param interpolator interpolator to copy from. The copy is a deep
+   * copy: its arrays are separated from the original arrays of the
+   * instance
+   */
+  EulerStepInterpolator(final EulerStepInterpolator interpolator) {
+    super(interpolator);
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  protected StepInterpolator doCopy() {
+    return new EulerStepInterpolator(this);
+  }
+
+
+  /** {@inheritDoc} */
+  @Override
+  protected void computeInterpolatedStateAndDerivatives(final double theta,
+                                          final double oneMinusThetaH) {
+      if ((previousState != null) && (theta <= 0.5)) {
+          for (int i = 0; i < interpolatedState.length; ++i) {
+              interpolatedState[i] = previousState[i] + theta * h * yDotK[0][i];
+          }
+          System.arraycopy(yDotK[0], 0, interpolatedDerivatives, 0, interpolatedDerivatives.length);
+      } else {
+          for (int i = 0; i < interpolatedState.length; ++i) {
+              interpolatedState[i] = currentState[i] - oneMinusThetaH * yDotK[0][i];
+          }
+          System.arraycopy(yDotK[0], 0, interpolatedDerivatives, 0, interpolatedDerivatives.length);
+      }
+
+  }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/FieldButcherArrayProvider.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/FieldButcherArrayProvider.java
new file mode 100644
index 0000000..b37d4cb
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/FieldButcherArrayProvider.java
@@ -0,0 +1,46 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.RealFieldElement;
+
+/** This interface represents an integrator  based on Butcher arrays.
+ * @see RungeKuttaFieldIntegrator
+ * @see EmbeddedRungeKuttaFieldIntegrator
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+
+public interface FieldButcherArrayProvider<T extends RealFieldElement<T>> {
+
+    /** Get the time steps from Butcher array (without the first zero).
+     * @return time steps from Butcher array (without the first zero
+     */
+    T[] getC();
+
+    /** Get the internal weights from Butcher array (without the first empty row).
+     * @return internal weights from Butcher array (without the first empty row)
+     */
+    T[][] getA();
+
+    /** Get the external weights for the high order method from Butcher array.
+     * @return external weights for the high order method from Butcher array
+     */
+    T[] getB();
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/GillFieldIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/GillFieldIntegrator.java
new file mode 100644
index 0000000..d5f7c64
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/GillFieldIntegrator.java
@@ -0,0 +1,121 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.ode.FieldEquationsMapper;
+import org.apache.commons.math3.ode.FieldODEStateAndDerivative;
+import org.apache.commons.math3.util.MathArrays;
+
+
+/**
+ * This class implements the Gill fourth order Runge-Kutta
+ * integrator for Ordinary Differential Equations .
+
+ * <p>This method is an explicit Runge-Kutta method, its Butcher-array
+ * is the following one :
+ * <pre>
+ *    0  |    0        0       0      0
+ *   1/2 |   1/2       0       0      0
+ *   1/2 | (q-1)/2  (2-q)/2    0      0
+ *    1  |    0       -q/2  (2+q)/2   0
+ *       |-------------------------------
+ *       |   1/6    (2-q)/6 (2+q)/6  1/6
+ * </pre>
+ * where q = sqrt(2)</p>
+ *
+ * @see EulerFieldIntegrator
+ * @see ClassicalRungeKuttaFieldIntegrator
+ * @see MidpointFieldIntegrator
+ * @see ThreeEighthesFieldIntegrator
+ * @see LutherFieldIntegrator
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+
+public class GillFieldIntegrator<T extends RealFieldElement<T>>
+    extends RungeKuttaFieldIntegrator<T> {
+
+    /** Simple constructor.
+     * Build a fourth-order Gill integrator with the given step.
+     * @param field field to which the time and state vector elements belong
+     * @param step integration step
+     */
+    public GillFieldIntegrator(final Field<T> field, final T step) {
+        super(field, "Gill", step);
+    }
+
+    /** {@inheritDoc} */
+    public T[] getC() {
+        final T[] c = MathArrays.buildArray(getField(), 3);
+        c[0] = fraction(1, 2);
+        c[1] = c[0];
+        c[2] = getField().getOne();
+        return c;
+    }
+
+    /** {@inheritDoc} */
+    public T[][] getA() {
+
+        final T two     = getField().getZero().add(2);
+        final T sqrtTwo = two.sqrt();
+
+        final T[][] a = MathArrays.buildArray(getField(), 3, -1);
+        for (int i = 0; i < a.length; ++i) {
+            a[i] = MathArrays.buildArray(getField(), i + 1);
+        }
+        a[0][0] = fraction(1, 2);
+        a[1][0] = sqrtTwo.subtract(1).multiply(0.5);
+        a[1][1] = sqrtTwo.subtract(2).multiply(-0.5);
+        a[2][0] = getField().getZero();
+        a[2][1] = sqrtTwo.multiply(-0.5);
+        a[2][2] = sqrtTwo.add(2).multiply(0.5);
+        return a;
+    }
+
+    /** {@inheritDoc} */
+    public T[] getB() {
+
+        final T two     = getField().getZero().add(2);
+        final T sqrtTwo = two.sqrt();
+
+        final T[] b = MathArrays.buildArray(getField(), 4);
+        b[0] = fraction(1, 6);
+        b[1] = sqrtTwo.subtract(2).divide(-6);
+        b[2] = sqrtTwo.add(2).divide(6);
+        b[3] = b[0];
+
+        return b;
+
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected GillFieldStepInterpolator<T>
+        createInterpolator(final boolean forward, T[][] yDotK,
+                           final FieldODEStateAndDerivative<T> globalPreviousState,
+                           final FieldODEStateAndDerivative<T> globalCurrentState,
+                           final FieldEquationsMapper<T> mapper) {
+        return new GillFieldStepInterpolator<T>(getField(), forward, yDotK,
+                                                globalPreviousState, globalCurrentState,
+                                                globalPreviousState, globalCurrentState,
+                                                mapper);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/GillFieldStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/GillFieldStepInterpolator.java
new file mode 100644
index 0000000..5639ed5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/GillFieldStepInterpolator.java
@@ -0,0 +1,148 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.ode.FieldEquationsMapper;
+import org.apache.commons.math3.ode.FieldODEStateAndDerivative;
+
+/**
+ * This class implements a step interpolator for the Gill fourth
+ * order Runge-Kutta integrator.
+ *
+ * <p>This interpolator allows to compute dense output inside the last
+ * step computed. The interpolation equation is consistent with the
+ * integration scheme :
+ * <ul>
+ *   <li>Using reference point at step start:<br>
+ *   y(t<sub>n</sub> + &theta; h) = y (t<sub>n</sub>)
+ *                    + &theta; (h/6) [ (6 - 9 &theta; + 4 &theta;<sup>2</sup>) y'<sub>1</sub>
+ *                                    + (    6 &theta; - 4 &theta;<sup>2</sup>) ((1-1/&radic;2) y'<sub>2</sub> + (1+1/&radic;2)) y'<sub>3</sub>)
+ *                                    + (  - 3 &theta; + 4 &theta;<sup>2</sup>) y'<sub>4</sub>
+ *                                    ]
+ *   </li>
+ *   <li>Using reference point at step start:<br>
+ *   y(t<sub>n</sub> + &theta; h) = y (t<sub>n</sub> + h)
+ *                    - (1 - &theta;) (h/6) [ (1 - 5 &theta; + 4 &theta;<sup>2</sup>) y'<sub>1</sub>
+ *                                          + (2 + 2 &theta; - 4 &theta;<sup>2</sup>) ((1-1/&radic;2) y'<sub>2</sub> + (1+1/&radic;2)) y'<sub>3</sub>)
+ *                                          + (1 +   &theta; + 4 &theta;<sup>2</sup>) y'<sub>4</sub>
+ *                                          ]
+ *   </li>
+ * </ul>
+ * </p>
+ * where &theta; belongs to [0 ; 1] and where y'<sub>1</sub> to y'<sub>4</sub>
+ * are the four evaluations of the derivatives already computed during
+ * the step.</p>
+ *
+ * @see GillFieldIntegrator
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+
+class GillFieldStepInterpolator<T extends RealFieldElement<T>>
+  extends RungeKuttaFieldStepInterpolator<T> {
+
+    /** First Gill coefficient. */
+    private final T one_minus_inv_sqrt_2;
+
+    /** Second Gill coefficient. */
+    private final T one_plus_inv_sqrt_2;
+
+    /** Simple constructor.
+     * @param field field to which the time and state vector elements belong
+     * @param forward integration direction indicator
+     * @param yDotK slopes at the intermediate points
+     * @param globalPreviousState start of the global step
+     * @param globalCurrentState end of the global step
+     * @param softPreviousState start of the restricted step
+     * @param softCurrentState end of the restricted step
+     * @param mapper equations mapper for the all equations
+     */
+    GillFieldStepInterpolator(final Field<T> field, final boolean forward,
+                              final T[][] yDotK,
+                              final FieldODEStateAndDerivative<T> globalPreviousState,
+                              final FieldODEStateAndDerivative<T> globalCurrentState,
+                              final FieldODEStateAndDerivative<T> softPreviousState,
+                              final FieldODEStateAndDerivative<T> softCurrentState,
+                              final FieldEquationsMapper<T> mapper) {
+        super(field, forward, yDotK,
+              globalPreviousState, globalCurrentState, softPreviousState, softCurrentState,
+              mapper);
+        final T sqrt = field.getZero().add(0.5).sqrt();
+        one_minus_inv_sqrt_2 = field.getOne().subtract(sqrt);
+        one_plus_inv_sqrt_2  = field.getOne().add(sqrt);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected GillFieldStepInterpolator<T> create(final Field<T> newField, final boolean newForward, final T[][] newYDotK,
+                                                  final FieldODEStateAndDerivative<T> newGlobalPreviousState,
+                                                  final FieldODEStateAndDerivative<T> newGlobalCurrentState,
+                                                  final FieldODEStateAndDerivative<T> newSoftPreviousState,
+                                                  final FieldODEStateAndDerivative<T> newSoftCurrentState,
+                                                  final FieldEquationsMapper<T> newMapper) {
+        return new GillFieldStepInterpolator<T>(newField, newForward, newYDotK,
+                                                newGlobalPreviousState, newGlobalCurrentState,
+                                                newSoftPreviousState, newSoftCurrentState,
+                                                newMapper);
+    }
+
+    /** {@inheritDoc} */
+    @SuppressWarnings("unchecked")
+    @Override
+    protected FieldODEStateAndDerivative<T> computeInterpolatedStateAndDerivatives(final FieldEquationsMapper<T> mapper,
+                                                                                   final T time, final T theta,
+                                                                                   final T thetaH, final T oneMinusThetaH) {
+
+        final T one        = time.getField().getOne();
+        final T twoTheta   = theta.multiply(2);
+        final T fourTheta2 = twoTheta.multiply(twoTheta);
+        final T coeffDot1  = theta.multiply(twoTheta.subtract(3)).add(1);
+        final T cDot23     = twoTheta.multiply(one.subtract(theta));
+        final T coeffDot2  = cDot23.multiply(one_minus_inv_sqrt_2);
+        final T coeffDot3  = cDot23.multiply(one_plus_inv_sqrt_2);
+        final T coeffDot4  = theta.multiply(twoTheta.subtract(1));
+        final T[] interpolatedState;
+        final T[] interpolatedDerivatives;
+
+        if (getGlobalPreviousState() != null && theta.getReal() <= 0.5) {
+            final T s               = thetaH.divide(6.0);
+            final T c23             = s.multiply(theta.multiply(6).subtract(fourTheta2));
+            final T coeff1          = s.multiply(fourTheta2.subtract(theta.multiply(9)).add(6));
+            final T coeff2          = c23.multiply(one_minus_inv_sqrt_2);
+            final T coeff3          = c23.multiply(one_plus_inv_sqrt_2);
+            final T coeff4          = s.multiply(fourTheta2.subtract(theta.multiply(3)));
+            interpolatedState       = previousStateLinearCombination(coeff1, coeff2, coeff3, coeff4);
+            interpolatedDerivatives = derivativeLinearCombination(coeffDot1, coeffDot2, coeffDot3, coeffDot4);
+        } else {
+            final T s      = oneMinusThetaH.divide(-6.0);
+            final T c23    = s.multiply(twoTheta.add(2).subtract(fourTheta2));
+            final T coeff1 = s.multiply(fourTheta2.subtract(theta.multiply(5)).add(1));
+            final T coeff2 = c23.multiply(one_minus_inv_sqrt_2);
+            final T coeff3 = c23.multiply(one_plus_inv_sqrt_2);
+            final T coeff4 = s.multiply(fourTheta2.add(theta).add(1));
+            interpolatedState       = currentStateLinearCombination(coeff1, coeff2, coeff3, coeff4);
+            interpolatedDerivatives = derivativeLinearCombination(coeffDot1, coeffDot2, coeffDot3, coeffDot4);
+        }
+
+        return new FieldODEStateAndDerivative<T>(time, interpolatedState, interpolatedDerivatives);
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/GillIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/GillIntegrator.java
new file mode 100644
index 0000000..5273e02
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/GillIntegrator.java
@@ -0,0 +1,74 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.util.FastMath;
+
+
+/**
+ * This class implements the Gill fourth order Runge-Kutta
+ * integrator for Ordinary Differential Equations .
+
+ * <p>This method is an explicit Runge-Kutta method, its Butcher-array
+ * is the following one :
+ * <pre>
+ *    0  |    0        0       0      0
+ *   1/2 |   1/2       0       0      0
+ *   1/2 | (q-1)/2  (2-q)/2    0      0
+ *    1  |    0       -q/2  (2+q)/2   0
+ *       |-------------------------------
+ *       |   1/6    (2-q)/6 (2+q)/6  1/6
+ * </pre>
+ * where q = sqrt(2)</p>
+ *
+ * @see EulerIntegrator
+ * @see ClassicalRungeKuttaIntegrator
+ * @see MidpointIntegrator
+ * @see ThreeEighthesIntegrator
+ * @see LutherIntegrator
+ * @since 1.2
+ */
+
+public class GillIntegrator extends RungeKuttaIntegrator {
+
+  /** Time steps Butcher array. */
+  private static final double[] STATIC_C = {
+    1.0 / 2.0, 1.0 / 2.0, 1.0
+  };
+
+  /** Internal weights Butcher array. */
+  private static final double[][] STATIC_A = {
+    { 1.0 / 2.0 },
+    { (FastMath.sqrt(2.0) - 1.0) / 2.0, (2.0 - FastMath.sqrt(2.0)) / 2.0 },
+    { 0.0, -FastMath.sqrt(2.0) / 2.0, (2.0 + FastMath.sqrt(2.0)) / 2.0 }
+  };
+
+  /** Propagation weights Butcher array. */
+  private static final double[] STATIC_B = {
+    1.0 / 6.0, (2.0 - FastMath.sqrt(2.0)) / 6.0, (2.0 + FastMath.sqrt(2.0)) / 6.0, 1.0 / 6.0
+  };
+
+  /** Simple constructor.
+   * Build a fourth-order Gill integrator with the given step.
+   * @param step integration step
+   */
+  public GillIntegrator(final double step) {
+    super("Gill", STATIC_C, STATIC_A, STATIC_B, new GillStepInterpolator(), step);
+  }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/GillStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/GillStepInterpolator.java
new file mode 100644
index 0000000..72dec69
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/GillStepInterpolator.java
@@ -0,0 +1,151 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.ode.sampling.StepInterpolator;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * This class implements a step interpolator for the Gill fourth
+ * order Runge-Kutta integrator.
+ *
+ * <p>This interpolator allows to compute dense output inside the last
+ * step computed. The interpolation equation is consistent with the
+ * integration scheme :
+ * <ul>
+ *   <li>Using reference point at step start:<br>
+ *   y(t<sub>n</sub> + &theta; h) = y (t<sub>n</sub>)
+ *                    + &theta; (h/6) [ (6 - 9 &theta; + 4 &theta;<sup>2</sup>) y'<sub>1</sub>
+ *                                    + (    6 &theta; - 4 &theta;<sup>2</sup>) ((1-1/&radic;2) y'<sub>2</sub> + (1+1/&radic;2)) y'<sub>3</sub>)
+ *                                    + (  - 3 &theta; + 4 &theta;<sup>2</sup>) y'<sub>4</sub>
+ *                                    ]
+ *   </li>
+ *   <li>Using reference point at step start:<br>
+ *   y(t<sub>n</sub> + &theta; h) = y (t<sub>n</sub> + h)
+ *                    - (1 - &theta;) (h/6) [ (1 - 5 &theta; + 4 &theta;<sup>2</sup>) y'<sub>1</sub>
+ *                                          + (2 + 2 &theta; - 4 &theta;<sup>2</sup>) ((1-1/&radic;2) y'<sub>2</sub> + (1+1/&radic;2)) y'<sub>3</sub>)
+ *                                          + (1 +   &theta; + 4 &theta;<sup>2</sup>) y'<sub>4</sub>
+ *                                          ]
+ *   </li>
+ * </ul>
+ * </p>
+ * where &theta; belongs to [0 ; 1] and where y'<sub>1</sub> to y'<sub>4</sub>
+ * are the four evaluations of the derivatives already computed during
+ * the step.</p>
+ *
+ * @see GillIntegrator
+ * @since 1.2
+ */
+
+class GillStepInterpolator
+  extends RungeKuttaStepInterpolator {
+
+    /** First Gill coefficient. */
+    private static final double ONE_MINUS_INV_SQRT_2 = 1 - FastMath.sqrt(0.5);
+
+    /** Second Gill coefficient. */
+    private static final double ONE_PLUS_INV_SQRT_2 = 1 + FastMath.sqrt(0.5);
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 20111120L;
+
+  /** Simple constructor.
+   * This constructor builds an instance that is not usable yet, the
+   * {@link
+   * org.apache.commons.math3.ode.sampling.AbstractStepInterpolator#reinitialize}
+   * method should be called before using the instance in order to
+   * initialize the internal arrays. This constructor is used only
+   * in order to delay the initialization in some cases. The {@link
+   * RungeKuttaIntegrator} class uses the prototyping design pattern
+   * to create the step interpolators by cloning an uninitialized model
+   * and later initializing the copy.
+   */
+  // CHECKSTYLE: stop RedundantModifier
+  // the public modifier here is needed for serialization
+  public GillStepInterpolator() {
+  }
+  // CHECKSTYLE: resume RedundantModifier
+
+  /** Copy constructor.
+   * @param interpolator interpolator to copy from. The copy is a deep
+   * copy: its arrays are separated from the original arrays of the
+   * instance
+   */
+  GillStepInterpolator(final GillStepInterpolator interpolator) {
+    super(interpolator);
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  protected StepInterpolator doCopy() {
+    return new GillStepInterpolator(this);
+  }
+
+
+  /** {@inheritDoc} */
+  @Override
+  protected void computeInterpolatedStateAndDerivatives(final double theta,
+                                          final double oneMinusThetaH) {
+
+    final double twoTheta   = 2 * theta;
+    final double fourTheta2 = twoTheta * twoTheta;
+    final double coeffDot1  = theta * (twoTheta - 3) + 1;
+    final double cDot23     = twoTheta * (1 - theta);
+    final double coeffDot2  = cDot23  * ONE_MINUS_INV_SQRT_2;
+    final double coeffDot3  = cDot23  * ONE_PLUS_INV_SQRT_2;
+    final double coeffDot4  = theta * (twoTheta - 1);
+
+    if ((previousState != null) && (theta <= 0.5)) {
+        final double s         = theta * h / 6.0;
+        final double c23       = s * (6 * theta - fourTheta2);
+        final double coeff1    = s * (6 - 9 * theta + fourTheta2);
+        final double coeff2    = c23  * ONE_MINUS_INV_SQRT_2;
+        final double coeff3    = c23  * ONE_PLUS_INV_SQRT_2;
+        final double coeff4    = s * (-3 * theta + fourTheta2);
+        for (int i = 0; i < interpolatedState.length; ++i) {
+            final double yDot1 = yDotK[0][i];
+            final double yDot2 = yDotK[1][i];
+            final double yDot3 = yDotK[2][i];
+            final double yDot4 = yDotK[3][i];
+            interpolatedState[i] =
+                    previousState[i] + coeff1 * yDot1 + coeff2 * yDot2 + coeff3 * yDot3 + coeff4 * yDot4;
+            interpolatedDerivatives[i] =
+                    coeffDot1 * yDot1 + coeffDot2 * yDot2 + coeffDot3 * yDot3 + coeffDot4 * yDot4;
+        }
+    } else {
+        final double s      = oneMinusThetaH / 6.0;
+        final double c23    = s * (2 + twoTheta - fourTheta2);
+        final double coeff1 = s * (1 - 5 * theta + fourTheta2);
+        final double coeff2 = c23  * ONE_MINUS_INV_SQRT_2;
+        final double coeff3 = c23  * ONE_PLUS_INV_SQRT_2;
+        final double coeff4 = s * (1 + theta + fourTheta2);
+        for (int i = 0; i < interpolatedState.length; ++i) {
+            final double yDot1 = yDotK[0][i];
+            final double yDot2 = yDotK[1][i];
+            final double yDot3 = yDotK[2][i];
+            final double yDot4 = yDotK[3][i];
+            interpolatedState[i] =
+                    currentState[i] - coeff1 * yDot1 - coeff2 * yDot2 - coeff3 * yDot3 - coeff4 * yDot4;
+            interpolatedDerivatives[i] =
+                    coeffDot1 * yDot1 + coeffDot2 * yDot2 + coeffDot3 * yDot3 + coeffDot4 * yDot4;
+        }
+    }
+
+  }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/GraggBulirschStoerIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/GraggBulirschStoerIntegrator.java
new file mode 100644
index 0000000..50a463e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/GraggBulirschStoerIntegrator.java
@@ -0,0 +1,949 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.analysis.solvers.UnivariateSolver;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NoBracketingException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.ode.ExpandableStatefulODE;
+import org.apache.commons.math3.ode.events.EventHandler;
+import org.apache.commons.math3.ode.sampling.AbstractStepInterpolator;
+import org.apache.commons.math3.ode.sampling.StepHandler;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * This class implements a Gragg-Bulirsch-Stoer integrator for
+ * Ordinary Differential Equations.
+ *
+ * <p>The Gragg-Bulirsch-Stoer algorithm is one of the most efficient
+ * ones currently available for smooth problems. It uses Richardson
+ * extrapolation to estimate what would be the solution if the step
+ * size could be decreased down to zero.</p>
+ *
+ * <p>
+ * This method changes both the step size and the order during
+ * integration, in order to minimize computation cost. It is
+ * particularly well suited when a very high precision is needed. The
+ * limit where this method becomes more efficient than high-order
+ * embedded Runge-Kutta methods like {@link DormandPrince853Integrator
+ * Dormand-Prince 8(5,3)} depends on the problem. Results given in the
+ * Hairer, Norsett and Wanner book show for example that this limit
+ * occurs for accuracy around 1e-6 when integrating Saltzam-Lorenz
+ * equations (the authors note this problem is <i>extremely sensitive
+ * to the errors in the first integration steps</i>), and around 1e-11
+ * for a two dimensional celestial mechanics problems with seven
+ * bodies (pleiades problem, involving quasi-collisions for which
+ * <i>automatic step size control is essential</i>).
+ * </p>
+ *
+ * <p>
+ * This implementation is basically a reimplementation in Java of the
+ * <a
+ * href="http://www.unige.ch/math/folks/hairer/prog/nonstiff/odex.f">odex</a>
+ * fortran code by E. Hairer and G. Wanner. The redistribution policy
+ * for this code is available <a
+ * href="http://www.unige.ch/~hairer/prog/licence.txt">here</a>, for
+ * convenience, it is reproduced below.</p>
+ * </p>
+ *
+ * <table border="0" width="80%" cellpadding="10" align="center" bgcolor="#E0E0E0">
+ * <tr><td>Copyright (c) 2004, Ernst Hairer</td></tr>
+ *
+ * <tr><td>Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ * <ul>
+ *  <li>Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.</li>
+ *  <li>Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.</li>
+ * </ul></td></tr>
+ *
+ * <tr><td><strong>THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+ * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.</strong></td></tr>
+ * </table>
+ *
+ * @since 1.2
+ */
+
+public class GraggBulirschStoerIntegrator extends AdaptiveStepsizeIntegrator {
+
+    /** Integrator method name. */
+    private static final String METHOD_NAME = "Gragg-Bulirsch-Stoer";
+
+    /** maximal order. */
+    private int maxOrder;
+
+    /** step size sequence. */
+    private int[] sequence;
+
+    /** overall cost of applying step reduction up to iteration k+1, in number of calls. */
+    private int[] costPerStep;
+
+    /** cost per unit step. */
+    private double[] costPerTimeUnit;
+
+    /** optimal steps for each order. */
+    private double[] optimalStep;
+
+    /** extrapolation coefficients. */
+    private double[][] coeff;
+
+    /** stability check enabling parameter. */
+    private boolean performTest;
+
+    /** maximal number of checks for each iteration. */
+    private int maxChecks;
+
+    /** maximal number of iterations for which checks are performed. */
+    private int maxIter;
+
+    /** stepsize reduction factor in case of stability check failure. */
+    private double stabilityReduction;
+
+    /** first stepsize control factor. */
+    private double stepControl1;
+
+    /** second stepsize control factor. */
+    private double stepControl2;
+
+    /** third stepsize control factor. */
+    private double stepControl3;
+
+    /** fourth stepsize control factor. */
+    private double stepControl4;
+
+    /** first order control factor. */
+    private double orderControl1;
+
+    /** second order control factor. */
+    private double orderControl2;
+
+    /** use interpolation error in stepsize control. */
+    private boolean useInterpolationError;
+
+    /** interpolation order control parameter. */
+    private int mudif;
+
+  /** Simple constructor.
+   * Build a Gragg-Bulirsch-Stoer integrator with the given step
+   * bounds. All tuning parameters are set to their default
+   * values. The default step handler does nothing.
+   * @param minStep minimal step (sign is irrelevant, regardless of
+   * integration direction, forward or backward), the last step can
+   * be smaller than this
+   * @param maxStep maximal step (sign is irrelevant, regardless of
+   * integration direction, forward or backward), the last step can
+   * be smaller than this
+   * @param scalAbsoluteTolerance allowed absolute error
+   * @param scalRelativeTolerance allowed relative error
+   */
+  public GraggBulirschStoerIntegrator(final double minStep, final double maxStep,
+                                      final double scalAbsoluteTolerance,
+                                      final double scalRelativeTolerance) {
+    super(METHOD_NAME, minStep, maxStep,
+          scalAbsoluteTolerance, scalRelativeTolerance);
+    setStabilityCheck(true, -1, -1, -1);
+    setControlFactors(-1, -1, -1, -1);
+    setOrderControl(-1, -1, -1);
+    setInterpolationControl(true, -1);
+  }
+
+  /** Simple constructor.
+   * Build a Gragg-Bulirsch-Stoer integrator with the given step
+   * bounds. All tuning parameters are set to their default
+   * values. The default step handler does nothing.
+   * @param minStep minimal step (must be positive even for backward
+   * integration), the last step can be smaller than this
+   * @param maxStep maximal step (must be positive even for backward
+   * integration)
+   * @param vecAbsoluteTolerance allowed absolute error
+   * @param vecRelativeTolerance allowed relative error
+   */
+  public GraggBulirschStoerIntegrator(final double minStep, final double maxStep,
+                                      final double[] vecAbsoluteTolerance,
+                                      final double[] vecRelativeTolerance) {
+    super(METHOD_NAME, minStep, maxStep,
+          vecAbsoluteTolerance, vecRelativeTolerance);
+    setStabilityCheck(true, -1, -1, -1);
+    setControlFactors(-1, -1, -1, -1);
+    setOrderControl(-1, -1, -1);
+    setInterpolationControl(true, -1);
+  }
+
+  /** Set the stability check controls.
+   * <p>The stability check is performed on the first few iterations of
+   * the extrapolation scheme. If this test fails, the step is rejected
+   * and the stepsize is reduced.</p>
+   * <p>By default, the test is performed, at most during two
+   * iterations at each step, and at most once for each of these
+   * iterations. The default stepsize reduction factor is 0.5.</p>
+   * @param performStabilityCheck if true, stability check will be performed,
+     if false, the check will be skipped
+   * @param maxNumIter maximal number of iterations for which checks are
+   * performed (the number of iterations is reset to default if negative
+   * or null)
+   * @param maxNumChecks maximal number of checks for each iteration
+   * (the number of checks is reset to default if negative or null)
+   * @param stepsizeReductionFactor stepsize reduction factor in case of
+   * failure (the factor is reset to default if lower than 0.0001 or
+   * greater than 0.9999)
+   */
+  public void setStabilityCheck(final boolean performStabilityCheck,
+                                final int maxNumIter, final int maxNumChecks,
+                                final double stepsizeReductionFactor) {
+
+    this.performTest = performStabilityCheck;
+    this.maxIter     = (maxNumIter   <= 0) ? 2 : maxNumIter;
+    this.maxChecks   = (maxNumChecks <= 0) ? 1 : maxNumChecks;
+
+    if ((stepsizeReductionFactor < 0.0001) || (stepsizeReductionFactor > 0.9999)) {
+      this.stabilityReduction = 0.5;
+    } else {
+      this.stabilityReduction = stepsizeReductionFactor;
+    }
+
+  }
+
+  /** Set the step size control factors.
+
+   * <p>The new step size hNew is computed from the old one h by:
+   * <pre>
+   * hNew = h * stepControl2 / (err/stepControl1)^(1/(2k+1))
+   * </pre>
+   * where err is the scaled error and k the iteration number of the
+   * extrapolation scheme (counting from 0). The default values are
+   * 0.65 for stepControl1 and 0.94 for stepControl2.</p>
+   * <p>The step size is subject to the restriction:
+   * <pre>
+   * stepControl3^(1/(2k+1))/stepControl4 <= hNew/h <= 1/stepControl3^(1/(2k+1))
+   * </pre>
+   * The default values are 0.02 for stepControl3 and 4.0 for
+   * stepControl4.</p>
+   * @param control1 first stepsize control factor (the factor is
+   * reset to default if lower than 0.0001 or greater than 0.9999)
+   * @param control2 second stepsize control factor (the factor
+   * is reset to default if lower than 0.0001 or greater than 0.9999)
+   * @param control3 third stepsize control factor (the factor is
+   * reset to default if lower than 0.0001 or greater than 0.9999)
+   * @param control4 fourth stepsize control factor (the factor
+   * is reset to default if lower than 1.0001 or greater than 999.9)
+   */
+  public void setControlFactors(final double control1, final double control2,
+                                final double control3, final double control4) {
+
+    if ((control1 < 0.0001) || (control1 > 0.9999)) {
+      this.stepControl1 = 0.65;
+    } else {
+      this.stepControl1 = control1;
+    }
+
+    if ((control2 < 0.0001) || (control2 > 0.9999)) {
+      this.stepControl2 = 0.94;
+    } else {
+      this.stepControl2 = control2;
+    }
+
+    if ((control3 < 0.0001) || (control3 > 0.9999)) {
+      this.stepControl3 = 0.02;
+    } else {
+      this.stepControl3 = control3;
+    }
+
+    if ((control4 < 1.0001) || (control4 > 999.9)) {
+      this.stepControl4 = 4.0;
+    } else {
+      this.stepControl4 = control4;
+    }
+
+  }
+
+  /** Set the order control parameters.
+   * <p>The Gragg-Bulirsch-Stoer method changes both the step size and
+   * the order during integration, in order to minimize computation
+   * cost. Each extrapolation step increases the order by 2, so the
+   * maximal order that will be used is always even, it is twice the
+   * maximal number of columns in the extrapolation table.</p>
+   * <pre>
+   * order is decreased if w(k-1) <= w(k)   * orderControl1
+   * order is increased if w(k)   <= w(k-1) * orderControl2
+   * </pre>
+   * <p>where w is the table of work per unit step for each order
+   * (number of function calls divided by the step length), and k is
+   * the current order.</p>
+   * <p>The default maximal order after construction is 18 (i.e. the
+   * maximal number of columns is 9). The default values are 0.8 for
+   * orderControl1 and 0.9 for orderControl2.</p>
+   * @param maximalOrder maximal order in the extrapolation table (the
+   * maximal order is reset to default if order <= 6 or odd)
+   * @param control1 first order control factor (the factor is
+   * reset to default if lower than 0.0001 or greater than 0.9999)
+   * @param control2 second order control factor (the factor
+   * is reset to default if lower than 0.0001 or greater than 0.9999)
+   */
+  public void setOrderControl(final int maximalOrder,
+                              final double control1, final double control2) {
+
+    if ((maximalOrder <= 6) || (maximalOrder % 2 != 0)) {
+      this.maxOrder = 18;
+    }
+
+    if ((control1 < 0.0001) || (control1 > 0.9999)) {
+      this.orderControl1 = 0.8;
+    } else {
+      this.orderControl1 = control1;
+    }
+
+    if ((control2 < 0.0001) || (control2 > 0.9999)) {
+      this.orderControl2 = 0.9;
+    } else {
+      this.orderControl2 = control2;
+    }
+
+    // reinitialize the arrays
+    initializeArrays();
+
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public void addStepHandler (final StepHandler handler) {
+
+    super.addStepHandler(handler);
+
+    // reinitialize the arrays
+    initializeArrays();
+
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public void addEventHandler(final EventHandler function,
+                              final double maxCheckInterval,
+                              final double convergence,
+                              final int maxIterationCount,
+                              final UnivariateSolver solver) {
+    super.addEventHandler(function, maxCheckInterval, convergence,
+                          maxIterationCount, solver);
+
+    // reinitialize the arrays
+    initializeArrays();
+
+  }
+
+  /** Initialize the integrator internal arrays. */
+  private void initializeArrays() {
+
+    final int size = maxOrder / 2;
+
+    if ((sequence == null) || (sequence.length != size)) {
+      // all arrays should be reallocated with the right size
+      sequence        = new int[size];
+      costPerStep     = new int[size];
+      coeff           = new double[size][];
+      costPerTimeUnit = new double[size];
+      optimalStep     = new double[size];
+    }
+
+    // step size sequence: 2, 6, 10, 14, ...
+    for (int k = 0; k < size; ++k) {
+        sequence[k] = 4 * k + 2;
+    }
+
+    // initialize the order selection cost array
+    // (number of function calls for each column of the extrapolation table)
+    costPerStep[0] = sequence[0] + 1;
+    for (int k = 1; k < size; ++k) {
+      costPerStep[k] = costPerStep[k-1] + sequence[k];
+    }
+
+    // initialize the extrapolation tables
+    for (int k = 0; k < size; ++k) {
+      coeff[k] = (k > 0) ? new double[k] : null;
+      for (int l = 0; l < k; ++l) {
+        final double ratio = ((double) sequence[k]) / sequence[k-l-1];
+        coeff[k][l] = 1.0 / (ratio * ratio - 1.0);
+      }
+    }
+
+  }
+
+  /** Set the interpolation order control parameter.
+   * The interpolation order for dense output is 2k - mudif + 1. The
+   * default value for mudif is 4 and the interpolation error is used
+   * in stepsize control by default.
+
+   * @param useInterpolationErrorForControl if true, interpolation error is used
+   * for stepsize control
+   * @param mudifControlParameter interpolation order control parameter (the parameter
+   * is reset to default if <= 0 or >= 7)
+   */
+  public void setInterpolationControl(final boolean useInterpolationErrorForControl,
+                                      final int mudifControlParameter) {
+
+    this.useInterpolationError = useInterpolationErrorForControl;
+
+    if ((mudifControlParameter <= 0) || (mudifControlParameter >= 7)) {
+      this.mudif = 4;
+    } else {
+      this.mudif = mudifControlParameter;
+    }
+
+  }
+
+  /** Update scaling array.
+   * @param y1 first state vector to use for scaling
+   * @param y2 second state vector to use for scaling
+   * @param scale scaling array to update (can be shorter than state)
+   */
+  private void rescale(final double[] y1, final double[] y2, final double[] scale) {
+    if (vecAbsoluteTolerance == null) {
+      for (int i = 0; i < scale.length; ++i) {
+        final double yi = FastMath.max(FastMath.abs(y1[i]), FastMath.abs(y2[i]));
+        scale[i] = scalAbsoluteTolerance + scalRelativeTolerance * yi;
+      }
+    } else {
+      for (int i = 0; i < scale.length; ++i) {
+        final double yi = FastMath.max(FastMath.abs(y1[i]), FastMath.abs(y2[i]));
+        scale[i] = vecAbsoluteTolerance[i] + vecRelativeTolerance[i] * yi;
+      }
+    }
+  }
+
+  /** Perform integration over one step using substeps of a modified
+   * midpoint method.
+   * @param t0 initial time
+   * @param y0 initial value of the state vector at t0
+   * @param step global step
+   * @param k iteration number (from 0 to sequence.length - 1)
+   * @param scale scaling array (can be shorter than state)
+   * @param f placeholder where to put the state vector derivatives at each substep
+   *          (element 0 already contains initial derivative)
+   * @param yMiddle placeholder where to put the state vector at the middle of the step
+   * @param yEnd placeholder where to put the state vector at the end
+   * @param yTmp placeholder for one state vector
+   * @return true if computation was done properly,
+   *         false if stability check failed before end of computation
+   * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+   * @exception DimensionMismatchException if arrays dimensions do not match equations settings
+   */
+  private boolean tryStep(final double t0, final double[] y0, final double step, final int k,
+                          final double[] scale, final double[][] f,
+                          final double[] yMiddle, final double[] yEnd,
+                          final double[] yTmp)
+      throws MaxCountExceededException, DimensionMismatchException {
+
+    final int    n        = sequence[k];
+    final double subStep  = step / n;
+    final double subStep2 = 2 * subStep;
+
+    // first substep
+    double t = t0 + subStep;
+    for (int i = 0; i < y0.length; ++i) {
+      yTmp[i] = y0[i];
+      yEnd[i] = y0[i] + subStep * f[0][i];
+    }
+    computeDerivatives(t, yEnd, f[1]);
+
+    // other substeps
+    for (int j = 1; j < n; ++j) {
+
+      if (2 * j == n) {
+        // save the point at the middle of the step
+        System.arraycopy(yEnd, 0, yMiddle, 0, y0.length);
+      }
+
+      t += subStep;
+      for (int i = 0; i < y0.length; ++i) {
+        final double middle = yEnd[i];
+        yEnd[i]       = yTmp[i] + subStep2 * f[j][i];
+        yTmp[i]       = middle;
+      }
+
+      computeDerivatives(t, yEnd, f[j+1]);
+
+      // stability check
+      if (performTest && (j <= maxChecks) && (k < maxIter)) {
+        double initialNorm = 0.0;
+        for (int l = 0; l < scale.length; ++l) {
+          final double ratio = f[0][l] / scale[l];
+          initialNorm += ratio * ratio;
+        }
+        double deltaNorm = 0.0;
+        for (int l = 0; l < scale.length; ++l) {
+          final double ratio = (f[j+1][l] - f[0][l]) / scale[l];
+          deltaNorm += ratio * ratio;
+        }
+        if (deltaNorm > 4 * FastMath.max(1.0e-15, initialNorm)) {
+          return false;
+        }
+      }
+
+    }
+
+    // correction of the last substep (at t0 + step)
+    for (int i = 0; i < y0.length; ++i) {
+      yEnd[i] = 0.5 * (yTmp[i] + yEnd[i] + subStep * f[n][i]);
+    }
+
+    return true;
+
+  }
+
+  /** Extrapolate a vector.
+   * @param offset offset to use in the coefficients table
+   * @param k index of the last updated point
+   * @param diag working diagonal of the Aitken-Neville's
+   * triangle, without the last element
+   * @param last last element
+   */
+  private void extrapolate(final int offset, final int k,
+                           final double[][] diag, final double[] last) {
+
+    // update the diagonal
+    for (int j = 1; j < k; ++j) {
+      for (int i = 0; i < last.length; ++i) {
+        // Aitken-Neville's recursive formula
+        diag[k-j-1][i] = diag[k-j][i] +
+                         coeff[k+offset][j-1] * (diag[k-j][i] - diag[k-j-1][i]);
+      }
+    }
+
+    // update the last element
+    for (int i = 0; i < last.length; ++i) {
+      // Aitken-Neville's recursive formula
+      last[i] = diag[0][i] + coeff[k+offset][k-1] * (diag[0][i] - last[i]);
+    }
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public void integrate(final ExpandableStatefulODE equations, final double t)
+      throws NumberIsTooSmallException, DimensionMismatchException,
+             MaxCountExceededException, NoBracketingException {
+
+    sanityChecks(equations, t);
+    setEquations(equations);
+    final boolean forward = t > equations.getTime();
+
+    // create some internal working arrays
+    final double[] y0      = equations.getCompleteState();
+    final double[] y       = y0.clone();
+    final double[] yDot0   = new double[y.length];
+    final double[] y1      = new double[y.length];
+    final double[] yTmp    = new double[y.length];
+    final double[] yTmpDot = new double[y.length];
+
+    final double[][] diagonal = new double[sequence.length-1][];
+    final double[][] y1Diag = new double[sequence.length-1][];
+    for (int k = 0; k < sequence.length-1; ++k) {
+      diagonal[k] = new double[y.length];
+      y1Diag[k] = new double[y.length];
+    }
+
+    final double[][][] fk  = new double[sequence.length][][];
+    for (int k = 0; k < sequence.length; ++k) {
+
+      fk[k]    = new double[sequence[k] + 1][];
+
+      // all substeps start at the same point, so share the first array
+      fk[k][0] = yDot0;
+
+      for (int l = 0; l < sequence[k]; ++l) {
+        fk[k][l+1] = new double[y0.length];
+      }
+
+    }
+
+    if (y != y0) {
+      System.arraycopy(y0, 0, y, 0, y0.length);
+    }
+
+    final double[] yDot1 = new double[y0.length];
+    final double[][] yMidDots = new double[1 + 2 * sequence.length][y0.length];
+
+    // initial scaling
+    final double[] scale = new double[mainSetDimension];
+    rescale(y, y, scale);
+
+    // initial order selection
+    final double tol =
+        (vecRelativeTolerance == null) ? scalRelativeTolerance : vecRelativeTolerance[0];
+    final double log10R = FastMath.log10(FastMath.max(1.0e-10, tol));
+    int targetIter = FastMath.max(1,
+                              FastMath.min(sequence.length - 2,
+                                       (int) FastMath.floor(0.5 - 0.6 * log10R)));
+
+    // set up an interpolator sharing the integrator arrays
+    final AbstractStepInterpolator interpolator =
+            new GraggBulirschStoerStepInterpolator(y, yDot0,
+                                                   y1, yDot1,
+                                                   yMidDots, forward,
+                                                   equations.getPrimaryMapper(),
+                                                   equations.getSecondaryMappers());
+    interpolator.storeTime(equations.getTime());
+
+    stepStart = equations.getTime();
+    double  hNew             = 0;
+    double  maxError         = Double.MAX_VALUE;
+    boolean previousRejected = false;
+    boolean firstTime        = true;
+    boolean newStep          = true;
+    boolean firstStepAlreadyComputed = false;
+    initIntegration(equations.getTime(), y0, t);
+    costPerTimeUnit[0] = 0;
+    isLastStep = false;
+    do {
+
+      double error;
+      boolean reject = false;
+
+      if (newStep) {
+
+        interpolator.shift();
+
+        // first evaluation, at the beginning of the step
+        if (! firstStepAlreadyComputed) {
+          computeDerivatives(stepStart, y, yDot0);
+        }
+
+        if (firstTime) {
+          hNew = initializeStep(forward, 2 * targetIter + 1, scale,
+                                stepStart, y, yDot0, yTmp, yTmpDot);
+        }
+
+        newStep = false;
+
+      }
+
+      stepSize = hNew;
+
+      // step adjustment near bounds
+      if ((forward && (stepStart + stepSize > t)) ||
+          ((! forward) && (stepStart + stepSize < t))) {
+        stepSize = t - stepStart;
+      }
+      final double nextT = stepStart + stepSize;
+      isLastStep = forward ? (nextT >= t) : (nextT <= t);
+
+      // iterate over several substep sizes
+      int k = -1;
+      for (boolean loop = true; loop; ) {
+
+        ++k;
+
+        // modified midpoint integration with the current substep
+        if ( ! tryStep(stepStart, y, stepSize, k, scale, fk[k],
+                       (k == 0) ? yMidDots[0] : diagonal[k-1],
+                       (k == 0) ? y1 : y1Diag[k-1],
+                       yTmp)) {
+
+          // the stability check failed, we reduce the global step
+          hNew   = FastMath.abs(filterStep(stepSize * stabilityReduction, forward, false));
+          reject = true;
+          loop   = false;
+
+        } else {
+
+          // the substep was computed successfully
+          if (k > 0) {
+
+            // extrapolate the state at the end of the step
+            // using last iteration data
+            extrapolate(0, k, y1Diag, y1);
+            rescale(y, y1, scale);
+
+            // estimate the error at the end of the step.
+            error = 0;
+            for (int j = 0; j < mainSetDimension; ++j) {
+              final double e = FastMath.abs(y1[j] - y1Diag[0][j]) / scale[j];
+              error += e * e;
+            }
+            error = FastMath.sqrt(error / mainSetDimension);
+
+            if ((error > 1.0e15) || ((k > 1) && (error > maxError))) {
+              // error is too big, we reduce the global step
+              hNew   = FastMath.abs(filterStep(stepSize * stabilityReduction, forward, false));
+              reject = true;
+              loop   = false;
+            } else {
+
+              maxError = FastMath.max(4 * error, 1.0);
+
+              // compute optimal stepsize for this order
+              final double exp = 1.0 / (2 * k + 1);
+              double fac = stepControl2 / FastMath.pow(error / stepControl1, exp);
+              final double pow = FastMath.pow(stepControl3, exp);
+              fac = FastMath.max(pow / stepControl4, FastMath.min(1 / pow, fac));
+              optimalStep[k]     = FastMath.abs(filterStep(stepSize * fac, forward, true));
+              costPerTimeUnit[k] = costPerStep[k] / optimalStep[k];
+
+              // check convergence
+              switch (k - targetIter) {
+
+              case -1 :
+                if ((targetIter > 1) && ! previousRejected) {
+
+                  // check if we can stop iterations now
+                  if (error <= 1.0) {
+                    // convergence have been reached just before targetIter
+                    loop = false;
+                  } else {
+                    // estimate if there is a chance convergence will
+                    // be reached on next iteration, using the
+                    // asymptotic evolution of error
+                    final double ratio = ((double) sequence [targetIter] * sequence[targetIter + 1]) /
+                                         (sequence[0] * sequence[0]);
+                    if (error > ratio * ratio) {
+                      // we don't expect to converge on next iteration
+                      // we reject the step immediately and reduce order
+                      reject = true;
+                      loop   = false;
+                      targetIter = k;
+                      if ((targetIter > 1) &&
+                          (costPerTimeUnit[targetIter-1] <
+                           orderControl1 * costPerTimeUnit[targetIter])) {
+                        --targetIter;
+                      }
+                      hNew = optimalStep[targetIter];
+                    }
+                  }
+                }
+                break;
+
+              case 0:
+                if (error <= 1.0) {
+                  // convergence has been reached exactly at targetIter
+                  loop = false;
+                } else {
+                  // estimate if there is a chance convergence will
+                  // be reached on next iteration, using the
+                  // asymptotic evolution of error
+                  final double ratio = ((double) sequence[k+1]) / sequence[0];
+                  if (error > ratio * ratio) {
+                    // we don't expect to converge on next iteration
+                    // we reject the step immediately
+                    reject = true;
+                    loop = false;
+                    if ((targetIter > 1) &&
+                        (costPerTimeUnit[targetIter-1] <
+                         orderControl1 * costPerTimeUnit[targetIter])) {
+                      --targetIter;
+                    }
+                    hNew = optimalStep[targetIter];
+                  }
+                }
+                break;
+
+              case 1 :
+                if (error > 1.0) {
+                  reject = true;
+                  if ((targetIter > 1) &&
+                      (costPerTimeUnit[targetIter-1] <
+                       orderControl1 * costPerTimeUnit[targetIter])) {
+                    --targetIter;
+                  }
+                  hNew = optimalStep[targetIter];
+                }
+                loop = false;
+                break;
+
+              default :
+                if ((firstTime || isLastStep) && (error <= 1.0)) {
+                  loop = false;
+                }
+                break;
+
+              }
+
+            }
+          }
+        }
+      }
+
+      if (! reject) {
+          // derivatives at end of step
+          computeDerivatives(stepStart + stepSize, y1, yDot1);
+      }
+
+      // dense output handling
+      double hInt = getMaxStep();
+      if (! reject) {
+
+        // extrapolate state at middle point of the step
+        for (int j = 1; j <= k; ++j) {
+          extrapolate(0, j, diagonal, yMidDots[0]);
+        }
+
+        final int mu = 2 * k - mudif + 3;
+
+        for (int l = 0; l < mu; ++l) {
+
+          // derivative at middle point of the step
+          final int l2 = l / 2;
+          double factor = FastMath.pow(0.5 * sequence[l2], l);
+          int middleIndex = fk[l2].length / 2;
+          for (int i = 0; i < y0.length; ++i) {
+            yMidDots[l+1][i] = factor * fk[l2][middleIndex + l][i];
+          }
+          for (int j = 1; j <= k - l2; ++j) {
+            factor = FastMath.pow(0.5 * sequence[j + l2], l);
+            middleIndex = fk[l2+j].length / 2;
+            for (int i = 0; i < y0.length; ++i) {
+              diagonal[j-1][i] = factor * fk[l2+j][middleIndex+l][i];
+            }
+            extrapolate(l2, j, diagonal, yMidDots[l+1]);
+          }
+          for (int i = 0; i < y0.length; ++i) {
+            yMidDots[l+1][i] *= stepSize;
+          }
+
+          // compute centered differences to evaluate next derivatives
+          for (int j = (l + 1) / 2; j <= k; ++j) {
+            for (int m = fk[j].length - 1; m >= 2 * (l + 1); --m) {
+              for (int i = 0; i < y0.length; ++i) {
+                fk[j][m][i] -= fk[j][m-2][i];
+              }
+            }
+          }
+
+        }
+
+        if (mu >= 0) {
+
+          // estimate the dense output coefficients
+          final GraggBulirschStoerStepInterpolator gbsInterpolator
+            = (GraggBulirschStoerStepInterpolator) interpolator;
+          gbsInterpolator.computeCoefficients(mu, stepSize);
+
+          if (useInterpolationError) {
+            // use the interpolation error to limit stepsize
+            final double interpError = gbsInterpolator.estimateError(scale);
+            hInt = FastMath.abs(stepSize / FastMath.max(FastMath.pow(interpError, 1.0 / (mu+4)),
+                                                0.01));
+            if (interpError > 10.0) {
+              hNew = hInt;
+              reject = true;
+            }
+          }
+
+        }
+
+      }
+
+      if (! reject) {
+
+        // Discrete events handling
+        interpolator.storeTime(stepStart + stepSize);
+        stepStart = acceptStep(interpolator, y1, yDot1, t);
+
+        // prepare next step
+        interpolator.storeTime(stepStart);
+        System.arraycopy(y1, 0, y, 0, y0.length);
+        System.arraycopy(yDot1, 0, yDot0, 0, y0.length);
+        firstStepAlreadyComputed = true;
+
+        int optimalIter;
+        if (k == 1) {
+          optimalIter = 2;
+          if (previousRejected) {
+            optimalIter = 1;
+          }
+        } else if (k <= targetIter) {
+          optimalIter = k;
+          if (costPerTimeUnit[k-1] < orderControl1 * costPerTimeUnit[k]) {
+            optimalIter = k-1;
+          } else if (costPerTimeUnit[k] < orderControl2 * costPerTimeUnit[k-1]) {
+            optimalIter = FastMath.min(k+1, sequence.length - 2);
+          }
+        } else {
+          optimalIter = k - 1;
+          if ((k > 2) &&
+              (costPerTimeUnit[k-2] < orderControl1 * costPerTimeUnit[k-1])) {
+            optimalIter = k - 2;
+          }
+          if (costPerTimeUnit[k] < orderControl2 * costPerTimeUnit[optimalIter]) {
+            optimalIter = FastMath.min(k, sequence.length - 2);
+          }
+        }
+
+        if (previousRejected) {
+          // after a rejected step neither order nor stepsize
+          // should increase
+          targetIter = FastMath.min(optimalIter, k);
+          hNew = FastMath.min(FastMath.abs(stepSize), optimalStep[targetIter]);
+        } else {
+          // stepsize control
+          if (optimalIter <= k) {
+            hNew = optimalStep[optimalIter];
+          } else {
+            if ((k < targetIter) &&
+                (costPerTimeUnit[k] < orderControl2 * costPerTimeUnit[k-1])) {
+              hNew = filterStep(optimalStep[k] * costPerStep[optimalIter+1] / costPerStep[k],
+                               forward, false);
+            } else {
+              hNew = filterStep(optimalStep[k] * costPerStep[optimalIter] / costPerStep[k],
+                                forward, false);
+            }
+          }
+
+          targetIter = optimalIter;
+
+        }
+
+        newStep = true;
+
+      }
+
+      hNew = FastMath.min(hNew, hInt);
+      if (! forward) {
+        hNew = -hNew;
+      }
+
+      firstTime = false;
+
+      if (reject) {
+        isLastStep = false;
+        previousRejected = true;
+      } else {
+        previousRejected = false;
+      }
+
+    } while (!isLastStep);
+
+    // dispatch results
+    equations.setTime(stepStart);
+    equations.setCompleteState(y);
+
+    resetInternalState();
+
+  }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/GraggBulirschStoerStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/GraggBulirschStoerStepInterpolator.java
new file mode 100644
index 0000000..33c2705
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/GraggBulirschStoerStepInterpolator.java
@@ -0,0 +1,407 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+import org.apache.commons.math3.ode.EquationsMapper;
+import org.apache.commons.math3.ode.sampling.AbstractStepInterpolator;
+import org.apache.commons.math3.ode.sampling.StepInterpolator;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * This class implements an interpolator for the Gragg-Bulirsch-Stoer
+ * integrator.
+ *
+ * <p>This interpolator compute dense output inside the last step
+ * produced by a Gragg-Bulirsch-Stoer integrator.</p>
+ *
+ * <p>
+ * This implementation is basically a reimplementation in Java of the
+ * <a
+ * href="http://www.unige.ch/math/folks/hairer/prog/nonstiff/odex.f">odex</a>
+ * fortran code by E. Hairer and G. Wanner. The redistribution policy
+ * for this code is available <a
+ * href="http://www.unige.ch/~hairer/prog/licence.txt">here</a>, for
+ * convenience, it is reproduced below.</p>
+ * </p>
+ *
+ * <table border="0" width="80%" cellpadding="10" align="center" bgcolor="#E0E0E0">
+ * <tr><td>Copyright (c) 2004, Ernst Hairer</td></tr>
+ *
+ * <tr><td>Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ * <ul>
+ *  <li>Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.</li>
+ *  <li>Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.</li>
+ * </ul></td></tr>
+ *
+ * <tr><td><strong>THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+ * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.</strong></td></tr>
+ * </table>
+ *
+ * @see GraggBulirschStoerIntegrator
+ * @since 1.2
+ */
+
+class GraggBulirschStoerStepInterpolator
+  extends AbstractStepInterpolator {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 20110928L;
+
+    /** Slope at the beginning of the step. */
+    private double[] y0Dot;
+
+    /** State at the end of the step. */
+    private double[] y1;
+
+    /** Slope at the end of the step. */
+    private double[] y1Dot;
+
+    /** Derivatives at the middle of the step.
+     * element 0 is state at midpoint, element 1 is first derivative ...
+     */
+    private double[][] yMidDots;
+
+    /** Interpolation polynomials. */
+    private double[][] polynomials;
+
+    /** Error coefficients for the interpolation. */
+    private double[] errfac;
+
+    /** Degree of the interpolation polynomials. */
+    private int currentDegree;
+
+  /** Simple constructor.
+   * This constructor should not be used directly, it is only intended
+   * for the serialization process.
+   */
+    // CHECKSTYLE: stop RedundantModifier
+    // the public modifier here is needed for serialization
+  public GraggBulirschStoerStepInterpolator() {
+    y0Dot    = null;
+    y1       = null;
+    y1Dot    = null;
+    yMidDots = null;
+    resetTables(-1);
+  }
+  // CHECKSTYLE: resume RedundantModifier
+
+  /** Simple constructor.
+   * @param y reference to the integrator array holding the current state
+   * @param y0Dot reference to the integrator array holding the slope
+   * at the beginning of the step
+   * @param y1 reference to the integrator array holding the state at
+   * the end of the step
+   * @param y1Dot reference to the integrator array holding the slope
+   * at the end of the step
+   * @param yMidDots reference to the integrator array holding the
+   * derivatives at the middle point of the step
+   * @param forward integration direction indicator
+   * @param primaryMapper equations mapper for the primary equations set
+   * @param secondaryMappers equations mappers for the secondary equations sets
+   */
+  GraggBulirschStoerStepInterpolator(final double[] y, final double[] y0Dot,
+                                     final double[] y1, final double[] y1Dot,
+                                     final double[][] yMidDots,
+                                     final boolean forward,
+                                     final EquationsMapper primaryMapper,
+                                     final EquationsMapper[] secondaryMappers) {
+
+    super(y, forward, primaryMapper, secondaryMappers);
+    this.y0Dot    = y0Dot;
+    this.y1       = y1;
+    this.y1Dot    = y1Dot;
+    this.yMidDots = yMidDots;
+
+    resetTables(yMidDots.length + 4);
+
+  }
+
+  /** Copy constructor.
+   * @param interpolator interpolator to copy from. The copy is a deep
+   * copy: its arrays are separated from the original arrays of the
+   * instance
+   */
+  GraggBulirschStoerStepInterpolator(final GraggBulirschStoerStepInterpolator interpolator) {
+
+    super(interpolator);
+
+    final int dimension = currentState.length;
+
+    // the interpolator has been finalized,
+    // the following arrays are not needed anymore
+    y0Dot    = null;
+    y1       = null;
+    y1Dot    = null;
+    yMidDots = null;
+
+    // copy the interpolation polynomials (up to the current degree only)
+    if (interpolator.polynomials == null) {
+      polynomials = null;
+      currentDegree = -1;
+    } else {
+      resetTables(interpolator.currentDegree);
+      for (int i = 0; i < polynomials.length; ++i) {
+        polynomials[i] = new double[dimension];
+        System.arraycopy(interpolator.polynomials[i], 0,
+                         polynomials[i], 0, dimension);
+      }
+      currentDegree = interpolator.currentDegree;
+    }
+
+  }
+
+  /** Reallocate the internal tables.
+   * Reallocate the internal tables in order to be able to handle
+   * interpolation polynomials up to the given degree
+   * @param maxDegree maximal degree to handle
+   */
+  private void resetTables(final int maxDegree) {
+
+    if (maxDegree < 0) {
+      polynomials   = null;
+      errfac        = null;
+      currentDegree = -1;
+    } else {
+
+      final double[][] newPols = new double[maxDegree + 1][];
+      if (polynomials != null) {
+        System.arraycopy(polynomials, 0, newPols, 0, polynomials.length);
+        for (int i = polynomials.length; i < newPols.length; ++i) {
+          newPols[i] = new double[currentState.length];
+        }
+      } else {
+        for (int i = 0; i < newPols.length; ++i) {
+          newPols[i] = new double[currentState.length];
+        }
+      }
+      polynomials = newPols;
+
+      // initialize the error factors array for interpolation
+      if (maxDegree <= 4) {
+        errfac = null;
+      } else {
+        errfac = new double[maxDegree - 4];
+        for (int i = 0; i < errfac.length; ++i) {
+          final int ip5 = i + 5;
+          errfac[i] = 1.0 / (ip5 * ip5);
+          final double e = 0.5 * FastMath.sqrt (((double) (i + 1)) / ip5);
+          for (int j = 0; j <= i; ++j) {
+            errfac[i] *= e / (j + 1);
+          }
+        }
+      }
+
+      currentDegree = 0;
+
+    }
+
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  protected StepInterpolator doCopy() {
+    return new GraggBulirschStoerStepInterpolator(this);
+  }
+
+
+  /** Compute the interpolation coefficients for dense output.
+   * @param mu degree of the interpolation polynomial
+   * @param h current step
+   */
+  public void computeCoefficients(final int mu, final double h) {
+
+    if ((polynomials == null) || (polynomials.length <= (mu + 4))) {
+      resetTables(mu + 4);
+    }
+
+    currentDegree = mu + 4;
+
+    for (int i = 0; i < currentState.length; ++i) {
+
+      final double yp0   = h * y0Dot[i];
+      final double yp1   = h * y1Dot[i];
+      final double ydiff = y1[i] - currentState[i];
+      final double aspl  = ydiff - yp1;
+      final double bspl  = yp0 - ydiff;
+
+      polynomials[0][i] = currentState[i];
+      polynomials[1][i] = ydiff;
+      polynomials[2][i] = aspl;
+      polynomials[3][i] = bspl;
+
+      if (mu < 0) {
+        return;
+      }
+
+      // compute the remaining coefficients
+      final double ph0 = 0.5 * (currentState[i] + y1[i]) + 0.125 * (aspl + bspl);
+      polynomials[4][i] = 16 * (yMidDots[0][i] - ph0);
+
+      if (mu > 0) {
+        final double ph1 = ydiff + 0.25 * (aspl - bspl);
+        polynomials[5][i] = 16 * (yMidDots[1][i] - ph1);
+
+        if (mu > 1) {
+          final double ph2 = yp1 - yp0;
+          polynomials[6][i] = 16 * (yMidDots[2][i] - ph2 + polynomials[4][i]);
+
+          if (mu > 2) {
+            final double ph3 = 6 * (bspl - aspl);
+            polynomials[7][i] = 16 * (yMidDots[3][i] - ph3 + 3 * polynomials[5][i]);
+
+            for (int j = 4; j <= mu; ++j) {
+              final double fac1 = 0.5 * j * (j - 1);
+              final double fac2 = 2 * fac1 * (j - 2) * (j - 3);
+              polynomials[j+4][i] =
+                  16 * (yMidDots[j][i] + fac1 * polynomials[j+2][i] - fac2 * polynomials[j][i]);
+            }
+
+          }
+        }
+      }
+    }
+
+  }
+
+  /** Estimate interpolation error.
+   * @param scale scaling array
+   * @return estimate of the interpolation error
+   */
+  public double estimateError(final double[] scale) {
+    double error = 0;
+    if (currentDegree >= 5) {
+      for (int i = 0; i < scale.length; ++i) {
+        final double e = polynomials[currentDegree][i] / scale[i];
+        error += e * e;
+      }
+      error = FastMath.sqrt(error / scale.length) * errfac[currentDegree - 5];
+    }
+    return error;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  protected void computeInterpolatedStateAndDerivatives(final double theta,
+                                                        final double oneMinusThetaH) {
+
+    final int dimension = currentState.length;
+
+    final double oneMinusTheta = 1.0 - theta;
+    final double theta05       = theta - 0.5;
+    final double tOmT          = theta * oneMinusTheta;
+    final double t4            = tOmT * tOmT;
+    final double t4Dot         = 2 * tOmT * (1 - 2 * theta);
+    final double dot1          = 1.0 / h;
+    final double dot2          = theta * (2 - 3 * theta) / h;
+    final double dot3          = ((3 * theta - 4) * theta + 1) / h;
+
+    for (int i = 0; i < dimension; ++i) {
+
+        final double p0 = polynomials[0][i];
+        final double p1 = polynomials[1][i];
+        final double p2 = polynomials[2][i];
+        final double p3 = polynomials[3][i];
+        interpolatedState[i] = p0 + theta * (p1 + oneMinusTheta * (p2 * theta + p3 * oneMinusTheta));
+        interpolatedDerivatives[i] = dot1 * p1 + dot2 * p2 + dot3 * p3;
+
+        if (currentDegree > 3) {
+            double cDot = 0;
+            double c = polynomials[currentDegree][i];
+            for (int j = currentDegree - 1; j > 3; --j) {
+                final double d = 1.0 / (j - 3);
+                cDot = d * (theta05 * cDot + c);
+                c = polynomials[j][i] + c * d * theta05;
+            }
+            interpolatedState[i]       += t4 * c;
+            interpolatedDerivatives[i] += (t4 * cDot + t4Dot * c) / h;
+        }
+
+    }
+
+    if (h == 0) {
+        // in this degenerated case, the previous computation leads to NaN for derivatives
+        // we fix this by using the derivatives at midpoint
+        System.arraycopy(yMidDots[1], 0, interpolatedDerivatives, 0, dimension);
+    }
+
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public void writeExternal(final ObjectOutput out)
+    throws IOException {
+
+    final int dimension = (currentState == null) ? -1 : currentState.length;
+
+    // save the state of the base class
+    writeBaseExternal(out);
+
+    // save the local attributes (but not the temporary vectors)
+    out.writeInt(currentDegree);
+    for (int k = 0; k <= currentDegree; ++k) {
+      for (int l = 0; l < dimension; ++l) {
+        out.writeDouble(polynomials[k][l]);
+      }
+    }
+
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public void readExternal(final ObjectInput in)
+    throws IOException, ClassNotFoundException {
+
+    // read the base class
+    final double t = readBaseExternal(in);
+    final int dimension = (currentState == null) ? -1 : currentState.length;
+
+    // read the local attributes
+    final int degree = in.readInt();
+    resetTables(degree);
+    currentDegree = degree;
+
+    for (int k = 0; k <= currentDegree; ++k) {
+      for (int l = 0; l < dimension; ++l) {
+        polynomials[k][l] = in.readDouble();
+      }
+    }
+
+    // we can now set the interpolated time and state
+    setInterpolatedTime(t);
+
+  }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/HighamHall54FieldIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/HighamHall54FieldIntegrator.java
new file mode 100644
index 0000000..d7d6f63
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/HighamHall54FieldIntegrator.java
@@ -0,0 +1,205 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.ode.FieldEquationsMapper;
+import org.apache.commons.math3.ode.FieldODEStateAndDerivative;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.util.MathUtils;
+
+
+/**
+ * This class implements the 5(4) Higham and Hall integrator for
+ * Ordinary Differential Equations.
+ *
+ * <p>This integrator is an embedded Runge-Kutta integrator
+ * of order 5(4) used in local extrapolation mode (i.e. the solution
+ * is computed using the high order formula) with stepsize control
+ * (and automatic step initialization) and continuous output. This
+ * method uses 7 functions evaluations per step.</p>
+ *
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+
+public class HighamHall54FieldIntegrator<T extends RealFieldElement<T>>
+    extends EmbeddedRungeKuttaFieldIntegrator<T> {
+
+    /** Integrator method name. */
+    private static final String METHOD_NAME = "Higham-Hall 5(4)";
+
+    /** Error weights Butcher array. */
+    private final T[] e ;
+
+    /** Simple constructor.
+     * Build a fifth order Higham and Hall integrator with the given step bounds
+     * @param field field to which the time and state vector elements belong
+     * @param minStep minimal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param maxStep maximal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param scalAbsoluteTolerance allowed absolute error
+     * @param scalRelativeTolerance allowed relative error
+     */
+    public HighamHall54FieldIntegrator(final Field<T> field,
+                                       final double minStep, final double maxStep,
+                                       final double scalAbsoluteTolerance,
+                                       final double scalRelativeTolerance) {
+        super(field, METHOD_NAME, -1,
+              minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance);
+        e = MathArrays.buildArray(field, 7);
+        e[0] = fraction(-1,  20);
+        e[1] = field.getZero();
+        e[2] = fraction(81, 160);
+        e[3] = fraction(-6,   5);
+        e[4] = fraction(25,  32);
+        e[5] = fraction( 1,  16);
+        e[6] = fraction(-1,  10);
+    }
+
+    /** Simple constructor.
+     * Build a fifth order Higham and Hall integrator with the given step bounds
+     * @param field field to which the time and state vector elements belong
+     * @param minStep minimal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param maxStep maximal step (sign is irrelevant, regardless of
+     * integration direction, forward or backward), the last step can
+     * be smaller than this
+     * @param vecAbsoluteTolerance allowed absolute error
+     * @param vecRelativeTolerance allowed relative error
+     */
+    public HighamHall54FieldIntegrator(final Field<T> field,
+                                       final double minStep, final double maxStep,
+                                       final double[] vecAbsoluteTolerance,
+                                       final double[] vecRelativeTolerance) {
+        super(field, METHOD_NAME, -1,
+              minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance);
+        e = MathArrays.buildArray(field, 7);
+        e[0] = fraction(-1,  20);
+        e[1] = field.getZero();
+        e[2] = fraction(81, 160);
+        e[3] = fraction(-6,   5);
+        e[4] = fraction(25,  32);
+        e[5] = fraction( 1,  16);
+        e[6] = fraction(-1,  10);
+    }
+
+    /** {@inheritDoc} */
+    public T[] getC() {
+        final T[] c = MathArrays.buildArray(getField(), 6);
+        c[0] = fraction(2, 9);
+        c[1] = fraction(1, 3);
+        c[2] = fraction(1, 2);
+        c[3] = fraction(3, 5);
+        c[4] = getField().getOne();
+        c[5] = getField().getOne();
+        return c;
+    }
+
+    /** {@inheritDoc} */
+    public T[][] getA() {
+        final T[][] a = MathArrays.buildArray(getField(), 6, -1);
+        for (int i = 0; i < a.length; ++i) {
+            a[i] = MathArrays.buildArray(getField(), i + 1);
+        }
+        a[0][0] = fraction(     2,     9);
+        a[1][0] = fraction(     1,    12);
+        a[1][1] = fraction(     1,     4);
+        a[2][0] = fraction(     1,     8);
+        a[2][1] = getField().getZero();
+        a[2][2] = fraction(     3,     8);
+        a[3][0] = fraction(    91,   500);
+        a[3][1] = fraction(   -27,   100);
+        a[3][2] = fraction(    78,   125);
+        a[3][3] = fraction(     8,   125);
+        a[4][0] = fraction(   -11,    20);
+        a[4][1] = fraction(    27,    20);
+        a[4][2] = fraction(    12,     5);
+        a[4][3] = fraction(   -36,     5);
+        a[4][4] = fraction(     5,     1);
+        a[5][0] = fraction(     1,    12);
+        a[5][1] = getField().getZero();
+        a[5][2] = fraction(    27,    32);
+        a[5][3] = fraction(    -4,     3);
+        a[5][4] = fraction(   125,    96);
+        a[5][5] = fraction(     5,    48);
+        return a;
+    }
+
+    /** {@inheritDoc} */
+    public T[] getB() {
+        final T[] b = MathArrays.buildArray(getField(), 7);
+        b[0] = fraction(  1, 12);
+        b[1] = getField().getZero();
+        b[2] = fraction( 27, 32);
+        b[3] = fraction( -4,  3);
+        b[4] = fraction(125, 96);
+        b[5] = fraction(  5, 48);
+        b[6] = getField().getZero();
+        return b;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected HighamHall54FieldStepInterpolator<T>
+        createInterpolator(final boolean forward, T[][] yDotK,
+                           final FieldODEStateAndDerivative<T> globalPreviousState,
+                           final FieldODEStateAndDerivative<T> globalCurrentState, final FieldEquationsMapper<T> mapper) {
+        return new HighamHall54FieldStepInterpolator<T>(getField(), forward, yDotK,
+                                                        globalPreviousState, globalCurrentState,
+                                                        globalPreviousState, globalCurrentState,
+                                                        mapper);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getOrder() {
+        return 5;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected T estimateError(final T[][] yDotK, final T[] y0, final T[] y1, final T h) {
+
+        T error = getField().getZero();
+
+        for (int j = 0; j < mainSetDimension; ++j) {
+            T errSum = yDotK[0][j].multiply(e[0]);
+            for (int l = 1; l < e.length; ++l) {
+                errSum = errSum.add(yDotK[l][j].multiply(e[l]));
+            }
+
+            final T yScale = MathUtils.max(y0[j].abs(), y1[j].abs());
+            final T tol    = (vecAbsoluteTolerance == null) ?
+                             yScale.multiply(scalRelativeTolerance).add(scalAbsoluteTolerance) :
+                             yScale.multiply(vecRelativeTolerance[j]).add(vecAbsoluteTolerance[j]);
+            final T ratio  = h.multiply(errSum).divide(tol);
+            error = error.add(ratio.multiply(ratio));
+
+        }
+
+        return error.divide(mainSetDimension).sqrt();
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/HighamHall54FieldStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/HighamHall54FieldStepInterpolator.java
new file mode 100644
index 0000000..10240fd
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/HighamHall54FieldStepInterpolator.java
@@ -0,0 +1,116 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.ode.FieldEquationsMapper;
+import org.apache.commons.math3.ode.FieldODEStateAndDerivative;
+
+/**
+ * This class represents an interpolator over the last step during an
+ * ODE integration for the 5(4) Higham and Hall integrator.
+ *
+ * @see HighamHall54FieldIntegrator
+ *
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+
+class HighamHall54FieldStepInterpolator<T extends RealFieldElement<T>>
+    extends RungeKuttaFieldStepInterpolator<T> {
+
+    /** Simple constructor.
+     * @param field field to which the time and state vector elements belong
+     * @param forward integration direction indicator
+     * @param yDotK slopes at the intermediate points
+     * @param globalPreviousState start of the global step
+     * @param globalCurrentState end of the global step
+     * @param softPreviousState start of the restricted step
+     * @param softCurrentState end of the restricted step
+     * @param mapper equations mapper for the all equations
+     */
+    HighamHall54FieldStepInterpolator(final Field<T> field, final boolean forward,
+                                      final T[][] yDotK,
+                                      final FieldODEStateAndDerivative<T> globalPreviousState,
+                                      final FieldODEStateAndDerivative<T> globalCurrentState,
+                                      final FieldODEStateAndDerivative<T> softPreviousState,
+                                      final FieldODEStateAndDerivative<T> softCurrentState,
+                                      final FieldEquationsMapper<T> mapper) {
+        super(field, forward, yDotK,
+              globalPreviousState, globalCurrentState, softPreviousState, softCurrentState,
+              mapper);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected HighamHall54FieldStepInterpolator<T> create(final Field<T> newField, final boolean newForward, final T[][] newYDotK,
+                                                          final FieldODEStateAndDerivative<T> newGlobalPreviousState,
+                                                          final FieldODEStateAndDerivative<T> newGlobalCurrentState,
+                                                          final FieldODEStateAndDerivative<T> newSoftPreviousState,
+                                                          final FieldODEStateAndDerivative<T> newSoftCurrentState,
+                                                          final FieldEquationsMapper<T> newMapper) {
+        return new HighamHall54FieldStepInterpolator<T>(newField, newForward, newYDotK,
+                                                        newGlobalPreviousState, newGlobalCurrentState,
+                                                        newSoftPreviousState, newSoftCurrentState,
+                                                        newMapper);
+    }
+
+    /** {@inheritDoc} */
+    @SuppressWarnings("unchecked")
+    @Override
+    protected FieldODEStateAndDerivative<T> computeInterpolatedStateAndDerivatives(final FieldEquationsMapper<T> mapper,
+                                                                                   final T time, final T theta,
+                                                                                   final T thetaH, final T oneMinusThetaH) {
+
+        final T bDot0 = theta.multiply(theta.multiply(theta.multiply( -10.0      ).add( 16.0       )).add(-15.0 /  2.0)).add(1);
+        final T bDot1 = time.getField().getZero();
+        final T bDot2 = theta.multiply(theta.multiply(theta.multiply( 135.0 / 2.0).add(-729.0 / 8.0)).add(459.0 / 16.0));
+        final T bDot3 = theta.multiply(theta.multiply(theta.multiply(-120.0      ).add( 152.0      )).add(-44.0       ));
+        final T bDot4 = theta.multiply(theta.multiply(theta.multiply( 125.0 / 2.0).add(-625.0 / 8.0)).add(375.0 / 16.0));
+        final T bDot5 = theta.multiply(  5.0 /  8.0).multiply(theta.multiply(2).subtract(1));
+        final T[] interpolatedState;
+        final T[] interpolatedDerivatives;
+
+        if (getGlobalPreviousState() != null && theta.getReal() <= 0.5) {
+            final T b0 = thetaH.multiply(theta.multiply(theta.multiply(theta.multiply( -5.0 / 2.0).add(  16.0 /  3.0)).add(-15.0 /  4.0)).add(1));
+            final T b1 = time.getField().getZero();
+            final T b2 = thetaH.multiply(theta.multiply(theta.multiply(theta.multiply(135.0 / 8.0).add(-243.0 /  8.0)).add(459.0 / 32.0)));
+            final T b3 = thetaH.multiply(theta.multiply(theta.multiply(theta.multiply(-30.0      ).add( 152.0 /  3.0)).add(-22.0       )));
+            final T b4 = thetaH.multiply(theta.multiply(theta.multiply(theta.multiply(125.0 / 8.0).add(-625.0 / 24.0)).add(375.0 / 32.0)));
+            final T b5 = thetaH.multiply(theta.multiply(theta.multiply(                                   5.0 / 12.0 ).add( -5.0 / 16.0)));
+            interpolatedState       = previousStateLinearCombination(b0, b1, b2, b3, b4, b5);
+            interpolatedDerivatives = derivativeLinearCombination(bDot0, bDot1, bDot2, bDot3, bDot4, bDot5);
+        } else {
+            final T theta2 = theta.multiply(theta);
+            final T h      = thetaH.divide(theta);
+            final T b0 = h.multiply( theta.multiply(theta.multiply(theta.multiply(theta.multiply(-5.0 / 2.0).add( 16.0 / 3.0)).add( -15.0 /  4.0)).add(  1.0       )).add(  -1.0 / 12.0));
+            final T b1 = time.getField().getZero();
+            final T b2 = h.multiply(theta2.multiply(theta.multiply(theta.multiply(                               135.0 / 8.0 ).add(-243.0 /  8.0)).add(459.0 / 32.0)).add( -27.0 / 32.0));
+            final T b3 = h.multiply(theta2.multiply(theta.multiply(theta.multiply(                               -30.0       ).add( 152.0 /  3.0)).add(-22.0       )).add(  4.0  /  3.0));
+            final T b4 = h.multiply(theta2.multiply(theta.multiply(theta.multiply(                               125.0 / 8.0 ).add(-625.0 / 24.0)).add(375.0 / 32.0)).add(-125.0 / 96.0));
+            final T b5 = h.multiply(theta2.multiply(theta.multiply(                                                                   5.0 / 12.0 ).add(-5.0  / 16.0)).add(  -5.0 / 48.0));
+            interpolatedState       = currentStateLinearCombination(b0, b1, b2, b3, b4, b5);
+            interpolatedDerivatives = derivativeLinearCombination(bDot0, bDot1, bDot2, bDot3, bDot4, bDot5);
+        }
+
+        return new FieldODEStateAndDerivative<T>(time, interpolatedState, interpolatedDerivatives);
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/HighamHall54Integrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/HighamHall54Integrator.java
new file mode 100644
index 0000000..c48c4f9
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/HighamHall54Integrator.java
@@ -0,0 +1,135 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.util.FastMath;
+
+
+/**
+ * This class implements the 5(4) Higham and Hall integrator for
+ * Ordinary Differential Equations.
+ *
+ * <p>This integrator is an embedded Runge-Kutta integrator
+ * of order 5(4) used in local extrapolation mode (i.e. the solution
+ * is computed using the high order formula) with stepsize control
+ * (and automatic step initialization) and continuous output. This
+ * method uses 7 functions evaluations per step.</p>
+ *
+ * @since 1.2
+ */
+
+public class HighamHall54Integrator extends EmbeddedRungeKuttaIntegrator {
+
+  /** Integrator method name. */
+  private static final String METHOD_NAME = "Higham-Hall 5(4)";
+
+  /** Time steps Butcher array. */
+  private static final double[] STATIC_C = {
+    2.0/9.0, 1.0/3.0, 1.0/2.0, 3.0/5.0, 1.0, 1.0
+  };
+
+  /** Internal weights Butcher array. */
+  private static final double[][] STATIC_A = {
+    {2.0/9.0},
+    {1.0/12.0, 1.0/4.0},
+    {1.0/8.0, 0.0, 3.0/8.0},
+    {91.0/500.0, -27.0/100.0, 78.0/125.0, 8.0/125.0},
+    {-11.0/20.0, 27.0/20.0, 12.0/5.0, -36.0/5.0, 5.0},
+    {1.0/12.0, 0.0, 27.0/32.0, -4.0/3.0, 125.0/96.0, 5.0/48.0}
+  };
+
+  /** Propagation weights Butcher array. */
+  private static final double[] STATIC_B = {
+    1.0/12.0, 0.0, 27.0/32.0, -4.0/3.0, 125.0/96.0, 5.0/48.0, 0.0
+  };
+
+  /** Error weights Butcher array. */
+  private static final double[] STATIC_E = {
+    -1.0/20.0, 0.0, 81.0/160.0, -6.0/5.0, 25.0/32.0, 1.0/16.0, -1.0/10.0
+  };
+
+  /** Simple constructor.
+   * Build a fifth order Higham and Hall integrator with the given step bounds
+   * @param minStep minimal step (sign is irrelevant, regardless of
+   * integration direction, forward or backward), the last step can
+   * be smaller than this
+   * @param maxStep maximal step (sign is irrelevant, regardless of
+   * integration direction, forward or backward), the last step can
+   * be smaller than this
+   * @param scalAbsoluteTolerance allowed absolute error
+   * @param scalRelativeTolerance allowed relative error
+   */
+  public HighamHall54Integrator(final double minStep, final double maxStep,
+                                final double scalAbsoluteTolerance,
+                                final double scalRelativeTolerance) {
+    super(METHOD_NAME, false, STATIC_C, STATIC_A, STATIC_B, new HighamHall54StepInterpolator(),
+          minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance);
+  }
+
+  /** Simple constructor.
+   * Build a fifth order Higham and Hall integrator with the given step bounds
+   * @param minStep minimal step (sign is irrelevant, regardless of
+   * integration direction, forward or backward), the last step can
+   * be smaller than this
+   * @param maxStep maximal step (sign is irrelevant, regardless of
+   * integration direction, forward or backward), the last step can
+   * be smaller than this
+   * @param vecAbsoluteTolerance allowed absolute error
+   * @param vecRelativeTolerance allowed relative error
+   */
+  public HighamHall54Integrator(final double minStep, final double maxStep,
+                                final double[] vecAbsoluteTolerance,
+                                final double[] vecRelativeTolerance) {
+    super(METHOD_NAME, false, STATIC_C, STATIC_A, STATIC_B, new HighamHall54StepInterpolator(),
+          minStep, maxStep, vecAbsoluteTolerance, vecRelativeTolerance);
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public int getOrder() {
+    return 5;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  protected double estimateError(final double[][] yDotK,
+                                 final double[] y0, final double[] y1,
+                                 final double h) {
+
+    double error = 0;
+
+    for (int j = 0; j < mainSetDimension; ++j) {
+      double errSum = STATIC_E[0] * yDotK[0][j];
+      for (int l = 1; l < STATIC_E.length; ++l) {
+        errSum += STATIC_E[l] * yDotK[l][j];
+      }
+
+      final double yScale = FastMath.max(FastMath.abs(y0[j]), FastMath.abs(y1[j]));
+      final double tol = (vecAbsoluteTolerance == null) ?
+                         (scalAbsoluteTolerance + scalRelativeTolerance * yScale) :
+                         (vecAbsoluteTolerance[j] + vecRelativeTolerance[j] * yScale);
+      final double ratio  = h * errSum / tol;
+      error += ratio * ratio;
+
+    }
+
+    return FastMath.sqrt(error / mainSetDimension);
+
+  }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/HighamHall54StepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/HighamHall54StepInterpolator.java
new file mode 100644
index 0000000..682ec7c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/HighamHall54StepInterpolator.java
@@ -0,0 +1,122 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.ode.sampling.StepInterpolator;
+
+/**
+ * This class represents an interpolator over the last step during an
+ * ODE integration for the 5(4) Higham and Hall integrator.
+ *
+ * @see HighamHall54Integrator
+ *
+ * @since 1.2
+ */
+
+class HighamHall54StepInterpolator
+  extends RungeKuttaStepInterpolator {
+
+  /** Serializable version identifier */
+  private static final long serialVersionUID = 20111120L;
+
+  /** Simple constructor.
+   * This constructor builds an instance that is not usable yet, the
+   * {@link
+   * org.apache.commons.math3.ode.sampling.AbstractStepInterpolator#reinitialize}
+   * method should be called before using the instance in order to
+   * initialize the internal arrays. This constructor is used only
+   * in order to delay the initialization in some cases. The {@link
+   * EmbeddedRungeKuttaIntegrator} uses the prototyping design pattern
+   * to create the step interpolators by cloning an uninitialized model
+   * and later initializing the copy.
+   */
+  // CHECKSTYLE: stop RedundantModifier
+  // the public modifier here is needed for serialization
+  public HighamHall54StepInterpolator() {
+    super();
+  }
+  // CHECKSTYLE: resume RedundantModifier
+
+  /** Copy constructor.
+   * @param interpolator interpolator to copy from. The copy is a deep
+   * copy: its arrays are separated from the original arrays of the
+   * instance
+   */
+  HighamHall54StepInterpolator(final HighamHall54StepInterpolator interpolator) {
+    super(interpolator);
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  protected StepInterpolator doCopy() {
+    return new HighamHall54StepInterpolator(this);
+  }
+
+
+  /** {@inheritDoc} */
+  @Override
+  protected void computeInterpolatedStateAndDerivatives(final double theta,
+                                          final double oneMinusThetaH) {
+
+    final double bDot0 = 1 + theta * (-15.0/2.0 + theta * (16.0 - 10.0 * theta));
+    final double bDot2 = theta * (459.0/16.0 + theta * (-729.0/8.0 + 135.0/2.0 * theta));
+    final double bDot3 = theta * (-44.0 + theta * (152.0 - 120.0 * theta));
+    final double bDot4 = theta * (375.0/16.0 + theta * (-625.0/8.0 + 125.0/2.0 * theta));
+    final double bDot5 = theta * 5.0/8.0 * (2 * theta - 1);
+
+    if ((previousState != null) && (theta <= 0.5)) {
+        final double hTheta = h * theta;
+        final double b0 = hTheta * (1.0 + theta * (-15.0/4.0  + theta * (16.0/3.0 - 5.0/2.0 * theta)));
+        final double b2 = hTheta * (      theta * (459.0/32.0 + theta * (-243.0/8.0 + theta * 135.0/8.0)));
+        final double b3 = hTheta * (      theta * (-22.0      + theta * (152.0/3.0  + theta * -30.0)));
+        final double b4 = hTheta * (      theta * (375.0/32.0 + theta * (-625.0/24.0 + theta * 125.0/8.0)));
+        final double b5 = hTheta * (      theta * (-5.0/16.0  + theta *  5.0/12.0));
+        for (int i = 0; i < interpolatedState.length; ++i) {
+            final double yDot0 = yDotK[0][i];
+            final double yDot2 = yDotK[2][i];
+            final double yDot3 = yDotK[3][i];
+            final double yDot4 = yDotK[4][i];
+            final double yDot5 = yDotK[5][i];
+            interpolatedState[i] =
+                    previousState[i] + b0 * yDot0 + b2 * yDot2 + b3 * yDot3 + b4 * yDot4 + b5 * yDot5;
+            interpolatedDerivatives[i] =
+                    bDot0 * yDot0 + bDot2 * yDot2 + bDot3 * yDot3 + bDot4 * yDot4 + bDot5 * yDot5;
+        }
+    } else {
+        final double theta2 = theta * theta;
+        final double b0 = h * (-1.0/12.0 + theta * (1.0 + theta * (-15.0/4.0 + theta * (16.0/3.0 + theta * -5.0/2.0))));
+        final double b2 = h * (-27.0/32.0 + theta2 * (459.0/32.0 + theta * (-243.0/8.0 + theta * 135.0/8.0)));
+        final double b3 = h * (4.0/3.0 + theta2 * (-22.0 + theta * (152.0/3.0  + theta * -30.0)));
+        final double b4 = h * (-125.0/96.0 + theta2 * (375.0/32.0 + theta * (-625.0/24.0 + theta * 125.0/8.0)));
+        final double b5 = h * (-5.0/48.0 + theta2 * (-5.0/16.0 + theta * 5.0/12.0));
+        for (int i = 0; i < interpolatedState.length; ++i) {
+            final double yDot0 = yDotK[0][i];
+            final double yDot2 = yDotK[2][i];
+            final double yDot3 = yDotK[3][i];
+            final double yDot4 = yDotK[4][i];
+            final double yDot5 = yDotK[5][i];
+            interpolatedState[i] =
+                    currentState[i] + b0 * yDot0 + b2 * yDot2 + b3 * yDot3 + b4 * yDot4 + b5 * yDot5;
+            interpolatedDerivatives[i] =
+                    bDot0 * yDot0 + bDot2 * yDot2 + bDot3 * yDot3 + bDot4 * yDot4 + bDot5 * yDot5;
+        }
+    }
+
+  }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/LutherFieldIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/LutherFieldIntegrator.java
new file mode 100644
index 0000000..7b57b5a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/LutherFieldIntegrator.java
@@ -0,0 +1,146 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.ode.FieldEquationsMapper;
+import org.apache.commons.math3.ode.FieldODEStateAndDerivative;
+import org.apache.commons.math3.util.MathArrays;
+
+
+/**
+ * This class implements the Luther sixth order Runge-Kutta
+ * integrator for Ordinary Differential Equations.
+
+ * <p>
+ * This method is described in H. A. Luther 1968 paper <a
+ * href="http://www.ams.org/journals/mcom/1968-22-102/S0025-5718-68-99876-1/S0025-5718-68-99876-1.pdf">
+ * An explicit Sixth-Order Runge-Kutta Formula</a>.
+ * </p>
+
+ * <p>This method is an explicit Runge-Kutta method, its Butcher-array
+ * is the following one :
+ * <pre>
+ *        0   |               0                     0                     0                     0                     0                     0
+ *        1   |               1                     0                     0                     0                     0                     0
+ *       1/2  |              3/8                   1/8                    0                     0                     0                     0
+ *       2/3  |              8/27                  2/27                  8/27                   0                     0                     0
+ *   (7-q)/14 | (  -21 +   9q)/392    (  -56 +   8q)/392    (  336 -  48q)/392    (  -63 +   3q)/392                  0                     0
+ *   (7+q)/14 | (-1155 - 255q)/1960   ( -280 -  40q)/1960   (    0 - 320q)/1960   (   63 + 363q)/1960   ( 2352 + 392q)/1960                 0
+ *        1   | (  330 + 105q)/180    (  120 +   0q)/180    ( -200 + 280q)/180    (  126 - 189q)/180    ( -686 - 126q)/180     ( 490 -  70q)/180
+ *            |--------------------------------------------------------------------------------------------------------------------------------------------------
+ *            |              1/20                   0                   16/45                  0                   49/180                 49/180         1/20
+ * </pre>
+ * where q = &radic;21</p>
+ *
+ * @see EulerFieldIntegrator
+ * @see ClassicalRungeKuttaFieldIntegrator
+ * @see GillFieldIntegrator
+ * @see MidpointFieldIntegrator
+ * @see ThreeEighthesFieldIntegrator
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+
+public class LutherFieldIntegrator<T extends RealFieldElement<T>>
+    extends RungeKuttaFieldIntegrator<T> {
+
+    /** Simple constructor.
+     * Build a fourth-order Luther integrator with the given step.
+     * @param field field to which the time and state vector elements belong
+     * @param step integration step
+     */
+    public LutherFieldIntegrator(final Field<T> field, final T step) {
+        super(field, "Luther", step);
+    }
+
+    /** {@inheritDoc} */
+    public T[] getC() {
+        final T q = getField().getZero().add(21).sqrt();
+        final T[] c = MathArrays.buildArray(getField(), 6);
+        c[0] = getField().getOne();
+        c[1] = fraction(1, 2);
+        c[2] = fraction(2, 3);
+        c[3] = q.subtract(7).divide(-14);
+        c[4] = q.add(7).divide(14);
+        c[5] = getField().getOne();
+        return c;
+    }
+
+    /** {@inheritDoc} */
+    public T[][] getA() {
+        final T q = getField().getZero().add(21).sqrt();
+        final T[][] a = MathArrays.buildArray(getField(), 6, -1);
+        for (int i = 0; i < a.length; ++i) {
+            a[i] = MathArrays.buildArray(getField(), i + 1);
+        }
+        a[0][0] = getField().getOne();
+        a[1][0] = fraction(3,  8);
+        a[1][1] = fraction(1,  8);
+        a[2][0] = fraction(8, 27);
+        a[2][1] = fraction(2, 27);
+        a[2][2] = a[2][0];
+        a[3][0] = q.multiply(   9).add(  -21).divide( 392);
+        a[3][1] = q.multiply(   8).add(  -56).divide( 392);
+        a[3][2] = q.multiply( -48).add(  336).divide( 392);
+        a[3][3] = q.multiply(   3).add(  -63).divide( 392);
+        a[4][0] = q.multiply(-255).add(-1155).divide(1960);
+        a[4][1] = q.multiply( -40).add( -280).divide(1960);
+        a[4][2] = q.multiply(-320)           .divide(1960);
+        a[4][3] = q.multiply( 363).add(   63).divide(1960);
+        a[4][4] = q.multiply( 392).add( 2352).divide(1960);
+        a[5][0] = q.multiply( 105).add(  330).divide( 180);
+        a[5][1] = fraction(2, 3);
+        a[5][2] = q.multiply( 280).add( -200).divide( 180);
+        a[5][3] = q.multiply(-189).add(  126).divide( 180);
+        a[5][4] = q.multiply(-126).add( -686).divide( 180);
+        a[5][5] = q.multiply( -70).add(  490).divide( 180);
+        return a;
+    }
+
+    /** {@inheritDoc} */
+    public T[] getB() {
+
+        final T[] b = MathArrays.buildArray(getField(), 7);
+        b[0] = fraction( 1,  20);
+        b[1] = getField().getZero();
+        b[2] = fraction(16,  45);
+        b[3] = getField().getZero();
+        b[4] = fraction(49, 180);
+        b[5] = b[4];
+        b[6] = b[0];
+
+        return b;
+
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected LutherFieldStepInterpolator<T>
+        createInterpolator(final boolean forward, T[][] yDotK,
+                           final FieldODEStateAndDerivative<T> globalPreviousState,
+                           final FieldODEStateAndDerivative<T> globalCurrentState,
+                           final FieldEquationsMapper<T> mapper) {
+        return new LutherFieldStepInterpolator<T>(getField(), forward, yDotK,
+                                                  globalPreviousState, globalCurrentState,
+                                                  globalPreviousState, globalCurrentState,
+                                                  mapper);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/LutherFieldStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/LutherFieldStepInterpolator.java
new file mode 100644
index 0000000..9e38a96
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/LutherFieldStepInterpolator.java
@@ -0,0 +1,224 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.ode.FieldEquationsMapper;
+import org.apache.commons.math3.ode.FieldODEStateAndDerivative;
+
+/**
+ * This class represents an interpolator over the last step during an
+ * ODE integration for the 6th order Luther integrator.
+ *
+ * <p>This interpolator computes dense output inside the last
+ * step computed. The interpolation equation is consistent with the
+ * integration scheme.</p>
+ *
+ * @see LutherFieldIntegrator
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+
+class LutherFieldStepInterpolator<T extends RealFieldElement<T>>
+    extends RungeKuttaFieldStepInterpolator<T> {
+
+    /** -49 - 49 q. */
+    private final T c5a;
+
+    /** 392 + 287 q. */
+    private final T c5b;
+
+    /** -637 - 357 q. */
+    private final T c5c;
+
+    /** 833 + 343 q. */
+    private final T c5d;
+
+    /** -49 + 49 q. */
+    private final T c6a;
+
+    /** -392 - 287 q. */
+    private final T c6b;
+
+    /** -637 + 357 q. */
+    private final T c6c;
+
+    /** 833 - 343 q. */
+    private final T c6d;
+
+    /** 49 + 49 q. */
+    private final T d5a;
+
+    /** -1372 - 847 q. */
+    private final T d5b;
+
+    /** 2254 + 1029 q */
+    private final T d5c;
+
+    /** 49 - 49 q. */
+    private final T d6a;
+
+    /** -1372 + 847 q. */
+    private final T d6b;
+
+    /** 2254 - 1029 q */
+    private final T d6c;
+
+    /** Simple constructor.
+     * @param field field to which the time and state vector elements belong
+     * @param forward integration direction indicator
+     * @param yDotK slopes at the intermediate points
+     * @param globalPreviousState start of the global step
+     * @param globalCurrentState end of the global step
+     * @param softPreviousState start of the restricted step
+     * @param softCurrentState end of the restricted step
+     * @param mapper equations mapper for the all equations
+     */
+    LutherFieldStepInterpolator(final Field<T> field, final boolean forward,
+                                final T[][] yDotK,
+                                final FieldODEStateAndDerivative<T> globalPreviousState,
+                                final FieldODEStateAndDerivative<T> globalCurrentState,
+                                final FieldODEStateAndDerivative<T> softPreviousState,
+                                final FieldODEStateAndDerivative<T> softCurrentState,
+                                final FieldEquationsMapper<T> mapper) {
+        super(field, forward, yDotK,
+              globalPreviousState, globalCurrentState, softPreviousState, softCurrentState,
+              mapper);
+        final T q = field.getZero().add(21).sqrt();
+        c5a = q.multiply(  -49).add(  -49);
+        c5b = q.multiply(  287).add(  392);
+        c5c = q.multiply( -357).add( -637);
+        c5d = q.multiply(  343).add(  833);
+        c6a = q.multiply(   49).add(  -49);
+        c6b = q.multiply( -287).add(  392);
+        c6c = q.multiply(  357).add( -637);
+        c6d = q.multiply( -343).add(  833);
+        d5a = q.multiply(   49).add(   49);
+        d5b = q.multiply( -847).add(-1372);
+        d5c = q.multiply( 1029).add( 2254);
+        d6a = q.multiply(  -49).add(   49);
+        d6b = q.multiply(  847).add(-1372);
+        d6c = q.multiply(-1029).add( 2254);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected LutherFieldStepInterpolator<T> create(final Field<T> newField, final boolean newForward, final T[][] newYDotK,
+                                                    final FieldODEStateAndDerivative<T> newGlobalPreviousState,
+                                                    final FieldODEStateAndDerivative<T> newGlobalCurrentState,
+                                                    final FieldODEStateAndDerivative<T> newSoftPreviousState,
+                                                    final FieldODEStateAndDerivative<T> newSoftCurrentState,
+                                                    final FieldEquationsMapper<T> newMapper) {
+        return new LutherFieldStepInterpolator<T>(newField, newForward, newYDotK,
+                                                  newGlobalPreviousState, newGlobalCurrentState,
+                                                  newSoftPreviousState, newSoftCurrentState,
+                                                  newMapper);
+    }
+
+    /** {@inheritDoc} */
+    @SuppressWarnings("unchecked")
+    @Override
+    protected FieldODEStateAndDerivative<T> computeInterpolatedStateAndDerivatives(final FieldEquationsMapper<T> mapper,
+                                                                                   final T time, final T theta,
+                                                                                   final T thetaH, final T oneMinusThetaH) {
+
+        // the coefficients below have been computed by solving the
+        // order conditions from a theorem from Butcher (1963), using
+        // the method explained in Folkmar Bornemann paper "Runge-Kutta
+        // Methods, Trees, and Maple", Center of Mathematical Sciences, Munich
+        // University of Technology, February 9, 2001
+        //<http://wwwzenger.informatik.tu-muenchen.de/selcuk/sjam012101.html>
+
+        // the method is implemented in the rkcheck tool
+        // <https://www.spaceroots.org/software/rkcheck/index.html>.
+        // Running it for order 5 gives the following order conditions
+        // for an interpolator:
+        // order 1 conditions
+        // \sum_{i=1}^{i=s}\left(b_{i} \right) =1
+        // order 2 conditions
+        // \sum_{i=1}^{i=s}\left(b_{i} c_{i}\right) = \frac{\theta}{2}
+        // order 3 conditions
+        // \sum_{i=2}^{i=s}\left(b_{i} \sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j} \right)}\right) = \frac{\theta^{2}}{6}
+        // \sum_{i=1}^{i=s}\left(b_{i} c_{i}^{2}\right) = \frac{\theta^{2}}{3}
+        // order 4 conditions
+        // \sum_{i=3}^{i=s}\left(b_{i} \sum_{j=2}^{j=i-1}{\left(a_{i,j} \sum_{k=1}^{k=j-1}{\left(a_{j,k} c_{k} \right)} \right)}\right) = \frac{\theta^{3}}{24}
+        // \sum_{i=2}^{i=s}\left(b_{i} \sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j}^{2} \right)}\right) = \frac{\theta^{3}}{12}
+        // \sum_{i=2}^{i=s}\left(b_{i} c_{i}\sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j} \right)}\right) = \frac{\theta^{3}}{8}
+        // \sum_{i=1}^{i=s}\left(b_{i} c_{i}^{3}\right) = \frac{\theta^{3}}{4}
+        // order 5 conditions
+        // \sum_{i=4}^{i=s}\left(b_{i} \sum_{j=3}^{j=i-1}{\left(a_{i,j} \sum_{k=2}^{k=j-1}{\left(a_{j,k} \sum_{l=1}^{l=k-1}{\left(a_{k,l} c_{l} \right)} \right)} \right)}\right) = \frac{\theta^{4}}{120}
+        // \sum_{i=3}^{i=s}\left(b_{i} \sum_{j=2}^{j=i-1}{\left(a_{i,j} \sum_{k=1}^{k=j-1}{\left(a_{j,k} c_{k}^{2} \right)} \right)}\right) = \frac{\theta^{4}}{60}
+        // \sum_{i=3}^{i=s}\left(b_{i} \sum_{j=2}^{j=i-1}{\left(a_{i,j} c_{j}\sum_{k=1}^{k=j-1}{\left(a_{j,k} c_{k} \right)} \right)}\right) = \frac{\theta^{4}}{40}
+        // \sum_{i=2}^{i=s}\left(b_{i} \sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j}^{3} \right)}\right) = \frac{\theta^{4}}{20}
+        // \sum_{i=3}^{i=s}\left(b_{i} c_{i}\sum_{j=2}^{j=i-1}{\left(a_{i,j} \sum_{k=1}^{k=j-1}{\left(a_{j,k} c_{k} \right)} \right)}\right) = \frac{\theta^{4}}{30}
+        // \sum_{i=2}^{i=s}\left(b_{i} c_{i}\sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j}^{2} \right)}\right) = \frac{\theta^{4}}{15}
+        // \sum_{i=2}^{i=s}\left(b_{i} \left(\sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j} \right)} \right)^{2}\right) = \frac{\theta^{4}}{20}
+        // \sum_{i=2}^{i=s}\left(b_{i} c_{i}^{2}\sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j} \right)}\right) = \frac{\theta^{4}}{10}
+        // \sum_{i=1}^{i=s}\left(b_{i} c_{i}^{4}\right) = \frac{\theta^{4}}{5}
+
+        // The a_{j,k} and c_{k} are given by the integrator Butcher arrays. What remains to solve
+        // are the b_i for the interpolator. They are found by solving the above equations.
+        // For a given interpolator, some equations are redundant, so in our case when we select
+        // all equations from order 1 to 4, we still don't have enough independent equations
+        // to solve from b_1 to b_7. We need to also select one equation from order 5. Here,
+        // we selected the last equation. It appears this choice implied at least the last 3 equations
+        // are fulfilled, but some of the former ones are not, so the resulting interpolator is order 5.
+        // At the end, we get the b_i as polynomials in theta.
+
+        final T coeffDot1 =  theta.multiply(theta.multiply(theta.multiply(theta.multiply(   21        ).add( -47          )).add(   36         )).add( -54     /   5.0)).add(1);
+        final T coeffDot2 =  time.getField().getZero();
+        final T coeffDot3 =  theta.multiply(theta.multiply(theta.multiply(theta.multiply(  112        ).add(-608    /  3.0)).add(  320   / 3.0 )).add(-208    /  15.0));
+        final T coeffDot4 =  theta.multiply(theta.multiply(theta.multiply(theta.multiply( -567  /  5.0).add( 972    /  5.0)).add( -486   / 5.0 )).add( 324    /  25.0));
+        final T coeffDot5 =  theta.multiply(theta.multiply(theta.multiply(theta.multiply(c5a.divide(5)).add(c5b.divide(15))).add(c5c.divide(30))).add(c5d.divide(150)));
+        final T coeffDot6 =  theta.multiply(theta.multiply(theta.multiply(theta.multiply(c6a.divide(5)).add(c6b.divide(15))).add(c6c.divide(30))).add(c6d.divide(150)));
+        final T coeffDot7 =  theta.multiply(theta.multiply(theta.multiply(                                             3.0 ).add(   -3         )).add(   3   /   5.0));
+        final T[] interpolatedState;
+        final T[] interpolatedDerivatives;
+
+        if (getGlobalPreviousState() != null && theta.getReal() <= 0.5) {
+
+            final T s         = thetaH;
+            final T coeff1    = s.multiply(theta.multiply(theta.multiply(theta.multiply(theta.multiply(  21    /  5.0).add( -47    /  4.0)).add(   12         )).add( -27    /   5.0)).add(1));
+            final T coeff2    = time.getField().getZero();
+            final T coeff3    = s.multiply(theta.multiply(theta.multiply(theta.multiply(theta.multiply( 112    /  5.0).add(-152    /  3.0)).add(  320   / 9.0 )).add(-104    /  15.0)));
+            final T coeff4    = s.multiply(theta.multiply(theta.multiply(theta.multiply(theta.multiply(-567    / 25.0).add( 243    /  5.0)).add( -162   / 5.0 )).add( 162    /  25.0)));
+            final T coeff5    = s.multiply(theta.multiply(theta.multiply(theta.multiply(theta.multiply(c5a.divide(25)).add(c5b.divide(60))).add(c5c.divide(90))).add(c5d.divide(300))));
+            final T coeff6    = s.multiply(theta.multiply(theta.multiply(theta.multiply(theta.multiply(c6a.divide(25)).add(c6b.divide(60))).add(c6c.divide(90))).add(c6d.divide(300))));
+            final T coeff7    = s.multiply(theta.multiply(theta.multiply(theta.multiply(                                      3    /  4.0 ).add(   -1         )).add(   3    /  10.0)));
+            interpolatedState       = previousStateLinearCombination(coeff1, coeff2, coeff3, coeff4, coeff5, coeff6, coeff7);
+            interpolatedDerivatives = derivativeLinearCombination(coeffDot1, coeffDot2, coeffDot3, coeffDot4, coeffDot5, coeffDot6, coeffDot7);
+        } else {
+
+            final T s         = oneMinusThetaH;
+            final T coeff1    = s.multiply(theta.multiply(theta.multiply(theta.multiply(theta.multiply( -21   /   5.0).add(   151  /  20.0)).add( -89   /   20.0)).add(  19 /  20.0)).add(- 1 / 20.0));
+            final T coeff2    = time.getField().getZero();
+            final T coeff3    = s.multiply(theta.multiply(theta.multiply(theta.multiply(theta.multiply(-112   /   5.0).add(   424  /  15.0)).add( -328  /   45.0)).add( -16 /  45.0)).add(-16 /  45.0));
+            final T coeff4    = s.multiply(theta.multiply(theta.multiply(theta.multiply(theta.multiply( 567   /  25.0).add(  -648  /  25.0)).add(  162  /   25.0))));
+            final T coeff5    = s.multiply(theta.multiply(theta.multiply(theta.multiply(theta.multiply(d5a.divide(25)).add(d5b.divide(300))).add(d5c.divide(900))).add( -49 / 180.0)).add(-49 / 180.0));
+            final T coeff6    = s.multiply(theta.multiply(theta.multiply(theta.multiply(theta.multiply(d6a.divide(25)).add(d6b.divide(300))).add(d6c.divide(900))).add( -49 / 180.0)).add(-49 / 180.0));
+            final T coeff7    = s.multiply(               theta.multiply(theta.multiply(theta.multiply(                        -3  /   4.0 ).add(   1   /    4.0)).add(  -1 /  20.0)).add( -1 /  20.0));
+            interpolatedState       = currentStateLinearCombination(coeff1, coeff2, coeff3, coeff4, coeff5, coeff6, coeff7);
+            interpolatedDerivatives = derivativeLinearCombination(coeffDot1, coeffDot2, coeffDot3, coeffDot4, coeffDot5, coeffDot6, coeffDot7);
+        }
+
+        return new FieldODEStateAndDerivative<T>(time, interpolatedState, interpolatedDerivatives);
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/LutherIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/LutherIntegrator.java
new file mode 100644
index 0000000..d3d0d6a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/LutherIntegrator.java
@@ -0,0 +1,89 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.util.FastMath;
+
+
+/**
+ * This class implements the Luther sixth order Runge-Kutta
+ * integrator for Ordinary Differential Equations.
+
+ * <p>
+ * This method is described in H. A. Luther 1968 paper <a
+ * href="http://www.ams.org/journals/mcom/1968-22-102/S0025-5718-68-99876-1/S0025-5718-68-99876-1.pdf">
+ * An explicit Sixth-Order Runge-Kutta Formula</a>.
+ * </p>
+
+ * <p>This method is an explicit Runge-Kutta method, its Butcher-array
+ * is the following one :
+ * <pre>
+ *        0   |               0                     0                     0                     0                     0                     0
+ *        1   |               1                     0                     0                     0                     0                     0
+ *       1/2  |              3/8                   1/8                    0                     0                     0                     0
+ *       2/3  |              8/27                  2/27                  8/27                   0                     0                     0
+ *   (7-q)/14 | (  -21 +   9q)/392    (  -56 +   8q)/392    (  336 -  48q)/392    (  -63 +   3q)/392                  0                     0
+ *   (7+q)/14 | (-1155 - 255q)/1960   ( -280 -  40q)/1960   (    0 - 320q)/1960   (   63 + 363q)/1960   ( 2352 + 392q)/1960                 0
+ *        1   | (  330 + 105q)/180    (  120 +   0q)/180    ( -200 + 280q)/180    (  126 - 189q)/180    ( -686 - 126q)/180     ( 490 -  70q)/180
+ *            |--------------------------------------------------------------------------------------------------------------------------------------------------
+ *            |              1/20                   0                   16/45                  0                   49/180                 49/180         1/20
+ * </pre>
+ * where q = &radic;21</p>
+ *
+ * @see EulerIntegrator
+ * @see ClassicalRungeKuttaIntegrator
+ * @see GillIntegrator
+ * @see MidpointIntegrator
+ * @see ThreeEighthesIntegrator
+ * @since 3.3
+ */
+
+public class LutherIntegrator extends RungeKuttaIntegrator {
+
+    /** Square root. */
+    private static final double Q = FastMath.sqrt(21);
+
+    /** Time steps Butcher array. */
+    private static final double[] STATIC_C = {
+        1.0, 1.0 / 2.0, 2.0 / 3.0, (7.0 - Q) / 14.0, (7.0 + Q) / 14.0, 1.0
+    };
+
+    /** Internal weights Butcher array. */
+    private static final double[][] STATIC_A = {
+        {                      1.0        },
+        {                   3.0 /   8.0,                  1.0 /   8.0  },
+        {                   8.0 /   27.0,                 2.0 /   27.0,                  8.0 /   27.0  },
+        { (  -21.0 +   9.0 * Q) /  392.0, ( -56.0 +  8.0 * Q) /  392.0, ( 336.0 -  48.0 * Q) /  392.0, (-63.0 +   3.0 * Q) /  392.0 },
+        { (-1155.0 - 255.0 * Q) / 1960.0, (-280.0 - 40.0 * Q) / 1960.0, (   0.0 - 320.0 * Q) / 1960.0, ( 63.0 + 363.0 * Q) / 1960.0,   (2352.0 + 392.0 * Q) / 1960.0 },
+        { (  330.0 + 105.0 * Q) /  180.0, ( 120.0 +  0.0 * Q) /  180.0, (-200.0 + 280.0 * Q) /  180.0, (126.0 - 189.0 * Q) /  180.0,   (-686.0 - 126.0 * Q) /  180.0,   (490.0 -  70.0 * Q) / 180.0 }
+    };
+
+    /** Propagation weights Butcher array. */
+    private static final double[] STATIC_B = {
+        1.0 / 20.0, 0, 16.0 / 45.0, 0, 49.0 / 180.0, 49.0 / 180.0, 1.0 / 20.0
+    };
+
+    /** Simple constructor.
+     * Build a fourth-order Luther integrator with the given step.
+     * @param step integration step
+     */
+    public LutherIntegrator(final double step) {
+        super("Luther", STATIC_C, STATIC_A, STATIC_B, new LutherStepInterpolator(), step);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/LutherStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/LutherStepInterpolator.java
new file mode 100644
index 0000000..207a9ea
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/LutherStepInterpolator.java
@@ -0,0 +1,182 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.ode.sampling.StepInterpolator;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * This class represents an interpolator over the last step during an
+ * ODE integration for the 6th order Luther integrator.
+ *
+ * <p>This interpolator computes dense output inside the last
+ * step computed. The interpolation equation is consistent with the
+ * integration scheme.</p>
+ *
+ * @see LutherIntegrator
+ * @since 3.3
+ */
+
+class LutherStepInterpolator extends RungeKuttaStepInterpolator {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 20140416L;
+
+    /** Square root. */
+    private static final double Q = FastMath.sqrt(21);
+
+    /** Simple constructor.
+     * This constructor builds an instance that is not usable yet, the
+     * {@link
+     * org.apache.commons.math3.ode.sampling.AbstractStepInterpolator#reinitialize}
+     * method should be called before using the instance in order to
+     * initialize the internal arrays. This constructor is used only
+     * in order to delay the initialization in some cases. The {@link
+     * RungeKuttaIntegrator} class uses the prototyping design pattern
+     * to create the step interpolators by cloning an uninitialized model
+     * and later initializing the copy.
+     */
+    // CHECKSTYLE: stop RedundantModifier
+    // the public modifier here is needed for serialization
+    public LutherStepInterpolator() {
+    }
+    // CHECKSTYLE: resume RedundantModifier
+
+    /** Copy constructor.
+     * @param interpolator interpolator to copy from. The copy is a deep
+     * copy: its arrays are separated from the original arrays of the
+     * instance
+     */
+    LutherStepInterpolator(final LutherStepInterpolator interpolator) {
+        super(interpolator);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected StepInterpolator doCopy() {
+        return new LutherStepInterpolator(this);
+    }
+
+
+    /** {@inheritDoc} */
+    @Override
+    protected void computeInterpolatedStateAndDerivatives(final double theta,
+                                                          final double oneMinusThetaH) {
+
+        // the coefficients below have been computed by solving the
+        // order conditions from a theorem from Butcher (1963), using
+        // the method explained in Folkmar Bornemann paper "Runge-Kutta
+        // Methods, Trees, and Maple", Center of Mathematical Sciences, Munich
+        // University of Technology, February 9, 2001
+        //<http://wwwzenger.informatik.tu-muenchen.de/selcuk/sjam012101.html>
+
+        // the method is implemented in the rkcheck tool
+        // <https://www.spaceroots.org/software/rkcheck/index.html>.
+        // Running it for order 5 gives the following order conditions
+        // for an interpolator:
+        // order 1 conditions
+        // \sum_{i=1}^{i=s}\left(b_{i} \right) =1
+        // order 2 conditions
+        // \sum_{i=1}^{i=s}\left(b_{i} c_{i}\right) = \frac{\theta}{2}
+        // order 3 conditions
+        // \sum_{i=2}^{i=s}\left(b_{i} \sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j} \right)}\right) = \frac{\theta^{2}}{6}
+        // \sum_{i=1}^{i=s}\left(b_{i} c_{i}^{2}\right) = \frac{\theta^{2}}{3}
+        // order 4 conditions
+        // \sum_{i=3}^{i=s}\left(b_{i} \sum_{j=2}^{j=i-1}{\left(a_{i,j} \sum_{k=1}^{k=j-1}{\left(a_{j,k} c_{k} \right)} \right)}\right) = \frac{\theta^{3}}{24}
+        // \sum_{i=2}^{i=s}\left(b_{i} \sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j}^{2} \right)}\right) = \frac{\theta^{3}}{12}
+        // \sum_{i=2}^{i=s}\left(b_{i} c_{i}\sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j} \right)}\right) = \frac{\theta^{3}}{8}
+        // \sum_{i=1}^{i=s}\left(b_{i} c_{i}^{3}\right) = \frac{\theta^{3}}{4}
+        // order 5 conditions
+        // \sum_{i=4}^{i=s}\left(b_{i} \sum_{j=3}^{j=i-1}{\left(a_{i,j} \sum_{k=2}^{k=j-1}{\left(a_{j,k} \sum_{l=1}^{l=k-1}{\left(a_{k,l} c_{l} \right)} \right)} \right)}\right) = \frac{\theta^{4}}{120}
+        // \sum_{i=3}^{i=s}\left(b_{i} \sum_{j=2}^{j=i-1}{\left(a_{i,j} \sum_{k=1}^{k=j-1}{\left(a_{j,k} c_{k}^{2} \right)} \right)}\right) = \frac{\theta^{4}}{60}
+        // \sum_{i=3}^{i=s}\left(b_{i} \sum_{j=2}^{j=i-1}{\left(a_{i,j} c_{j}\sum_{k=1}^{k=j-1}{\left(a_{j,k} c_{k} \right)} \right)}\right) = \frac{\theta^{4}}{40}
+        // \sum_{i=2}^{i=s}\left(b_{i} \sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j}^{3} \right)}\right) = \frac{\theta^{4}}{20}
+        // \sum_{i=3}^{i=s}\left(b_{i} c_{i}\sum_{j=2}^{j=i-1}{\left(a_{i,j} \sum_{k=1}^{k=j-1}{\left(a_{j,k} c_{k} \right)} \right)}\right) = \frac{\theta^{4}}{30}
+        // \sum_{i=2}^{i=s}\left(b_{i} c_{i}\sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j}^{2} \right)}\right) = \frac{\theta^{4}}{15}
+        // \sum_{i=2}^{i=s}\left(b_{i} \left(\sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j} \right)} \right)^{2}\right) = \frac{\theta^{4}}{20}
+        // \sum_{i=2}^{i=s}\left(b_{i} c_{i}^{2}\sum_{j=1}^{j=i-1}{\left(a_{i,j} c_{j} \right)}\right) = \frac{\theta^{4}}{10}
+        // \sum_{i=1}^{i=s}\left(b_{i} c_{i}^{4}\right) = \frac{\theta^{4}}{5}
+
+        // The a_{j,k} and c_{k} are given by the integrator Butcher arrays. What remains to solve
+        // are the b_i for the interpolator. They are found by solving the above equations.
+        // For a given interpolator, some equations are redundant, so in our case when we select
+        // all equations from order 1 to 4, we still don't have enough independent equations
+        // to solve from b_1 to b_7. We need to also select one equation from order 5. Here,
+        // we selected the last equation. It appears this choice implied at least the last 3 equations
+        // are fulfilled, but some of the former ones are not, so the resulting interpolator is order 5.
+        // At the end, we get the b_i as polynomials in theta.
+
+        final double coeffDot1 =  1 + theta * ( -54            /   5.0 + theta * (   36                   + theta * ( -47                   + theta *   21)));
+        final double coeffDot2 =  0;
+        final double coeffDot3 =      theta * (-208            /  15.0 + theta * (  320            / 3.0  + theta * (-608            /  3.0 + theta *  112)));
+        final double coeffDot4 =      theta * ( 324            /  25.0 + theta * ( -486            / 5.0  + theta * ( 972            /  5.0 + theta * -567           /  5.0)));
+        final double coeffDot5 =      theta * ((833 + 343 * Q) / 150.0 + theta * ((-637 - 357 * Q) / 30.0 + theta * ((392 + 287 * Q) / 15.0 + theta * (-49 - 49 * Q) /  5.0)));
+        final double coeffDot6 =      theta * ((833 - 343 * Q) / 150.0 + theta * ((-637 + 357 * Q) / 30.0 + theta * ((392 - 287 * Q) / 15.0 + theta * (-49 + 49 * Q) /  5.0)));
+        final double coeffDot7 =      theta * (   3            /   5.0 + theta * (   -3                   + theta *     3));
+
+        if ((previousState != null) && (theta <= 0.5)) {
+
+            final double coeff1    =  1 + theta * ( -27            /   5.0 + theta * (   12                   + theta * ( -47            /  4.0 + theta *   21           /  5.0)));
+            final double coeff2    =  0;
+            final double coeff3    =      theta * (-104            /  15.0 + theta * (  320            / 9.0  + theta * (-152            /  3.0 + theta *  112           /  5.0)));
+            final double coeff4    =      theta * ( 162            /  25.0 + theta * ( -162            / 5.0  + theta * ( 243            /  5.0 + theta * -567           / 25.0)));
+            final double coeff5    =      theta * ((833 + 343 * Q) / 300.0 + theta * ((-637 - 357 * Q) / 90.0 + theta * ((392 + 287 * Q) / 60.0 + theta * (-49 - 49 * Q) / 25.0)));
+            final double coeff6    =      theta * ((833 - 343 * Q) / 300.0 + theta * ((-637 + 357 * Q) / 90.0 + theta * ((392 - 287 * Q) / 60.0 + theta * (-49 + 49 * Q) / 25.0)));
+            final double coeff7    =      theta * (   3            /  10.0 + theta * (   -1                   + theta * (   3            /  4.0)));
+            for (int i = 0; i < interpolatedState.length; ++i) {
+                final double yDot1 = yDotK[0][i];
+                final double yDot2 = yDotK[1][i];
+                final double yDot3 = yDotK[2][i];
+                final double yDot4 = yDotK[3][i];
+                final double yDot5 = yDotK[4][i];
+                final double yDot6 = yDotK[5][i];
+                final double yDot7 = yDotK[6][i];
+                interpolatedState[i] = previousState[i] +
+                        theta * h * (coeff1 * yDot1 + coeff2 * yDot2 + coeff3 * yDot3 +
+                                     coeff4 * yDot4 + coeff5 * yDot5 + coeff6 * yDot6 + coeff7 * yDot7);
+                interpolatedDerivatives[i] = coeffDot1 * yDot1 + coeffDot2 * yDot2 + coeffDot3 * yDot3 +
+                        coeffDot4 * yDot4 + coeffDot5 * yDot5 + coeffDot6 * yDot6 + coeffDot7 * yDot7;
+            }
+        } else {
+
+            final double coeff1    =  -1 /  20.0 + theta * (  19            /  20.0 + theta * (  -89             /  20.0  + theta * (   151            /  20.0 + theta *  -21           /   5.0)));
+            final double coeff2    =  0;
+            final double coeff3    = -16 /  45.0 + theta * ( -16            /  45.0 + theta * ( -328             /  45.0  + theta * (   424            /  15.0 + theta * -112           /   5.0)));
+            final double coeff4    =               theta * (                          theta * (  162             /  25.0  + theta * (  -648            /  25.0 + theta *  567           /  25.0)));
+            final double coeff5    = -49 / 180.0 + theta * ( -49            / 180.0 + theta * ((2254 + 1029 * Q) / 900.0  + theta * ((-1372 - 847 * Q) / 300.0 + theta * ( 49 + 49 * Q) /  25.0)));
+            final double coeff6    = -49 / 180.0 + theta * ( -49            / 180.0 + theta * ((2254 - 1029 * Q) / 900.0  + theta * ((-1372 + 847 * Q) / 300.0 + theta * ( 49 - 49 * Q) /  25.0)));
+            final double coeff7    =  -1 /  20.0 + theta * (  -1            /  20.0 + theta * (    1             /   4.0  + theta * (    -3            /   4.0)));
+            for (int i = 0; i < interpolatedState.length; ++i) {
+                final double yDot1 = yDotK[0][i];
+                final double yDot2 = yDotK[1][i];
+                final double yDot3 = yDotK[2][i];
+                final double yDot4 = yDotK[3][i];
+                final double yDot5 = yDotK[4][i];
+                final double yDot6 = yDotK[5][i];
+                final double yDot7 = yDotK[6][i];
+                interpolatedState[i] = currentState[i] +
+                        oneMinusThetaH * (coeff1 * yDot1 + coeff2 * yDot2 + coeff3 * yDot3 +
+                                          coeff4 * yDot4 + coeff5 * yDot5 + coeff6 * yDot6 + coeff7 * yDot7);
+                interpolatedDerivatives[i] = coeffDot1 * yDot1 + coeffDot2 * yDot2 + coeffDot3 * yDot3 +
+                        coeffDot4 * yDot4 + coeffDot5 * yDot5 + coeffDot6 * yDot6 + coeffDot7 * yDot7;
+            }
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/MidpointFieldIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/MidpointFieldIntegrator.java
new file mode 100644
index 0000000..e0dd8cf
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/MidpointFieldIntegrator.java
@@ -0,0 +1,96 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.ode.FieldEquationsMapper;
+import org.apache.commons.math3.ode.FieldODEStateAndDerivative;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * This class implements a second order Runge-Kutta integrator for
+ * Ordinary Differential Equations.
+ *
+ * <p>This method is an explicit Runge-Kutta method, its Butcher-array
+ * is the following one :
+ * <pre>
+ *    0  |  0    0
+ *   1/2 | 1/2   0
+ *       |----------
+ *       |  0    1
+ * </pre>
+ * </p>
+ *
+ * @see EulerFieldIntegrator
+ * @see ClassicalRungeKuttaFieldIntegrator
+ * @see GillFieldIntegrator
+ * @see ThreeEighthesFieldIntegrator
+ * @see LutherFieldIntegrator
+ *
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+
+public class MidpointFieldIntegrator<T extends RealFieldElement<T>> extends RungeKuttaFieldIntegrator<T> {
+
+    /** Simple constructor.
+     * Build a midpoint integrator with the given step.
+     * @param field field to which the time and state vector elements belong
+     * @param step integration step
+     */
+    public MidpointFieldIntegrator(final Field<T> field, final T step) {
+        super(field, "midpoint", step);
+    }
+
+    /** {@inheritDoc} */
+    public T[] getC() {
+        final T[] c = MathArrays.buildArray(getField(), 1);
+        c[0] = getField().getOne().multiply(0.5);
+        return c;
+    }
+
+    /** {@inheritDoc} */
+    public T[][] getA() {
+        final T[][] a = MathArrays.buildArray(getField(), 1, 1);
+        a[0][0] = fraction(1, 2);
+        return a;
+    }
+
+    /** {@inheritDoc} */
+    public T[] getB() {
+        final T[] b = MathArrays.buildArray(getField(), 2);
+        b[0] = getField().getZero();
+        b[1] = getField().getOne();
+        return b;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected MidpointFieldStepInterpolator<T>
+        createInterpolator(final boolean forward, T[][] yDotK,
+                           final FieldODEStateAndDerivative<T> globalPreviousState,
+                           final FieldODEStateAndDerivative<T> globalCurrentState,
+                           final FieldEquationsMapper<T> mapper) {
+        return new MidpointFieldStepInterpolator<T>(getField(), forward, yDotK,
+                                                    globalPreviousState, globalCurrentState,
+                                                    globalPreviousState, globalCurrentState,
+                                                    mapper);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/MidpointFieldStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/MidpointFieldStepInterpolator.java
new file mode 100644
index 0000000..911e3b2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/MidpointFieldStepInterpolator.java
@@ -0,0 +1,118 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.ode.FieldEquationsMapper;
+import org.apache.commons.math3.ode.FieldODEStateAndDerivative;
+
+/**
+ * This class implements a step interpolator for second order
+ * Runge-Kutta integrator.
+ *
+ * <p>This interpolator computes dense output inside the last
+ * step computed. The interpolation equation is consistent with the
+ * integration scheme :
+ * <ul>
+ *   <li>Using reference point at step start:<br>
+ *   y(t<sub>n</sub> + &theta; h) = y (t<sub>n</sub>) + &theta; h [(1 - &theta;) y'<sub>1</sub> + &theta; y'<sub>2</sub>]
+ *   </li>
+ *   <li>Using reference point at step end:<br>
+ *   y(t<sub>n</sub> + &theta; h) = y (t<sub>n</sub> + h) + (1-&theta;) h [&theta; y'<sub>1</sub> - (1+&theta;) y'<sub>2</sub>]
+ *   </li>
+ * </ul>
+ * </p>
+ *
+ * where &theta; belongs to [0 ; 1] and where y'<sub>1</sub> and y'<sub>2</sub> are the two
+ * evaluations of the derivatives already computed during the
+ * step.</p>
+ *
+ * @see MidpointFieldIntegrator
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+
+class MidpointFieldStepInterpolator<T extends RealFieldElement<T>>
+    extends RungeKuttaFieldStepInterpolator<T> {
+
+    /** Simple constructor.
+     * @param field field to which the time and state vector elements belong
+     * @param forward integration direction indicator
+     * @param yDotK slopes at the intermediate points
+     * @param globalPreviousState start of the global step
+     * @param globalCurrentState end of the global step
+     * @param softPreviousState start of the restricted step
+     * @param softCurrentState end of the restricted step
+     * @param mapper equations mapper for the all equations
+     */
+    MidpointFieldStepInterpolator(final Field<T> field, final boolean forward,
+                                             final T[][] yDotK,
+                                             final FieldODEStateAndDerivative<T> globalPreviousState,
+                                             final FieldODEStateAndDerivative<T> globalCurrentState,
+                                             final FieldODEStateAndDerivative<T> softPreviousState,
+                                             final FieldODEStateAndDerivative<T> softCurrentState,
+                                             final FieldEquationsMapper<T> mapper) {
+        super(field, forward, yDotK,
+              globalPreviousState, globalCurrentState, softPreviousState, softCurrentState,
+              mapper);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected MidpointFieldStepInterpolator<T> create(final Field<T> newField, final boolean newForward, final T[][] newYDotK,
+                                                      final FieldODEStateAndDerivative<T> newGlobalPreviousState,
+                                                      final FieldODEStateAndDerivative<T> newGlobalCurrentState,
+                                                      final FieldODEStateAndDerivative<T> newSoftPreviousState,
+                                                      final FieldODEStateAndDerivative<T> newSoftCurrentState,
+                                                      final FieldEquationsMapper<T> newMapper) {
+        return new MidpointFieldStepInterpolator<T>(newField, newForward, newYDotK,
+                                                    newGlobalPreviousState, newGlobalCurrentState,
+                                                    newSoftPreviousState, newSoftCurrentState,
+                                                    newMapper);
+    }
+
+    /** {@inheritDoc} */
+    @SuppressWarnings("unchecked")
+    @Override
+    protected FieldODEStateAndDerivative<T> computeInterpolatedStateAndDerivatives(final FieldEquationsMapper<T> mapper,
+                                                                                   final T time, final T theta,
+                                                                                   final T thetaH, final T oneMinusThetaH) {
+
+        final T coeffDot2 = theta.multiply(2);
+        final T coeffDot1 = time.getField().getOne().subtract(coeffDot2);
+        final T[] interpolatedState;
+        final T[] interpolatedDerivatives;
+
+        if (getGlobalPreviousState() != null && theta.getReal() <= 0.5) {
+            final T coeff1 = theta.multiply(oneMinusThetaH);
+            final T coeff2 = theta.multiply(thetaH);
+            interpolatedState       = previousStateLinearCombination(coeff1, coeff2);
+            interpolatedDerivatives = derivativeLinearCombination(coeffDot1, coeffDot2);
+        } else {
+            final T coeff1 = oneMinusThetaH.multiply(theta);
+            final T coeff2 = oneMinusThetaH.multiply(theta.add(1)).negate();
+            interpolatedState       = currentStateLinearCombination(coeff1, coeff2);
+            interpolatedDerivatives = derivativeLinearCombination(coeffDot1, coeffDot2);
+        }
+
+        return new FieldODEStateAndDerivative<T>(time, interpolatedState, interpolatedDerivatives);
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/MidpointIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/MidpointIntegrator.java
new file mode 100644
index 0000000..fa834a1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/MidpointIntegrator.java
@@ -0,0 +1,69 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+
+/**
+ * This class implements a second order Runge-Kutta integrator for
+ * Ordinary Differential Equations.
+ *
+ * <p>This method is an explicit Runge-Kutta method, its Butcher-array
+ * is the following one :
+ * <pre>
+ *    0  |  0    0
+ *   1/2 | 1/2   0
+ *       |----------
+ *       |  0    1
+ * </pre>
+ * </p>
+ *
+ * @see EulerIntegrator
+ * @see ClassicalRungeKuttaIntegrator
+ * @see GillIntegrator
+ * @see ThreeEighthesIntegrator
+ * @see LutherIntegrator
+ *
+ * @since 1.2
+ */
+
+public class MidpointIntegrator extends RungeKuttaIntegrator {
+
+  /** Time steps Butcher array. */
+  private static final double[] STATIC_C = {
+    1.0 / 2.0
+  };
+
+  /** Internal weights Butcher array. */
+  private static final double[][] STATIC_A = {
+    { 1.0 / 2.0 }
+  };
+
+  /** Propagation weights Butcher array. */
+  private static final double[] STATIC_B = {
+    0.0, 1.0
+  };
+
+  /** Simple constructor.
+   * Build a midpoint integrator with the given step.
+   * @param step integration step
+   */
+  public MidpointIntegrator(final double step) {
+    super("midpoint", STATIC_C, STATIC_A, STATIC_B, new MidpointStepInterpolator(), step);
+  }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/MidpointStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/MidpointStepInterpolator.java
new file mode 100644
index 0000000..89a5dc1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/MidpointStepInterpolator.java
@@ -0,0 +1,116 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.ode.sampling.StepInterpolator;
+
+/**
+ * This class implements a step interpolator for second order
+ * Runge-Kutta integrator.
+ *
+ * <p>This interpolator computes dense output inside the last
+ * step computed. The interpolation equation is consistent with the
+ * integration scheme :
+ * <ul>
+ *   <li>Using reference point at step start:<br>
+ *   y(t<sub>n</sub> + &theta; h) = y (t<sub>n</sub>) + &theta; h [(1 - &theta;) y'<sub>1</sub> + &theta; y'<sub>2</sub>]
+ *   </li>
+ *   <li>Using reference point at step end:<br>
+ *   y(t<sub>n</sub> + &theta; h) = y (t<sub>n</sub> + h) + (1-&theta;) h [&theta; y'<sub>1</sub> - (1+&theta;) y'<sub>2</sub>]
+ *   </li>
+ * </ul>
+ * </p>
+ *
+ * where &theta; belongs to [0 ; 1] and where y'<sub>1</sub> and y'<sub>2</sub> are the two
+ * evaluations of the derivatives already computed during the
+ * step.</p>
+ *
+ * @see MidpointIntegrator
+ * @since 1.2
+ */
+
+class MidpointStepInterpolator
+  extends RungeKuttaStepInterpolator {
+
+  /** Serializable version identifier */
+  private static final long serialVersionUID = 20111120L;
+
+  /** Simple constructor.
+   * This constructor builds an instance that is not usable yet, the
+   * {@link
+   * org.apache.commons.math3.ode.sampling.AbstractStepInterpolator#reinitialize}
+   * method should be called before using the instance in order to
+   * initialize the internal arrays. This constructor is used only
+   * in order to delay the initialization in some cases. The {@link
+   * RungeKuttaIntegrator} class uses the prototyping design pattern
+   * to create the step interpolators by cloning an uninitialized model
+   * and later initializing the copy.
+   */
+  // CHECKSTYLE: stop RedundantModifier
+  // the public modifier here is needed for serialization
+  public MidpointStepInterpolator() {
+  }
+  // CHECKSTYLE: resume RedundantModifier
+
+  /** Copy constructor.
+   * @param interpolator interpolator to copy from. The copy is a deep
+   * copy: its arrays are separated from the original arrays of the
+   * instance
+   */
+  MidpointStepInterpolator(final MidpointStepInterpolator interpolator) {
+    super(interpolator);
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  protected StepInterpolator doCopy() {
+    return new MidpointStepInterpolator(this);
+  }
+
+
+  /** {@inheritDoc} */
+  @Override
+  protected void computeInterpolatedStateAndDerivatives(final double theta,
+                                          final double oneMinusThetaH) {
+
+    final double coeffDot2 = 2 * theta;
+    final double coeffDot1 = 1 - coeffDot2;
+
+    if ((previousState != null) && (theta <= 0.5)) {
+        final double coeff1    = theta * oneMinusThetaH;
+        final double coeff2    = theta * theta * h;
+        for (int i = 0; i < interpolatedState.length; ++i) {
+            final double yDot1 = yDotK[0][i];
+            final double yDot2 = yDotK[1][i];
+            interpolatedState[i] = previousState[i] + coeff1 * yDot1 + coeff2 * yDot2;
+            interpolatedDerivatives[i] = coeffDot1 * yDot1 + coeffDot2 * yDot2;
+        }
+    } else {
+        final double coeff1    = oneMinusThetaH * theta;
+        final double coeff2    = oneMinusThetaH * (1.0 + theta);
+        for (int i = 0; i < interpolatedState.length; ++i) {
+            final double yDot1 = yDotK[0][i];
+            final double yDot2 = yDotK[1][i];
+            interpolatedState[i] = currentState[i] + coeff1 * yDot1 - coeff2 * yDot2;
+            interpolatedDerivatives[i] = coeffDot1 * yDot1 + coeffDot2 * yDot2;
+        }
+    }
+
+  }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/RungeKuttaFieldIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/RungeKuttaFieldIntegrator.java
new file mode 100644
index 0000000..a97e9f5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/RungeKuttaFieldIntegrator.java
@@ -0,0 +1,273 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NoBracketingException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.ode.AbstractFieldIntegrator;
+import org.apache.commons.math3.ode.FieldEquationsMapper;
+import org.apache.commons.math3.ode.FieldExpandableODE;
+import org.apache.commons.math3.ode.FirstOrderFieldDifferentialEquations;
+import org.apache.commons.math3.ode.FieldODEState;
+import org.apache.commons.math3.ode.FieldODEStateAndDerivative;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * This class implements the common part of all fixed step Runge-Kutta
+ * integrators for Ordinary Differential Equations.
+ *
+ * <p>These methods are explicit Runge-Kutta methods, their Butcher
+ * arrays are as follows :
+ * <pre>
+ *    0  |
+ *   c2  | a21
+ *   c3  | a31  a32
+ *   ... |        ...
+ *   cs  | as1  as2  ...  ass-1
+ *       |--------------------------
+ *       |  b1   b2  ...   bs-1  bs
+ * </pre>
+ * </p>
+ *
+ * @see EulerFieldIntegrator
+ * @see ClassicalRungeKuttaFieldIntegrator
+ * @see GillFieldIntegrator
+ * @see MidpointFieldIntegrator
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+
+public abstract class RungeKuttaFieldIntegrator<T extends RealFieldElement<T>>
+    extends AbstractFieldIntegrator<T>
+    implements FieldButcherArrayProvider<T> {
+
+    /** Time steps from Butcher array (without the first zero). */
+    private final T[] c;
+
+    /** Internal weights from Butcher array (without the first empty row). */
+    private final T[][] a;
+
+    /** External weights for the high order method from Butcher array. */
+    private final T[] b;
+
+    /** Integration step. */
+    private final T step;
+
+    /** Simple constructor.
+     * Build a Runge-Kutta integrator with the given
+     * step. The default step handler does nothing.
+     * @param field field to which the time and state vector elements belong
+     * @param name name of the method
+     * @param step integration step
+     */
+    protected RungeKuttaFieldIntegrator(final Field<T> field, final String name, final T step) {
+        super(field, name);
+        this.c    = getC();
+        this.a    = getA();
+        this.b    = getB();
+        this.step = step.abs();
+    }
+
+    /** Create a fraction.
+     * @param p numerator
+     * @param q denominator
+     * @return p/q computed in the instance field
+     */
+    protected T fraction(final int p, final int q) {
+        return getField().getZero().add(p).divide(q);
+    }
+
+    /** Create an interpolator.
+     * @param forward integration direction indicator
+     * @param yDotK slopes at the intermediate points
+     * @param globalPreviousState start of the global step
+     * @param globalCurrentState end of the global step
+     * @param mapper equations mapper for the all equations
+     * @return external weights for the high order method from Butcher array
+     */
+    protected abstract RungeKuttaFieldStepInterpolator<T> createInterpolator(boolean forward, T[][] yDotK,
+                                                                             final FieldODEStateAndDerivative<T> globalPreviousState,
+                                                                             final FieldODEStateAndDerivative<T> globalCurrentState,
+                                                                             FieldEquationsMapper<T> mapper);
+
+    /** {@inheritDoc} */
+    public FieldODEStateAndDerivative<T> integrate(final FieldExpandableODE<T> equations,
+                                                   final FieldODEState<T> initialState, final T finalTime)
+        throws NumberIsTooSmallException, DimensionMismatchException,
+        MaxCountExceededException, NoBracketingException {
+
+        sanityChecks(initialState, finalTime);
+        final T   t0 = initialState.getTime();
+        final T[] y0 = equations.getMapper().mapState(initialState);
+        setStepStart(initIntegration(equations, t0, y0, finalTime));
+        final boolean forward = finalTime.subtract(initialState.getTime()).getReal() > 0;
+
+        // create some internal working arrays
+        final int   stages = c.length + 1;
+        T[]         y      = y0;
+        final T[][] yDotK  = MathArrays.buildArray(getField(), stages, -1);
+        final T[]   yTmp   = MathArrays.buildArray(getField(), y0.length);
+
+        // set up integration control objects
+        if (forward) {
+            if (getStepStart().getTime().add(step).subtract(finalTime).getReal() >= 0) {
+                setStepSize(finalTime.subtract(getStepStart().getTime()));
+            } else {
+                setStepSize(step);
+            }
+        } else {
+            if (getStepStart().getTime().subtract(step).subtract(finalTime).getReal() <= 0) {
+                setStepSize(finalTime.subtract(getStepStart().getTime()));
+            } else {
+                setStepSize(step.negate());
+            }
+        }
+
+        // main integration loop
+        setIsLastStep(false);
+        do {
+
+            // first stage
+            y        = equations.getMapper().mapState(getStepStart());
+            yDotK[0] = equations.getMapper().mapDerivative(getStepStart());
+
+            // next stages
+            for (int k = 1; k < stages; ++k) {
+
+                for (int j = 0; j < y0.length; ++j) {
+                    T sum = yDotK[0][j].multiply(a[k-1][0]);
+                    for (int l = 1; l < k; ++l) {
+                        sum = sum.add(yDotK[l][j].multiply(a[k-1][l]));
+                    }
+                    yTmp[j] = y[j].add(getStepSize().multiply(sum));
+                }
+
+                yDotK[k] = computeDerivatives(getStepStart().getTime().add(getStepSize().multiply(c[k-1])), yTmp);
+
+            }
+
+            // estimate the state at the end of the step
+            for (int j = 0; j < y0.length; ++j) {
+                T sum = yDotK[0][j].multiply(b[0]);
+                for (int l = 1; l < stages; ++l) {
+                    sum = sum.add(yDotK[l][j].multiply(b[l]));
+                }
+                yTmp[j] = y[j].add(getStepSize().multiply(sum));
+            }
+            final T stepEnd   = getStepStart().getTime().add(getStepSize());
+            final T[] yDotTmp = computeDerivatives(stepEnd, yTmp);
+            final FieldODEStateAndDerivative<T> stateTmp = new FieldODEStateAndDerivative<T>(stepEnd, yTmp, yDotTmp);
+
+            // discrete events handling
+            System.arraycopy(yTmp, 0, y, 0, y0.length);
+            setStepStart(acceptStep(createInterpolator(forward, yDotK, getStepStart(), stateTmp, equations.getMapper()),
+                                    finalTime));
+
+            if (!isLastStep()) {
+
+                // stepsize control for next step
+                final T  nextT      = getStepStart().getTime().add(getStepSize());
+                final boolean nextIsLast = forward ?
+                                           (nextT.subtract(finalTime).getReal() >= 0) :
+                                           (nextT.subtract(finalTime).getReal() <= 0);
+                if (nextIsLast) {
+                    setStepSize(finalTime.subtract(getStepStart().getTime()));
+                }
+            }
+
+        } while (!isLastStep());
+
+        final FieldODEStateAndDerivative<T> finalState = getStepStart();
+        setStepStart(null);
+        setStepSize(null);
+        return finalState;
+
+    }
+
+    /** Fast computation of a single step of ODE integration.
+     * <p>This method is intended for the limited use case of
+     * very fast computation of only one step without using any of the
+     * rich features of general integrators that may take some time
+     * to set up (i.e. no step handlers, no events handlers, no additional
+     * states, no interpolators, no error control, no evaluations count,
+     * no sanity checks ...). It handles the strict minimum of computation,
+     * so it can be embedded in outer loops.</p>
+     * <p>
+     * This method is <em>not</em> used at all by the {@link #integrate(FieldExpandableODE,
+     * FieldODEState, RealFieldElement)} method. It also completely ignores the step set at
+     * construction time, and uses only a single step to go from {@code t0} to {@code t}.
+     * </p>
+     * <p>
+     * As this method does not use any of the state-dependent features of the integrator,
+     * it should be reasonably thread-safe <em>if and only if</em> the provided differential
+     * equations are themselves thread-safe.
+     * </p>
+     * @param equations differential equations to integrate
+     * @param t0 initial time
+     * @param y0 initial value of the state vector at t0
+     * @param t target time for the integration
+     * (can be set to a value smaller than {@code t0} for backward integration)
+     * @return state vector at {@code t}
+     */
+    public T[] singleStep(final FirstOrderFieldDifferentialEquations<T> equations,
+                          final T t0, final T[] y0, final T t) {
+
+        // create some internal working arrays
+        final T[] y       = y0.clone();
+        final int stages  = c.length + 1;
+        final T[][] yDotK = MathArrays.buildArray(getField(), stages, -1);
+        final T[] yTmp    = y0.clone();
+
+        // first stage
+        final T h = t.subtract(t0);
+        yDotK[0] = equations.computeDerivatives(t0, y);
+
+        // next stages
+        for (int k = 1; k < stages; ++k) {
+
+            for (int j = 0; j < y0.length; ++j) {
+                T sum = yDotK[0][j].multiply(a[k-1][0]);
+                for (int l = 1; l < k; ++l) {
+                    sum = sum.add(yDotK[l][j].multiply(a[k-1][l]));
+                }
+                yTmp[j] = y[j].add(h.multiply(sum));
+            }
+
+            yDotK[k] = equations.computeDerivatives(t0.add(h.multiply(c[k-1])), yTmp);
+
+        }
+
+        // estimate the state at the end of the step
+        for (int j = 0; j < y0.length; ++j) {
+            T sum = yDotK[0][j].multiply(b[0]);
+            for (int l = 1; l < stages; ++l) {
+                sum = sum.add(yDotK[l][j].multiply(b[l]));
+            }
+            y[j] = y[j].add(h.multiply(sum));
+        }
+
+        return y;
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/RungeKuttaFieldStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/RungeKuttaFieldStepInterpolator.java
new file mode 100644
index 0000000..7d92d78
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/RungeKuttaFieldStepInterpolator.java
@@ -0,0 +1,143 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.ode.FieldEquationsMapper;
+import org.apache.commons.math3.ode.FieldODEStateAndDerivative;
+import org.apache.commons.math3.ode.sampling.AbstractFieldStepInterpolator;
+import org.apache.commons.math3.util.MathArrays;
+
+/** This class represents an interpolator over the last step during an
+ * ODE integration for Runge-Kutta and embedded Runge-Kutta integrators.
+ *
+ * @see RungeKuttaFieldIntegrator
+ * @see EmbeddedRungeKuttaFieldIntegrator
+ *
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+
+abstract class RungeKuttaFieldStepInterpolator<T extends RealFieldElement<T>>
+    extends AbstractFieldStepInterpolator<T> {
+
+    /** Field to which the time and state vector elements belong. */
+    private final Field<T> field;
+
+    /** Slopes at the intermediate points. */
+    private final T[][] yDotK;
+
+    /** Simple constructor.
+     * @param field field to which the time and state vector elements belong
+     * @param forward integration direction indicator
+     * @param yDotK slopes at the intermediate points
+     * @param globalPreviousState start of the global step
+     * @param globalCurrentState end of the global step
+     * @param softPreviousState start of the restricted step
+     * @param softCurrentState end of the restricted step
+     * @param mapper equations mapper for the all equations
+     */
+    protected RungeKuttaFieldStepInterpolator(final Field<T> field, final boolean forward,
+                                              final T[][] yDotK,
+                                              final FieldODEStateAndDerivative<T> globalPreviousState,
+                                              final FieldODEStateAndDerivative<T> globalCurrentState,
+                                              final FieldODEStateAndDerivative<T> softPreviousState,
+                                              final FieldODEStateAndDerivative<T> softCurrentState,
+                                              final FieldEquationsMapper<T> mapper) {
+        super(forward, globalPreviousState, globalCurrentState, softPreviousState, softCurrentState, mapper);
+        this.field = field;
+        this.yDotK = MathArrays.buildArray(field, yDotK.length, -1);
+        for (int i = 0; i < yDotK.length; ++i) {
+            this.yDotK[i] = yDotK[i].clone();
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected RungeKuttaFieldStepInterpolator<T> create(boolean newForward,
+                                                        FieldODEStateAndDerivative<T> newGlobalPreviousState,
+                                                        FieldODEStateAndDerivative<T> newGlobalCurrentState,
+                                                        FieldODEStateAndDerivative<T> newSoftPreviousState,
+                                                        FieldODEStateAndDerivative<T> newSoftCurrentState,
+                                                        FieldEquationsMapper<T> newMapper) {
+        return create(field, newForward, yDotK,
+                      newGlobalPreviousState, newGlobalCurrentState,
+                      newSoftPreviousState, newSoftCurrentState,
+                      newMapper);
+    }
+
+    /** Create a new instance.
+     * @param newField field to which the time and state vector elements belong
+     * @param newForward integration direction indicator
+     * @param newYDotK slopes at the intermediate points
+     * @param newGlobalPreviousState start of the global step
+     * @param newGlobalCurrentState end of the global step
+     * @param newSoftPreviousState start of the restricted step
+     * @param newSoftCurrentState end of the restricted step
+     * @param newMapper equations mapper for the all equations
+     * @return a new instance
+     */
+    protected abstract RungeKuttaFieldStepInterpolator<T> create(Field<T> newField, boolean newForward, T[][] newYDotK,
+                                                                 FieldODEStateAndDerivative<T> newGlobalPreviousState,
+                                                                 FieldODEStateAndDerivative<T> newGlobalCurrentState,
+                                                                 FieldODEStateAndDerivative<T> newSoftPreviousState,
+                                                                 FieldODEStateAndDerivative<T> newSoftCurrentState,
+                                                                 FieldEquationsMapper<T> newMapper);
+
+    /** Compute a state by linear combination added to previous state.
+     * @param coefficients coefficients to apply to the method staged derivatives
+     * @return combined state
+     */
+    protected final T[] previousStateLinearCombination(final T ... coefficients) {
+        return combine(getPreviousState().getState(),
+                       coefficients);
+    }
+
+    /** Compute a state by linear combination added to current state.
+     * @param coefficients coefficients to apply to the method staged derivatives
+     * @return combined state
+     */
+    protected T[] currentStateLinearCombination(final T ... coefficients) {
+        return combine(getCurrentState().getState(),
+                       coefficients);
+    }
+
+    /** Compute a state derivative by linear combination.
+     * @param coefficients coefficients to apply to the method staged derivatives
+     * @return combined state
+     */
+    protected T[] derivativeLinearCombination(final T ... coefficients) {
+        return combine(MathArrays.buildArray(field, yDotK[0].length), coefficients);
+    }
+
+    /** Linearly combine arrays.
+     * @param a array to add to
+     * @param coefficients coefficients to apply to the method staged derivatives
+     * @return a itself, as a convenience for fluent API
+     */
+    private T[] combine(final T[] a, final T ... coefficients) {
+        for (int i = 0; i < a.length; ++i) {
+            for (int k = 0; k < coefficients.length; ++k) {
+                a[i] = a[i].add(coefficients[k].multiply(yDotK[k][i]));
+            }
+        }
+        return a;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/RungeKuttaIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/RungeKuttaIntegrator.java
new file mode 100644
index 0000000..5f7d5d8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/RungeKuttaIntegrator.java
@@ -0,0 +1,269 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NoBracketingException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.ode.AbstractIntegrator;
+import org.apache.commons.math3.ode.ExpandableStatefulODE;
+import org.apache.commons.math3.ode.FirstOrderDifferentialEquations;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * This class implements the common part of all fixed step Runge-Kutta
+ * integrators for Ordinary Differential Equations.
+ *
+ * <p>These methods are explicit Runge-Kutta methods, their Butcher
+ * arrays are as follows :
+ * <pre>
+ *    0  |
+ *   c2  | a21
+ *   c3  | a31  a32
+ *   ... |        ...
+ *   cs  | as1  as2  ...  ass-1
+ *       |--------------------------
+ *       |  b1   b2  ...   bs-1  bs
+ * </pre>
+ * </p>
+ *
+ * @see EulerIntegrator
+ * @see ClassicalRungeKuttaIntegrator
+ * @see GillIntegrator
+ * @see MidpointIntegrator
+ * @since 1.2
+ */
+
+public abstract class RungeKuttaIntegrator extends AbstractIntegrator {
+
+    /** Time steps from Butcher array (without the first zero). */
+    private final double[] c;
+
+    /** Internal weights from Butcher array (without the first empty row). */
+    private final double[][] a;
+
+    /** External weights for the high order method from Butcher array. */
+    private final double[] b;
+
+    /** Prototype of the step interpolator. */
+    private final RungeKuttaStepInterpolator prototype;
+
+    /** Integration step. */
+    private final double step;
+
+  /** Simple constructor.
+   * Build a Runge-Kutta integrator with the given
+   * step. The default step handler does nothing.
+   * @param name name of the method
+   * @param c time steps from Butcher array (without the first zero)
+   * @param a internal weights from Butcher array (without the first empty row)
+   * @param b propagation weights for the high order method from Butcher array
+   * @param prototype prototype of the step interpolator to use
+   * @param step integration step
+   */
+  protected RungeKuttaIntegrator(final String name,
+                                 final double[] c, final double[][] a, final double[] b,
+                                 final RungeKuttaStepInterpolator prototype,
+                                 final double step) {
+    super(name);
+    this.c          = c;
+    this.a          = a;
+    this.b          = b;
+    this.prototype  = prototype;
+    this.step       = FastMath.abs(step);
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public void integrate(final ExpandableStatefulODE equations, final double t)
+      throws NumberIsTooSmallException, DimensionMismatchException,
+             MaxCountExceededException, NoBracketingException {
+
+    sanityChecks(equations, t);
+    setEquations(equations);
+    final boolean forward = t > equations.getTime();
+
+    // create some internal working arrays
+    final double[] y0      = equations.getCompleteState();
+    final double[] y       = y0.clone();
+    final int stages       = c.length + 1;
+    final double[][] yDotK = new double[stages][];
+    for (int i = 0; i < stages; ++i) {
+      yDotK [i] = new double[y0.length];
+    }
+    final double[] yTmp    = y0.clone();
+    final double[] yDotTmp = new double[y0.length];
+
+    // set up an interpolator sharing the integrator arrays
+    final RungeKuttaStepInterpolator interpolator = (RungeKuttaStepInterpolator) prototype.copy();
+    interpolator.reinitialize(this, yTmp, yDotK, forward,
+                              equations.getPrimaryMapper(), equations.getSecondaryMappers());
+    interpolator.storeTime(equations.getTime());
+
+    // set up integration control objects
+    stepStart = equations.getTime();
+    if (forward) {
+        if (stepStart + step >= t) {
+            stepSize = t - stepStart;
+        } else {
+            stepSize = step;
+        }
+    } else {
+        if (stepStart - step <= t) {
+            stepSize = t - stepStart;
+        } else {
+            stepSize = -step;
+        }
+    }
+    initIntegration(equations.getTime(), y0, t);
+
+    // main integration loop
+    isLastStep = false;
+    do {
+
+      interpolator.shift();
+
+      // first stage
+      computeDerivatives(stepStart, y, yDotK[0]);
+
+      // next stages
+      for (int k = 1; k < stages; ++k) {
+
+          for (int j = 0; j < y0.length; ++j) {
+              double sum = a[k-1][0] * yDotK[0][j];
+              for (int l = 1; l < k; ++l) {
+                  sum += a[k-1][l] * yDotK[l][j];
+              }
+              yTmp[j] = y[j] + stepSize * sum;
+          }
+
+          computeDerivatives(stepStart + c[k-1] * stepSize, yTmp, yDotK[k]);
+
+      }
+
+      // estimate the state at the end of the step
+      for (int j = 0; j < y0.length; ++j) {
+          double sum    = b[0] * yDotK[0][j];
+          for (int l = 1; l < stages; ++l) {
+              sum    += b[l] * yDotK[l][j];
+          }
+          yTmp[j] = y[j] + stepSize * sum;
+      }
+
+      // discrete events handling
+      interpolator.storeTime(stepStart + stepSize);
+      System.arraycopy(yTmp, 0, y, 0, y0.length);
+      System.arraycopy(yDotK[stages - 1], 0, yDotTmp, 0, y0.length);
+      stepStart = acceptStep(interpolator, y, yDotTmp, t);
+
+      if (!isLastStep) {
+
+          // prepare next step
+          interpolator.storeTime(stepStart);
+
+          // stepsize control for next step
+          final double  nextT      = stepStart + stepSize;
+          final boolean nextIsLast = forward ? (nextT >= t) : (nextT <= t);
+          if (nextIsLast) {
+              stepSize = t - stepStart;
+          }
+      }
+
+    } while (!isLastStep);
+
+    // dispatch results
+    equations.setTime(stepStart);
+    equations.setCompleteState(y);
+
+    stepStart = Double.NaN;
+    stepSize  = Double.NaN;
+
+  }
+
+  /** Fast computation of a single step of ODE integration.
+   * <p>This method is intended for the limited use case of
+   * very fast computation of only one step without using any of the
+   * rich features of general integrators that may take some time
+   * to set up (i.e. no step handlers, no events handlers, no additional
+   * states, no interpolators, no error control, no evaluations count,
+   * no sanity checks ...). It handles the strict minimum of computation,
+   * so it can be embedded in outer loops.</p>
+   * <p>
+   * This method is <em>not</em> used at all by the {@link #integrate(ExpandableStatefulODE, double)}
+   * method. It also completely ignores the step set at construction time, and
+   * uses only a single step to go from {@code t0} to {@code t}.
+   * </p>
+   * <p>
+   * As this method does not use any of the state-dependent features of the integrator,
+   * it should be reasonably thread-safe <em>if and only if</em> the provided differential
+   * equations are themselves thread-safe.
+   * </p>
+   * @param equations differential equations to integrate
+   * @param t0 initial time
+   * @param y0 initial value of the state vector at t0
+   * @param t target time for the integration
+   * (can be set to a value smaller than {@code t0} for backward integration)
+   * @return state vector at {@code t}
+   */
+  public double[] singleStep(final FirstOrderDifferentialEquations equations,
+                             final double t0, final double[] y0, final double t) {
+
+      // create some internal working arrays
+      final double[] y       = y0.clone();
+      final int stages       = c.length + 1;
+      final double[][] yDotK = new double[stages][];
+      for (int i = 0; i < stages; ++i) {
+          yDotK [i] = new double[y0.length];
+      }
+      final double[] yTmp    = y0.clone();
+
+      // first stage
+      final double h = t - t0;
+      equations.computeDerivatives(t0, y, yDotK[0]);
+
+      // next stages
+      for (int k = 1; k < stages; ++k) {
+
+          for (int j = 0; j < y0.length; ++j) {
+              double sum = a[k-1][0] * yDotK[0][j];
+              for (int l = 1; l < k; ++l) {
+                  sum += a[k-1][l] * yDotK[l][j];
+              }
+              yTmp[j] = y[j] + h * sum;
+          }
+
+          equations.computeDerivatives(t0 + c[k-1] * h, yTmp, yDotK[k]);
+
+      }
+
+      // estimate the state at the end of the step
+      for (int j = 0; j < y0.length; ++j) {
+          double sum = b[0] * yDotK[0][j];
+          for (int l = 1; l < stages; ++l) {
+              sum += b[l] * yDotK[l][j];
+          }
+          y[j] += h * sum;
+      }
+
+      return y;
+
+  }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/RungeKuttaStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/RungeKuttaStepInterpolator.java
new file mode 100644
index 0000000..1ae7cb9
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/RungeKuttaStepInterpolator.java
@@ -0,0 +1,211 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+import org.apache.commons.math3.ode.AbstractIntegrator;
+import org.apache.commons.math3.ode.EquationsMapper;
+import org.apache.commons.math3.ode.sampling.AbstractStepInterpolator;
+
+/** This class represents an interpolator over the last step during an
+ * ODE integration for Runge-Kutta and embedded Runge-Kutta integrators.
+ *
+ * @see RungeKuttaIntegrator
+ * @see EmbeddedRungeKuttaIntegrator
+ *
+ * @since 1.2
+ */
+
+abstract class RungeKuttaStepInterpolator
+  extends AbstractStepInterpolator {
+
+    /** Previous state. */
+    protected double[] previousState;
+
+    /** Slopes at the intermediate points */
+    protected double[][] yDotK;
+
+    /** Reference to the integrator. */
+    protected AbstractIntegrator integrator;
+
+  /** Simple constructor.
+   * This constructor builds an instance that is not usable yet, the
+   * {@link #reinitialize} method should be called before using the
+   * instance in order to initialize the internal arrays. This
+   * constructor is used only in order to delay the initialization in
+   * some cases. The {@link RungeKuttaIntegrator} and {@link
+   * EmbeddedRungeKuttaIntegrator} classes use the prototyping design
+   * pattern to create the step interpolators by cloning an
+   * uninitialized model and latter initializing the copy.
+   */
+  protected RungeKuttaStepInterpolator() {
+    previousState = null;
+    yDotK         = null;
+    integrator    = null;
+  }
+
+  /** Copy constructor.
+
+  * <p>The copied interpolator should have been finalized before the
+  * copy, otherwise the copy will not be able to perform correctly any
+  * interpolation and will throw a {@link NullPointerException}
+  * later. Since we don't want this constructor to throw the
+  * exceptions finalization may involve and since we don't want this
+  * method to modify the state of the copied interpolator,
+  * finalization is <strong>not</strong> done automatically, it
+  * remains under user control.</p>
+
+  * <p>The copy is a deep copy: its arrays are separated from the
+  * original arrays of the instance.</p>
+
+  * @param interpolator interpolator to copy from.
+
+  */
+  RungeKuttaStepInterpolator(final RungeKuttaStepInterpolator interpolator) {
+
+    super(interpolator);
+
+    if (interpolator.currentState != null) {
+
+      previousState = interpolator.previousState.clone();
+
+      yDotK = new double[interpolator.yDotK.length][];
+      for (int k = 0; k < interpolator.yDotK.length; ++k) {
+        yDotK[k] = interpolator.yDotK[k].clone();
+      }
+
+    } else {
+      previousState = null;
+      yDotK = null;
+    }
+
+    // we cannot keep any reference to the equations in the copy
+    // the interpolator should have been finalized before
+    integrator = null;
+
+  }
+
+  /** Reinitialize the instance
+   * <p>Some Runge-Kutta integrators need fewer functions evaluations
+   * than their counterpart step interpolators. So the interpolator
+   * should perform the last evaluations they need by themselves. The
+   * {@link RungeKuttaIntegrator RungeKuttaIntegrator} and {@link
+   * EmbeddedRungeKuttaIntegrator EmbeddedRungeKuttaIntegrator}
+   * abstract classes call this method in order to let the step
+   * interpolator perform the evaluations it needs. These evaluations
+   * will be performed during the call to <code>doFinalize</code> if
+   * any, i.e. only if the step handler either calls the {@link
+   * AbstractStepInterpolator#finalizeStep finalizeStep} method or the
+   * {@link AbstractStepInterpolator#getInterpolatedState
+   * getInterpolatedState} method (for an interpolator which needs a
+   * finalization) or if it clones the step interpolator.</p>
+   * @param rkIntegrator integrator being used
+   * @param y reference to the integrator array holding the state at
+   * the end of the step
+   * @param yDotArray reference to the integrator array holding all the
+   * intermediate slopes
+   * @param forward integration direction indicator
+   * @param primaryMapper equations mapper for the primary equations set
+   * @param secondaryMappers equations mappers for the secondary equations sets
+   */
+  public void reinitialize(final AbstractIntegrator rkIntegrator,
+                           final double[] y, final double[][] yDotArray, final boolean forward,
+                           final EquationsMapper primaryMapper,
+                           final EquationsMapper[] secondaryMappers) {
+    reinitialize(y, forward, primaryMapper, secondaryMappers);
+    this.previousState = null;
+    this.yDotK = yDotArray;
+    this.integrator = rkIntegrator;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public void shift() {
+    previousState = currentState.clone();
+    super.shift();
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public void writeExternal(final ObjectOutput out)
+    throws IOException {
+
+    // save the state of the base class
+    writeBaseExternal(out);
+
+    // save the local attributes
+    final int n = (currentState == null) ? -1 : currentState.length;
+    for (int i = 0; i < n; ++i) {
+      out.writeDouble(previousState[i]);
+    }
+
+    final int kMax = (yDotK == null) ? -1 : yDotK.length;
+    out.writeInt(kMax);
+    for (int k = 0; k < kMax; ++k) {
+      for (int i = 0; i < n; ++i) {
+        out.writeDouble(yDotK[k][i]);
+      }
+    }
+
+    // we do not save any reference to the equations
+
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public void readExternal(final ObjectInput in)
+    throws IOException, ClassNotFoundException {
+
+    // read the base class
+    final double t = readBaseExternal(in);
+
+    // read the local attributes
+    final int n = (currentState == null) ? -1 : currentState.length;
+    if (n < 0) {
+      previousState = null;
+    } else {
+      previousState = new double[n];
+      for (int i = 0; i < n; ++i) {
+        previousState[i] = in.readDouble();
+      }
+    }
+
+    final int kMax = in.readInt();
+    yDotK = (kMax < 0) ? null : new double[kMax][];
+    for (int k = 0; k < kMax; ++k) {
+      yDotK[k] = (n < 0) ? null : new double[n];
+      for (int i = 0; i < n; ++i) {
+        yDotK[k][i] = in.readDouble();
+      }
+    }
+
+    integrator = null;
+
+    if (currentState != null) {
+        // we can now set the interpolated time and state
+        setInterpolatedTime(t);
+    } else {
+        interpolatedTime = t;
+    }
+
+  }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/ThreeEighthesFieldIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/ThreeEighthesFieldIntegrator.java
new file mode 100644
index 0000000..7e91de8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/ThreeEighthesFieldIntegrator.java
@@ -0,0 +1,110 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.ode.FieldEquationsMapper;
+import org.apache.commons.math3.ode.FieldODEStateAndDerivative;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * This class implements the 3/8 fourth order Runge-Kutta
+ * integrator for Ordinary Differential Equations.
+ *
+ * <p>This method is an explicit Runge-Kutta method, its Butcher-array
+ * is the following one :
+ * <pre>
+ *    0  |  0    0    0    0
+ *   1/3 | 1/3   0    0    0
+ *   2/3 |-1/3   1    0    0
+ *    1  |  1   -1    1    0
+ *       |--------------------
+ *       | 1/8  3/8  3/8  1/8
+ * </pre>
+ * </p>
+ *
+ * @see EulerFieldIntegrator
+ * @see ClassicalRungeKuttaFieldIntegrator
+ * @see GillFieldIntegrator
+ * @see MidpointFieldIntegrator
+ * @see LutherFieldIntegrator
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+
+public class ThreeEighthesFieldIntegrator<T extends RealFieldElement<T>>
+    extends RungeKuttaFieldIntegrator<T> {
+
+    /** Simple constructor.
+     * Build a 3/8 integrator with the given step.
+     * @param field field to which the time and state vector elements belong
+     * @param step integration step
+     */
+    public ThreeEighthesFieldIntegrator(final Field<T> field, final T step) {
+        super(field, "3/8", step);
+    }
+
+    /** {@inheritDoc} */
+    public T[] getC() {
+        final T[] c = MathArrays.buildArray(getField(), 3);
+        c[0] = fraction(1, 3);
+        c[1] = c[0].add(c[0]);
+        c[2] = getField().getOne();
+        return c;
+    }
+
+    /** {@inheritDoc} */
+    public T[][] getA() {
+        final T[][] a = MathArrays.buildArray(getField(), 3, -1);
+        for (int i = 0; i < a.length; ++i) {
+            a[i] = MathArrays.buildArray(getField(), i + 1);
+        }
+        a[0][0] = fraction(1, 3);
+        a[1][0] = a[0][0].negate();
+        a[1][1] = getField().getOne();
+        a[2][0] = getField().getOne();
+        a[2][1] = getField().getOne().negate();
+        a[2][2] = getField().getOne();
+        return a;
+    }
+
+    /** {@inheritDoc} */
+    public T[] getB() {
+        final T[] b = MathArrays.buildArray(getField(), 4);
+        b[0] = fraction(1, 8);
+        b[1] = fraction(3, 8);
+        b[2] = b[1];
+        b[3] = b[0];
+        return b;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected ThreeEighthesFieldStepInterpolator<T>
+        createInterpolator(final boolean forward, T[][] yDotK,
+                           final FieldODEStateAndDerivative<T> globalPreviousState,
+                           final FieldODEStateAndDerivative<T> globalCurrentState,
+                           final FieldEquationsMapper<T> mapper) {
+        return new ThreeEighthesFieldStepInterpolator<T>(getField(), forward, yDotK,
+                                                         globalPreviousState, globalCurrentState,
+                                                         globalPreviousState, globalCurrentState,
+                                                         mapper);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/ThreeEighthesFieldStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/ThreeEighthesFieldStepInterpolator.java
new file mode 100644
index 0000000..14a4eb8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/ThreeEighthesFieldStepInterpolator.java
@@ -0,0 +1,139 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.ode.FieldEquationsMapper;
+import org.apache.commons.math3.ode.FieldODEStateAndDerivative;
+
+/**
+ * This class implements a step interpolator for the 3/8 fourth
+ * order Runge-Kutta integrator.
+ *
+ * <p>This interpolator allows to compute dense output inside the last
+ * step computed. The interpolation equation is consistent with the
+ * integration scheme :
+ * <ul>
+ *   <li>Using reference point at step start:<br>
+ *     y(t<sub>n</sub> + &theta; h) = y (t<sub>n</sub>)
+ *                      + &theta; (h/8) [ (8 - 15 &theta; +  8 &theta;<sup>2</sup>) y'<sub>1</sub>
+ *                                     +  3 * (15 &theta; - 12 &theta;<sup>2</sup>) y'<sub>2</sub>
+ *                                     +        3 &theta;                           y'<sub>3</sub>
+ *                                     +      (-3 &theta; +  4 &theta;<sup>2</sup>) y'<sub>4</sub>
+ *                                    ]
+ *   </li>
+ *   <li>Using reference point at step end:<br>
+ *     y(t<sub>n</sub> + &theta; h) = y (t<sub>n</sub> + h)
+ *                      - (1 - &theta;) (h/8) [(1 - 7 &theta; + 8 &theta;<sup>2</sup>) y'<sub>1</sub>
+ *                                         + 3 (1 +   &theta; - 4 &theta;<sup>2</sup>) y'<sub>2</sub>
+ *                                         + 3 (1 +   &theta;)                         y'<sub>3</sub>
+ *                                         +   (1 +   &theta; + 4 &theta;<sup>2</sup>) y'<sub>4</sub>
+ *                                          ]
+ *   </li>
+ * </ul>
+ * </p>
+ *
+ * where &theta; belongs to [0 ; 1] and where y'<sub>1</sub> to y'<sub>4</sub> are the four
+ * evaluations of the derivatives already computed during the
+ * step.</p>
+ *
+ * @see ThreeEighthesFieldIntegrator
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+
+class ThreeEighthesFieldStepInterpolator<T extends RealFieldElement<T>>
+      extends RungeKuttaFieldStepInterpolator<T> {
+
+    /** Simple constructor.
+     * @param field field to which the time and state vector elements belong
+     * @param forward integration direction indicator
+     * @param yDotK slopes at the intermediate points
+     * @param globalPreviousState start of the global step
+     * @param globalCurrentState end of the global step
+     * @param softPreviousState start of the restricted step
+     * @param softCurrentState end of the restricted step
+     * @param mapper equations mapper for the all equations
+     */
+    ThreeEighthesFieldStepInterpolator(final Field<T> field, final boolean forward,
+                                       final T[][] yDotK,
+                                       final FieldODEStateAndDerivative<T> globalPreviousState,
+                                       final FieldODEStateAndDerivative<T> globalCurrentState,
+                                       final FieldODEStateAndDerivative<T> softPreviousState,
+                                       final FieldODEStateAndDerivative<T> softCurrentState,
+                                       final FieldEquationsMapper<T> mapper) {
+        super(field, forward, yDotK,
+              globalPreviousState, globalCurrentState, softPreviousState, softCurrentState,
+              mapper);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected ThreeEighthesFieldStepInterpolator<T> create(final Field<T> newField, final boolean newForward, final T[][] newYDotK,
+                                                           final FieldODEStateAndDerivative<T> newGlobalPreviousState,
+                                                           final FieldODEStateAndDerivative<T> newGlobalCurrentState,
+                                                           final FieldODEStateAndDerivative<T> newSoftPreviousState,
+                                                           final FieldODEStateAndDerivative<T> newSoftCurrentState,
+                                                           final FieldEquationsMapper<T> newMapper) {
+        return new ThreeEighthesFieldStepInterpolator<T>(newField, newForward, newYDotK,
+                                                         newGlobalPreviousState, newGlobalCurrentState,
+                                                         newSoftPreviousState, newSoftCurrentState,
+                                                         newMapper);
+    }
+
+    /** {@inheritDoc} */
+    @SuppressWarnings("unchecked")
+    @Override
+    protected FieldODEStateAndDerivative<T> computeInterpolatedStateAndDerivatives(final FieldEquationsMapper<T> mapper,
+                                                                                   final T time, final T theta,
+                                                                                   final T thetaH, final T oneMinusThetaH) {
+
+        final T coeffDot3  = theta.multiply(0.75);
+        final T coeffDot1  = coeffDot3.multiply(theta.multiply(4).subtract(5)).add(1);
+        final T coeffDot2  = coeffDot3.multiply(theta.multiply(-6).add(5));
+        final T coeffDot4  = coeffDot3.multiply(theta.multiply(2).subtract(1));
+        final T[] interpolatedState;
+        final T[] interpolatedDerivatives;
+
+        if (getGlobalPreviousState() != null && theta.getReal() <= 0.5) {
+            final T s          = thetaH.divide(8);
+            final T fourTheta2 = theta.multiply(theta).multiply(4);
+            final T coeff1     = s.multiply(fourTheta2.multiply(2).subtract(theta.multiply(15)).add(8));
+            final T coeff2     = s.multiply(theta.multiply(5).subtract(fourTheta2)).multiply(3);
+            final T coeff3     = s.multiply(theta).multiply(3);
+            final T coeff4     = s.multiply(fourTheta2.subtract(theta.multiply(3)));
+            interpolatedState       = previousStateLinearCombination(coeff1, coeff2, coeff3, coeff4);
+            interpolatedDerivatives = derivativeLinearCombination(coeffDot1, coeffDot2, coeffDot3, coeffDot4);
+        } else {
+            final T s          = oneMinusThetaH.divide(-8);
+            final T fourTheta2 = theta.multiply(theta).multiply(4);
+            final T thetaPlus1 = theta.add(1);
+            final T coeff1     = s.multiply(fourTheta2.multiply(2).subtract(theta.multiply(7)).add(1));
+            final T coeff2     = s.multiply(thetaPlus1.subtract(fourTheta2)).multiply(3);
+            final T coeff3     = s.multiply(thetaPlus1).multiply(3);
+            final T coeff4     = s.multiply(thetaPlus1.add(fourTheta2));
+            interpolatedState       = currentStateLinearCombination(coeff1, coeff2, coeff3, coeff4);
+            interpolatedDerivatives = derivativeLinearCombination(coeffDot1, coeffDot2, coeffDot3, coeffDot4);
+        }
+
+        return new FieldODEStateAndDerivative<T>(time, interpolatedState, interpolatedDerivatives);
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/ThreeEighthesIntegrator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/ThreeEighthesIntegrator.java
new file mode 100644
index 0000000..c5f8216
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/ThreeEighthesIntegrator.java
@@ -0,0 +1,72 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+
+/**
+ * This class implements the 3/8 fourth order Runge-Kutta
+ * integrator for Ordinary Differential Equations.
+ *
+ * <p>This method is an explicit Runge-Kutta method, its Butcher-array
+ * is the following one :
+ * <pre>
+ *    0  |  0    0    0    0
+ *   1/3 | 1/3   0    0    0
+ *   2/3 |-1/3   1    0    0
+ *    1  |  1   -1    1    0
+ *       |--------------------
+ *       | 1/8  3/8  3/8  1/8
+ * </pre>
+ * </p>
+ *
+ * @see EulerIntegrator
+ * @see ClassicalRungeKuttaIntegrator
+ * @see GillIntegrator
+ * @see MidpointIntegrator
+ * @see LutherIntegrator
+ * @since 1.2
+ */
+
+public class ThreeEighthesIntegrator extends RungeKuttaIntegrator {
+
+  /** Time steps Butcher array. */
+  private static final double[] STATIC_C = {
+    1.0 / 3.0, 2.0 / 3.0, 1.0
+  };
+
+  /** Internal weights Butcher array. */
+  private static final double[][] STATIC_A = {
+    {  1.0 / 3.0 },
+    { -1.0 / 3.0, 1.0 },
+    {  1.0, -1.0, 1.0 }
+  };
+
+  /** Propagation weights Butcher array. */
+  private static final double[] STATIC_B = {
+    1.0 / 8.0, 3.0 / 8.0, 3.0 / 8.0, 1.0 / 8.0
+  };
+
+  /** Simple constructor.
+   * Build a 3/8 integrator with the given step.
+   * @param step integration step
+   */
+  public ThreeEighthesIntegrator(final double step) {
+    super("3/8", STATIC_C, STATIC_A, STATIC_B, new ThreeEighthesStepInterpolator(), step);
+  }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/ThreeEighthesStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/ThreeEighthesStepInterpolator.java
new file mode 100644
index 0000000..df288fd
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/ThreeEighthesStepInterpolator.java
@@ -0,0 +1,146 @@
+/*
+ * 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.math3.ode.nonstiff;
+
+import org.apache.commons.math3.ode.sampling.StepInterpolator;
+
+/**
+ * This class implements a step interpolator for the 3/8 fourth
+ * order Runge-Kutta integrator.
+ *
+ * <p>This interpolator allows to compute dense output inside the last
+ * step computed. The interpolation equation is consistent with the
+ * integration scheme :
+ * <ul>
+ *   <li>Using reference point at step start:<br>
+ *     y(t<sub>n</sub> + &theta; h) = y (t<sub>n</sub>)
+ *                      + &theta; (h/8) [ (8 - 15 &theta; +  8 &theta;<sup>2</sup>) y'<sub>1</sub>
+ *                                     +  3 * (15 &theta; - 12 &theta;<sup>2</sup>) y'<sub>2</sub>
+ *                                     +        3 &theta;                           y'<sub>3</sub>
+ *                                     +      (-3 &theta; +  4 &theta;<sup>2</sup>) y'<sub>4</sub>
+ *                                    ]
+ *   </li>
+ *   <li>Using reference point at step end:<br>
+ *     y(t<sub>n</sub> + &theta; h) = y (t<sub>n</sub> + h)
+ *                      - (1 - &theta;) (h/8) [(1 - 7 &theta; + 8 &theta;<sup>2</sup>) y'<sub>1</sub>
+ *                                         + 3 (1 +   &theta; - 4 &theta;<sup>2</sup>) y'<sub>2</sub>
+ *                                         + 3 (1 +   &theta;)                         y'<sub>3</sub>
+ *                                         +   (1 +   &theta; + 4 &theta;<sup>2</sup>) y'<sub>4</sub>
+ *                                          ]
+ *   </li>
+ * </ul>
+ * </p>
+ *
+ * where &theta; belongs to [0 ; 1] and where y'<sub>1</sub> to y'<sub>4</sub> are the four
+ * evaluations of the derivatives already computed during the
+ * step.</p>
+ *
+ * @see ThreeEighthesIntegrator
+ * @since 1.2
+ */
+
+class ThreeEighthesStepInterpolator
+  extends RungeKuttaStepInterpolator {
+
+  /** Serializable version identifier */
+  private static final long serialVersionUID = 20111120L;
+
+  /** Simple constructor.
+   * This constructor builds an instance that is not usable yet, the
+   * {@link
+   * org.apache.commons.math3.ode.sampling.AbstractStepInterpolator#reinitialize}
+   * method should be called before using the instance in order to
+   * initialize the internal arrays. This constructor is used only
+   * in order to delay the initialization in some cases. The {@link
+   * RungeKuttaIntegrator} class uses the prototyping design pattern
+   * to create the step interpolators by cloning an uninitialized model
+   * and later initializing the copy.
+   */
+  // CHECKSTYLE: stop RedundantModifier
+  // the public modifier here is needed for serialization
+  public ThreeEighthesStepInterpolator() {
+  }
+  // CHECKSTYLE: resume RedundantModifier
+
+  /** Copy constructor.
+   * @param interpolator interpolator to copy from. The copy is a deep
+   * copy: its arrays are separated from the original arrays of the
+   * instance
+   */
+  ThreeEighthesStepInterpolator(final ThreeEighthesStepInterpolator interpolator) {
+    super(interpolator);
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  protected StepInterpolator doCopy() {
+    return new ThreeEighthesStepInterpolator(this);
+  }
+
+
+  /** {@inheritDoc} */
+  @Override
+  protected void computeInterpolatedStateAndDerivatives(final double theta,
+                                          final double oneMinusThetaH) {
+
+      final double coeffDot3  = 0.75 * theta;
+      final double coeffDot1  = coeffDot3 * (4 * theta - 5) + 1;
+      final double coeffDot2  = coeffDot3 * (5 - 6 * theta);
+      final double coeffDot4  = coeffDot3 * (2 * theta - 1);
+
+      if ((previousState != null) && (theta <= 0.5)) {
+          final double s          = theta * h / 8.0;
+          final double fourTheta2 = 4 * theta * theta;
+          final double coeff1     = s * (8 - 15 * theta + 2 * fourTheta2);
+          final double coeff2     = 3 * s * (5 * theta - fourTheta2);
+          final double coeff3     = 3 * s * theta;
+          final double coeff4     = s * (-3 * theta + fourTheta2);
+          for (int i = 0; i < interpolatedState.length; ++i) {
+              final double yDot1 = yDotK[0][i];
+              final double yDot2 = yDotK[1][i];
+              final double yDot3 = yDotK[2][i];
+              final double yDot4 = yDotK[3][i];
+              interpolatedState[i] =
+                      previousState[i] + coeff1 * yDot1 + coeff2 * yDot2 + coeff3 * yDot3 + coeff4 * yDot4;
+              interpolatedDerivatives[i] =
+                      coeffDot1 * yDot1 + coeffDot2 * yDot2 + coeffDot3 * yDot3 + coeffDot4 * yDot4;
+
+          }
+      } else {
+          final double s          = oneMinusThetaH / 8.0;
+          final double fourTheta2 = 4 * theta * theta;
+          final double coeff1     = s * (1 - 7 * theta + 2 * fourTheta2);
+          final double coeff2     = 3 * s * (1 + theta - fourTheta2);
+          final double coeff3     = 3 * s * (1 + theta);
+          final double coeff4     = s * (1 + theta + fourTheta2);
+          for (int i = 0; i < interpolatedState.length; ++i) {
+              final double yDot1 = yDotK[0][i];
+              final double yDot2 = yDotK[1][i];
+              final double yDot3 = yDotK[2][i];
+              final double yDot4 = yDotK[3][i];
+              interpolatedState[i] =
+                      currentState[i] - coeff1 * yDot1 - coeff2 * yDot2 - coeff3 * yDot3 - coeff4 * yDot4;
+              interpolatedDerivatives[i] =
+                      coeffDot1 * yDot1 + coeffDot2 * yDot2 + coeffDot3 * yDot3 + coeffDot4 * yDot4;
+
+          }
+      }
+
+  }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/nonstiff/package-info.java b/src/main/java/org/apache/commons/math3/ode/nonstiff/package-info.java
new file mode 100644
index 0000000..b2387ce
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/nonstiff/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * <p>
+ * This package provides classes to solve non-stiff Ordinary Differential Equations problems.
+ * </p>
+ *
+ *
+ */
+package org.apache.commons.math3.ode.nonstiff;
diff --git a/src/main/java/org/apache/commons/math3/ode/package-info.java b/src/main/java/org/apache/commons/math3/ode/package-info.java
new file mode 100644
index 0000000..1e412f8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/package-info.java
@@ -0,0 +1,120 @@
+/*
+ * 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.
+ */
+/**
+ * This package provides classes to solve Ordinary Differential Equations problems.
+ *
+ * <p>This package solves Initial Value Problems of the form <code>y'=f(t,y)</code> with <code>
+ * t<sub>0</sub></code> and <code>y(t<sub>0</sub>)=y<sub>0</sub></code> known. The provided
+ * integrators compute an estimate of <code>y(t)</code> from <code>t=t<sub>0</sub></code> to <code>
+ * t=t<sub>1</sub></code>. It is also possible to get thederivatives with respect to the initial
+ * state <code>dy(t)/dy(t<sub>0</sub>)</code> or the derivatives with respect to some ODE parameters
+ * <code>dy(t)/dp</code>.
+ *
+ * <p>All integrators provide dense output. This means that besides computing the state vector at
+ * discrete times, they also provide a cheap mean to get the state between the time steps. They do
+ * so through classes extending the {@link org.apache.commons.math3.ode.sampling.StepInterpolator
+ * StepInterpolator} abstract class, which are made available to the user at the end of each step.
+ *
+ * <p>All integrators handle multiple discrete events detection based on switching functions. This
+ * means that the integrator can be driven by user specified discrete events. The steps are
+ * shortened as needed to ensure the events occur at step boundaries (even if the integrator is a
+ * fixed-step integrator). When the events are triggered, integration can be stopped (this is called
+ * a G-stop facility), the state vector can be changed, or integration can simply go on. The latter
+ * case is useful to handle discontinuities in the differential equations gracefully and get
+ * accurate dense output even close to the discontinuity.
+ *
+ * <p>The user should describe his problem in his own classes (<code>UserProblem</code> in the
+ * diagram below) which should implement the {@link
+ * org.apache.commons.math3.ode.FirstOrderDifferentialEquations FirstOrderDifferentialEquations}
+ * interface. Then he should pass it to the integrator he prefers among all the classes that
+ * implement the {@link org.apache.commons.math3.ode.FirstOrderIntegrator FirstOrderIntegrator}
+ * interface.
+ *
+ * <p>The solution of the integration problem is provided by two means. The first one is aimed
+ * towards simple use: the state vector at the end of the integration process is copied in the
+ * <code>y</code> array of the {@link org.apache.commons.math3.ode.FirstOrderIntegrator#integrate
+ * FirstOrderIntegrator.integrate} method. The second one should be used when more in-depth
+ * information is needed throughout the integration process. The user can register an object
+ * implementing the {@link org.apache.commons.math3.ode.sampling.StepHandler StepHandler} interface
+ * or a {@link org.apache.commons.math3.ode.sampling.StepNormalizer StepNormalizer} object wrapping
+ * a user-specified object implementing the {@link
+ * org.apache.commons.math3.ode.sampling.FixedStepHandler FixedStepHandler} interface into the
+ * integrator before calling the {@link org.apache.commons.math3.ode.FirstOrderIntegrator#integrate
+ * FirstOrderIntegrator.integrate} method. The user object will be called appropriately during the
+ * integration process, allowing the user to process intermediate results. The default step handler
+ * does nothing.
+ *
+ * <p>{@link org.apache.commons.math3.ode.ContinuousOutputModel ContinuousOutputModel} is a
+ * special-purpose step handler that is able to store all steps and to provide transparent access to
+ * any intermediate result once the integration is over. An important feature of this class is that
+ * it implements the <code>Serializable</code> interface. This means that a complete continuous
+ * model of the integrated function throughout the integration range can be serialized and reused
+ * later (if stored into a persistent medium like a filesystem or a database) or elsewhere (if sent
+ * to another application). Only the result of the integration is stored, there is no reference to
+ * the integrated problem by itself.
+ *
+ * <p>Other default implementations of the {@link org.apache.commons.math3.ode.sampling.StepHandler
+ * StepHandler} interface are available for general needs ({@link
+ * org.apache.commons.math3.ode.sampling.DummyStepHandler DummyStepHandler}, {@link
+ * org.apache.commons.math3.ode.sampling.StepNormalizer StepNormalizer}) and custom implementations
+ * can be developed for specific needs. As an example, if an application is to be completely driven
+ * by the integration process, then most of the application code will be run inside a step handler
+ * specific to this application.
+ *
+ * <p>Some integrators (the simple ones) use fixed steps that are set at creation time. The more
+ * efficient integrators use variable steps that are handled internally in order to control the
+ * integration error with respect to a specified accuracy (these integrators extend the {@link
+ * org.apache.commons.math3.ode.nonstiff.AdaptiveStepsizeIntegrator AdaptiveStepsizeIntegrator}
+ * abstract class). In this case, the step handler which is called after each successful step shows
+ * up the variable stepsize. The {@link org.apache.commons.math3.ode.sampling.StepNormalizer
+ * StepNormalizer} class can be used to convert the variable stepsize into a fixed stepsize that can
+ * be handled by classes implementing the {@link
+ * org.apache.commons.math3.ode.sampling.FixedStepHandler FixedStepHandler} interface. Adaptive
+ * stepsize integrators can automatically compute the initial stepsize by themselves, however the
+ * user can specify it if he prefers to retain full control over the integration or if the automatic
+ * guess is wrong.
+ *
+ * <p>
+ *
+ * <table border="1" align="center">
+ * <tr BGCOLOR="#CCCCFF"><td colspan=2><font size="+2">Fixed Step Integrators</font></td></tr>
+ * <tr BGCOLOR="#EEEEFF"><font size="+1"><td>Name</td><td>Order</td></font></tr>
+ * <tr><td>{@link org.apache.commons.math3.ode.nonstiff.EulerIntegrator Euler}</td><td>1</td></tr>
+ * <tr><td>{@link org.apache.commons.math3.ode.nonstiff.MidpointIntegrator Midpoint}</td><td>2</td></tr>
+ * <tr><td>{@link org.apache.commons.math3.ode.nonstiff.ClassicalRungeKuttaIntegrator Classical Runge-Kutta}</td><td>4</td></tr>
+ * <tr><td>{@link org.apache.commons.math3.ode.nonstiff.GillIntegrator Gill}</td><td>4</td></tr>
+ * <tr><td>{@link org.apache.commons.math3.ode.nonstiff.ThreeEighthesIntegrator 3/8}</td><td>4</td></tr>
+ * <tr><td>{@link org.apache.commons.math3.ode.nonstiff.LutherIntegrator Luther}</td><td>6</td></tr>
+ * </table>
+ *
+ * <table border="1" align="center">
+ * <tr BGCOLOR="#CCCCFF"><td colspan=3><font size="+2">Adaptive Stepsize Integrators</font></td></tr>
+ * <tr BGCOLOR="#EEEEFF"><font size="+1"><td>Name</td><td>Integration Order</td><td>Error Estimation Order</td></font></tr>
+ * <tr><td>{@link org.apache.commons.math3.ode.nonstiff.HighamHall54Integrator Higham and Hall}</td><td>5</td><td>4</td></tr>
+ * <tr><td>{@link org.apache.commons.math3.ode.nonstiff.DormandPrince54Integrator Dormand-Prince 5(4)}</td><td>5</td><td>4</td></tr>
+ * <tr><td>{@link org.apache.commons.math3.ode.nonstiff.DormandPrince853Integrator Dormand-Prince 8(5,3)}</td><td>8</td><td>5 and 3</td></tr>
+ * <tr><td>{@link org.apache.commons.math3.ode.nonstiff.GraggBulirschStoerIntegrator Gragg-Bulirsch-Stoer}</td><td>variable (up to 18 by default)</td><td>variable</td></tr>
+ * <tr><td>{@link org.apache.commons.math3.ode.nonstiff.AdamsBashforthIntegrator Adams-Bashforth}</td><td>variable</td><td>variable</td></tr>
+ * <tr><td>{@link org.apache.commons.math3.ode.nonstiff.AdamsMoultonIntegrator Adams-Moulton}</td><td>variable</td><td>variable</td></tr>
+ * </table>
+ *
+ * <p>In the table above, the {@link org.apache.commons.math3.ode.nonstiff.AdamsBashforthIntegrator
+ * Adams-Bashforth} and {@link org.apache.commons.math3.ode.nonstiff.AdamsMoultonIntegrator
+ * Adams-Moulton} integrators appear as variable-step ones. This is an experimental extension to the
+ * classical algorithms using the Nordsieck vector representation.
+ */
+package org.apache.commons.math3.ode;
diff --git a/src/main/java/org/apache/commons/math3/ode/sampling/AbstractFieldStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/sampling/AbstractFieldStepInterpolator.java
new file mode 100644
index 0000000..e674752
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/sampling/AbstractFieldStepInterpolator.java
@@ -0,0 +1,171 @@
+/*
+ * 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.math3.ode.sampling;
+
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.ode.FieldEquationsMapper;
+import org.apache.commons.math3.ode.FieldODEStateAndDerivative;
+
+/** This abstract class represents an interpolator over the last step
+ * during an ODE integration.
+ *
+ * <p>The various ODE integrators provide objects extending this class
+ * to the step handlers. The handlers can use these objects to
+ * retrieve the state vector at intermediate times between the
+ * previous and the current grid points (dense output).</p>
+ *
+ * @see org.apache.commons.math3.ode.FirstOrderFieldIntegrator
+ * @see StepHandler
+ *
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+
+public abstract class AbstractFieldStepInterpolator<T extends RealFieldElement<T>>
+    implements FieldStepInterpolator<T> {
+
+    /** Global previous state. */
+    private final FieldODEStateAndDerivative<T> globalPreviousState;
+
+    /** Global current state. */
+    private final FieldODEStateAndDerivative<T> globalCurrentState;
+
+    /** Soft previous state. */
+    private final FieldODEStateAndDerivative<T> softPreviousState;
+
+    /** Soft current state. */
+    private final FieldODEStateAndDerivative<T> softCurrentState;
+
+    /** integration direction. */
+    private final boolean forward;
+
+    /** Mapper for ODE equations primary and secondary components. */
+    private FieldEquationsMapper<T> mapper;
+
+    /** Simple constructor.
+     * @param isForward integration direction indicator
+     * @param globalPreviousState start of the global step
+     * @param globalCurrentState end of the global step
+     * @param softPreviousState start of the restricted step
+     * @param softCurrentState end of the restricted step
+     * @param equationsMapper mapper for ODE equations primary and secondary components
+     */
+    protected AbstractFieldStepInterpolator(final boolean isForward,
+                                            final FieldODEStateAndDerivative<T> globalPreviousState,
+                                            final FieldODEStateAndDerivative<T> globalCurrentState,
+                                            final FieldODEStateAndDerivative<T> softPreviousState,
+                                            final FieldODEStateAndDerivative<T> softCurrentState,
+                                            final FieldEquationsMapper<T> equationsMapper) {
+        this.forward             = isForward;
+        this.globalPreviousState = globalPreviousState;
+        this.globalCurrentState  = globalCurrentState;
+        this.softPreviousState   = softPreviousState;
+        this.softCurrentState    = softCurrentState;
+        this.mapper              = equationsMapper;
+    }
+
+    /** Create a new restricted version of the instance.
+     * <p>
+     * The instance is not changed at all.
+     * </p>
+     * @param previousState start of the restricted step
+     * @param currentState end of the restricted step
+     * @return restricted version of the instance
+     * @see #getPreviousState()
+     * @see #getCurrentState()
+     */
+    public AbstractFieldStepInterpolator<T> restrictStep(final FieldODEStateAndDerivative<T> previousState,
+                                                         final FieldODEStateAndDerivative<T> currentState) {
+        return create(forward, globalPreviousState, globalCurrentState, previousState, currentState, mapper);
+    }
+
+    /** Create a new instance.
+     * @param newForward integration direction indicator
+     * @param newGlobalPreviousState start of the global step
+     * @param newGlobalCurrentState end of the global step
+     * @param newSoftPreviousState start of the restricted step
+     * @param newSoftCurrentState end of the restricted step
+     * @param newMapper equations mapper for the all equations
+     * @return a new instance
+     */
+    protected abstract AbstractFieldStepInterpolator<T> create(boolean newForward,
+                                                               FieldODEStateAndDerivative<T> newGlobalPreviousState,
+                                                               FieldODEStateAndDerivative<T> newGlobalCurrentState,
+                                                               FieldODEStateAndDerivative<T> newSoftPreviousState,
+                                                               FieldODEStateAndDerivative<T> newSoftCurrentState,
+                                                               FieldEquationsMapper<T> newMapper);
+
+    /**
+     * Get the previous global grid point state.
+     * @return previous global grid point state
+     */
+    public FieldODEStateAndDerivative<T> getGlobalPreviousState() {
+        return globalPreviousState;
+    }
+
+    /**
+     * Get the current global grid point state.
+     * @return current global grid point state
+     */
+    public FieldODEStateAndDerivative<T> getGlobalCurrentState() {
+        return globalCurrentState;
+    }
+
+    /** {@inheritDoc} */
+    public FieldODEStateAndDerivative<T> getPreviousState() {
+        return softPreviousState;
+    }
+
+    /** {@inheritDoc} */
+    public FieldODEStateAndDerivative<T> getCurrentState() {
+        return softCurrentState;
+    }
+
+    /** {@inheritDoc} */
+    public FieldODEStateAndDerivative<T> getInterpolatedState(final T time) {
+        final T thetaH         = time.subtract(globalPreviousState.getTime());
+        final T oneMinusThetaH = globalCurrentState.getTime().subtract(time);
+        final T theta          = thetaH.divide(globalCurrentState.getTime().subtract(globalPreviousState.getTime()));
+        return computeInterpolatedStateAndDerivatives(mapper, time, theta, thetaH, oneMinusThetaH);
+    }
+
+    /** {@inheritDoc} */
+    public boolean isForward() {
+        return forward;
+    }
+
+    /** Compute the state and derivatives at the interpolated time.
+     * This is the main processing method that should be implemented by
+     * the derived classes to perform the interpolation.
+     * @param equationsMapper mapper for ODE equations primary and secondary components
+     * @param time interpolation time
+     * @param theta normalized interpolation abscissa within the step
+     * (theta is zero at the previous time step and one at the current time step)
+     * @param thetaH time gap between the previous time and the interpolated time
+     * @param oneMinusThetaH time gap between the interpolated time and
+     * the current time
+     * @return interpolated state and derivatives
+     * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+     */
+    protected abstract FieldODEStateAndDerivative<T> computeInterpolatedStateAndDerivatives(FieldEquationsMapper<T> equationsMapper,
+                                                                                            T time, T theta,
+                                                                                            T thetaH, T oneMinusThetaH)
+        throws MaxCountExceededException;
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/sampling/AbstractStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/sampling/AbstractStepInterpolator.java
new file mode 100644
index 0000000..1fbc04a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/sampling/AbstractStepInterpolator.java
@@ -0,0 +1,605 @@
+/*
+ * 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.math3.ode.sampling;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.ode.EquationsMapper;
+
+/** This abstract class represents an interpolator over the last step
+ * during an ODE integration.
+ *
+ * <p>The various ODE integrators provide objects extending this class
+ * to the step handlers. The handlers can use these objects to
+ * retrieve the state vector at intermediate times between the
+ * previous and the current grid points (dense output).</p>
+ *
+ * @see org.apache.commons.math3.ode.FirstOrderIntegrator
+ * @see org.apache.commons.math3.ode.SecondOrderIntegrator
+ * @see StepHandler
+ *
+ * @since 1.2
+ *
+ */
+
+public abstract class AbstractStepInterpolator
+  implements StepInterpolator {
+
+  /** current time step */
+  protected double h;
+
+  /** current state */
+  protected double[] currentState;
+
+  /** interpolated time */
+  protected double interpolatedTime;
+
+  /** interpolated state */
+  protected double[] interpolatedState;
+
+  /** interpolated derivatives */
+  protected double[] interpolatedDerivatives;
+
+  /** interpolated primary state */
+  protected double[] interpolatedPrimaryState;
+
+  /** interpolated primary derivatives */
+  protected double[] interpolatedPrimaryDerivatives;
+
+  /** interpolated secondary state */
+  protected double[][] interpolatedSecondaryState;
+
+  /** interpolated secondary derivatives */
+  protected double[][] interpolatedSecondaryDerivatives;
+
+  /** global previous time */
+  private double globalPreviousTime;
+
+  /** global current time */
+  private double globalCurrentTime;
+
+  /** soft previous time */
+  private double softPreviousTime;
+
+  /** soft current time */
+  private double softCurrentTime;
+
+  /** indicate if the step has been finalized or not. */
+  private boolean finalized;
+
+  /** integration direction. */
+  private boolean forward;
+
+  /** indicator for dirty state. */
+  private boolean dirtyState;
+
+  /** Equations mapper for the primary equations set. */
+  private EquationsMapper primaryMapper;
+
+  /** Equations mappers for the secondary equations sets. */
+  private EquationsMapper[] secondaryMappers;
+
+  /** Simple constructor.
+   * This constructor builds an instance that is not usable yet, the
+   * {@link #reinitialize} method should be called before using the
+   * instance in order to initialize the internal arrays. This
+   * constructor is used only in order to delay the initialization in
+   * some cases. As an example, the {@link
+   * org.apache.commons.math3.ode.nonstiff.EmbeddedRungeKuttaIntegrator}
+   * class uses the prototyping design pattern to create the step
+   * interpolators by cloning an uninitialized model and latter
+   * initializing the copy.
+   */
+  protected AbstractStepInterpolator() {
+    globalPreviousTime = Double.NaN;
+    globalCurrentTime  = Double.NaN;
+    softPreviousTime   = Double.NaN;
+    softCurrentTime    = Double.NaN;
+    h                  = Double.NaN;
+    interpolatedTime   = Double.NaN;
+    currentState       = null;
+    finalized          = false;
+    this.forward       = true;
+    this.dirtyState    = true;
+    primaryMapper      = null;
+    secondaryMappers   = null;
+    allocateInterpolatedArrays(-1);
+  }
+
+  /** Simple constructor.
+   * @param y reference to the integrator array holding the state at
+   * the end of the step
+   * @param forward integration direction indicator
+   * @param primaryMapper equations mapper for the primary equations set
+   * @param secondaryMappers equations mappers for the secondary equations sets
+   */
+  protected AbstractStepInterpolator(final double[] y, final boolean forward,
+                                     final EquationsMapper primaryMapper,
+                                     final EquationsMapper[] secondaryMappers) {
+
+    globalPreviousTime    = Double.NaN;
+    globalCurrentTime     = Double.NaN;
+    softPreviousTime      = Double.NaN;
+    softCurrentTime       = Double.NaN;
+    h                     = Double.NaN;
+    interpolatedTime      = Double.NaN;
+    currentState          = y;
+    finalized             = false;
+    this.forward          = forward;
+    this.dirtyState       = true;
+    this.primaryMapper    = primaryMapper;
+    this.secondaryMappers = (secondaryMappers == null) ? null : secondaryMappers.clone();
+    allocateInterpolatedArrays(y.length);
+
+  }
+
+  /** Copy constructor.
+
+   * <p>The copied interpolator should have been finalized before the
+   * copy, otherwise the copy will not be able to perform correctly
+   * any derivative computation and will throw a {@link
+   * NullPointerException} later. Since we don't want this constructor
+   * to throw the exceptions finalization may involve and since we
+   * don't want this method to modify the state of the copied
+   * interpolator, finalization is <strong>not</strong> done
+   * automatically, it remains under user control.</p>
+
+   * <p>The copy is a deep copy: its arrays are separated from the
+   * original arrays of the instance.</p>
+
+   * @param interpolator interpolator to copy from.
+
+   */
+  protected AbstractStepInterpolator(final AbstractStepInterpolator interpolator) {
+
+    globalPreviousTime = interpolator.globalPreviousTime;
+    globalCurrentTime  = interpolator.globalCurrentTime;
+    softPreviousTime   = interpolator.softPreviousTime;
+    softCurrentTime    = interpolator.softCurrentTime;
+    h                  = interpolator.h;
+    interpolatedTime   = interpolator.interpolatedTime;
+
+    if (interpolator.currentState == null) {
+        currentState     = null;
+        primaryMapper    = null;
+        secondaryMappers = null;
+        allocateInterpolatedArrays(-1);
+    } else {
+      currentState                     = interpolator.currentState.clone();
+      interpolatedState                = interpolator.interpolatedState.clone();
+      interpolatedDerivatives          = interpolator.interpolatedDerivatives.clone();
+      interpolatedPrimaryState         = interpolator.interpolatedPrimaryState.clone();
+      interpolatedPrimaryDerivatives   = interpolator.interpolatedPrimaryDerivatives.clone();
+      interpolatedSecondaryState       = new double[interpolator.interpolatedSecondaryState.length][];
+      interpolatedSecondaryDerivatives = new double[interpolator.interpolatedSecondaryDerivatives.length][];
+      for (int i = 0; i < interpolatedSecondaryState.length; ++i) {
+          interpolatedSecondaryState[i]       = interpolator.interpolatedSecondaryState[i].clone();
+          interpolatedSecondaryDerivatives[i] = interpolator.interpolatedSecondaryDerivatives[i].clone();
+      }
+    }
+
+    finalized        = interpolator.finalized;
+    forward          = interpolator.forward;
+    dirtyState       = interpolator.dirtyState;
+    primaryMapper    = interpolator.primaryMapper;
+    secondaryMappers = (interpolator.secondaryMappers == null) ?
+                       null : interpolator.secondaryMappers.clone();
+
+  }
+
+  /** Allocate the various interpolated states arrays.
+   * @param dimension total dimension (negative if arrays should be set to null)
+   */
+  private void allocateInterpolatedArrays(final int dimension) {
+      if (dimension < 0) {
+          interpolatedState                = null;
+          interpolatedDerivatives          = null;
+          interpolatedPrimaryState         = null;
+          interpolatedPrimaryDerivatives   = null;
+          interpolatedSecondaryState       = null;
+          interpolatedSecondaryDerivatives = null;
+      } else {
+          interpolatedState                = new double[dimension];
+          interpolatedDerivatives          = new double[dimension];
+          interpolatedPrimaryState         = new double[primaryMapper.getDimension()];
+          interpolatedPrimaryDerivatives   = new double[primaryMapper.getDimension()];
+          if (secondaryMappers == null) {
+              interpolatedSecondaryState       = null;
+              interpolatedSecondaryDerivatives = null;
+          } else {
+              interpolatedSecondaryState       = new double[secondaryMappers.length][];
+              interpolatedSecondaryDerivatives = new double[secondaryMappers.length][];
+              for (int i = 0; i < secondaryMappers.length; ++i) {
+                  interpolatedSecondaryState[i]       = new double[secondaryMappers[i].getDimension()];
+                  interpolatedSecondaryDerivatives[i] = new double[secondaryMappers[i].getDimension()];
+              }
+          }
+      }
+  }
+
+  /** Reinitialize the instance
+   * @param y reference to the integrator array holding the state at the end of the step
+   * @param isForward integration direction indicator
+   * @param primary equations mapper for the primary equations set
+   * @param secondary equations mappers for the secondary equations sets
+   */
+  protected void reinitialize(final double[] y, final boolean isForward,
+                              final EquationsMapper primary,
+                              final EquationsMapper[] secondary) {
+
+    globalPreviousTime    = Double.NaN;
+    globalCurrentTime     = Double.NaN;
+    softPreviousTime      = Double.NaN;
+    softCurrentTime       = Double.NaN;
+    h                     = Double.NaN;
+    interpolatedTime      = Double.NaN;
+    currentState          = y;
+    finalized             = false;
+    this.forward          = isForward;
+    this.dirtyState       = true;
+    this.primaryMapper    = primary;
+    this.secondaryMappers = secondary.clone();
+    allocateInterpolatedArrays(y.length);
+
+  }
+
+  /** {@inheritDoc} */
+   public StepInterpolator copy() throws MaxCountExceededException {
+
+     // finalize the step before performing copy
+     finalizeStep();
+
+     // create the new independent instance
+     return doCopy();
+
+   }
+
+   /** Really copy the finalized instance.
+    * <p>This method is called by {@link #copy()} after the
+    * step has been finalized. It must perform a deep copy
+    * to have an new instance completely independent for the
+    * original instance.
+    * @return a copy of the finalized instance
+    */
+   protected abstract StepInterpolator doCopy();
+
+  /** Shift one step forward.
+   * Copy the current time into the previous time, hence preparing the
+   * interpolator for future calls to {@link #storeTime storeTime}
+   */
+  public void shift() {
+    globalPreviousTime = globalCurrentTime;
+    softPreviousTime   = globalPreviousTime;
+    softCurrentTime    = globalCurrentTime;
+  }
+
+  /** Store the current step time.
+   * @param t current time
+   */
+  public void storeTime(final double t) {
+
+    globalCurrentTime = t;
+    softCurrentTime   = globalCurrentTime;
+    h                 = globalCurrentTime - globalPreviousTime;
+    setInterpolatedTime(t);
+
+    // the step is not finalized anymore
+    finalized  = false;
+
+  }
+
+  /** Restrict step range to a limited part of the global step.
+   * <p>
+   * This method can be used to restrict a step and make it appear
+   * as if the original step was smaller. Calling this method
+   * <em>only</em> changes the value returned by {@link #getPreviousTime()},
+   * it does not change any other property
+   * </p>
+   * @param softPreviousTime start of the restricted step
+   * @since 2.2
+   */
+  public void setSoftPreviousTime(final double softPreviousTime) {
+      this.softPreviousTime = softPreviousTime;
+  }
+
+  /** Restrict step range to a limited part of the global step.
+   * <p>
+   * This method can be used to restrict a step and make it appear
+   * as if the original step was smaller. Calling this method
+   * <em>only</em> changes the value returned by {@link #getCurrentTime()},
+   * it does not change any other property
+   * </p>
+   * @param softCurrentTime end of the restricted step
+   * @since 2.2
+   */
+  public void setSoftCurrentTime(final double softCurrentTime) {
+      this.softCurrentTime  = softCurrentTime;
+  }
+
+  /**
+   * Get the previous global grid point time.
+   * @return previous global grid point time
+   */
+  public double getGlobalPreviousTime() {
+    return globalPreviousTime;
+  }
+
+  /**
+   * Get the current global grid point time.
+   * @return current global grid point time
+   */
+  public double getGlobalCurrentTime() {
+    return globalCurrentTime;
+  }
+
+  /**
+   * Get the previous soft grid point time.
+   * @return previous soft grid point time
+   * @see #setSoftPreviousTime(double)
+   */
+  public double getPreviousTime() {
+    return softPreviousTime;
+  }
+
+  /**
+   * Get the current soft grid point time.
+   * @return current soft grid point time
+   * @see #setSoftCurrentTime(double)
+   */
+  public double getCurrentTime() {
+    return softCurrentTime;
+  }
+
+  /** {@inheritDoc} */
+  public double getInterpolatedTime() {
+    return interpolatedTime;
+  }
+
+  /** {@inheritDoc} */
+  public void setInterpolatedTime(final double time) {
+      interpolatedTime = time;
+      dirtyState       = true;
+  }
+
+  /** {@inheritDoc} */
+  public boolean isForward() {
+    return forward;
+  }
+
+  /** Compute the state and derivatives at the interpolated time.
+   * This is the main processing method that should be implemented by
+   * the derived classes to perform the interpolation.
+   * @param theta normalized interpolation abscissa within the step
+   * (theta is zero at the previous time step and one at the current time step)
+   * @param oneMinusThetaH time gap between the interpolated time and
+   * the current time
+   * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+   */
+  protected abstract void computeInterpolatedStateAndDerivatives(double theta,
+                                                                 double oneMinusThetaH)
+      throws MaxCountExceededException;
+
+  /** Lazy evaluation of complete interpolated state.
+   * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+   */
+  private void evaluateCompleteInterpolatedState()
+      throws MaxCountExceededException {
+      // lazy evaluation of the state
+      if (dirtyState) {
+          final double oneMinusThetaH = globalCurrentTime - interpolatedTime;
+          final double theta = (h == 0) ? 0 : (h - oneMinusThetaH) / h;
+          computeInterpolatedStateAndDerivatives(theta, oneMinusThetaH);
+          dirtyState = false;
+      }
+  }
+
+  /** {@inheritDoc} */
+  public double[] getInterpolatedState() throws MaxCountExceededException {
+      evaluateCompleteInterpolatedState();
+      primaryMapper.extractEquationData(interpolatedState,
+                                        interpolatedPrimaryState);
+      return interpolatedPrimaryState;
+  }
+
+  /** {@inheritDoc} */
+  public double[] getInterpolatedDerivatives() throws MaxCountExceededException {
+      evaluateCompleteInterpolatedState();
+      primaryMapper.extractEquationData(interpolatedDerivatives,
+                                        interpolatedPrimaryDerivatives);
+      return interpolatedPrimaryDerivatives;
+  }
+
+  /** {@inheritDoc} */
+  public double[] getInterpolatedSecondaryState(final int index) throws MaxCountExceededException {
+      evaluateCompleteInterpolatedState();
+      secondaryMappers[index].extractEquationData(interpolatedState,
+                                                  interpolatedSecondaryState[index]);
+      return interpolatedSecondaryState[index];
+  }
+
+  /** {@inheritDoc} */
+  public double[] getInterpolatedSecondaryDerivatives(final int index) throws MaxCountExceededException {
+      evaluateCompleteInterpolatedState();
+      secondaryMappers[index].extractEquationData(interpolatedDerivatives,
+                                                  interpolatedSecondaryDerivatives[index]);
+      return interpolatedSecondaryDerivatives[index];
+  }
+
+  /**
+   * Finalize the step.
+
+   * <p>Some embedded Runge-Kutta integrators need fewer functions
+   * evaluations than their counterpart step interpolators. These
+   * interpolators should perform the last evaluations they need by
+   * themselves only if they need them. This method triggers these
+   * extra evaluations. It can be called directly by the user step
+   * handler and it is called automatically if {@link
+   * #setInterpolatedTime} is called.</p>
+
+   * <p>Once this method has been called, <strong>no</strong> other
+   * evaluation will be performed on this step. If there is a need to
+   * have some side effects between the step handler and the
+   * differential equations (for example update some data in the
+   * equations once the step has been done), it is advised to call
+   * this method explicitly from the step handler before these side
+   * effects are set up. If the step handler induces no side effect,
+   * then this method can safely be ignored, it will be called
+   * transparently as needed.</p>
+
+   * <p><strong>Warning</strong>: since the step interpolator provided
+   * to the step handler as a parameter of the {@link
+   * StepHandler#handleStep handleStep} is valid only for the duration
+   * of the {@link StepHandler#handleStep handleStep} call, one cannot
+   * simply store a reference and reuse it later. One should first
+   * finalize the instance, then copy this finalized instance into a
+   * new object that can be kept.</p>
+
+   * <p>This method calls the protected <code>doFinalize</code> method
+   * if it has never been called during this step and set a flag
+   * indicating that it has been called once. It is the <code>
+   * doFinalize</code> method which should perform the evaluations.
+   * This wrapping prevents from calling <code>doFinalize</code> several
+   * times and hence evaluating the differential equations too often.
+   * Therefore, subclasses are not allowed not reimplement it, they
+   * should rather reimplement <code>doFinalize</code>.</p>
+
+   * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+
+   */
+  public final void finalizeStep() throws MaxCountExceededException {
+    if (! finalized) {
+      doFinalize();
+      finalized = true;
+    }
+  }
+
+  /**
+   * Really finalize the step.
+   * The default implementation of this method does nothing.
+   * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+   */
+  protected void doFinalize() throws MaxCountExceededException {
+  }
+
+  /** {@inheritDoc} */
+  public abstract void writeExternal(ObjectOutput out)
+    throws IOException;
+
+  /** {@inheritDoc} */
+  public abstract void readExternal(ObjectInput in)
+    throws IOException, ClassNotFoundException;
+
+  /** Save the base state of the instance.
+   * This method performs step finalization if it has not been done
+   * before.
+   * @param out stream where to save the state
+   * @exception IOException in case of write error
+   */
+  protected void writeBaseExternal(final ObjectOutput out)
+    throws IOException {
+
+    if (currentState == null) {
+        out.writeInt(-1);
+    } else {
+        out.writeInt(currentState.length);
+    }
+    out.writeDouble(globalPreviousTime);
+    out.writeDouble(globalCurrentTime);
+    out.writeDouble(softPreviousTime);
+    out.writeDouble(softCurrentTime);
+    out.writeDouble(h);
+    out.writeBoolean(forward);
+    out.writeObject(primaryMapper);
+    out.write(secondaryMappers.length);
+    for (final EquationsMapper  mapper : secondaryMappers) {
+        out.writeObject(mapper);
+    }
+
+    if (currentState != null) {
+        for (int i = 0; i < currentState.length; ++i) {
+            out.writeDouble(currentState[i]);
+        }
+    }
+
+    out.writeDouble(interpolatedTime);
+
+    // we do not store the interpolated state,
+    // it will be recomputed as needed after reading
+
+    try {
+        // finalize the step (and don't bother saving the now true flag)
+        finalizeStep();
+    } catch (MaxCountExceededException mcee) {
+        final IOException ioe = new IOException(mcee.getLocalizedMessage());
+        ioe.initCause(mcee);
+        throw ioe;
+    }
+
+  }
+
+  /** Read the base state of the instance.
+   * This method does <strong>neither</strong> set the interpolated
+   * time nor state. It is up to the derived class to reset it
+   * properly calling the {@link #setInterpolatedTime} method later,
+   * once all rest of the object state has been set up properly.
+   * @param in stream where to read the state from
+   * @return interpolated time to be set later by the caller
+   * @exception IOException in case of read error
+   * @exception ClassNotFoundException if an equation mapper class
+   * cannot be found
+   */
+  protected double readBaseExternal(final ObjectInput in)
+    throws IOException, ClassNotFoundException {
+
+    final int dimension = in.readInt();
+    globalPreviousTime  = in.readDouble();
+    globalCurrentTime   = in.readDouble();
+    softPreviousTime    = in.readDouble();
+    softCurrentTime     = in.readDouble();
+    h                   = in.readDouble();
+    forward             = in.readBoolean();
+    primaryMapper       = (EquationsMapper) in.readObject();
+    secondaryMappers    = new EquationsMapper[in.read()];
+    for (int i = 0; i < secondaryMappers.length; ++i) {
+        secondaryMappers[i] = (EquationsMapper) in.readObject();
+    }
+    dirtyState          = true;
+
+    if (dimension < 0) {
+        currentState = null;
+    } else {
+        currentState  = new double[dimension];
+        for (int i = 0; i < currentState.length; ++i) {
+            currentState[i] = in.readDouble();
+        }
+    }
+
+    // we do NOT handle the interpolated time and state here
+    interpolatedTime = Double.NaN;
+    allocateInterpolatedArrays(dimension);
+
+    finalized = true;
+
+    return in.readDouble();
+
+  }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/sampling/DummyStepHandler.java b/src/main/java/org/apache/commons/math3/ode/sampling/DummyStepHandler.java
new file mode 100644
index 0000000..c1d4fe3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/sampling/DummyStepHandler.java
@@ -0,0 +1,89 @@
+/*
+ * 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.math3.ode.sampling;
+
+/**
+ * This class is a step handler that does nothing.
+
+ * <p>This class is provided as a convenience for users who are only
+ * interested in the final state of an integration and not in the
+ * intermediate steps. Its handleStep method does nothing.</p>
+ *
+ * <p>Since this class has no internal state, it is implemented using
+ * the Singleton design pattern. This means that only one instance is
+ * ever created, which can be retrieved using the getInstance
+ * method. This explains why there is no public constructor.</p>
+ *
+ * @see StepHandler
+ * @since 1.2
+ */
+
+public class DummyStepHandler implements StepHandler {
+
+    /** Private constructor.
+     * The constructor is private to prevent users from creating
+     * instances (Singleton design-pattern).
+     */
+    private DummyStepHandler() {
+    }
+
+    /** Get the only instance.
+     * @return the only instance
+     */
+    public static DummyStepHandler getInstance() {
+        return LazyHolder.INSTANCE;
+    }
+
+    /** {@inheritDoc} */
+    public void init(double t0, double[] y0, double t) {
+    }
+
+    /**
+     * Handle the last accepted step.
+     * This method does nothing in this class.
+     * @param interpolator interpolator for the last accepted step. For
+     * efficiency purposes, the various integrators reuse the same
+     * object on each call, so if the instance wants to keep it across
+     * all calls (for example to provide at the end of the integration a
+     * continuous model valid throughout the integration range), it
+     * should build a local copy using the clone method and store this
+     * copy.
+     * @param isLast true if the step is the last one
+     */
+    public void handleStep(final StepInterpolator interpolator, final boolean isLast) {
+    }
+
+    // CHECKSTYLE: stop HideUtilityClassConstructor
+    /** Holder for the instance.
+     * <p>We use here the Initialization On Demand Holder Idiom.</p>
+     */
+    private static class LazyHolder {
+        /** Cached field instance. */
+        private static final DummyStepHandler INSTANCE = new DummyStepHandler();
+    }
+    // CHECKSTYLE: resume HideUtilityClassConstructor
+
+    /** Handle deserialization of the singleton.
+     * @return the singleton instance
+     */
+    private Object readResolve() {
+        // return the singleton instance
+        return LazyHolder.INSTANCE;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/sampling/FieldFixedStepHandler.java b/src/main/java/org/apache/commons/math3/ode/sampling/FieldFixedStepHandler.java
new file mode 100644
index 0000000..9d4fd70
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/sampling/FieldFixedStepHandler.java
@@ -0,0 +1,69 @@
+/*
+ * 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.math3.ode.sampling;
+
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.ode.FieldODEStateAndDerivative;
+
+/**
+ * This interface represents a handler that should be called after
+ * each successful fixed step.
+
+ * <p>This interface should be implemented by anyone who is interested
+ * in getting the solution of an ordinary differential equation at
+ * fixed time steps. Objects implementing this interface should be
+ * wrapped within an instance of {@link FieldStepNormalizer} that itself
+ * is used as the general {@link FieldStepHandler} by the integrator. The
+ * {@link FieldStepNormalizer} object is called according to the integrator
+ * internal algorithms and it calls objects implementing this
+ * interface as necessary at fixed time steps.</p>
+ *
+ * @see FieldStepHandler
+ * @see FieldStepNormalizer
+ * @see FieldStepInterpolator
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+
+public interface FieldFixedStepHandler<T extends RealFieldElement<T>> {
+
+    /** Initialize step handler at the start of an ODE integration.
+     * <p>
+     * This method is called once at the start of the integration. It
+     * may be used by the step handler to initialize some internal data
+     * if needed.
+     * </p>
+     * @param initialState initial time, state vector and derivative
+     * @param finalTime target time for the integration
+     */
+    void init(FieldODEStateAndDerivative<T> initialState, T finalTime);
+
+    /**
+     * Handle the last accepted step
+     * @param state current value of the independent <i>time</i> variable,
+     * state vector and derivative
+     * For efficiency purposes, the {@link FieldStepNormalizer} class reuses
+     * the same array on each call, so if
+     * the instance wants to keep it across all calls (for example to
+     * provide at the end of the integration a complete array of all
+     * steps), it should build a local copy store this copy.
+     * @param isLast true if the step is the last one
+     */
+    void handleStep(FieldODEStateAndDerivative<T> state, boolean isLast);
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/sampling/FieldStepHandler.java b/src/main/java/org/apache/commons/math3/ode/sampling/FieldStepHandler.java
new file mode 100644
index 0000000..c56911b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/sampling/FieldStepHandler.java
@@ -0,0 +1,75 @@
+/*
+ * 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.math3.ode.sampling;
+
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.ode.FieldODEStateAndDerivative;
+
+/**
+ * This interface represents a handler that should be called after
+ * each successful step.
+ *
+ * <p>The ODE integrators compute the evolution of the state vector at
+ * some grid points that depend on their own internal algorithm. Once
+ * they have found a new grid point (possibly after having computed
+ * several evaluation of the derivative at intermediate points), they
+ * provide it to objects implementing this interface. These objects
+ * typically either ignore the intermediate steps and wait for the
+ * last one, store the points in an ephemeris, or forward them to
+ * specialized processing or output methods.</p>
+ *
+ * @see org.apache.commons.math3.ode.FirstOrderFieldIntegrator
+ * @see FieldStepInterpolator
+ * @param <T> the type of the field elements
+ * @since 3.6
+ */
+
+public interface FieldStepHandler<T extends RealFieldElement<T>> {
+
+    /** Initialize step handler at the start of an ODE integration.
+     * <p>
+     * This method is called once at the start of the integration. It
+     * may be used by the step handler to initialize some internal data
+     * if needed.
+     * </p>
+     * @param initialState initial time, state vector and derivative
+     * @param finalTime target time for the integration
+     */
+    void init(FieldODEStateAndDerivative<T> initialState, T finalTime);
+
+    /**
+     * Handle the last accepted step
+     * @param interpolator interpolator for the last accepted step. For
+     * efficiency purposes, the various integrators reuse the same
+     * object on each call, so if the instance wants to keep it across
+     * all calls (for example to provide at the end of the integration a
+     * continuous model valid throughout the integration range, as the
+     * {@link org.apache.commons.math3.ode.ContinuousOutputModel
+     * ContinuousOutputModel} class does), it should build a local copy
+     * using the clone method of the interpolator and store this copy.
+     * Keeping only a reference to the interpolator and reusing it will
+     * result in unpredictable behavior (potentially crashing the application).
+     * @param isLast true if the step is the last one
+     * @exception MaxCountExceededException if the interpolator throws one because
+     * the number of functions evaluations is exceeded
+     */
+    void handleStep(FieldStepInterpolator<T> interpolator, boolean isLast)
+        throws MaxCountExceededException;
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/sampling/FieldStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/sampling/FieldStepInterpolator.java
new file mode 100644
index 0000000..a005fb1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/sampling/FieldStepInterpolator.java
@@ -0,0 +1,76 @@
+/*
+ * 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.math3.ode.sampling;
+
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.ode.FieldODEStateAndDerivative;
+
+/** This interface represents an interpolator over the last step
+ * during an ODE integration.
+ *
+ * <p>The various ODE integrators provide objects implementing this
+ * interface to the step handlers. These objects are often custom
+ * objects tightly bound to the integrator internal algorithms. The
+ * handlers can use these objects to retrieve the state vector at
+ * intermediate times between the previous and the current grid points
+ * (this feature is often called dense output).</p>
+ *
+ * @param <T> the type of the field elements
+ * @see org.apache.commons.math3.ode.FirstOrderFieldIntegrator
+ * @see FieldStepHandler
+ * @since 3.6
+ */
+
+public interface FieldStepInterpolator<T extends RealFieldElement<T>> {
+
+  /**
+   * Get the state at previous grid point time.
+   * @return state at previous grid point time
+   */
+  FieldODEStateAndDerivative<T> getPreviousState();
+
+  /**
+   * Get the state at current grid point time.
+   * @return state at current grid point time
+   */
+  FieldODEStateAndDerivative<T> getCurrentState();
+
+  /**
+   * Get the state at interpolated time.
+   * <p>Setting the time outside of the current step is allowed, but
+   * should be used with care since the accuracy of the interpolator will
+   * probably be very poor far from this step. This allowance has been
+   * added to simplify implementation of search algorithms near the
+   * step endpoints.</p>
+   * @param time time of the interpolated point
+   * @return state at interpolated time
+   */
+  FieldODEStateAndDerivative<T> getInterpolatedState(T time);
+
+  /** Check if the natural integration direction is forward.
+   * <p>This method provides the integration direction as specified by
+   * the integrator itself, it avoid some nasty problems in
+   * degenerated cases like null steps due to cancellation at step
+   * initialization, step control or discrete events
+   * triggering.</p>
+   * @return true if the integration variable (time) increases during
+   * integration
+   */
+  boolean isForward();
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/sampling/FieldStepNormalizer.java b/src/main/java/org/apache/commons/math3/ode/sampling/FieldStepNormalizer.java
new file mode 100644
index 0000000..892fcf7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/sampling/FieldStepNormalizer.java
@@ -0,0 +1,273 @@
+/*
+ * 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.math3.ode.sampling;
+
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.ode.FieldODEStateAndDerivative;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.Precision;
+
+/**
+ * This class wraps an object implementing {@link FieldFixedStepHandler}
+ * into a {@link FieldStepHandler}.
+
+ * <p>This wrapper allows to use fixed step handlers with general
+ * integrators which cannot guaranty their integration steps will
+ * remain constant and therefore only accept general step
+ * handlers.</p>
+ *
+ * <p>The stepsize used is selected at construction time. The {@link
+ * FieldFixedStepHandler#handleStep handleStep} method of the underlying
+ * {@link FieldFixedStepHandler} object is called at normalized times. The
+ * normalized times can be influenced by the {@link StepNormalizerMode} and
+ * {@link StepNormalizerBounds}.</p>
+ *
+ * <p>There is no constraint on the integrator, it can use any time step
+ * it needs (time steps longer or shorter than the fixed time step and
+ * non-integer ratios are all allowed).</p>
+ *
+ * <p>
+ * <table border="1" align="center">
+ * <tr BGCOLOR="#CCCCFF"><td colspan=6><font size="+2">Examples (step size = 0.5)</font></td></tr>
+ * <tr BGCOLOR="#EEEEFF"><font size="+1"><td>Start time</td><td>End time</td>
+ *  <td>Direction</td><td>{@link StepNormalizerMode Mode}</td>
+ *  <td>{@link StepNormalizerBounds Bounds}</td><td>Output</td></font></tr>
+ * <tr><td>0.3</td><td>3.1</td><td>forward</td><td>{@link StepNormalizerMode#INCREMENT INCREMENT}</td><td>{@link StepNormalizerBounds#NEITHER NEITHER}</td><td>0.8, 1.3, 1.8, 2.3, 2.8</td></tr>
+ * <tr><td>0.3</td><td>3.1</td><td>forward</td><td>{@link StepNormalizerMode#INCREMENT INCREMENT}</td><td>{@link StepNormalizerBounds#FIRST FIRST}</td><td>0.3, 0.8, 1.3, 1.8, 2.3, 2.8</td></tr>
+ * <tr><td>0.3</td><td>3.1</td><td>forward</td><td>{@link StepNormalizerMode#INCREMENT INCREMENT}</td><td>{@link StepNormalizerBounds#LAST LAST}</td><td>0.8, 1.3, 1.8, 2.3, 2.8, 3.1</td></tr>
+ * <tr><td>0.3</td><td>3.1</td><td>forward</td><td>{@link StepNormalizerMode#INCREMENT INCREMENT}</td><td>{@link StepNormalizerBounds#BOTH BOTH}</td><td>0.3, 0.8, 1.3, 1.8, 2.3, 2.8, 3.1</td></tr>
+ * <tr><td>0.3</td><td>3.1</td><td>forward</td><td>{@link StepNormalizerMode#MULTIPLES MULTIPLES}</td><td>{@link StepNormalizerBounds#NEITHER NEITHER}</td><td>0.5, 1.0, 1.5, 2.0, 2.5, 3.0</td></tr>
+ * <tr><td>0.3</td><td>3.1</td><td>forward</td><td>{@link StepNormalizerMode#MULTIPLES MULTIPLES}</td><td>{@link StepNormalizerBounds#FIRST FIRST}</td><td>0.3, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0</td></tr>
+ * <tr><td>0.3</td><td>3.1</td><td>forward</td><td>{@link StepNormalizerMode#MULTIPLES MULTIPLES}</td><td>{@link StepNormalizerBounds#LAST LAST}</td><td>0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.1</td></tr>
+ * <tr><td>0.3</td><td>3.1</td><td>forward</td><td>{@link StepNormalizerMode#MULTIPLES MULTIPLES}</td><td>{@link StepNormalizerBounds#BOTH BOTH}</td><td>0.3, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.1</td></tr>
+ * <tr><td>0.0</td><td>3.0</td><td>forward</td><td>{@link StepNormalizerMode#INCREMENT INCREMENT}</td><td>{@link StepNormalizerBounds#NEITHER NEITHER}</td><td>0.5, 1.0, 1.5, 2.0, 2.5, 3.0</td></tr>
+ * <tr><td>0.0</td><td>3.0</td><td>forward</td><td>{@link StepNormalizerMode#INCREMENT INCREMENT}</td><td>{@link StepNormalizerBounds#FIRST FIRST}</td><td>0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0</td></tr>
+ * <tr><td>0.0</td><td>3.0</td><td>forward</td><td>{@link StepNormalizerMode#INCREMENT INCREMENT}</td><td>{@link StepNormalizerBounds#LAST LAST}</td><td>0.5, 1.0, 1.5, 2.0, 2.5, 3.0</td></tr>
+ * <tr><td>0.0</td><td>3.0</td><td>forward</td><td>{@link StepNormalizerMode#INCREMENT INCREMENT}</td><td>{@link StepNormalizerBounds#BOTH BOTH}</td><td>0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0</td></tr>
+ * <tr><td>0.0</td><td>3.0</td><td>forward</td><td>{@link StepNormalizerMode#MULTIPLES MULTIPLES}</td><td>{@link StepNormalizerBounds#NEITHER NEITHER}</td><td>0.5, 1.0, 1.5, 2.0, 2.5, 3.0</td></tr>
+ * <tr><td>0.0</td><td>3.0</td><td>forward</td><td>{@link StepNormalizerMode#MULTIPLES MULTIPLES}</td><td>{@link StepNormalizerBounds#FIRST FIRST}</td><td>0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0</td></tr>
+ * <tr><td>0.0</td><td>3.0</td><td>forward</td><td>{@link StepNormalizerMode#MULTIPLES MULTIPLES}</td><td>{@link StepNormalizerBounds#LAST LAST}</td><td>0.5, 1.0, 1.5, 2.0, 2.5, 3.0</td></tr>
+ * <tr><td>0.0</td><td>3.0</td><td>forward</td><td>{@link StepNormalizerMode#MULTIPLES MULTIPLES}</td><td>{@link StepNormalizerBounds#BOTH BOTH}</td><td>0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0</td></tr>
+ * <tr><td>3.1</td><td>0.3</td><td>backward</td><td>{@link StepNormalizerMode#INCREMENT INCREMENT}</td><td>{@link StepNormalizerBounds#NEITHER NEITHER}</td><td>2.6, 2.1, 1.6, 1.1, 0.6</td></tr>
+ * <tr><td>3.1</td><td>0.3</td><td>backward</td><td>{@link StepNormalizerMode#INCREMENT INCREMENT}</td><td>{@link StepNormalizerBounds#FIRST FIRST}</td><td>3.1, 2.6, 2.1, 1.6, 1.1, 0.6</td></tr>
+ * <tr><td>3.1</td><td>0.3</td><td>backward</td><td>{@link StepNormalizerMode#INCREMENT INCREMENT}</td><td>{@link StepNormalizerBounds#LAST LAST}</td><td>2.6, 2.1, 1.6, 1.1, 0.6, 0.3</td></tr>
+ * <tr><td>3.1</td><td>0.3</td><td>backward</td><td>{@link StepNormalizerMode#INCREMENT INCREMENT}</td><td>{@link StepNormalizerBounds#BOTH BOTH}</td><td>3.1, 2.6, 2.1, 1.6, 1.1, 0.6, 0.3</td></tr>
+ * <tr><td>3.1</td><td>0.3</td><td>backward</td><td>{@link StepNormalizerMode#MULTIPLES MULTIPLES}</td><td>{@link StepNormalizerBounds#NEITHER NEITHER}</td><td>3.0, 2.5, 2.0, 1.5, 1.0, 0.5</td></tr>
+ * <tr><td>3.1</td><td>0.3</td><td>backward</td><td>{@link StepNormalizerMode#MULTIPLES MULTIPLES}</td><td>{@link StepNormalizerBounds#FIRST FIRST}</td><td>3.1, 3.0, 2.5, 2.0, 1.5, 1.0, 0.5</td></tr>
+ * <tr><td>3.1</td><td>0.3</td><td>backward</td><td>{@link StepNormalizerMode#MULTIPLES MULTIPLES}</td><td>{@link StepNormalizerBounds#LAST LAST}</td><td>3.0, 2.5, 2.0, 1.5, 1.0, 0.5, 0.3</td></tr>
+ * <tr><td>3.1</td><td>0.3</td><td>backward</td><td>{@link StepNormalizerMode#MULTIPLES MULTIPLES}</td><td>{@link StepNormalizerBounds#BOTH BOTH}</td><td>3.1, 3.0, 2.5, 2.0, 1.5, 1.0, 0.5, 0.3</td></tr>
+ * <tr><td>3.0</td><td>0.0</td><td>backward</td><td>{@link StepNormalizerMode#INCREMENT INCREMENT}</td><td>{@link StepNormalizerBounds#NEITHER NEITHER}</td><td>2.5, 2.0, 1.5, 1.0, 0.5, 0.0</td></tr>
+ * <tr><td>3.0</td><td>0.0</td><td>backward</td><td>{@link StepNormalizerMode#INCREMENT INCREMENT}</td><td>{@link StepNormalizerBounds#FIRST FIRST}</td><td>3.0, 2.5, 2.0, 1.5, 1.0, 0.5, 0.0</td></tr>
+ * <tr><td>3.0</td><td>0.0</td><td>backward</td><td>{@link StepNormalizerMode#INCREMENT INCREMENT}</td><td>{@link StepNormalizerBounds#LAST LAST}</td><td>2.5, 2.0, 1.5, 1.0, 0.5, 0.0</td></tr>
+ * <tr><td>3.0</td><td>0.0</td><td>backward</td><td>{@link StepNormalizerMode#INCREMENT INCREMENT}</td><td>{@link StepNormalizerBounds#BOTH BOTH}</td><td>3.0, 2.5, 2.0, 1.5, 1.0, 0.5, 0.0</td></tr>
+ * <tr><td>3.0</td><td>0.0</td><td>backward</td><td>{@link StepNormalizerMode#MULTIPLES MULTIPLES}</td><td>{@link StepNormalizerBounds#NEITHER NEITHER}</td><td>2.5, 2.0, 1.5, 1.0, 0.5, 0.0</td></tr>
+ * <tr><td>3.0</td><td>0.0</td><td>backward</td><td>{@link StepNormalizerMode#MULTIPLES MULTIPLES}</td><td>{@link StepNormalizerBounds#FIRST FIRST}</td><td>3.0, 2.5, 2.0, 1.5, 1.0, 0.5, 0.0</td></tr>
+ * <tr><td>3.0</td><td>0.0</td><td>backward</td><td>{@link StepNormalizerMode#MULTIPLES MULTIPLES}</td><td>{@link StepNormalizerBounds#LAST LAST}</td><td>2.5, 2.0, 1.5, 1.0, 0.5, 0.0</td></tr>
+ * <tr><td>3.0</td><td>0.0</td><td>backward</td><td>{@link StepNormalizerMode#MULTIPLES MULTIPLES}</td><td>{@link StepNormalizerBounds#BOTH BOTH}</td><td>3.0, 2.5, 2.0, 1.5, 1.0, 0.5, 0.0</td></tr>
+ * </table>
+ * </p>
+ *
+ * @param <T> the type of the field elements
+ * @see FieldStepHandler
+ * @see FieldFixedStepHandler
+ * @see StepNormalizerMode
+ * @see StepNormalizerBounds
+ * @since 3.6
+ */
+
+public class FieldStepNormalizer<T extends RealFieldElement<T>> implements FieldStepHandler<T> {
+
+    /** Fixed time step. */
+    private double h;
+
+    /** Underlying step handler. */
+    private final FieldFixedStepHandler<T> handler;
+
+    /** First step state. */
+    private FieldODEStateAndDerivative<T> first;
+
+    /** Last step step. */
+    private FieldODEStateAndDerivative<T> last;
+
+    /** Integration direction indicator. */
+    private boolean forward;
+
+    /** The step normalizer bounds settings to use. */
+    private final StepNormalizerBounds bounds;
+
+    /** The step normalizer mode to use. */
+    private final StepNormalizerMode mode;
+
+    /** Simple constructor. Uses {@link StepNormalizerMode#INCREMENT INCREMENT}
+     * mode, and {@link StepNormalizerBounds#FIRST FIRST} bounds setting, for
+     * backwards compatibility.
+     * @param h fixed time step (sign is not used)
+     * @param handler fixed time step handler to wrap
+     */
+    public FieldStepNormalizer(final double h, final FieldFixedStepHandler<T> handler) {
+        this(h, handler, StepNormalizerMode.INCREMENT,
+             StepNormalizerBounds.FIRST);
+    }
+
+    /** Simple constructor. Uses {@link StepNormalizerBounds#FIRST FIRST}
+     * bounds setting.
+     * @param h fixed time step (sign is not used)
+     * @param handler fixed time step handler to wrap
+     * @param mode step normalizer mode to use
+     * @since 3.0
+     */
+    public FieldStepNormalizer(final double h, final FieldFixedStepHandler<T> handler,
+                               final StepNormalizerMode mode) {
+        this(h, handler, mode, StepNormalizerBounds.FIRST);
+    }
+
+    /** Simple constructor. Uses {@link StepNormalizerMode#INCREMENT INCREMENT}
+     * mode.
+     * @param h fixed time step (sign is not used)
+     * @param handler fixed time step handler to wrap
+     * @param bounds step normalizer bounds setting to use
+     * @since 3.0
+     */
+    public FieldStepNormalizer(final double h, final FieldFixedStepHandler<T> handler,
+                               final StepNormalizerBounds bounds) {
+        this(h, handler, StepNormalizerMode.INCREMENT, bounds);
+    }
+
+    /** Simple constructor.
+     * @param h fixed time step (sign is not used)
+     * @param handler fixed time step handler to wrap
+     * @param mode step normalizer mode to use
+     * @param bounds step normalizer bounds setting to use
+     * @since 3.0
+     */
+    public FieldStepNormalizer(final double h, final FieldFixedStepHandler<T> handler,
+                               final StepNormalizerMode mode, final StepNormalizerBounds bounds) {
+        this.h       = FastMath.abs(h);
+        this.handler = handler;
+        this.mode    = mode;
+        this.bounds  = bounds;
+        first        = null;
+        last         = null;
+        forward      = true;
+    }
+
+    /** {@inheritDoc} */
+    public void init(final FieldODEStateAndDerivative<T> initialState, final T finalTime) {
+
+        first   = null;
+        last    = null;
+        forward = true;
+
+        // initialize the underlying handler
+        handler.init(initialState, finalTime);
+
+    }
+
+    /**
+     * Handle the last accepted step
+     * @param interpolator interpolator for the last accepted step. For
+     * efficiency purposes, the various integrators reuse the same
+     * object on each call, so if the instance wants to keep it across
+     * all calls (for example to provide at the end of the integration a
+     * continuous model valid throughout the integration range), it
+     * should build a local copy using the clone method and store this
+     * copy.
+     * @param isLast true if the step is the last one
+     * @exception MaxCountExceededException if the interpolator throws one because
+     * the number of functions evaluations is exceeded
+     */
+    public void handleStep(final FieldStepInterpolator<T> interpolator, final boolean isLast)
+        throws MaxCountExceededException {
+        // The first time, update the last state with the start information.
+        if (last == null) {
+
+            first   = interpolator.getPreviousState();
+            last    = first;
+
+            // Take the integration direction into account.
+            forward = interpolator.isForward();
+            if (!forward) {
+                h = -h;
+            }
+        }
+
+        // Calculate next normalized step time.
+        T nextTime = (mode == StepNormalizerMode.INCREMENT) ?
+                     last.getTime().add(h) :
+                     last.getTime().getField().getZero().add((FastMath.floor(last.getTime().getReal() / h) + 1) * h);
+        if (mode == StepNormalizerMode.MULTIPLES &&
+            Precision.equals(nextTime.getReal(), last.getTime().getReal(), 1)) {
+            nextTime = nextTime.add(h);
+        }
+
+        // Process normalized steps as long as they are in the current step.
+        boolean nextInStep = isNextInStep(nextTime, interpolator);
+        while (nextInStep) {
+            // Output the stored previous step.
+            doNormalizedStep(false);
+
+            // Store the next step as last step.
+            last = interpolator.getInterpolatedState(nextTime);
+
+            // Move on to the next step.
+            nextTime = nextTime.add(h);
+            nextInStep = isNextInStep(nextTime, interpolator);
+        }
+
+        if (isLast) {
+            // There will be no more steps. The stored one should be given to
+            // the handler. We may have to output one more step. Only the last
+            // one of those should be flagged as being the last.
+            final boolean addLast = bounds.lastIncluded() &&
+                                    last.getTime().getReal() != interpolator.getCurrentState().getTime().getReal();
+            doNormalizedStep(!addLast);
+            if (addLast) {
+                last = interpolator.getCurrentState();
+                doNormalizedStep(true);
+            }
+        }
+    }
+
+    /**
+     * Returns a value indicating whether the next normalized time is in the
+     * current step.
+     * @param nextTime the next normalized time
+     * @param interpolator interpolator for the last accepted step, to use to
+     * get the end time of the current step
+     * @return value indicating whether the next normalized time is in the
+     * current step
+     */
+    private boolean isNextInStep(final T nextTime, final FieldStepInterpolator<T> interpolator) {
+        return forward ?
+               nextTime.getReal() <= interpolator.getCurrentState().getTime().getReal() :
+               nextTime.getReal() >= interpolator.getCurrentState().getTime().getReal();
+    }
+
+    /**
+     * Invokes the underlying step handler for the current normalized step.
+     * @param isLast true if the step is the last one
+     */
+    private void doNormalizedStep(final boolean isLast) {
+        if (!bounds.firstIncluded() && first.getTime().getReal() == last.getTime().getReal()) {
+            return;
+        }
+        handler.handleStep(last, isLast);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/sampling/FixedStepHandler.java b/src/main/java/org/apache/commons/math3/ode/sampling/FixedStepHandler.java
new file mode 100644
index 0000000..bbe6ac5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/sampling/FixedStepHandler.java
@@ -0,0 +1,71 @@
+/*
+ * 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.math3.ode.sampling;
+
+
+/**
+ * This interface represents a handler that should be called after
+ * each successful fixed step.
+
+ * <p>This interface should be implemented by anyone who is interested
+ * in getting the solution of an ordinary differential equation at
+ * fixed time steps. Objects implementing this interface should be
+ * wrapped within an instance of {@link StepNormalizer} that itself
+ * is used as the general {@link StepHandler} by the integrator. The
+ * {@link StepNormalizer} object is called according to the integrator
+ * internal algorithms and it calls objects implementing this
+ * interface as necessary at fixed time steps.</p>
+ *
+ * @see StepHandler
+ * @see StepNormalizer
+ * @since 1.2
+ */
+
+public interface FixedStepHandler  {
+
+  /** Initialize step handler at the start of an ODE integration.
+   * <p>
+   * This method is called once at the start of the integration. It
+   * may be used by the step handler to initialize some internal data
+   * if needed.
+   * </p>
+   * @param t0 start value of the independent <i>time</i> variable
+   * @param y0 array containing the start value of the state vector
+   * @param t target time for the integration
+   */
+  void init(double t0, double[] y0, double t);
+
+  /**
+   * Handle the last accepted step
+   * @param t time of the current step
+   * @param y state vector at t. For efficiency purposes, the {@link
+   * StepNormalizer} class reuses the same array on each call, so if
+   * the instance wants to keep it across all calls (for example to
+   * provide at the end of the integration a complete array of all
+   * steps), it should build a local copy store this copy.
+   * @param yDot derivatives of the state vector state vector at t.
+   * For efficiency purposes, the {@link StepNormalizer} class reuses
+   * the same array on each call, so if
+   * the instance wants to keep it across all calls (for example to
+   * provide at the end of the integration a complete array of all
+   * steps), it should build a local copy store this copy.
+   * @param isLast true if the step is the last one
+   */
+  void handleStep(double t, double[] y, double[] yDot, boolean isLast);
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/sampling/NordsieckStepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/sampling/NordsieckStepInterpolator.java
new file mode 100644
index 0000000..39b05ab
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/sampling/NordsieckStepInterpolator.java
@@ -0,0 +1,293 @@
+/*
+ * 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.math3.ode.sampling;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.Arrays;
+
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.linear.Array2DRowRealMatrix;
+import org.apache.commons.math3.ode.EquationsMapper;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * This class implements an interpolator for integrators using Nordsieck representation.
+ *
+ * <p>This interpolator computes dense output around the current point.
+ * The interpolation equation is based on Taylor series formulas.
+ *
+ * @see org.apache.commons.math3.ode.nonstiff.AdamsBashforthIntegrator
+ * @see org.apache.commons.math3.ode.nonstiff.AdamsMoultonIntegrator
+ * @since 2.0
+ */
+
+public class NordsieckStepInterpolator extends AbstractStepInterpolator {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -7179861704951334960L;
+
+    /** State variation. */
+    protected double[] stateVariation;
+
+    /** Step size used in the first scaled derivative and Nordsieck vector. */
+    private double scalingH;
+
+    /** Reference time for all arrays.
+     * <p>Sometimes, the reference time is the same as previousTime,
+     * sometimes it is the same as currentTime, so we use a separate
+     * field to avoid any confusion.
+     * </p>
+     */
+    private double referenceTime;
+
+    /** First scaled derivative. */
+    private double[] scaled;
+
+    /** Nordsieck vector. */
+    private Array2DRowRealMatrix nordsieck;
+
+    /** Simple constructor.
+     * This constructor builds an instance that is not usable yet, the
+     * {@link AbstractStepInterpolator#reinitialize} method should be called
+     * before using the instance in order to initialize the internal arrays. This
+     * constructor is used only in order to delay the initialization in
+     * some cases.
+     */
+    public NordsieckStepInterpolator() {
+    }
+
+    /** Copy constructor.
+     * @param interpolator interpolator to copy from. The copy is a deep
+     * copy: its arrays are separated from the original arrays of the
+     * instance
+     */
+    public NordsieckStepInterpolator(final NordsieckStepInterpolator interpolator) {
+        super(interpolator);
+        scalingH      = interpolator.scalingH;
+        referenceTime = interpolator.referenceTime;
+        if (interpolator.scaled != null) {
+            scaled = interpolator.scaled.clone();
+        }
+        if (interpolator.nordsieck != null) {
+            nordsieck = new Array2DRowRealMatrix(interpolator.nordsieck.getDataRef(), true);
+        }
+        if (interpolator.stateVariation != null) {
+            stateVariation = interpolator.stateVariation.clone();
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected StepInterpolator doCopy() {
+        return new NordsieckStepInterpolator(this);
+    }
+
+    /** Reinitialize the instance.
+     * <p>Beware that all arrays <em>must</em> be references to integrator
+     * arrays, in order to ensure proper update without copy.</p>
+     * @param y reference to the integrator array holding the state at
+     * the end of the step
+     * @param forward integration direction indicator
+     * @param primaryMapper equations mapper for the primary equations set
+     * @param secondaryMappers equations mappers for the secondary equations sets
+     */
+    @Override
+    public void reinitialize(final double[] y, final boolean forward,
+                             final EquationsMapper primaryMapper,
+                             final EquationsMapper[] secondaryMappers) {
+        super.reinitialize(y, forward, primaryMapper, secondaryMappers);
+        stateVariation = new double[y.length];
+    }
+
+    /** Reinitialize the instance.
+     * <p>Beware that all arrays <em>must</em> be references to integrator
+     * arrays, in order to ensure proper update without copy.</p>
+     * @param time time at which all arrays are defined
+     * @param stepSize step size used in the scaled and Nordsieck arrays
+     * @param scaledDerivative reference to the integrator array holding the first
+     * scaled derivative
+     * @param nordsieckVector reference to the integrator matrix holding the
+     * Nordsieck vector
+     */
+    public void reinitialize(final double time, final double stepSize,
+                             final double[] scaledDerivative,
+                             final Array2DRowRealMatrix nordsieckVector) {
+        this.referenceTime = time;
+        this.scalingH      = stepSize;
+        this.scaled        = scaledDerivative;
+        this.nordsieck     = nordsieckVector;
+
+        // make sure the state and derivatives will depend on the new arrays
+        setInterpolatedTime(getInterpolatedTime());
+
+    }
+
+    /** Rescale the instance.
+     * <p>Since the scaled and Nordsieck arrays are shared with the caller,
+     * this method has the side effect of rescaling this arrays in the caller too.</p>
+     * @param stepSize new step size to use in the scaled and Nordsieck arrays
+     */
+    public void rescale(final double stepSize) {
+
+        final double ratio = stepSize / scalingH;
+        for (int i = 0; i < scaled.length; ++i) {
+            scaled[i] *= ratio;
+        }
+
+        final double[][] nData = nordsieck.getDataRef();
+        double power = ratio;
+        for (int i = 0; i < nData.length; ++i) {
+            power *= ratio;
+            final double[] nDataI = nData[i];
+            for (int j = 0; j < nDataI.length; ++j) {
+                nDataI[j] *= power;
+            }
+        }
+
+        scalingH = stepSize;
+
+    }
+
+    /**
+     * Get the state vector variation from current to interpolated state.
+     * <p>This method is aimed at computing y(t<sub>interpolation</sub>)
+     * -y(t<sub>current</sub>) accurately by avoiding the cancellation errors
+     * that would occur if the subtraction were performed explicitly.</p>
+     * <p>The returned vector is a reference to a reused array, so
+     * it should not be modified and it should be copied if it needs
+     * to be preserved across several calls.</p>
+     * @return state vector at time {@link #getInterpolatedTime}
+     * @see #getInterpolatedDerivatives()
+     * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+     */
+    public double[] getInterpolatedStateVariation() throws MaxCountExceededException {
+        // compute and ignore interpolated state
+        // to make sure state variation is computed as a side effect
+        getInterpolatedState();
+        return stateVariation;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void computeInterpolatedStateAndDerivatives(final double theta, final double oneMinusThetaH) {
+
+        final double x = interpolatedTime - referenceTime;
+        final double normalizedAbscissa = x / scalingH;
+
+        Arrays.fill(stateVariation, 0.0);
+        Arrays.fill(interpolatedDerivatives, 0.0);
+
+        // apply Taylor formula from high order to low order,
+        // for the sake of numerical accuracy
+        final double[][] nData = nordsieck.getDataRef();
+        for (int i = nData.length - 1; i >= 0; --i) {
+            final int order = i + 2;
+            final double[] nDataI = nData[i];
+            final double power = FastMath.pow(normalizedAbscissa, order);
+            for (int j = 0; j < nDataI.length; ++j) {
+                final double d = nDataI[j] * power;
+                stateVariation[j]          += d;
+                interpolatedDerivatives[j] += order * d;
+            }
+        }
+
+        for (int j = 0; j < currentState.length; ++j) {
+            stateVariation[j] += scaled[j] * normalizedAbscissa;
+            interpolatedState[j] = currentState[j] + stateVariation[j];
+            interpolatedDerivatives[j] =
+                (interpolatedDerivatives[j] + scaled[j] * normalizedAbscissa) / x;
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void writeExternal(final ObjectOutput out)
+        throws IOException {
+
+        // save the state of the base class
+        writeBaseExternal(out);
+
+        // save the local attributes
+        out.writeDouble(scalingH);
+        out.writeDouble(referenceTime);
+
+        final int n = (currentState == null) ? -1 : currentState.length;
+        if (scaled == null) {
+            out.writeBoolean(false);
+        } else {
+            out.writeBoolean(true);
+            for (int j = 0; j < n; ++j) {
+                out.writeDouble(scaled[j]);
+            }
+        }
+
+        if (nordsieck == null) {
+            out.writeBoolean(false);
+        } else {
+            out.writeBoolean(true);
+            out.writeObject(nordsieck);
+        }
+
+        // we don't save state variation, it will be recomputed
+
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void readExternal(final ObjectInput in)
+        throws IOException, ClassNotFoundException {
+
+        // read the base class
+        final double t = readBaseExternal(in);
+
+        // read the local attributes
+        scalingH      = in.readDouble();
+        referenceTime = in.readDouble();
+
+        final int n = (currentState == null) ? -1 : currentState.length;
+        final boolean hasScaled = in.readBoolean();
+        if (hasScaled) {
+            scaled = new double[n];
+            for (int j = 0; j < n; ++j) {
+                scaled[j] = in.readDouble();
+            }
+        } else {
+            scaled = null;
+        }
+
+        final boolean hasNordsieck = in.readBoolean();
+        if (hasNordsieck) {
+            nordsieck = (Array2DRowRealMatrix) in.readObject();
+        } else {
+            nordsieck = null;
+        }
+
+        if (hasScaled && hasNordsieck) {
+            // we can now set the interpolated time and state
+            stateVariation = new double[n];
+            setInterpolatedTime(t);
+        } else {
+            stateVariation = null;
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/sampling/StepHandler.java b/src/main/java/org/apache/commons/math3/ode/sampling/StepHandler.java
new file mode 100644
index 0000000..f4c63df
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/sampling/StepHandler.java
@@ -0,0 +1,75 @@
+/*
+ * 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.math3.ode.sampling;
+
+import org.apache.commons.math3.exception.MaxCountExceededException;
+
+
+/**
+ * This interface represents a handler that should be called after
+ * each successful step.
+ *
+ * <p>The ODE integrators compute the evolution of the state vector at
+ * some grid points that depend on their own internal algorithm. Once
+ * they have found a new grid point (possibly after having computed
+ * several evaluation of the derivative at intermediate points), they
+ * provide it to objects implementing this interface. These objects
+ * typically either ignore the intermediate steps and wait for the
+ * last one, store the points in an ephemeris, or forward them to
+ * specialized processing or output methods.</p>
+ *
+ * @see org.apache.commons.math3.ode.FirstOrderIntegrator
+ * @see org.apache.commons.math3.ode.SecondOrderIntegrator
+ * @see StepInterpolator
+ * @since 1.2
+ */
+
+public interface StepHandler {
+
+    /** Initialize step handler at the start of an ODE integration.
+     * <p>
+     * This method is called once at the start of the integration. It
+     * may be used by the step handler to initialize some internal data
+     * if needed.
+     * </p>
+     * @param t0 start value of the independent <i>time</i> variable
+     * @param y0 array containing the start value of the state vector
+     * @param t target time for the integration
+     */
+    void init(double t0, double[] y0, double t);
+
+    /**
+     * Handle the last accepted step
+     * @param interpolator interpolator for the last accepted step. For
+     * efficiency purposes, the various integrators reuse the same
+     * object on each call, so if the instance wants to keep it across
+     * all calls (for example to provide at the end of the integration a
+     * continuous model valid throughout the integration range, as the
+     * {@link org.apache.commons.math3.ode.ContinuousOutputModel
+     * ContinuousOutputModel} class does), it should build a local copy
+     * using the clone method of the interpolator and store this copy.
+     * Keeping only a reference to the interpolator and reusing it will
+     * result in unpredictable behavior (potentially crashing the application).
+     * @param isLast true if the step is the last one
+     * @exception MaxCountExceededException if the interpolator throws one because
+     * the number of functions evaluations is exceeded
+     */
+    void handleStep(StepInterpolator interpolator, boolean isLast)
+        throws MaxCountExceededException;
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/sampling/StepInterpolator.java b/src/main/java/org/apache/commons/math3/ode/sampling/StepInterpolator.java
new file mode 100644
index 0000000..5d27bf2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/sampling/StepInterpolator.java
@@ -0,0 +1,181 @@
+/*
+ * 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.math3.ode.sampling;
+
+import java.io.Externalizable;
+
+import org.apache.commons.math3.exception.MaxCountExceededException;
+
+/** This interface represents an interpolator over the last step
+ * during an ODE integration.
+ *
+ * <p>The various ODE integrators provide objects implementing this
+ * interface to the step handlers. These objects are often custom
+ * objects tightly bound to the integrator internal algorithms. The
+ * handlers can use these objects to retrieve the state vector at
+ * intermediate times between the previous and the current grid points
+ * (this feature is often called dense output).</p>
+ * <p>One important thing to note is that the step handlers may be so
+ * tightly bound to the integrators that they often share some internal
+ * state arrays. This imply that one should <em>never</em> use a direct
+ * reference to a step interpolator outside of the step handler, either
+ * for future use or for use in another thread. If such a need arise, the
+ * step interpolator <em>must</em> be copied using the dedicated
+ * {@link #copy()} method.
+ * </p>
+ *
+ * @see org.apache.commons.math3.ode.FirstOrderIntegrator
+ * @see org.apache.commons.math3.ode.SecondOrderIntegrator
+ * @see StepHandler
+ * @since 1.2
+ */
+
+public interface StepInterpolator extends Externalizable {
+
+  /**
+   * Get the previous grid point time.
+   * @return previous grid point time
+   */
+  double getPreviousTime();
+
+  /**
+   * Get the current grid point time.
+   * @return current grid point time
+   */
+  double getCurrentTime();
+
+  /**
+   * Get the time of the interpolated point.
+   * If {@link #setInterpolatedTime} has not been called, it returns
+   * the current grid point time.
+   * @return interpolation point time
+   */
+  double getInterpolatedTime();
+
+  /**
+   * Set the time of the interpolated point.
+   * <p>Setting the time outside of the current step is now allowed, but
+   * should be used with care since the accuracy of the interpolator will
+   * probably be very poor far from this step. This allowance has been
+   * added to simplify implementation of search algorithms near the
+   * step endpoints.</p>
+   * <p>Setting the time changes the instance internal state. This includes
+   * the internal arrays returned in {@link #getInterpolatedState()},
+   * {@link #getInterpolatedDerivatives()}, {@link
+   * #getInterpolatedSecondaryState(int)} and {@link
+   * #getInterpolatedSecondaryDerivatives(int)}. So if their content must be preserved
+   * across several calls, user must copy them.</p>
+   * @param time time of the interpolated point
+   * @see #getInterpolatedState()
+   * @see #getInterpolatedDerivatives()
+   * @see #getInterpolatedSecondaryState(int)
+   * @see #getInterpolatedSecondaryDerivatives(int)
+   */
+  void setInterpolatedTime(double time);
+
+  /**
+   * Get the state vector of the interpolated point.
+   * <p>The returned vector is a reference to a reused array, so
+   * it should not be modified and it should be copied if it needs
+   * to be preserved across several calls to the associated
+   * {@link #setInterpolatedTime(double)} method.</p>
+   * @return state vector at time {@link #getInterpolatedTime}
+   * @see #getInterpolatedDerivatives()
+   * @see #getInterpolatedSecondaryState(int)
+   * @see #getInterpolatedSecondaryDerivatives(int)
+   * @see #setInterpolatedTime(double)
+   * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+   */
+  double[] getInterpolatedState() throws MaxCountExceededException;
+
+  /**
+   * Get the derivatives of the state vector of the interpolated point.
+   * <p>The returned vector is a reference to a reused array, so
+   * it should not be modified and it should be copied if it needs
+   * to be preserved across several calls to the associated
+   * {@link #setInterpolatedTime(double)} method.</p>
+   * @return derivatives of the state vector at time {@link #getInterpolatedTime}
+   * @see #getInterpolatedState()
+   * @see #getInterpolatedSecondaryState(int)
+   * @see #getInterpolatedSecondaryDerivatives(int)
+   * @see #setInterpolatedTime(double)
+   * @since 2.0
+   * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+   */
+  double[] getInterpolatedDerivatives() throws MaxCountExceededException;
+
+  /** Get the interpolated secondary state corresponding to the secondary equations.
+   * <p>The returned vector is a reference to a reused array, so
+   * it should not be modified and it should be copied if it needs
+   * to be preserved across several calls to the associated
+   * {@link #setInterpolatedTime(double)} method.</p>
+   * @param index index of the secondary set, as returned by {@link
+   * org.apache.commons.math3.ode.ExpandableStatefulODE#addSecondaryEquations(
+   * org.apache.commons.math3.ode.SecondaryEquations)
+   * ExpandableStatefulODE.addSecondaryEquations(SecondaryEquations)}
+   * @return interpolated secondary state at the current interpolation date
+   * @see #getInterpolatedState()
+   * @see #getInterpolatedDerivatives()
+   * @see #getInterpolatedSecondaryDerivatives(int)
+   * @see #setInterpolatedTime(double)
+   * @since 3.0
+   * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+   */
+  double[] getInterpolatedSecondaryState(int index) throws MaxCountExceededException;
+
+  /** Get the interpolated secondary derivatives corresponding to the secondary equations.
+   * <p>The returned vector is a reference to a reused array, so
+   * it should not be modified and it should be copied if it needs
+   * to be preserved across several calls.</p>
+   * @param index index of the secondary set, as returned by {@link
+   * org.apache.commons.math3.ode.ExpandableStatefulODE#addSecondaryEquations(
+   * org.apache.commons.math3.ode.SecondaryEquations)
+   * ExpandableStatefulODE.addSecondaryEquations(SecondaryEquations)}
+   * @return interpolated secondary derivatives at the current interpolation date
+   * @see #getInterpolatedState()
+   * @see #getInterpolatedDerivatives()
+   * @see #getInterpolatedSecondaryState(int)
+   * @see #setInterpolatedTime(double)
+   * @since 3.0
+   * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+   */
+  double[] getInterpolatedSecondaryDerivatives(int index) throws MaxCountExceededException;
+
+  /** Check if the natural integration direction is forward.
+   * <p>This method provides the integration direction as specified by
+   * the integrator itself, it avoid some nasty problems in
+   * degenerated cases like null steps due to cancellation at step
+   * initialization, step control or discrete events
+   * triggering.</p>
+   * @return true if the integration variable (time) increases during
+   * integration
+   */
+  boolean isForward();
+
+  /** Copy the instance.
+   * <p>The copied instance is guaranteed to be independent from the
+   * original one. Both can be used with different settings for
+   * interpolated time without any side effect.</p>
+   * @return a deep copy of the instance, which can be used independently.
+   * @see #setInterpolatedTime(double)
+   * @exception MaxCountExceededException if the number of functions evaluations is exceeded
+   * during step finalization
+   */
+   StepInterpolator copy() throws MaxCountExceededException;
+
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/sampling/StepNormalizer.java b/src/main/java/org/apache/commons/math3/ode/sampling/StepNormalizer.java
new file mode 100644
index 0000000..307c0d9
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/sampling/StepNormalizer.java
@@ -0,0 +1,300 @@
+/*
+ * 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.math3.ode.sampling;
+
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.Precision;
+
+/**
+ * This class wraps an object implementing {@link FixedStepHandler}
+ * into a {@link StepHandler}.
+
+ * <p>This wrapper allows to use fixed step handlers with general
+ * integrators which cannot guaranty their integration steps will
+ * remain constant and therefore only accept general step
+ * handlers.</p>
+ *
+ * <p>The stepsize used is selected at construction time. The {@link
+ * FixedStepHandler#handleStep handleStep} method of the underlying
+ * {@link FixedStepHandler} object is called at normalized times. The
+ * normalized times can be influenced by the {@link StepNormalizerMode} and
+ * {@link StepNormalizerBounds}.</p>
+ *
+ * <p>There is no constraint on the integrator, it can use any time step
+ * it needs (time steps longer or shorter than the fixed time step and
+ * non-integer ratios are all allowed).</p>
+ *
+ * <p>
+ * <table border="1" align="center">
+ * <tr BGCOLOR="#CCCCFF"><td colspan=6><font size="+2">Examples (step size = 0.5)</font></td></tr>
+ * <tr BGCOLOR="#EEEEFF"><font size="+1"><td>Start time</td><td>End time</td>
+ *  <td>Direction</td><td>{@link StepNormalizerMode Mode}</td>
+ *  <td>{@link StepNormalizerBounds Bounds}</td><td>Output</td></font></tr>
+ * <tr><td>0.3</td><td>3.1</td><td>forward</td><td>{@link StepNormalizerMode#INCREMENT INCREMENT}</td><td>{@link StepNormalizerBounds#NEITHER NEITHER}</td><td>0.8, 1.3, 1.8, 2.3, 2.8</td></tr>
+ * <tr><td>0.3</td><td>3.1</td><td>forward</td><td>{@link StepNormalizerMode#INCREMENT INCREMENT}</td><td>{@link StepNormalizerBounds#FIRST FIRST}</td><td>0.3, 0.8, 1.3, 1.8, 2.3, 2.8</td></tr>
+ * <tr><td>0.3</td><td>3.1</td><td>forward</td><td>{@link StepNormalizerMode#INCREMENT INCREMENT}</td><td>{@link StepNormalizerBounds#LAST LAST}</td><td>0.8, 1.3, 1.8, 2.3, 2.8, 3.1</td></tr>
+ * <tr><td>0.3</td><td>3.1</td><td>forward</td><td>{@link StepNormalizerMode#INCREMENT INCREMENT}</td><td>{@link StepNormalizerBounds#BOTH BOTH}</td><td>0.3, 0.8, 1.3, 1.8, 2.3, 2.8, 3.1</td></tr>
+ * <tr><td>0.3</td><td>3.1</td><td>forward</td><td>{@link StepNormalizerMode#MULTIPLES MULTIPLES}</td><td>{@link StepNormalizerBounds#NEITHER NEITHER}</td><td>0.5, 1.0, 1.5, 2.0, 2.5, 3.0</td></tr>
+ * <tr><td>0.3</td><td>3.1</td><td>forward</td><td>{@link StepNormalizerMode#MULTIPLES MULTIPLES}</td><td>{@link StepNormalizerBounds#FIRST FIRST}</td><td>0.3, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0</td></tr>
+ * <tr><td>0.3</td><td>3.1</td><td>forward</td><td>{@link StepNormalizerMode#MULTIPLES MULTIPLES}</td><td>{@link StepNormalizerBounds#LAST LAST}</td><td>0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.1</td></tr>
+ * <tr><td>0.3</td><td>3.1</td><td>forward</td><td>{@link StepNormalizerMode#MULTIPLES MULTIPLES}</td><td>{@link StepNormalizerBounds#BOTH BOTH}</td><td>0.3, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.1</td></tr>
+ * <tr><td>0.0</td><td>3.0</td><td>forward</td><td>{@link StepNormalizerMode#INCREMENT INCREMENT}</td><td>{@link StepNormalizerBounds#NEITHER NEITHER}</td><td>0.5, 1.0, 1.5, 2.0, 2.5, 3.0</td></tr>
+ * <tr><td>0.0</td><td>3.0</td><td>forward</td><td>{@link StepNormalizerMode#INCREMENT INCREMENT}</td><td>{@link StepNormalizerBounds#FIRST FIRST}</td><td>0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0</td></tr>
+ * <tr><td>0.0</td><td>3.0</td><td>forward</td><td>{@link StepNormalizerMode#INCREMENT INCREMENT}</td><td>{@link StepNormalizerBounds#LAST LAST}</td><td>0.5, 1.0, 1.5, 2.0, 2.5, 3.0</td></tr>
+ * <tr><td>0.0</td><td>3.0</td><td>forward</td><td>{@link StepNormalizerMode#INCREMENT INCREMENT}</td><td>{@link StepNormalizerBounds#BOTH BOTH}</td><td>0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0</td></tr>
+ * <tr><td>0.0</td><td>3.0</td><td>forward</td><td>{@link StepNormalizerMode#MULTIPLES MULTIPLES}</td><td>{@link StepNormalizerBounds#NEITHER NEITHER}</td><td>0.5, 1.0, 1.5, 2.0, 2.5, 3.0</td></tr>
+ * <tr><td>0.0</td><td>3.0</td><td>forward</td><td>{@link StepNormalizerMode#MULTIPLES MULTIPLES}</td><td>{@link StepNormalizerBounds#FIRST FIRST}</td><td>0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0</td></tr>
+ * <tr><td>0.0</td><td>3.0</td><td>forward</td><td>{@link StepNormalizerMode#MULTIPLES MULTIPLES}</td><td>{@link StepNormalizerBounds#LAST LAST}</td><td>0.5, 1.0, 1.5, 2.0, 2.5, 3.0</td></tr>
+ * <tr><td>0.0</td><td>3.0</td><td>forward</td><td>{@link StepNormalizerMode#MULTIPLES MULTIPLES}</td><td>{@link StepNormalizerBounds#BOTH BOTH}</td><td>0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0</td></tr>
+ * <tr><td>3.1</td><td>0.3</td><td>backward</td><td>{@link StepNormalizerMode#INCREMENT INCREMENT}</td><td>{@link StepNormalizerBounds#NEITHER NEITHER}</td><td>2.6, 2.1, 1.6, 1.1, 0.6</td></tr>
+ * <tr><td>3.1</td><td>0.3</td><td>backward</td><td>{@link StepNormalizerMode#INCREMENT INCREMENT}</td><td>{@link StepNormalizerBounds#FIRST FIRST}</td><td>3.1, 2.6, 2.1, 1.6, 1.1, 0.6</td></tr>
+ * <tr><td>3.1</td><td>0.3</td><td>backward</td><td>{@link StepNormalizerMode#INCREMENT INCREMENT}</td><td>{@link StepNormalizerBounds#LAST LAST}</td><td>2.6, 2.1, 1.6, 1.1, 0.6, 0.3</td></tr>
+ * <tr><td>3.1</td><td>0.3</td><td>backward</td><td>{@link StepNormalizerMode#INCREMENT INCREMENT}</td><td>{@link StepNormalizerBounds#BOTH BOTH}</td><td>3.1, 2.6, 2.1, 1.6, 1.1, 0.6, 0.3</td></tr>
+ * <tr><td>3.1</td><td>0.3</td><td>backward</td><td>{@link StepNormalizerMode#MULTIPLES MULTIPLES}</td><td>{@link StepNormalizerBounds#NEITHER NEITHER}</td><td>3.0, 2.5, 2.0, 1.5, 1.0, 0.5</td></tr>
+ * <tr><td>3.1</td><td>0.3</td><td>backward</td><td>{@link StepNormalizerMode#MULTIPLES MULTIPLES}</td><td>{@link StepNormalizerBounds#FIRST FIRST}</td><td>3.1, 3.0, 2.5, 2.0, 1.5, 1.0, 0.5</td></tr>
+ * <tr><td>3.1</td><td>0.3</td><td>backward</td><td>{@link StepNormalizerMode#MULTIPLES MULTIPLES}</td><td>{@link StepNormalizerBounds#LAST LAST}</td><td>3.0, 2.5, 2.0, 1.5, 1.0, 0.5, 0.3</td></tr>
+ * <tr><td>3.1</td><td>0.3</td><td>backward</td><td>{@link StepNormalizerMode#MULTIPLES MULTIPLES}</td><td>{@link StepNormalizerBounds#BOTH BOTH}</td><td>3.1, 3.0, 2.5, 2.0, 1.5, 1.0, 0.5, 0.3</td></tr>
+ * <tr><td>3.0</td><td>0.0</td><td>backward</td><td>{@link StepNormalizerMode#INCREMENT INCREMENT}</td><td>{@link StepNormalizerBounds#NEITHER NEITHER}</td><td>2.5, 2.0, 1.5, 1.0, 0.5, 0.0</td></tr>
+ * <tr><td>3.0</td><td>0.0</td><td>backward</td><td>{@link StepNormalizerMode#INCREMENT INCREMENT}</td><td>{@link StepNormalizerBounds#FIRST FIRST}</td><td>3.0, 2.5, 2.0, 1.5, 1.0, 0.5, 0.0</td></tr>
+ * <tr><td>3.0</td><td>0.0</td><td>backward</td><td>{@link StepNormalizerMode#INCREMENT INCREMENT}</td><td>{@link StepNormalizerBounds#LAST LAST}</td><td>2.5, 2.0, 1.5, 1.0, 0.5, 0.0</td></tr>
+ * <tr><td>3.0</td><td>0.0</td><td>backward</td><td>{@link StepNormalizerMode#INCREMENT INCREMENT}</td><td>{@link StepNormalizerBounds#BOTH BOTH}</td><td>3.0, 2.5, 2.0, 1.5, 1.0, 0.5, 0.0</td></tr>
+ * <tr><td>3.0</td><td>0.0</td><td>backward</td><td>{@link StepNormalizerMode#MULTIPLES MULTIPLES}</td><td>{@link StepNormalizerBounds#NEITHER NEITHER}</td><td>2.5, 2.0, 1.5, 1.0, 0.5, 0.0</td></tr>
+ * <tr><td>3.0</td><td>0.0</td><td>backward</td><td>{@link StepNormalizerMode#MULTIPLES MULTIPLES}</td><td>{@link StepNormalizerBounds#FIRST FIRST}</td><td>3.0, 2.5, 2.0, 1.5, 1.0, 0.5, 0.0</td></tr>
+ * <tr><td>3.0</td><td>0.0</td><td>backward</td><td>{@link StepNormalizerMode#MULTIPLES MULTIPLES}</td><td>{@link StepNormalizerBounds#LAST LAST}</td><td>2.5, 2.0, 1.5, 1.0, 0.5, 0.0</td></tr>
+ * <tr><td>3.0</td><td>0.0</td><td>backward</td><td>{@link StepNormalizerMode#MULTIPLES MULTIPLES}</td><td>{@link StepNormalizerBounds#BOTH BOTH}</td><td>3.0, 2.5, 2.0, 1.5, 1.0, 0.5, 0.0</td></tr>
+ * </table>
+ * </p>
+ *
+ * @see StepHandler
+ * @see FixedStepHandler
+ * @see StepNormalizerMode
+ * @see StepNormalizerBounds
+ * @since 1.2
+ */
+
+public class StepNormalizer implements StepHandler {
+    /** Fixed time step. */
+    private double h;
+
+    /** Underlying step handler. */
+    private final FixedStepHandler handler;
+
+    /** First step time. */
+    private double firstTime;
+
+    /** Last step time. */
+    private double lastTime;
+
+    /** Last state vector. */
+    private double[] lastState;
+
+    /** Last derivatives vector. */
+    private double[] lastDerivatives;
+
+    /** Integration direction indicator. */
+    private boolean forward;
+
+    /** The step normalizer bounds settings to use. */
+    private final StepNormalizerBounds bounds;
+
+    /** The step normalizer mode to use. */
+    private final StepNormalizerMode mode;
+
+    /** Simple constructor. Uses {@link StepNormalizerMode#INCREMENT INCREMENT}
+     * mode, and {@link StepNormalizerBounds#FIRST FIRST} bounds setting, for
+     * backwards compatibility.
+     * @param h fixed time step (sign is not used)
+     * @param handler fixed time step handler to wrap
+     */
+    public StepNormalizer(final double h, final FixedStepHandler handler) {
+        this(h, handler, StepNormalizerMode.INCREMENT,
+             StepNormalizerBounds.FIRST);
+    }
+
+    /** Simple constructor. Uses {@link StepNormalizerBounds#FIRST FIRST}
+     * bounds setting.
+     * @param h fixed time step (sign is not used)
+     * @param handler fixed time step handler to wrap
+     * @param mode step normalizer mode to use
+     * @since 3.0
+     */
+    public StepNormalizer(final double h, final FixedStepHandler handler,
+                          final StepNormalizerMode mode) {
+        this(h, handler, mode, StepNormalizerBounds.FIRST);
+    }
+
+    /** Simple constructor. Uses {@link StepNormalizerMode#INCREMENT INCREMENT}
+     * mode.
+     * @param h fixed time step (sign is not used)
+     * @param handler fixed time step handler to wrap
+     * @param bounds step normalizer bounds setting to use
+     * @since 3.0
+     */
+    public StepNormalizer(final double h, final FixedStepHandler handler,
+                          final StepNormalizerBounds bounds) {
+        this(h, handler, StepNormalizerMode.INCREMENT, bounds);
+    }
+
+    /** Simple constructor.
+     * @param h fixed time step (sign is not used)
+     * @param handler fixed time step handler to wrap
+     * @param mode step normalizer mode to use
+     * @param bounds step normalizer bounds setting to use
+     * @since 3.0
+     */
+    public StepNormalizer(final double h, final FixedStepHandler handler,
+                          final StepNormalizerMode mode,
+                          final StepNormalizerBounds bounds) {
+        this.h          = FastMath.abs(h);
+        this.handler    = handler;
+        this.mode       = mode;
+        this.bounds     = bounds;
+        firstTime       = Double.NaN;
+        lastTime        = Double.NaN;
+        lastState       = null;
+        lastDerivatives = null;
+        forward         = true;
+    }
+
+    /** {@inheritDoc} */
+    public void init(double t0, double[] y0, double t) {
+
+        firstTime       = Double.NaN;
+        lastTime        = Double.NaN;
+        lastState       = null;
+        lastDerivatives = null;
+        forward         = true;
+
+        // initialize the underlying handler
+        handler.init(t0, y0, t);
+
+    }
+
+    /**
+     * Handle the last accepted step
+     * @param interpolator interpolator for the last accepted step. For
+     * efficiency purposes, the various integrators reuse the same
+     * object on each call, so if the instance wants to keep it across
+     * all calls (for example to provide at the end of the integration a
+     * continuous model valid throughout the integration range), it
+     * should build a local copy using the clone method and store this
+     * copy.
+     * @param isLast true if the step is the last one
+     * @exception MaxCountExceededException if the interpolator throws one because
+     * the number of functions evaluations is exceeded
+     */
+    public void handleStep(final StepInterpolator interpolator, final boolean isLast)
+        throws MaxCountExceededException {
+        // The first time, update the last state with the start information.
+        if (lastState == null) {
+            firstTime = interpolator.getPreviousTime();
+            lastTime = interpolator.getPreviousTime();
+            interpolator.setInterpolatedTime(lastTime);
+            lastState = interpolator.getInterpolatedState().clone();
+            lastDerivatives = interpolator.getInterpolatedDerivatives().clone();
+
+            // Take the integration direction into account.
+            forward = interpolator.getCurrentTime() >= lastTime;
+            if (!forward) {
+                h = -h;
+            }
+        }
+
+        // Calculate next normalized step time.
+        double nextTime = (mode == StepNormalizerMode.INCREMENT) ?
+                          lastTime + h :
+                          (FastMath.floor(lastTime / h) + 1) * h;
+        if (mode == StepNormalizerMode.MULTIPLES &&
+            Precision.equals(nextTime, lastTime, 1)) {
+            nextTime += h;
+        }
+
+        // Process normalized steps as long as they are in the current step.
+        boolean nextInStep = isNextInStep(nextTime, interpolator);
+        while (nextInStep) {
+            // Output the stored previous step.
+            doNormalizedStep(false);
+
+            // Store the next step as last step.
+            storeStep(interpolator, nextTime);
+
+            // Move on to the next step.
+            nextTime += h;
+            nextInStep = isNextInStep(nextTime, interpolator);
+        }
+
+        if (isLast) {
+            // There will be no more steps. The stored one should be given to
+            // the handler. We may have to output one more step. Only the last
+            // one of those should be flagged as being the last.
+            boolean addLast = bounds.lastIncluded() &&
+                              lastTime != interpolator.getCurrentTime();
+            doNormalizedStep(!addLast);
+            if (addLast) {
+                storeStep(interpolator, interpolator.getCurrentTime());
+                doNormalizedStep(true);
+            }
+        }
+    }
+
+    /**
+     * Returns a value indicating whether the next normalized time is in the
+     * current step.
+     * @param nextTime the next normalized time
+     * @param interpolator interpolator for the last accepted step, to use to
+     * get the end time of the current step
+     * @return value indicating whether the next normalized time is in the
+     * current step
+     */
+    private boolean isNextInStep(double nextTime,
+                                 StepInterpolator interpolator) {
+        return forward ?
+               nextTime <= interpolator.getCurrentTime() :
+               nextTime >= interpolator.getCurrentTime();
+    }
+
+    /**
+     * Invokes the underlying step handler for the current normalized step.
+     * @param isLast true if the step is the last one
+     */
+    private void doNormalizedStep(boolean isLast) {
+        if (!bounds.firstIncluded() && firstTime == lastTime) {
+            return;
+        }
+        handler.handleStep(lastTime, lastState, lastDerivatives, isLast);
+    }
+
+    /** Stores the interpolated information for the given time in the current
+     * state.
+     * @param interpolator interpolator for the last accepted step, to use to
+     * get the interpolated information
+     * @param t the time for which to store the interpolated information
+     * @exception MaxCountExceededException if the interpolator throws one because
+     * the number of functions evaluations is exceeded
+     */
+    private void storeStep(StepInterpolator interpolator, double t)
+        throws MaxCountExceededException {
+        lastTime = t;
+        interpolator.setInterpolatedTime(lastTime);
+        System.arraycopy(interpolator.getInterpolatedState(), 0,
+                         lastState, 0, lastState.length);
+        System.arraycopy(interpolator.getInterpolatedDerivatives(), 0,
+                         lastDerivatives, 0, lastDerivatives.length);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/sampling/StepNormalizerBounds.java b/src/main/java/org/apache/commons/math3/ode/sampling/StepNormalizerBounds.java
new file mode 100644
index 0000000..ca35e82
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/sampling/StepNormalizerBounds.java
@@ -0,0 +1,84 @@
+/*
+ * 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.math3.ode.sampling;
+
+/** {@link StepNormalizer Step normalizer} bounds settings. They influence
+ * whether the underlying fixed step size step handler is called for the first
+ * and last points. Note that if the last point coincides with a normalized
+ * point, then the underlying fixed step size step handler is always called,
+ * regardless of these settings.
+ * @see FieldStepNormalizer
+ * @see StepNormalizer
+ * @see StepNormalizerMode
+ * @since 3.0
+ */
+public enum StepNormalizerBounds {
+    /** Do not include the first and last points. */
+    NEITHER(false, false),
+
+    /** Include the first point, but not the last point. */
+    FIRST(true, false),
+
+    /** Include the last point, but not the first point. */
+    LAST(false, true),
+
+    /** Include both the first and last points. */
+    BOTH(true, true);
+
+    /** Whether the first point should be passed to the underlying fixed
+     * step size step handler.
+     */
+    private final boolean first;
+
+    /** Whether the last point should be passed to the underlying fixed
+     * step size step handler.
+     */
+    private final boolean last;
+
+    /**
+     * Simple constructor.
+     * @param first Whether the first point should be passed to the
+     * underlying fixed step size step handler.
+     * @param last Whether the last point should be passed to the
+     * underlying fixed step size step handler.
+     */
+    StepNormalizerBounds(final boolean first, final boolean last) {
+        this.first = first;
+        this.last = last;
+    }
+
+    /**
+     * Returns a value indicating whether the first point should be passed
+     * to the underlying fixed step size step handler.
+     * @return value indicating whether the first point should be passed
+     * to the underlying fixed step size step handler.
+     */
+    public boolean firstIncluded() {
+        return first;
+    }
+
+    /**
+     * Returns a value indicating whether the last point should be passed
+     * to the underlying fixed step size step handler.
+     * @return value indicating whether the last point should be passed
+     * to the underlying fixed step size step handler.
+     */
+    public boolean lastIncluded() {
+        return last;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/sampling/StepNormalizerMode.java b/src/main/java/org/apache/commons/math3/ode/sampling/StepNormalizerMode.java
new file mode 100644
index 0000000..c6f4c69
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/sampling/StepNormalizerMode.java
@@ -0,0 +1,70 @@
+/*
+ * 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.math3.ode.sampling;
+
+
+/** {@link StepNormalizer Step normalizer} modes. Determines how the step size
+ * is interpreted.
+ * @see FieldStepNormalizer
+ * @see StepNormalizer
+ * @see StepNormalizerBounds
+ * @since 3.0
+ */
+public enum StepNormalizerMode {
+    /**
+     * Steps are fixed increments of the start value. In other words, they
+     * are relative to the start value.
+     *
+     * <p>If the integration start time is t0, then the points handled by
+     * the underlying fixed step size step handler are t0 (depending on
+     * the {@link StepNormalizerBounds bounds settings}), t0+h, t0+2h, ...</p>
+     *
+     * <p>If the integration range is an integer multiple of the step size
+     * (h), then the last point handled will be the end point of the
+     * integration (tend). If not, the last point may be the end point
+     * tend, or it may be a point belonging to the interval [tend - h ;
+     * tend], depending on the {@link StepNormalizerBounds bounds settings}.
+     * </p>
+     *
+     * @see StepNormalizer
+     * @see StepNormalizerBounds
+     */
+    INCREMENT,
+
+    /** Steps are multiples of a fixed value. In other words, they are
+     * relative to the first multiple of the step size that is encountered
+     * after the start value.
+     *
+     * <p>If the integration start time is t0, and the first multiple of
+     * the fixed step size that is encountered is t1, then the points
+     * handled by the underlying fixed step size step handler are t0
+     * (depending on the {@link StepNormalizerBounds bounds settings}), t1,
+     * t1+h, t1+2h, ...</p>
+     *
+     * <p>If the end point of the integration range (tend) is an integer
+     * multiple of the step size (h) added to t1, then the last point
+     * handled will be the end point of the integration (tend). If not,
+     * the last point may be the end point tend, or it may be a point
+     * belonging to the interval [tend - h ; tend], depending on the
+     * {@link StepNormalizerBounds bounds settings}.</p>
+     *
+     * @see StepNormalizer
+     * @see StepNormalizerBounds
+     */
+    MULTIPLES;
+}
diff --git a/src/main/java/org/apache/commons/math3/ode/sampling/package-info.java b/src/main/java/org/apache/commons/math3/ode/sampling/package-info.java
new file mode 100644
index 0000000..29c65e0
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/ode/sampling/package-info.java
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * <p>
+ * This package provides classes to handle sampling steps during
+ * Ordinary Differential Equations integration.
+ * </p>
+ *
+ * <p>
+ * In addition to computing the evolution of the state vector at some grid points, all
+ * ODE integrators also build up interpolation models of this evolution <em>inside</em> the
+ * last computed step. If users are interested in these interpolators, they can register a
+ * {@link org.apache.commons.math3.ode.sampling.StepHandler StepHandler} instance using the
+ * {@link org.apache.commons.math3.ode.FirstOrderIntegrator#addStepHandler addStepHandler}
+ * method which is supported by all integrators. The integrator will call this instance
+ * at the end of each accepted step and provide it the interpolator. The user can do
+ * whatever he wants with this interpolator, which computes both the state and its
+ * time-derivative. A typical use of step handler is to provide some output to monitor
+ * the integration process.
+ * </p>
+ *
+ * <p>
+ * In a sense, this is a kind of Inversion Of Control: rather than having the master
+ * application driving the slave integrator by providing the target end value for
+ * the free variable, we get a master integrator scheduling the free variable
+ * evolution and calling the slave application callbacks that were registered at
+ * configuration time.
+ * </p>
+ *
+ * <p>
+ * Since some integrators may use variable step size, the generic {@link
+ * org.apache.commons.math3.ode.sampling.StepHandler StepHandler} interface can be called
+ * either at regular or irregular rate. This interface allows to navigate to any location
+ * within the last computed step, thanks to the provided {@link
+ * org.apache.commons.math3.ode.sampling.StepInterpolator StepInterpolator} object.
+ * If regular output is desired (for example in order to write an ephemeris file), then
+ * the simpler {@link org.apache.commons.math3.ode.sampling.FixedStepHandler FixedStepHandler}
+ * interface can be used. Objects implementing this interface should be wrapped within a
+ * {@link org.apache.commons.math3.ode.sampling.StepNormalizer StepNormalizer} instance
+ * in order to be registered to the integrator.
+ * </p>
+ *
+ *
+ */
+package org.apache.commons.math3.ode.sampling;
diff --git a/src/main/java/org/apache/commons/math3/optim/AbstractConvergenceChecker.java b/src/main/java/org/apache/commons/math3/optim/AbstractConvergenceChecker.java
new file mode 100644
index 0000000..19c3f62
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/AbstractConvergenceChecker.java
@@ -0,0 +1,60 @@
+/*
+ * 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.math3.optim;
+
+/**
+ * Base class for all convergence checker implementations.
+ *
+ * @param <PAIR> Type of (point, value) pair.
+ * @since 3.0
+ */
+public abstract class AbstractConvergenceChecker<PAIR> implements ConvergenceChecker<PAIR> {
+    /** Relative tolerance threshold. */
+    private final double relativeThreshold;
+
+    /** Absolute tolerance threshold. */
+    private final double absoluteThreshold;
+
+    /**
+     * Build an instance with a specified thresholds.
+     *
+     * @param relativeThreshold relative tolerance threshold
+     * @param absoluteThreshold absolute tolerance threshold
+     */
+    public AbstractConvergenceChecker(
+            final double relativeThreshold, final double absoluteThreshold) {
+        this.relativeThreshold = relativeThreshold;
+        this.absoluteThreshold = absoluteThreshold;
+    }
+
+    /**
+     * @return the relative threshold.
+     */
+    public double getRelativeThreshold() {
+        return relativeThreshold;
+    }
+
+    /**
+     * @return the absolute threshold.
+     */
+    public double getAbsoluteThreshold() {
+        return absoluteThreshold;
+    }
+
+    /** {@inheritDoc} */
+    public abstract boolean converged(int iteration, PAIR previous, PAIR current);
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/AbstractOptimizationProblem.java b/src/main/java/org/apache/commons/math3/optim/AbstractOptimizationProblem.java
new file mode 100644
index 0000000..84da354
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/AbstractOptimizationProblem.java
@@ -0,0 +1,102 @@
+/*
+ * 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.math3.optim;
+
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+import org.apache.commons.math3.exception.TooManyIterationsException;
+import org.apache.commons.math3.util.Incrementor;
+
+/**
+ * Base class for implementing optimization problems. It contains the boiler-plate code for counting
+ * the number of evaluations of the objective function and the number of iterations of the
+ * algorithm, and storing the convergence checker.
+ *
+ * @param <PAIR> Type of the point/value pair returned by the optimization algorithm.
+ * @since 3.3
+ */
+public abstract class AbstractOptimizationProblem<PAIR> implements OptimizationProblem<PAIR> {
+
+    /** Callback to use for the evaluation counter. */
+    private static final MaxEvalCallback MAX_EVAL_CALLBACK = new MaxEvalCallback();
+
+    /** Callback to use for the iteration counter. */
+    private static final MaxIterCallback MAX_ITER_CALLBACK = new MaxIterCallback();
+
+    /** max evaluations */
+    private final int maxEvaluations;
+
+    /** max iterations */
+    private final int maxIterations;
+
+    /** Convergence checker. */
+    private final ConvergenceChecker<PAIR> checker;
+
+    /**
+     * Create an {@link AbstractOptimizationProblem} from the given data.
+     *
+     * @param maxEvaluations the number of allowed model function evaluations.
+     * @param maxIterations the number of allowed iterations.
+     * @param checker the convergence checker.
+     */
+    protected AbstractOptimizationProblem(
+            final int maxEvaluations,
+            final int maxIterations,
+            final ConvergenceChecker<PAIR> checker) {
+        this.maxEvaluations = maxEvaluations;
+        this.maxIterations = maxIterations;
+        this.checker = checker;
+    }
+
+    /** {@inheritDoc} */
+    public Incrementor getEvaluationCounter() {
+        return new Incrementor(this.maxEvaluations, MAX_EVAL_CALLBACK);
+    }
+
+    /** {@inheritDoc} */
+    public Incrementor getIterationCounter() {
+        return new Incrementor(this.maxIterations, MAX_ITER_CALLBACK);
+    }
+
+    /** {@inheritDoc} */
+    public ConvergenceChecker<PAIR> getConvergenceChecker() {
+        return checker;
+    }
+
+    /** Defines the action to perform when reaching the maximum number of evaluations. */
+    private static class MaxEvalCallback implements Incrementor.MaxCountExceededCallback {
+        /**
+         * {@inheritDoc}
+         *
+         * @throws TooManyEvaluationsException
+         */
+        public void trigger(int max) {
+            throw new TooManyEvaluationsException(max);
+        }
+    }
+
+    /** Defines the action to perform when reaching the maximum number of evaluations. */
+    private static class MaxIterCallback implements Incrementor.MaxCountExceededCallback {
+        /**
+         * {@inheritDoc}
+         *
+         * @throws TooManyIterationsException
+         */
+        public void trigger(int max) {
+            throw new TooManyIterationsException(max);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/BaseMultiStartMultivariateOptimizer.java b/src/main/java/org/apache/commons/math3/optim/BaseMultiStartMultivariateOptimizer.java
new file mode 100644
index 0000000..ede4e59
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/BaseMultiStartMultivariateOptimizer.java
@@ -0,0 +1,221 @@
+/*
+ * 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.math3.optim;
+
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+import org.apache.commons.math3.random.RandomVectorGenerator;
+
+/**
+ * Base class multi-start optimizer for a multivariate function. <br>
+ * This class wraps an optimizer in order to use it several times in turn with different starting
+ * points (trying to avoid being trapped in a local extremum when looking for a global one). <em>It
+ * is not a "user" class.</em>
+ *
+ * @param <PAIR> Type of the point/value pair returned by the optimization algorithm.
+ * @since 3.0
+ */
+public abstract class BaseMultiStartMultivariateOptimizer<PAIR>
+        extends BaseMultivariateOptimizer<PAIR> {
+    /** Underlying classical optimizer. */
+    private final BaseMultivariateOptimizer<PAIR> optimizer;
+
+    /** Number of evaluations already performed for all starts. */
+    private int totalEvaluations;
+
+    /** Number of starts to go. */
+    private int starts;
+
+    /** Random generator for multi-start. */
+    private RandomVectorGenerator generator;
+
+    /** Optimization data. */
+    private OptimizationData[] optimData;
+
+    /**
+     * Location in {@link #optimData} where the updated maximum number of evaluations will be
+     * stored.
+     */
+    private int maxEvalIndex = -1;
+
+    /** Location in {@link #optimData} where the updated start value will be stored. */
+    private int initialGuessIndex = -1;
+
+    /**
+     * Create a multi-start optimizer from a single-start optimizer.
+     *
+     * <p>Note that if there are bounds constraints (see {@link #getLowerBound()} and {@link
+     * #getUpperBound()}), then a simple rejection algorithm is used at each restart. This implies
+     * that the random vector generator should have a good probability to generate vectors in the
+     * bounded domain, otherwise the rejection algorithm will hit the {@link #getMaxEvaluations()}
+     * count without generating a proper restart point. Users must be take great care of the <a
+     * href="http://en.wikipedia.org/wiki/Curse_of_dimensionality">curse of dimensionality</a>.
+     *
+     * @param optimizer Single-start optimizer to wrap.
+     * @param starts Number of starts to perform. If {@code starts == 1}, the {@link
+     *     #optimize(OptimizationData[]) optimize} will return the same solution as the given {@code
+     *     optimizer} would return.
+     * @param generator Random vector generator to use for restarts.
+     * @throws NotStrictlyPositiveException if {@code starts < 1}.
+     */
+    public BaseMultiStartMultivariateOptimizer(
+            final BaseMultivariateOptimizer<PAIR> optimizer,
+            final int starts,
+            final RandomVectorGenerator generator) {
+        super(optimizer.getConvergenceChecker());
+
+        if (starts < 1) {
+            throw new NotStrictlyPositiveException(starts);
+        }
+
+        this.optimizer = optimizer;
+        this.starts = starts;
+        this.generator = generator;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getEvaluations() {
+        return totalEvaluations;
+    }
+
+    /**
+     * Gets all the optima found during the last call to {@code optimize}. The optimizer stores all
+     * the optima found during a set of restarts. The {@code optimize} method returns the best point
+     * only. This method returns all the points found at the end of each starts, including the best
+     * one already returned by the {@code optimize} method. <br>
+     * The returned array as one element for each start as specified in the constructor. It is
+     * ordered with the results from the runs that did converge first, sorted from best to worst
+     * objective value (i.e in ascending order if minimizing and in descending order if maximizing),
+     * followed by {@code null} elements corresponding to the runs that did not converge. This means
+     * all elements will be {@code null} if the {@code optimize} method did throw an exception. This
+     * also means that if the first element is not {@code null}, it is the best point found across
+     * all starts. <br>
+     * The behaviour is undefined if this method is called before {@code optimize}; it will likely
+     * throw {@code NullPointerException}.
+     *
+     * @return an array containing the optima sorted from best to worst.
+     */
+    public abstract PAIR[] getOptima();
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws MathIllegalStateException if {@code optData} does not contain an instance of {@link
+     *     MaxEval} or {@link InitialGuess}.
+     */
+    @Override
+    public PAIR optimize(OptimizationData... optData) {
+        // Store arguments in order to pass them to the internal optimizer.
+        optimData = optData;
+        // Set up base class and perform computations.
+        return super.optimize(optData);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected PAIR doOptimize() {
+        // Remove all instances of "MaxEval" and "InitialGuess" from the
+        // array that will be passed to the internal optimizer.
+        // The former is to enforce smaller numbers of allowed evaluations
+        // (according to how many have been used up already), and the latter
+        // to impose a different start value for each start.
+        for (int i = 0; i < optimData.length; i++) {
+            if (optimData[i] instanceof MaxEval) {
+                optimData[i] = null;
+                maxEvalIndex = i;
+            }
+            if (optimData[i] instanceof InitialGuess) {
+                optimData[i] = null;
+                initialGuessIndex = i;
+                continue;
+            }
+        }
+        if (maxEvalIndex == -1) {
+            throw new MathIllegalStateException();
+        }
+        if (initialGuessIndex == -1) {
+            throw new MathIllegalStateException();
+        }
+
+        RuntimeException lastException = null;
+        totalEvaluations = 0;
+        clear();
+
+        final int maxEval = getMaxEvaluations();
+        final double[] min = getLowerBound();
+        final double[] max = getUpperBound();
+        final double[] startPoint = getStartPoint();
+
+        // Multi-start loop.
+        for (int i = 0; i < starts; i++) {
+            // CHECKSTYLE: stop IllegalCatch
+            try {
+                // Decrease number of allowed evaluations.
+                optimData[maxEvalIndex] = new MaxEval(maxEval - totalEvaluations);
+                // New start value.
+                double[] s = null;
+                if (i == 0) {
+                    s = startPoint;
+                } else {
+                    int attempts = 0;
+                    while (s == null) {
+                        if (attempts++ >= getMaxEvaluations()) {
+                            throw new TooManyEvaluationsException(getMaxEvaluations());
+                        }
+                        s = generator.nextVector();
+                        for (int k = 0; s != null && k < s.length; ++k) {
+                            if ((min != null && s[k] < min[k]) || (max != null && s[k] > max[k])) {
+                                // reject the vector
+                                s = null;
+                            }
+                        }
+                    }
+                }
+                optimData[initialGuessIndex] = new InitialGuess(s);
+                // Optimize.
+                final PAIR result = optimizer.optimize(optimData);
+                store(result);
+            } catch (RuntimeException mue) {
+                lastException = mue;
+            }
+            // CHECKSTYLE: resume IllegalCatch
+
+            totalEvaluations += optimizer.getEvaluations();
+        }
+
+        final PAIR[] optima = getOptima();
+        if (optima.length == 0) {
+            // All runs failed.
+            throw lastException; // Cannot be null if starts >= 1.
+        }
+
+        // Return the best optimum.
+        return optima[0];
+    }
+
+    /**
+     * Method that will be called in order to store each found optimum.
+     *
+     * @param optimum Result of an optimization run.
+     */
+    protected abstract void store(PAIR optimum);
+
+    /** Method that will called in order to clear all stored optima. */
+    protected abstract void clear();
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/BaseMultivariateOptimizer.java b/src/main/java/org/apache/commons/math3/optim/BaseMultivariateOptimizer.java
new file mode 100644
index 0000000..e70ab8e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/BaseMultivariateOptimizer.java
@@ -0,0 +1,152 @@
+/*
+ * 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.math3.optim;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+
+/**
+ * Base class for implementing optimizers for multivariate functions. It contains the boiler-plate
+ * code for initial guess and bounds specifications. <em>It is not a "user" class.</em>
+ *
+ * @param <PAIR> Type of the point/value pair returned by the optimization algorithm.
+ * @since 3.1
+ */
+public abstract class BaseMultivariateOptimizer<PAIR> extends BaseOptimizer<PAIR> {
+    /** Initial guess. */
+    private double[] start;
+
+    /** Lower bounds. */
+    private double[] lowerBound;
+
+    /** Upper bounds. */
+    private double[] upperBound;
+
+    /**
+     * @param checker Convergence checker.
+     */
+    protected BaseMultivariateOptimizer(ConvergenceChecker<PAIR> checker) {
+        super(checker);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @param optData Optimization data. In addition to those documented in {@link
+     *     BaseOptimizer#parseOptimizationData(OptimizationData[]) BaseOptimizer}, this method will
+     *     register the following data:
+     *     <ul>
+     *       <li>{@link InitialGuess}
+     *       <li>{@link SimpleBounds}
+     *     </ul>
+     *
+     * @return {@inheritDoc}
+     */
+    @Override
+    public PAIR optimize(OptimizationData... optData) {
+        // Perform optimization.
+        return super.optimize(optData);
+    }
+
+    /**
+     * Scans the list of (required and optional) optimization data that characterize the problem.
+     *
+     * @param optData Optimization data. The following data will be looked for:
+     *     <ul>
+     *       <li>{@link InitialGuess}
+     *       <li>{@link SimpleBounds}
+     *     </ul>
+     */
+    @Override
+    protected void parseOptimizationData(OptimizationData... optData) {
+        // Allow base class to register its own data.
+        super.parseOptimizationData(optData);
+
+        // The existing values (as set by the previous call) are reused if
+        // not provided in the argument list.
+        for (OptimizationData data : optData) {
+            if (data instanceof InitialGuess) {
+                start = ((InitialGuess) data).getInitialGuess();
+                continue;
+            }
+            if (data instanceof SimpleBounds) {
+                final SimpleBounds bounds = (SimpleBounds) data;
+                lowerBound = bounds.getLower();
+                upperBound = bounds.getUpper();
+                continue;
+            }
+        }
+
+        // Check input consistency.
+        checkParameters();
+    }
+
+    /**
+     * Gets the initial guess.
+     *
+     * @return the initial guess, or {@code null} if not set.
+     */
+    public double[] getStartPoint() {
+        return start == null ? null : start.clone();
+    }
+
+    /**
+     * @return the lower bounds, or {@code null} if not set.
+     */
+    public double[] getLowerBound() {
+        return lowerBound == null ? null : lowerBound.clone();
+    }
+
+    /**
+     * @return the upper bounds, or {@code null} if not set.
+     */
+    public double[] getUpperBound() {
+        return upperBound == null ? null : upperBound.clone();
+    }
+
+    /** Check parameters consistency. */
+    private void checkParameters() {
+        if (start != null) {
+            final int dim = start.length;
+            if (lowerBound != null) {
+                if (lowerBound.length != dim) {
+                    throw new DimensionMismatchException(lowerBound.length, dim);
+                }
+                for (int i = 0; i < dim; i++) {
+                    final double v = start[i];
+                    final double lo = lowerBound[i];
+                    if (v < lo) {
+                        throw new NumberIsTooSmallException(v, lo, true);
+                    }
+                }
+            }
+            if (upperBound != null) {
+                if (upperBound.length != dim) {
+                    throw new DimensionMismatchException(upperBound.length, dim);
+                }
+                for (int i = 0; i < dim; i++) {
+                    final double v = start[i];
+                    final double hi = upperBound[i];
+                    if (v > hi) {
+                        throw new NumberIsTooLargeException(v, hi, true);
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/BaseOptimizer.java b/src/main/java/org/apache/commons/math3/optim/BaseOptimizer.java
new file mode 100644
index 0000000..80f7527
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/BaseOptimizer.java
@@ -0,0 +1,233 @@
+/*
+ * 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.math3.optim;
+
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+import org.apache.commons.math3.exception.TooManyIterationsException;
+import org.apache.commons.math3.util.Incrementor;
+
+/**
+ * Base class for implementing optimizers. It contains the boiler-plate code for counting the number
+ * of evaluations of the objective function and the number of iterations of the algorithm, and
+ * storing the convergence checker. <em>It is not a "user" class.</em>
+ *
+ * @param <PAIR> Type of the point/value pair returned by the optimization algorithm.
+ * @since 3.1
+ */
+public abstract class BaseOptimizer<PAIR> {
+    /** Evaluations counter. */
+    protected final Incrementor evaluations;
+
+    /** Iterations counter. */
+    protected final Incrementor iterations;
+
+    /** Convergence checker. */
+    private final ConvergenceChecker<PAIR> checker;
+
+    /**
+     * @param checker Convergence checker.
+     */
+    protected BaseOptimizer(ConvergenceChecker<PAIR> checker) {
+        this(checker, 0, Integer.MAX_VALUE);
+    }
+
+    /**
+     * @param checker Convergence checker.
+     * @param maxEval Maximum number of objective function evaluations.
+     * @param maxIter Maximum number of algorithm iterations.
+     */
+    protected BaseOptimizer(ConvergenceChecker<PAIR> checker, int maxEval, int maxIter) {
+        this.checker = checker;
+
+        evaluations = new Incrementor(maxEval, new MaxEvalCallback());
+        iterations = new Incrementor(maxIter, new MaxIterCallback());
+    }
+
+    /**
+     * Gets the maximal number of function evaluations.
+     *
+     * @return the maximal number of function evaluations.
+     */
+    public int getMaxEvaluations() {
+        return evaluations.getMaximalCount();
+    }
+
+    /**
+     * Gets the number of evaluations of the objective function. The number of evaluations
+     * corresponds to the last call to the {@code optimize} method. It is 0 if the method has not
+     * been called yet.
+     *
+     * @return the number of evaluations of the objective function.
+     */
+    public int getEvaluations() {
+        return evaluations.getCount();
+    }
+
+    /**
+     * Gets the maximal number of iterations.
+     *
+     * @return the maximal number of iterations.
+     */
+    public int getMaxIterations() {
+        return iterations.getMaximalCount();
+    }
+
+    /**
+     * Gets the number of iterations performed by the algorithm. The number iterations corresponds
+     * to the last call to the {@code optimize} method. It is 0 if the method has not been called
+     * yet.
+     *
+     * @return the number of evaluations of the objective function.
+     */
+    public int getIterations() {
+        return iterations.getCount();
+    }
+
+    /**
+     * Gets the convergence checker.
+     *
+     * @return the object used to check for convergence.
+     */
+    public ConvergenceChecker<PAIR> getConvergenceChecker() {
+        return checker;
+    }
+
+    /**
+     * Stores data and performs the optimization.
+     *
+     * <p>The list of parameters is open-ended so that sub-classes can extend it with arguments
+     * specific to their concrete implementations.
+     *
+     * <p>When the method is called multiple times, instance data is overwritten only when actually
+     * present in the list of arguments: when not specified, data set in a previous call is retained
+     * (and thus is optional in subsequent calls).
+     *
+     * <p>Important note: Subclasses <em>must</em> override {@link
+     * #parseOptimizationData(OptimizationData[])} if they need to register their own options; but
+     * then, they <em>must</em> also call {@code super.parseOptimizationData(optData)} within that
+     * method.
+     *
+     * @param optData Optimization data. This method will register the following data:
+     *     <ul>
+     *       <li>{@link MaxEval}
+     *       <li>{@link MaxIter}
+     *     </ul>
+     *
+     * @return a point/value pair that satisfies the convergence criteria.
+     * @throws TooManyEvaluationsException if the maximal number of evaluations is exceeded.
+     * @throws TooManyIterationsException if the maximal number of iterations is exceeded.
+     */
+    public PAIR optimize(OptimizationData... optData)
+            throws TooManyEvaluationsException, TooManyIterationsException {
+        // Parse options.
+        parseOptimizationData(optData);
+
+        // Reset counters.
+        evaluations.resetCount();
+        iterations.resetCount();
+        // Perform optimization.
+        return doOptimize();
+    }
+
+    /**
+     * Performs the optimization.
+     *
+     * @return a point/value pair that satisfies the convergence criteria.
+     * @throws TooManyEvaluationsException if the maximal number of evaluations is exceeded.
+     * @throws TooManyIterationsException if the maximal number of iterations is exceeded.
+     */
+    public PAIR optimize() throws TooManyEvaluationsException, TooManyIterationsException {
+        // Reset counters.
+        evaluations.resetCount();
+        iterations.resetCount();
+        // Perform optimization.
+        return doOptimize();
+    }
+
+    /**
+     * Performs the bulk of the optimization algorithm.
+     *
+     * @return the point/value pair giving the optimal value of the objective function.
+     */
+    protected abstract PAIR doOptimize();
+
+    /**
+     * Increment the evaluation count.
+     *
+     * @throws TooManyEvaluationsException if the allowed evaluations have been exhausted.
+     */
+    protected void incrementEvaluationCount() throws TooManyEvaluationsException {
+        evaluations.incrementCount();
+    }
+
+    /**
+     * Increment the iteration count.
+     *
+     * @throws TooManyIterationsException if the allowed iterations have been exhausted.
+     */
+    protected void incrementIterationCount() throws TooManyIterationsException {
+        iterations.incrementCount();
+    }
+
+    /**
+     * Scans the list of (required and optional) optimization data that characterize the problem.
+     *
+     * @param optData Optimization data. The following data will be looked for:
+     *     <ul>
+     *       <li>{@link MaxEval}
+     *       <li>{@link MaxIter}
+     *     </ul>
+     */
+    protected void parseOptimizationData(OptimizationData... optData) {
+        // The existing values (as set by the previous call) are reused if
+        // not provided in the argument list.
+        for (OptimizationData data : optData) {
+            if (data instanceof MaxEval) {
+                evaluations.setMaximalCount(((MaxEval) data).getMaxEval());
+                continue;
+            }
+            if (data instanceof MaxIter) {
+                iterations.setMaximalCount(((MaxIter) data).getMaxIter());
+                continue;
+            }
+        }
+    }
+
+    /** Defines the action to perform when reaching the maximum number of evaluations. */
+    private static class MaxEvalCallback implements Incrementor.MaxCountExceededCallback {
+        /**
+         * {@inheritDoc}
+         *
+         * @throws TooManyEvaluationsException
+         */
+        public void trigger(int max) {
+            throw new TooManyEvaluationsException(max);
+        }
+    }
+
+    /** Defines the action to perform when reaching the maximum number of evaluations. */
+    private static class MaxIterCallback implements Incrementor.MaxCountExceededCallback {
+        /**
+         * {@inheritDoc}
+         *
+         * @throws TooManyIterationsException
+         */
+        public void trigger(int max) {
+            throw new TooManyIterationsException(max);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/ConvergenceChecker.java b/src/main/java/org/apache/commons/math3/optim/ConvergenceChecker.java
new file mode 100644
index 0000000..8064560
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/ConvergenceChecker.java
@@ -0,0 +1,48 @@
+/*
+ * 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.math3.optim;
+
+/**
+ * This interface specifies how to check if an optimization algorithm has converged. <br>
+ * Deciding if convergence has been reached is a problem-dependent issue. The user should provide a
+ * class implementing this interface to allow the optimization algorithm to stop its search
+ * according to the problem at hand. <br>
+ * For convenience, three implementations that fit simple needs are already provided: {@link
+ * SimpleValueChecker}, {@link SimpleVectorValueChecker} and {@link SimplePointChecker}. The first
+ * two consider that convergence is reached when the objective function value does not change much
+ * anymore, it does not use the point set at all. The third one considers that convergence is
+ * reached when the input point set does not change much anymore, it does not use objective function
+ * value at all.
+ *
+ * @param <PAIR> Type of the (point, objective value) pair.
+ * @see org.apache.commons.math3.optim.SimplePointChecker
+ * @see org.apache.commons.math3.optim.SimpleValueChecker
+ * @see org.apache.commons.math3.optim.SimpleVectorValueChecker
+ * @since 3.0
+ */
+public interface ConvergenceChecker<PAIR> {
+    /**
+     * Check if the optimization algorithm has converged.
+     *
+     * @param iteration Current iteration.
+     * @param previous Best point in the previous iteration.
+     * @param current Best point in the current iteration.
+     * @return {@code true} if the algorithm is considered to have converged.
+     */
+    boolean converged(int iteration, PAIR previous, PAIR current);
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/InitialGuess.java b/src/main/java/org/apache/commons/math3/optim/InitialGuess.java
new file mode 100644
index 0000000..9323c6f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/InitialGuess.java
@@ -0,0 +1,45 @@
+/*
+ * 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.math3.optim;
+
+/**
+ * Starting point (first guess) of the optimization procedure. <br>
+ * Immutable class.
+ *
+ * @since 3.1
+ */
+public class InitialGuess implements OptimizationData {
+    /** Initial guess. */
+    private final double[] init;
+
+    /**
+     * @param startPoint Initial guess.
+     */
+    public InitialGuess(double[] startPoint) {
+        init = startPoint.clone();
+    }
+
+    /**
+     * Gets the initial guess.
+     *
+     * @return the initial guess.
+     */
+    public double[] getInitialGuess() {
+        return init.clone();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/MaxEval.java b/src/main/java/org/apache/commons/math3/optim/MaxEval.java
new file mode 100644
index 0000000..3c6478e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/MaxEval.java
@@ -0,0 +1,60 @@
+/*
+ * 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.math3.optim;
+
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+
+/**
+ * Maximum number of evaluations of the function to be optimized.
+ *
+ * @since 3.1
+ */
+public class MaxEval implements OptimizationData {
+    /** Allowed number of evalutations. */
+    private final int maxEval;
+
+    /**
+     * @param max Allowed number of evalutations.
+     * @throws NotStrictlyPositiveException if {@code max <= 0}.
+     */
+    public MaxEval(int max) {
+        if (max <= 0) {
+            throw new NotStrictlyPositiveException(max);
+        }
+
+        maxEval = max;
+    }
+
+    /**
+     * Gets the maximum number of evaluations.
+     *
+     * @return the allowed number of evaluations.
+     */
+    public int getMaxEval() {
+        return maxEval;
+    }
+
+    /**
+     * Factory method that creates instance of this class that represents a virtually unlimited
+     * number of evaluations.
+     *
+     * @return a new instance suitable for allowing {@link Integer#MAX_VALUE} evaluations.
+     */
+    public static MaxEval unlimited() {
+        return new MaxEval(Integer.MAX_VALUE);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/MaxIter.java b/src/main/java/org/apache/commons/math3/optim/MaxIter.java
new file mode 100644
index 0000000..dc9c917
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/MaxIter.java
@@ -0,0 +1,60 @@
+/*
+ * 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.math3.optim;
+
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+
+/**
+ * Maximum number of iterations performed by an (iterative) algorithm.
+ *
+ * @since 3.1
+ */
+public class MaxIter implements OptimizationData {
+    /** Allowed number of evalutations. */
+    private final int maxIter;
+
+    /**
+     * @param max Allowed number of iterations.
+     * @throws NotStrictlyPositiveException if {@code max <= 0}.
+     */
+    public MaxIter(int max) {
+        if (max <= 0) {
+            throw new NotStrictlyPositiveException(max);
+        }
+
+        maxIter = max;
+    }
+
+    /**
+     * Gets the maximum number of evaluations.
+     *
+     * @return the allowed number of evaluations.
+     */
+    public int getMaxIter() {
+        return maxIter;
+    }
+
+    /**
+     * Factory method that creates instance of this class that represents a virtually unlimited
+     * number of iterations.
+     *
+     * @return a new instance suitable for allowing {@link Integer#MAX_VALUE} evaluations.
+     */
+    public static MaxIter unlimited() {
+        return new MaxIter(Integer.MAX_VALUE);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/OptimizationData.java b/src/main/java/org/apache/commons/math3/optim/OptimizationData.java
new file mode 100644
index 0000000..4f55472
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/OptimizationData.java
@@ -0,0 +1,26 @@
+/*
+ * 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.math3.optim;
+
+/**
+ * Marker interface. Implementations will provide functionality (optional or required) needed by the
+ * optimizers, and those will need to check the actual type of the arguments and perform the
+ * appropriate cast in order to access the data they need.
+ *
+ * @since 3.1
+ */
+public interface OptimizationData {}
diff --git a/src/main/java/org/apache/commons/math3/optim/OptimizationProblem.java b/src/main/java/org/apache/commons/math3/optim/OptimizationProblem.java
new file mode 100644
index 0000000..1b77117
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/OptimizationProblem.java
@@ -0,0 +1,52 @@
+/*
+ * 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.math3.optim;
+
+import org.apache.commons.math3.util.Incrementor;
+
+/**
+ * Common settings for all optimization problems. Includes divergence and convergence criteria.
+ *
+ * @param <PAIR> The type of value the {@link #getConvergenceChecker() convergence checker} will
+ *     operate on. It should include the value of the model function and point where it was
+ *     evaluated.
+ * @since 3.3
+ */
+public interface OptimizationProblem<PAIR> {
+    /**
+     * Get a independent Incrementor that counts up to the maximum number of evaluations and then
+     * throws an exception.
+     *
+     * @return a counter for the evaluations.
+     */
+    Incrementor getEvaluationCounter();
+
+    /**
+     * Get a independent Incrementor that counts up to the maximum number of iterations and then
+     * throws an exception.
+     *
+     * @return a counter for the evaluations.
+     */
+    Incrementor getIterationCounter();
+
+    /**
+     * Gets the convergence checker.
+     *
+     * @return the object used to check for convergence.
+     */
+    ConvergenceChecker<PAIR> getConvergenceChecker();
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/PointValuePair.java b/src/main/java/org/apache/commons/math3/optim/PointValuePair.java
new file mode 100644
index 0000000..8f7cc01
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/PointValuePair.java
@@ -0,0 +1,116 @@
+/*
+ * 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.math3.optim;
+
+import org.apache.commons.math3.util.Pair;
+
+import java.io.Serializable;
+
+/**
+ * This class holds a point and the value of an objective function at that point.
+ *
+ * @see PointVectorValuePair
+ * @see org.apache.commons.math3.analysis.MultivariateFunction
+ * @since 3.0
+ */
+public class PointValuePair extends Pair<double[], Double> implements Serializable {
+    /** Serializable UID. */
+    private static final long serialVersionUID = 20120513L;
+
+    /**
+     * Builds a point/objective function value pair.
+     *
+     * @param point Point coordinates. This instance will store a copy of the array, not the array
+     *     passed as argument.
+     * @param value Value of the objective function at the point.
+     */
+    public PointValuePair(final double[] point, final double value) {
+        this(point, value, true);
+    }
+
+    /**
+     * Builds a point/objective function value pair.
+     *
+     * @param point Point coordinates.
+     * @param value Value of the objective function at the point.
+     * @param copyArray if {@code true}, the input array will be copied, otherwise it will be
+     *     referenced.
+     */
+    public PointValuePair(final double[] point, final double value, final boolean copyArray) {
+        super(copyArray ? ((point == null) ? null : point.clone()) : point, value);
+    }
+
+    /**
+     * Gets the point.
+     *
+     * @return a copy of the stored point.
+     */
+    public double[] getPoint() {
+        final double[] p = getKey();
+        return p == null ? null : p.clone();
+    }
+
+    /**
+     * Gets a reference to the point.
+     *
+     * @return a reference to the internal array storing the point.
+     */
+    public double[] getPointRef() {
+        return getKey();
+    }
+
+    /**
+     * Replace the instance with a data transfer object for serialization.
+     *
+     * @return data transfer object that will be serialized
+     */
+    private Object writeReplace() {
+        return new DataTransferObject(getKey(), getValue());
+    }
+
+    /** Internal class used only for serialization. */
+    private static class DataTransferObject implements Serializable {
+        /** Serializable UID. */
+        private static final long serialVersionUID = 20120513L;
+
+        /** Point coordinates. @Serial */
+        private final double[] point;
+
+        /** Value of the objective function at the point. @Serial */
+        private final double value;
+
+        /**
+         * Simple constructor.
+         *
+         * @param point Point coordinates.
+         * @param value Value of the objective function at the point.
+         */
+        DataTransferObject(final double[] point, final double value) {
+            this.point = point.clone();
+            this.value = value;
+        }
+
+        /**
+         * Replace the deserialized data transfer object with a {@link PointValuePair}.
+         *
+         * @return replacement {@link PointValuePair}
+         */
+        private Object readResolve() {
+            return new PointValuePair(point, value, false);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/PointVectorValuePair.java b/src/main/java/org/apache/commons/math3/optim/PointVectorValuePair.java
new file mode 100644
index 0000000..c0ba93e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/PointVectorValuePair.java
@@ -0,0 +1,139 @@
+/*
+ * 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.math3.optim;
+
+import org.apache.commons.math3.util.Pair;
+
+import java.io.Serializable;
+
+/**
+ * This class holds a point and the vectorial value of an objective function at that point.
+ *
+ * @see PointValuePair
+ * @see org.apache.commons.math3.analysis.MultivariateVectorFunction
+ * @since 3.0
+ */
+public class PointVectorValuePair extends Pair<double[], double[]> implements Serializable {
+    /** Serializable UID. */
+    private static final long serialVersionUID = 20120513L;
+
+    /**
+     * Builds a point/objective function value pair.
+     *
+     * @param point Point coordinates. This instance will store a copy of the array, not the array
+     *     passed as argument.
+     * @param value Value of the objective function at the point.
+     */
+    public PointVectorValuePair(final double[] point, final double[] value) {
+        this(point, value, true);
+    }
+
+    /**
+     * Build a point/objective function value pair.
+     *
+     * @param point Point coordinates.
+     * @param value Value of the objective function at the point.
+     * @param copyArray if {@code true}, the input arrays will be copied, otherwise they will be
+     *     referenced.
+     */
+    public PointVectorValuePair(
+            final double[] point, final double[] value, final boolean copyArray) {
+        super(
+                copyArray ? ((point == null) ? null : point.clone()) : point,
+                copyArray ? ((value == null) ? null : value.clone()) : value);
+    }
+
+    /**
+     * Gets the point.
+     *
+     * @return a copy of the stored point.
+     */
+    public double[] getPoint() {
+        final double[] p = getKey();
+        return p == null ? null : p.clone();
+    }
+
+    /**
+     * Gets a reference to the point.
+     *
+     * @return a reference to the internal array storing the point.
+     */
+    public double[] getPointRef() {
+        return getKey();
+    }
+
+    /**
+     * Gets the value of the objective function.
+     *
+     * @return a copy of the stored value of the objective function.
+     */
+    @Override
+    public double[] getValue() {
+        final double[] v = super.getValue();
+        return v == null ? null : v.clone();
+    }
+
+    /**
+     * Gets a reference to the value of the objective function.
+     *
+     * @return a reference to the internal array storing the value of the objective function.
+     */
+    public double[] getValueRef() {
+        return super.getValue();
+    }
+
+    /**
+     * Replace the instance with a data transfer object for serialization.
+     *
+     * @return data transfer object that will be serialized
+     */
+    private Object writeReplace() {
+        return new DataTransferObject(getKey(), getValue());
+    }
+
+    /** Internal class used only for serialization. */
+    private static class DataTransferObject implements Serializable {
+        /** Serializable UID. */
+        private static final long serialVersionUID = 20120513L;
+
+        /** Point coordinates. @Serial */
+        private final double[] point;
+
+        /** Value of the objective function at the point. @Serial */
+        private final double[] value;
+
+        /**
+         * Simple constructor.
+         *
+         * @param point Point coordinates.
+         * @param value Value of the objective function at the point.
+         */
+        DataTransferObject(final double[] point, final double[] value) {
+            this.point = point.clone();
+            this.value = value.clone();
+        }
+
+        /**
+         * Replace the deserialized data transfer object with a {@link PointValuePair}.
+         *
+         * @return replacement {@link PointValuePair}
+         */
+        private Object readResolve() {
+            return new PointVectorValuePair(point, value, false);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/SimpleBounds.java b/src/main/java/org/apache/commons/math3/optim/SimpleBounds.java
new file mode 100644
index 0000000..8fffb25
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/SimpleBounds.java
@@ -0,0 +1,77 @@
+/*
+ * 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.math3.optim;
+
+import java.util.Arrays;
+
+/**
+ * Simple optimization constraints: lower and upper bounds. The valid range of the parameters is an
+ * interval that can be infinite (in one or both directions). <br>
+ * Immutable class.
+ *
+ * @since 3.1
+ */
+public class SimpleBounds implements OptimizationData {
+    /** Lower bounds. */
+    private final double[] lower;
+
+    /** Upper bounds. */
+    private final double[] upper;
+
+    /**
+     * @param lB Lower bounds.
+     * @param uB Upper bounds.
+     */
+    public SimpleBounds(double[] lB, double[] uB) {
+        lower = lB.clone();
+        upper = uB.clone();
+    }
+
+    /**
+     * Gets the lower bounds.
+     *
+     * @return the lower bounds.
+     */
+    public double[] getLower() {
+        return lower.clone();
+    }
+
+    /**
+     * Gets the upper bounds.
+     *
+     * @return the upper bounds.
+     */
+    public double[] getUpper() {
+        return upper.clone();
+    }
+
+    /**
+     * Factory method that creates instance of this class that represents unbounded ranges.
+     *
+     * @param dim Number of parameters.
+     * @return a new instance suitable for passing to an optimizer that requires bounds
+     *     specification.
+     */
+    public static SimpleBounds unbounded(int dim) {
+        final double[] lB = new double[dim];
+        Arrays.fill(lB, Double.NEGATIVE_INFINITY);
+        final double[] uB = new double[dim];
+        Arrays.fill(uB, Double.POSITIVE_INFINITY);
+
+        return new SimpleBounds(lB, uB);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/SimplePointChecker.java b/src/main/java/org/apache/commons/math3/optim/SimplePointChecker.java
new file mode 100644
index 0000000..f831de6
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/SimplePointChecker.java
@@ -0,0 +1,117 @@
+/*
+ * 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.math3.optim;
+
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.Pair;
+
+/**
+ * Simple implementation of the {@link ConvergenceChecker} interface using only point coordinates.
+ *
+ * <p>Convergence is considered to have been reached if either the relative difference between each
+ * point coordinate are smaller than a threshold or if either the absolute difference between the
+ * point coordinates are smaller than another threshold. <br>
+ * The {@link #converged(int,Pair,Pair) converged} method will also return {@code true} if the
+ * number of iterations has been set (see {@link #SimplePointChecker(double,double,int) this
+ * constructor}).
+ *
+ * @param <PAIR> Type of the (point, value) pair. The type of the "value" part of the pair (not used
+ *     by this class).
+ * @since 3.0
+ */
+public class SimplePointChecker<PAIR extends Pair<double[], ? extends Object>>
+        extends AbstractConvergenceChecker<PAIR> {
+    /**
+     * If {@link #maxIterationCount} is set to this value, the number of iterations will never cause
+     * {@link #converged(int, Pair, Pair)} to return {@code true}.
+     */
+    private static final int ITERATION_CHECK_DISABLED = -1;
+
+    /**
+     * Number of iterations after which the {@link #converged(int, Pair, Pair)} method will return
+     * true (unless the check is disabled).
+     */
+    private final int maxIterationCount;
+
+    /**
+     * Build an instance with specified thresholds. In order to perform only relative checks, the
+     * absolute tolerance must be set to a negative value. In order to perform only absolute checks,
+     * the relative tolerance must be set to a negative value.
+     *
+     * @param relativeThreshold relative tolerance threshold
+     * @param absoluteThreshold absolute tolerance threshold
+     */
+    public SimplePointChecker(final double relativeThreshold, final double absoluteThreshold) {
+        super(relativeThreshold, absoluteThreshold);
+        maxIterationCount = ITERATION_CHECK_DISABLED;
+    }
+
+    /**
+     * Builds an instance with specified thresholds. In order to perform only relative checks, the
+     * absolute tolerance must be set to a negative value. In order to perform only absolute checks,
+     * the relative tolerance must be set to a negative value.
+     *
+     * @param relativeThreshold Relative tolerance threshold.
+     * @param absoluteThreshold Absolute tolerance threshold.
+     * @param maxIter Maximum iteration count.
+     * @throws NotStrictlyPositiveException if {@code maxIter <= 0}.
+     * @since 3.1
+     */
+    public SimplePointChecker(
+            final double relativeThreshold, final double absoluteThreshold, final int maxIter) {
+        super(relativeThreshold, absoluteThreshold);
+
+        if (maxIter <= 0) {
+            throw new NotStrictlyPositiveException(maxIter);
+        }
+        maxIterationCount = maxIter;
+    }
+
+    /**
+     * Check if the optimization algorithm has converged considering the last two points. This
+     * method may be called several times from the same algorithm iteration with different points.
+     * This can be detected by checking the iteration number at each call if needed. Each time this
+     * method is called, the previous and current point correspond to points with the same role at
+     * each iteration, so they can be compared. As an example, simplex-based algorithms call this
+     * method for all points of the simplex, not only for the best or worst ones.
+     *
+     * @param iteration Index of current iteration
+     * @param previous Best point in the previous iteration.
+     * @param current Best point in the current iteration.
+     * @return {@code true} if the arguments satify the convergence criterion.
+     */
+    @Override
+    public boolean converged(final int iteration, final PAIR previous, final PAIR current) {
+        if (maxIterationCount != ITERATION_CHECK_DISABLED && iteration >= maxIterationCount) {
+            return true;
+        }
+
+        final double[] p = previous.getKey();
+        final double[] c = current.getKey();
+        for (int i = 0; i < p.length; ++i) {
+            final double pi = p[i];
+            final double ci = c[i];
+            final double difference = FastMath.abs(pi - ci);
+            final double size = FastMath.max(FastMath.abs(pi), FastMath.abs(ci));
+            if (difference > size * getRelativeThreshold() && difference > getAbsoluteThreshold()) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/SimpleValueChecker.java b/src/main/java/org/apache/commons/math3/optim/SimpleValueChecker.java
new file mode 100644
index 0000000..968fc6e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/SimpleValueChecker.java
@@ -0,0 +1,113 @@
+/*
+ * 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.math3.optim;
+
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Simple implementation of the {@link ConvergenceChecker} interface using only objective function
+ * values.
+ *
+ * <p>Convergence is considered to have been reached if either the relative difference between the
+ * objective function values is smaller than a threshold or if either the absolute difference
+ * between the objective function values is smaller than another threshold. <br>
+ * The {@link #converged(int,PointValuePair,PointValuePair) converged} method will also return
+ * {@code true} if the number of iterations has been set (see {@link
+ * #SimpleValueChecker(double,double,int) this constructor}).
+ *
+ * @since 3.0
+ */
+public class SimpleValueChecker extends AbstractConvergenceChecker<PointValuePair> {
+    /**
+     * If {@link #maxIterationCount} is set to this value, the number of iterations will never cause
+     * {@link #converged(int,PointValuePair,PointValuePair)} to return {@code true}.
+     */
+    private static final int ITERATION_CHECK_DISABLED = -1;
+
+    /**
+     * Number of iterations after which the {@link #converged(int,PointValuePair,PointValuePair)}
+     * method will return true (unless the check is disabled).
+     */
+    private final int maxIterationCount;
+
+    /**
+     * Build an instance with specified thresholds.
+     *
+     * <p>In order to perform only relative checks, the absolute tolerance must be set to a negative
+     * value. In order to perform only absolute checks, the relative tolerance must be set to a
+     * negative value.
+     *
+     * @param relativeThreshold relative tolerance threshold
+     * @param absoluteThreshold absolute tolerance threshold
+     */
+    public SimpleValueChecker(final double relativeThreshold, final double absoluteThreshold) {
+        super(relativeThreshold, absoluteThreshold);
+        maxIterationCount = ITERATION_CHECK_DISABLED;
+    }
+
+    /**
+     * Builds an instance with specified thresholds.
+     *
+     * <p>In order to perform only relative checks, the absolute tolerance must be set to a negative
+     * value. In order to perform only absolute checks, the relative tolerance must be set to a
+     * negative value.
+     *
+     * @param relativeThreshold relative tolerance threshold
+     * @param absoluteThreshold absolute tolerance threshold
+     * @param maxIter Maximum iteration count.
+     * @throws NotStrictlyPositiveException if {@code maxIter <= 0}.
+     * @since 3.1
+     */
+    public SimpleValueChecker(
+            final double relativeThreshold, final double absoluteThreshold, final int maxIter) {
+        super(relativeThreshold, absoluteThreshold);
+
+        if (maxIter <= 0) {
+            throw new NotStrictlyPositiveException(maxIter);
+        }
+        maxIterationCount = maxIter;
+    }
+
+    /**
+     * Check if the optimization algorithm has converged considering the last two points. This
+     * method may be called several time from the same algorithm iteration with different points.
+     * This can be detected by checking the iteration number at each call if needed. Each time this
+     * method is called, the previous and current point correspond to points with the same role at
+     * each iteration, so they can be compared. As an example, simplex-based algorithms call this
+     * method for all points of the simplex, not only for the best or worst ones.
+     *
+     * @param iteration Index of current iteration
+     * @param previous Best point in the previous iteration.
+     * @param current Best point in the current iteration.
+     * @return {@code true} if the algorithm has converged.
+     */
+    @Override
+    public boolean converged(
+            final int iteration, final PointValuePair previous, final PointValuePair current) {
+        if (maxIterationCount != ITERATION_CHECK_DISABLED && iteration >= maxIterationCount) {
+            return true;
+        }
+
+        final double p = previous.getValue();
+        final double c = current.getValue();
+        final double difference = FastMath.abs(p - c);
+        final double size = FastMath.max(FastMath.abs(p), FastMath.abs(c));
+        return difference <= size * getRelativeThreshold() || difference <= getAbsoluteThreshold();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/SimpleVectorValueChecker.java b/src/main/java/org/apache/commons/math3/optim/SimpleVectorValueChecker.java
new file mode 100644
index 0000000..0327bd1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/SimpleVectorValueChecker.java
@@ -0,0 +1,125 @@
+/*
+ * 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.math3.optim;
+
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Simple implementation of the {@link ConvergenceChecker} interface using only objective function
+ * values.
+ *
+ * <p>Convergence is considered to have been reached if either the relative difference between the
+ * objective function values is smaller than a threshold or if either the absolute difference
+ * between the objective function values is smaller than another threshold for all vectors elements.
+ * <br>
+ * The {@link #converged(int,PointVectorValuePair,PointVectorValuePair) converged} method will also
+ * return {@code true} if the number of iterations has been set (see {@link
+ * #SimpleVectorValueChecker(double,double,int) this constructor}).
+ *
+ * @since 3.0
+ */
+public class SimpleVectorValueChecker extends AbstractConvergenceChecker<PointVectorValuePair> {
+    /**
+     * If {@link #maxIterationCount} is set to this value, the number of iterations will never cause
+     * {@link #converged(int,PointVectorValuePair,PointVectorValuePair)} to return {@code true}.
+     */
+    private static final int ITERATION_CHECK_DISABLED = -1;
+
+    /**
+     * Number of iterations after which the {@link
+     * #converged(int,PointVectorValuePair,PointVectorValuePair)} method will return true (unless
+     * the check is disabled).
+     */
+    private final int maxIterationCount;
+
+    /**
+     * Build an instance with specified thresholds.
+     *
+     * <p>In order to perform only relative checks, the absolute tolerance must be set to a negative
+     * value. In order to perform only absolute checks, the relative tolerance must be set to a
+     * negative value.
+     *
+     * @param relativeThreshold relative tolerance threshold
+     * @param absoluteThreshold absolute tolerance threshold
+     */
+    public SimpleVectorValueChecker(
+            final double relativeThreshold, final double absoluteThreshold) {
+        super(relativeThreshold, absoluteThreshold);
+        maxIterationCount = ITERATION_CHECK_DISABLED;
+    }
+
+    /**
+     * Builds an instance with specified tolerance thresholds and iteration count.
+     *
+     * <p>In order to perform only relative checks, the absolute tolerance must be set to a negative
+     * value. In order to perform only absolute checks, the relative tolerance must be set to a
+     * negative value.
+     *
+     * @param relativeThreshold Relative tolerance threshold.
+     * @param absoluteThreshold Absolute tolerance threshold.
+     * @param maxIter Maximum iteration count.
+     * @throws NotStrictlyPositiveException if {@code maxIter <= 0}.
+     * @since 3.1
+     */
+    public SimpleVectorValueChecker(
+            final double relativeThreshold, final double absoluteThreshold, final int maxIter) {
+        super(relativeThreshold, absoluteThreshold);
+
+        if (maxIter <= 0) {
+            throw new NotStrictlyPositiveException(maxIter);
+        }
+        maxIterationCount = maxIter;
+    }
+
+    /**
+     * Check if the optimization algorithm has converged considering the last two points. This
+     * method may be called several times from the same algorithm iteration with different points.
+     * This can be detected by checking the iteration number at each call if needed. Each time this
+     * method is called, the previous and current point correspond to points with the same role at
+     * each iteration, so they can be compared. As an example, simplex-based algorithms call this
+     * method for all points of the simplex, not only for the best or worst ones.
+     *
+     * @param iteration Index of current iteration
+     * @param previous Best point in the previous iteration.
+     * @param current Best point in the current iteration.
+     * @return {@code true} if the arguments satify the convergence criterion.
+     */
+    @Override
+    public boolean converged(
+            final int iteration,
+            final PointVectorValuePair previous,
+            final PointVectorValuePair current) {
+        if (maxIterationCount != ITERATION_CHECK_DISABLED && iteration >= maxIterationCount) {
+            return true;
+        }
+
+        final double[] p = previous.getValueRef();
+        final double[] c = current.getValueRef();
+        for (int i = 0; i < p.length; ++i) {
+            final double pi = p[i];
+            final double ci = c[i];
+            final double difference = FastMath.abs(pi - ci);
+            final double size = FastMath.max(FastMath.abs(pi), FastMath.abs(ci));
+            if (difference > size * getRelativeThreshold() && difference > getAbsoluteThreshold()) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/linear/LinearConstraint.java b/src/main/java/org/apache/commons/math3/optim/linear/LinearConstraint.java
new file mode 100644
index 0000000..b9fa390
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/linear/LinearConstraint.java
@@ -0,0 +1,230 @@
+/*
+ * 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.math3.optim.linear;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import org.apache.commons.math3.linear.MatrixUtils;
+import org.apache.commons.math3.linear.RealVector;
+import org.apache.commons.math3.linear.ArrayRealVector;
+
+/**
+ * A linear constraint for a linear optimization problem.
+ * <p>
+ * A linear constraint has one of the forms:
+ * <ul>
+ *   <li>c<sub>1</sub>x<sub>1</sub> + ... c<sub>n</sub>x<sub>n</sub> = v</li>
+ *   <li>c<sub>1</sub>x<sub>1</sub> + ... c<sub>n</sub>x<sub>n</sub> &lt;= v</li>
+ *   <li>c<sub>1</sub>x<sub>1</sub> + ... c<sub>n</sub>x<sub>n</sub> >= v</li>
+ *   <li>l<sub>1</sub>x<sub>1</sub> + ... l<sub>n</sub>x<sub>n</sub> + l<sub>cst</sub> =
+ *       r<sub>1</sub>x<sub>1</sub> + ... r<sub>n</sub>x<sub>n</sub> + r<sub>cst</sub></li>
+ *   <li>l<sub>1</sub>x<sub>1</sub> + ... l<sub>n</sub>x<sub>n</sub> + l<sub>cst</sub> &lt;=
+ *       r<sub>1</sub>x<sub>1</sub> + ... r<sub>n</sub>x<sub>n</sub> + r<sub>cst</sub></li>
+ *   <li>l<sub>1</sub>x<sub>1</sub> + ... l<sub>n</sub>x<sub>n</sub> + l<sub>cst</sub> >=
+ *       r<sub>1</sub>x<sub>1</sub> + ... r<sub>n</sub>x<sub>n</sub> + r<sub>cst</sub></li>
+ * </ul>
+ * The c<sub>i</sub>, l<sub>i</sub> or r<sub>i</sub> are the coefficients of the constraints, the x<sub>i</sub>
+ * are the coordinates of the current point and v is the value of the constraint.
+ * </p>
+ *
+ * @since 2.0
+ */
+public class LinearConstraint implements Serializable {
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -764632794033034092L;
+    /** Coefficients of the constraint (left hand side). */
+    private final transient RealVector coefficients;
+    /** Relationship between left and right hand sides (=, &lt;=, >=). */
+    private final Relationship relationship;
+    /** Value of the constraint (right hand side). */
+    private final double value;
+
+    /**
+     * Build a constraint involving a single linear equation.
+     * <p>
+     * A linear constraint with a single linear equation has one of the forms:
+     * <ul>
+     *   <li>c<sub>1</sub>x<sub>1</sub> + ... c<sub>n</sub>x<sub>n</sub> = v</li>
+     *   <li>c<sub>1</sub>x<sub>1</sub> + ... c<sub>n</sub>x<sub>n</sub> &lt;= v</li>
+     *   <li>c<sub>1</sub>x<sub>1</sub> + ... c<sub>n</sub>x<sub>n</sub> >= v</li>
+     * </ul>
+     * </p>
+     * @param coefficients The coefficients of the constraint (left hand side)
+     * @param relationship The type of (in)equality used in the constraint
+     * @param value The value of the constraint (right hand side)
+     */
+    public LinearConstraint(final double[] coefficients,
+                            final Relationship relationship,
+                            final double value) {
+        this(new ArrayRealVector(coefficients), relationship, value);
+    }
+
+    /**
+     * Build a constraint involving a single linear equation.
+     * <p>
+     * A linear constraint with a single linear equation has one of the forms:
+     * <ul>
+     *   <li>c<sub>1</sub>x<sub>1</sub> + ... c<sub>n</sub>x<sub>n</sub> = v</li>
+     *   <li>c<sub>1</sub>x<sub>1</sub> + ... c<sub>n</sub>x<sub>n</sub> &lt;= v</li>
+     *   <li>c<sub>1</sub>x<sub>1</sub> + ... c<sub>n</sub>x<sub>n</sub> >= v</li>
+     * </ul>
+     * </p>
+     * @param coefficients The coefficients of the constraint (left hand side)
+     * @param relationship The type of (in)equality used in the constraint
+     * @param value The value of the constraint (right hand side)
+     */
+    public LinearConstraint(final RealVector coefficients,
+                            final Relationship relationship,
+                            final double value) {
+        this.coefficients = coefficients;
+        this.relationship = relationship;
+        this.value        = value;
+    }
+
+    /**
+     * Build a constraint involving two linear equations.
+     * <p>
+     * A linear constraint with two linear equation has one of the forms:
+     * <ul>
+     *   <li>l<sub>1</sub>x<sub>1</sub> + ... l<sub>n</sub>x<sub>n</sub> + l<sub>cst</sub> =
+     *       r<sub>1</sub>x<sub>1</sub> + ... r<sub>n</sub>x<sub>n</sub> + r<sub>cst</sub></li>
+     *   <li>l<sub>1</sub>x<sub>1</sub> + ... l<sub>n</sub>x<sub>n</sub> + l<sub>cst</sub> &lt;=
+     *       r<sub>1</sub>x<sub>1</sub> + ... r<sub>n</sub>x<sub>n</sub> + r<sub>cst</sub></li>
+     *   <li>l<sub>1</sub>x<sub>1</sub> + ... l<sub>n</sub>x<sub>n</sub> + l<sub>cst</sub> >=
+     *       r<sub>1</sub>x<sub>1</sub> + ... r<sub>n</sub>x<sub>n</sub> + r<sub>cst</sub></li>
+     * </ul>
+     * </p>
+     * @param lhsCoefficients The coefficients of the linear expression on the left hand side of the constraint
+     * @param lhsConstant The constant term of the linear expression on the left hand side of the constraint
+     * @param relationship The type of (in)equality used in the constraint
+     * @param rhsCoefficients The coefficients of the linear expression on the right hand side of the constraint
+     * @param rhsConstant The constant term of the linear expression on the right hand side of the constraint
+     */
+    public LinearConstraint(final double[] lhsCoefficients, final double lhsConstant,
+                            final Relationship relationship,
+                            final double[] rhsCoefficients, final double rhsConstant) {
+        double[] sub = new double[lhsCoefficients.length];
+        for (int i = 0; i < sub.length; ++i) {
+            sub[i] = lhsCoefficients[i] - rhsCoefficients[i];
+        }
+        this.coefficients = new ArrayRealVector(sub, false);
+        this.relationship = relationship;
+        this.value        = rhsConstant - lhsConstant;
+    }
+
+    /**
+     * Build a constraint involving two linear equations.
+     * <p>
+     * A linear constraint with two linear equation has one of the forms:
+     * <ul>
+     *   <li>l<sub>1</sub>x<sub>1</sub> + ... l<sub>n</sub>x<sub>n</sub> + l<sub>cst</sub> =
+     *       r<sub>1</sub>x<sub>1</sub> + ... r<sub>n</sub>x<sub>n</sub> + r<sub>cst</sub></li>
+     *   <li>l<sub>1</sub>x<sub>1</sub> + ... l<sub>n</sub>x<sub>n</sub> + l<sub>cst</sub> &lt;=
+     *       r<sub>1</sub>x<sub>1</sub> + ... r<sub>n</sub>x<sub>n</sub> + r<sub>cst</sub></li>
+     *   <li>l<sub>1</sub>x<sub>1</sub> + ... l<sub>n</sub>x<sub>n</sub> + l<sub>cst</sub> >=
+     *       r<sub>1</sub>x<sub>1</sub> + ... r<sub>n</sub>x<sub>n</sub> + r<sub>cst</sub></li>
+     * </ul>
+     * </p>
+     * @param lhsCoefficients The coefficients of the linear expression on the left hand side of the constraint
+     * @param lhsConstant The constant term of the linear expression on the left hand side of the constraint
+     * @param relationship The type of (in)equality used in the constraint
+     * @param rhsCoefficients The coefficients of the linear expression on the right hand side of the constraint
+     * @param rhsConstant The constant term of the linear expression on the right hand side of the constraint
+     */
+    public LinearConstraint(final RealVector lhsCoefficients, final double lhsConstant,
+                            final Relationship relationship,
+                            final RealVector rhsCoefficients, final double rhsConstant) {
+        this.coefficients = lhsCoefficients.subtract(rhsCoefficients);
+        this.relationship = relationship;
+        this.value        = rhsConstant - lhsConstant;
+    }
+
+    /**
+     * Gets the coefficients of the constraint (left hand side).
+     *
+     * @return the coefficients of the constraint (left hand side).
+     */
+    public RealVector getCoefficients() {
+        return coefficients;
+    }
+
+    /**
+     * Gets the relationship between left and right hand sides.
+     *
+     * @return the relationship between left and right hand sides.
+     */
+    public Relationship getRelationship() {
+        return relationship;
+    }
+
+    /**
+     * Gets the value of the constraint (right hand side).
+     *
+     * @return the value of the constraint (right hand side).
+     */
+    public double getValue() {
+        return value;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+        if (other instanceof LinearConstraint) {
+            LinearConstraint rhs = (LinearConstraint) other;
+            return relationship == rhs.relationship &&
+                value == rhs.value &&
+                coefficients.equals(rhs.coefficients);
+        }
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return relationship.hashCode() ^
+            Double.valueOf(value).hashCode() ^
+            coefficients.hashCode();
+    }
+
+    /**
+     * Serialize the instance.
+     * @param oos stream where object should be written
+     * @throws IOException if object cannot be written to stream
+     */
+    private void writeObject(ObjectOutputStream oos)
+        throws IOException {
+        oos.defaultWriteObject();
+        MatrixUtils.serializeRealVector(coefficients, oos);
+    }
+
+    /**
+     * Deserialize the instance.
+     * @param ois stream from which the object should be read
+     * @throws ClassNotFoundException if a class in the stream cannot be found
+     * @throws IOException if object cannot be read from the stream
+     */
+    private void readObject(ObjectInputStream ois)
+      throws ClassNotFoundException, IOException {
+        ois.defaultReadObject();
+        MatrixUtils.deserializeRealVector(this, "coefficients", ois);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/linear/LinearConstraintSet.java b/src/main/java/org/apache/commons/math3/optim/linear/LinearConstraintSet.java
new file mode 100644
index 0000000..d54bd61
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/linear/LinearConstraintSet.java
@@ -0,0 +1,63 @@
+/*
+ * 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.math3.optim.linear;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.Collection;
+import java.util.Collections;
+
+import org.apache.commons.math3.optim.OptimizationData;
+
+/**
+ * Class that represents a set of {@link LinearConstraint linear constraints}.
+ *
+ * @since 3.1
+ */
+public class LinearConstraintSet implements OptimizationData {
+    /** Set of constraints. */
+    private final Set<LinearConstraint> linearConstraints = new LinkedHashSet<LinearConstraint>();
+
+    /**
+     * Creates a set containing the given constraints.
+     *
+     * @param constraints Constraints.
+     */
+    public LinearConstraintSet(LinearConstraint... constraints) {
+        for (LinearConstraint c : constraints) {
+            linearConstraints.add(c);
+        }
+    }
+
+    /**
+     * Creates a set containing the given constraints.
+     *
+     * @param constraints Constraints.
+     */
+    public LinearConstraintSet(Collection<LinearConstraint> constraints) {
+        linearConstraints.addAll(constraints);
+    }
+
+    /**
+     * Gets the set of linear constraints.
+     *
+     * @return the constraints.
+     */
+    public Collection<LinearConstraint> getConstraints() {
+        return Collections.unmodifiableSet(linearConstraints);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/linear/LinearObjectiveFunction.java b/src/main/java/org/apache/commons/math3/optim/linear/LinearObjectiveFunction.java
new file mode 100644
index 0000000..6cff81f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/linear/LinearObjectiveFunction.java
@@ -0,0 +1,150 @@
+/*
+ * 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.math3.optim.linear;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import org.apache.commons.math3.analysis.MultivariateFunction;
+import org.apache.commons.math3.linear.MatrixUtils;
+import org.apache.commons.math3.linear.RealVector;
+import org.apache.commons.math3.linear.ArrayRealVector;
+import org.apache.commons.math3.optim.OptimizationData;
+
+/**
+ * An objective function for a linear optimization problem.
+ * <p>
+ * A linear objective function has one the form:
+ * <pre>
+ * c<sub>1</sub>x<sub>1</sub> + ... c<sub>n</sub>x<sub>n</sub> + d
+ * </pre>
+ * The c<sub>i</sub> and d are the coefficients of the equation,
+ * the x<sub>i</sub> are the coordinates of the current point.
+ * </p>
+ *
+ * @since 2.0
+ */
+public class LinearObjectiveFunction
+    implements MultivariateFunction,
+               OptimizationData,
+               Serializable {
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -4531815507568396090L;
+    /** Coefficients of the linear equation (c<sub>i</sub>). */
+    private final transient RealVector coefficients;
+    /** Constant term of the linear equation. */
+    private final double constantTerm;
+
+    /**
+     * @param coefficients Coefficients for the linear equation being optimized.
+     * @param constantTerm Constant term of the linear equation.
+     */
+    public LinearObjectiveFunction(double[] coefficients, double constantTerm) {
+        this(new ArrayRealVector(coefficients), constantTerm);
+    }
+
+    /**
+     * @param coefficients Coefficients for the linear equation being optimized.
+     * @param constantTerm Constant term of the linear equation.
+     */
+    public LinearObjectiveFunction(RealVector coefficients, double constantTerm) {
+        this.coefficients = coefficients;
+        this.constantTerm = constantTerm;
+    }
+
+    /**
+     * Gets the coefficients of the linear equation being optimized.
+     *
+     * @return coefficients of the linear equation being optimized.
+     */
+    public RealVector getCoefficients() {
+        return coefficients;
+    }
+
+    /**
+     * Gets the constant of the linear equation being optimized.
+     *
+     * @return constant of the linear equation being optimized.
+     */
+    public double getConstantTerm() {
+        return constantTerm;
+    }
+
+    /**
+     * Computes the value of the linear equation at the current point.
+     *
+     * @param point Point at which linear equation must be evaluated.
+     * @return the value of the linear equation at the current point.
+     */
+    public double value(final double[] point) {
+        return value(new ArrayRealVector(point, false));
+    }
+
+    /**
+     * Computes the value of the linear equation at the current point.
+     *
+     * @param point Point at which linear equation must be evaluated.
+     * @return the value of the linear equation at the current point.
+     */
+    public double value(final RealVector point) {
+        return coefficients.dotProduct(point) + constantTerm;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+        if (other instanceof LinearObjectiveFunction) {
+            LinearObjectiveFunction rhs = (LinearObjectiveFunction) other;
+          return (constantTerm == rhs.constantTerm) && coefficients.equals(rhs.coefficients);
+        }
+
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return Double.valueOf(constantTerm).hashCode() ^ coefficients.hashCode();
+    }
+
+    /**
+     * Serialize the instance.
+     * @param oos stream where object should be written
+     * @throws IOException if object cannot be written to stream
+     */
+    private void writeObject(ObjectOutputStream oos)
+        throws IOException {
+        oos.defaultWriteObject();
+        MatrixUtils.serializeRealVector(coefficients, oos);
+    }
+
+    /**
+     * Deserialize the instance.
+     * @param ois stream from which the object should be read
+     * @throws ClassNotFoundException if a class in the stream cannot be found
+     * @throws IOException if object cannot be read from the stream
+     */
+    private void readObject(ObjectInputStream ois)
+      throws ClassNotFoundException, IOException {
+        ois.defaultReadObject();
+        MatrixUtils.deserializeRealVector(this, "coefficients", ois);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/linear/LinearOptimizer.java b/src/main/java/org/apache/commons/math3/optim/linear/LinearOptimizer.java
new file mode 100644
index 0000000..7e80687
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/linear/LinearOptimizer.java
@@ -0,0 +1,131 @@
+/*
+ * 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.math3.optim.linear;
+
+import java.util.Collection;
+import java.util.Collections;
+import org.apache.commons.math3.exception.TooManyIterationsException;
+import org.apache.commons.math3.optim.OptimizationData;
+import org.apache.commons.math3.optim.PointValuePair;
+import org.apache.commons.math3.optim.nonlinear.scalar.MultivariateOptimizer;
+
+/**
+ * Base class for implementing linear optimizers.
+ *
+ * @since 3.1
+ */
+public abstract class LinearOptimizer
+    extends MultivariateOptimizer {
+    /**
+     * Linear objective function.
+     */
+    private LinearObjectiveFunction function;
+    /**
+     * Linear constraints.
+     */
+    private Collection<LinearConstraint> linearConstraints;
+    /**
+     * Whether to restrict the variables to non-negative values.
+     */
+    private boolean nonNegative;
+
+    /**
+     * Simple constructor with default settings.
+     *
+     */
+    protected LinearOptimizer() {
+        super(null); // No convergence checker.
+    }
+
+    /**
+     * @return {@code true} if the variables are restricted to non-negative values.
+     */
+    protected boolean isRestrictedToNonNegative() {
+        return nonNegative;
+    }
+
+    /**
+     * @return the optimization type.
+     */
+    protected LinearObjectiveFunction getFunction() {
+        return function;
+    }
+
+    /**
+     * @return the optimization type.
+     */
+    protected Collection<LinearConstraint> getConstraints() {
+        return Collections.unmodifiableCollection(linearConstraints);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @param optData Optimization data. In addition to those documented in
+     * {@link MultivariateOptimizer#parseOptimizationData(OptimizationData[])
+     * MultivariateOptimizer}, this method will register the following data:
+     * <ul>
+     *  <li>{@link LinearObjectiveFunction}</li>
+     *  <li>{@link LinearConstraintSet}</li>
+     *  <li>{@link NonNegativeConstraint}</li>
+     * </ul>
+     * @return {@inheritDoc}
+     * @throws TooManyIterationsException if the maximal number of
+     * iterations is exceeded.
+     */
+    @Override
+    public PointValuePair optimize(OptimizationData... optData)
+        throws TooManyIterationsException {
+        // Set up base class and perform computation.
+        return super.optimize(optData);
+    }
+
+    /**
+     * Scans the list of (required and optional) optimization data that
+     * characterize the problem.
+     *
+     * @param optData Optimization data.
+     * The following data will be looked for:
+     * <ul>
+     *  <li>{@link LinearObjectiveFunction}</li>
+     *  <li>{@link LinearConstraintSet}</li>
+     *  <li>{@link NonNegativeConstraint}</li>
+     * </ul>
+     */
+    @Override
+    protected void parseOptimizationData(OptimizationData... optData) {
+        // Allow base class to register its own data.
+        super.parseOptimizationData(optData);
+
+        // The existing values (as set by the previous call) are reused if
+        // not provided in the argument list.
+        for (OptimizationData data : optData) {
+            if (data instanceof LinearObjectiveFunction) {
+                function = (LinearObjectiveFunction) data;
+                continue;
+            }
+            if (data instanceof LinearConstraintSet) {
+                linearConstraints = ((LinearConstraintSet) data).getConstraints();
+                continue;
+            }
+            if  (data instanceof NonNegativeConstraint) {
+                nonNegative = ((NonNegativeConstraint) data).isRestrictedToNonNegative();
+                continue;
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/linear/NoFeasibleSolutionException.java b/src/main/java/org/apache/commons/math3/optim/linear/NoFeasibleSolutionException.java
new file mode 100644
index 0000000..cbe8321
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/linear/NoFeasibleSolutionException.java
@@ -0,0 +1,37 @@
+/*
+ * 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.math3.optim.linear;
+
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * This class represents exceptions thrown by optimizers when no solution fulfills the constraints.
+ *
+ * @since 2.0
+ */
+public class NoFeasibleSolutionException extends MathIllegalStateException {
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -3044253632189082760L;
+
+    /**
+     * Simple constructor using a default message.
+     */
+    public NoFeasibleSolutionException() {
+        super(LocalizedFormats.NO_FEASIBLE_SOLUTION);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/linear/NonNegativeConstraint.java b/src/main/java/org/apache/commons/math3/optim/linear/NonNegativeConstraint.java
new file mode 100644
index 0000000..dafcb63
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/linear/NonNegativeConstraint.java
@@ -0,0 +1,47 @@
+/*
+ * 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.math3.optim.linear;
+
+import org.apache.commons.math3.optim.OptimizationData;
+
+/**
+ * A constraint for a linear optimization problem indicating whether all
+ * variables must be restricted to non-negative values.
+ *
+ * @since 3.1
+ */
+public class NonNegativeConstraint implements OptimizationData {
+    /** Whether the variables are all positive. */
+    private final boolean isRestricted;
+
+    /**
+     * @param restricted If {@code true}, all the variables must be positive.
+     */
+    public NonNegativeConstraint(boolean restricted) {
+        isRestricted = restricted;
+    }
+
+    /**
+     * Indicates whether all the variables must be restricted to non-negative
+     * values.
+     *
+     * @return {@code true} if all the variables must be positive.
+     */
+    public boolean isRestrictedToNonNegative() {
+        return isRestricted;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/linear/PivotSelectionRule.java b/src/main/java/org/apache/commons/math3/optim/linear/PivotSelectionRule.java
new file mode 100644
index 0000000..a2a2765
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/linear/PivotSelectionRule.java
@@ -0,0 +1,38 @@
+/*
+ * 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.math3.optim.linear;
+
+import org.apache.commons.math3.optim.OptimizationData;
+
+/**
+ * Pivot selection rule to the use for a Simplex solver.
+ *
+ * @since 3.3
+ */
+public enum PivotSelectionRule implements OptimizationData {
+    /**
+     * The classical rule, the variable with the most negative coefficient
+     * in the objective function row will be chosen as entering variable.
+     */
+    DANTZIG,
+    /**
+     * The first variable with a negative coefficient in the objective function
+     * row will be chosen as entering variable. This rule guarantees to prevent
+     * cycles, but may take longer to find an optimal solution.
+     */
+    BLAND
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/linear/Relationship.java b/src/main/java/org/apache/commons/math3/optim/linear/Relationship.java
new file mode 100644
index 0000000..f88c938
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/linear/Relationship.java
@@ -0,0 +1,65 @@
+/*
+ * 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.math3.optim.linear;
+
+/**
+ * Types of relationships between two cells in a Solver {@link LinearConstraint}.
+ *
+ * @since 2.0
+ */
+public enum Relationship {
+    /** Equality relationship. */
+    EQ("="),
+    /** Lesser than or equal relationship. */
+    LEQ("<="),
+    /** Greater than or equal relationship. */
+    GEQ(">=");
+
+    /** Display string for the relationship. */
+    private final String stringValue;
+
+    /**
+     * Simple constructor.
+     *
+     * @param stringValue Display string for the relationship.
+     */
+    Relationship(String stringValue) {
+        this.stringValue = stringValue;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return stringValue;
+    }
+
+    /**
+     * Gets the relationship obtained when multiplying all coefficients by -1.
+     *
+     * @return the opposite relationship.
+     */
+    public Relationship oppositeRelationship() {
+        switch (this) {
+        case LEQ :
+            return GEQ;
+        case GEQ :
+            return LEQ;
+        default :
+            return EQ;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/linear/SimplexSolver.java b/src/main/java/org/apache/commons/math3/optim/linear/SimplexSolver.java
new file mode 100644
index 0000000..e95b657
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/linear/SimplexSolver.java
@@ -0,0 +1,407 @@
+/*
+ * 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.math3.optim.linear;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.math3.exception.TooManyIterationsException;
+import org.apache.commons.math3.optim.OptimizationData;
+import org.apache.commons.math3.optim.PointValuePair;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.Precision;
+
+/**
+ * Solves a linear problem using the "Two-Phase Simplex" method.
+ * <p>
+ * The {@link SimplexSolver} supports the following {@link OptimizationData} data provided
+ * as arguments to {@link #optimize(OptimizationData...)}:
+ * <ul>
+ *   <li>objective function: {@link LinearObjectiveFunction} - mandatory</li>
+ *   <li>linear constraints {@link LinearConstraintSet} - mandatory</li>
+ *   <li>type of optimization: {@link org.apache.commons.math3.optim.nonlinear.scalar.GoalType GoalType}
+ *    - optional, default: {@link org.apache.commons.math3.optim.nonlinear.scalar.GoalType#MINIMIZE MINIMIZE}</li>
+ *   <li>whether to allow negative values as solution: {@link NonNegativeConstraint} - optional, default: true</li>
+ *   <li>pivot selection rule: {@link PivotSelectionRule} - optional, default {@link PivotSelectionRule#DANTZIG}</li>
+ *   <li>callback for the best solution: {@link SolutionCallback} - optional</li>
+ *   <li>maximum number of iterations: {@link org.apache.commons.math3.optim.MaxIter} - optional, default: {@link Integer#MAX_VALUE}</li>
+ * </ul>
+ * <p>
+ * <b>Note:</b> Depending on the problem definition, the default convergence criteria
+ * may be too strict, resulting in {@link NoFeasibleSolutionException} or
+ * {@link TooManyIterationsException}. In such a case it is advised to adjust these
+ * criteria with more appropriate values, e.g. relaxing the epsilon value.
+ * <p>
+ * Default convergence criteria:
+ * <ul>
+ *   <li>Algorithm convergence: 1e-6</li>
+ *   <li>Floating-point comparisons: 10 ulp</li>
+ *   <li>Cut-Off value: 1e-10</li>
+  * </ul>
+ * <p>
+ * The cut-off value has been introduced to handle the case of very small pivot elements
+ * in the Simplex tableau, as these may lead to numerical instabilities and degeneracy.
+ * Potential pivot elements smaller than this value will be treated as if they were zero
+ * and are thus not considered by the pivot selection mechanism. The default value is safe
+ * for many problems, but may need to be adjusted in case of very small coefficients
+ * used in either the {@link LinearConstraint} or {@link LinearObjectiveFunction}.
+ *
+ * @since 2.0
+ */
+public class SimplexSolver extends LinearOptimizer {
+    /** Default amount of error to accept in floating point comparisons (as ulps). */
+    static final int DEFAULT_ULPS = 10;
+
+    /** Default cut-off value. */
+    static final double DEFAULT_CUT_OFF = 1e-10;
+
+    /** Default amount of error to accept for algorithm convergence. */
+    private static final double DEFAULT_EPSILON = 1.0e-6;
+
+    /** Amount of error to accept for algorithm convergence. */
+    private final double epsilon;
+
+    /** Amount of error to accept in floating point comparisons (as ulps). */
+    private final int maxUlps;
+
+    /**
+     * Cut-off value for entries in the tableau: values smaller than the cut-off
+     * are treated as zero to improve numerical stability.
+     */
+    private final double cutOff;
+
+    /** The pivot selection method to use. */
+    private PivotSelectionRule pivotSelection;
+
+    /**
+     * The solution callback to access the best solution found so far in case
+     * the optimizer fails to find an optimal solution within the iteration limits.
+     */
+    private SolutionCallback solutionCallback;
+
+    /**
+     * Builds a simplex solver with default settings.
+     */
+    public SimplexSolver() {
+        this(DEFAULT_EPSILON, DEFAULT_ULPS, DEFAULT_CUT_OFF);
+    }
+
+    /**
+     * Builds a simplex solver with a specified accepted amount of error.
+     *
+     * @param epsilon Amount of error to accept for algorithm convergence.
+     */
+    public SimplexSolver(final double epsilon) {
+        this(epsilon, DEFAULT_ULPS, DEFAULT_CUT_OFF);
+    }
+
+    /**
+     * Builds a simplex solver with a specified accepted amount of error.
+     *
+     * @param epsilon Amount of error to accept for algorithm convergence.
+     * @param maxUlps Amount of error to accept in floating point comparisons.
+     */
+    public SimplexSolver(final double epsilon, final int maxUlps) {
+        this(epsilon, maxUlps, DEFAULT_CUT_OFF);
+    }
+
+    /**
+     * Builds a simplex solver with a specified accepted amount of error.
+     *
+     * @param epsilon Amount of error to accept for algorithm convergence.
+     * @param maxUlps Amount of error to accept in floating point comparisons.
+     * @param cutOff Values smaller than the cutOff are treated as zero.
+     */
+    public SimplexSolver(final double epsilon, final int maxUlps, final double cutOff) {
+        this.epsilon = epsilon;
+        this.maxUlps = maxUlps;
+        this.cutOff = cutOff;
+        this.pivotSelection = PivotSelectionRule.DANTZIG;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @param optData Optimization data. In addition to those documented in
+     * {@link LinearOptimizer#optimize(OptimizationData...)
+     * LinearOptimizer}, this method will register the following data:
+     * <ul>
+     *  <li>{@link SolutionCallback}</li>
+     *  <li>{@link PivotSelectionRule}</li>
+     * </ul>
+     *
+     * @return {@inheritDoc}
+     * @throws TooManyIterationsException if the maximal number of iterations is exceeded.
+     */
+    @Override
+    public PointValuePair optimize(OptimizationData... optData)
+        throws TooManyIterationsException {
+        // Set up base class and perform computation.
+        return super.optimize(optData);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @param optData Optimization data.
+     * In addition to those documented in
+     * {@link LinearOptimizer#parseOptimizationData(OptimizationData[])
+     * LinearOptimizer}, this method will register the following data:
+     * <ul>
+     *  <li>{@link SolutionCallback}</li>
+     *  <li>{@link PivotSelectionRule}</li>
+     * </ul>
+     */
+    @Override
+    protected void parseOptimizationData(OptimizationData... optData) {
+        // Allow base class to register its own data.
+        super.parseOptimizationData(optData);
+
+        // reset the callback before parsing
+        solutionCallback = null;
+
+        for (OptimizationData data : optData) {
+            if (data instanceof SolutionCallback) {
+                solutionCallback = (SolutionCallback) data;
+                continue;
+            }
+            if (data instanceof PivotSelectionRule) {
+                pivotSelection = (PivotSelectionRule) data;
+                continue;
+            }
+        }
+    }
+
+    /**
+     * Returns the column with the most negative coefficient in the objective function row.
+     *
+     * @param tableau Simple tableau for the problem.
+     * @return the column with the most negative coefficient.
+     */
+    private Integer getPivotColumn(SimplexTableau tableau) {
+        double minValue = 0;
+        Integer minPos = null;
+        for (int i = tableau.getNumObjectiveFunctions(); i < tableau.getWidth() - 1; i++) {
+            final double entry = tableau.getEntry(0, i);
+            // check if the entry is strictly smaller than the current minimum
+            // do not use a ulp/epsilon check
+            if (entry < minValue) {
+                minValue = entry;
+                minPos = i;
+
+                // Bland's rule: chose the entering column with the lowest index
+                if (pivotSelection == PivotSelectionRule.BLAND && isValidPivotColumn(tableau, i)) {
+                    break;
+                }
+            }
+        }
+        return minPos;
+    }
+
+    /**
+     * Checks whether the given column is valid pivot column, i.e. will result
+     * in a valid pivot row.
+     * <p>
+     * When applying Bland's rule to select the pivot column, it may happen that
+     * there is no corresponding pivot row. This method will check if the selected
+     * pivot column will return a valid pivot row.
+     *
+     * @param tableau simplex tableau for the problem
+     * @param col the column to test
+     * @return {@code true} if the pivot column is valid, {@code false} otherwise
+     */
+    private boolean isValidPivotColumn(SimplexTableau tableau, int col) {
+        for (int i = tableau.getNumObjectiveFunctions(); i < tableau.getHeight(); i++) {
+            final double entry = tableau.getEntry(i, col);
+
+            // do the same check as in getPivotRow
+            if (Precision.compareTo(entry, 0d, cutOff) > 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns the row with the minimum ratio as given by the minimum ratio test (MRT).
+     *
+     * @param tableau Simplex tableau for the problem.
+     * @param col Column to test the ratio of (see {@link #getPivotColumn(SimplexTableau)}).
+     * @return the row with the minimum ratio.
+     */
+    private Integer getPivotRow(SimplexTableau tableau, final int col) {
+        // create a list of all the rows that tie for the lowest score in the minimum ratio test
+        List<Integer> minRatioPositions = new ArrayList<Integer>();
+        double minRatio = Double.MAX_VALUE;
+        for (int i = tableau.getNumObjectiveFunctions(); i < tableau.getHeight(); i++) {
+            final double rhs = tableau.getEntry(i, tableau.getWidth() - 1);
+            final double entry = tableau.getEntry(i, col);
+
+            // only consider pivot elements larger than the cutOff threshold
+            // selecting others may lead to degeneracy or numerical instabilities
+            if (Precision.compareTo(entry, 0d, cutOff) > 0) {
+                final double ratio = FastMath.abs(rhs / entry);
+                // check if the entry is strictly equal to the current min ratio
+                // do not use a ulp/epsilon check
+                final int cmp = Double.compare(ratio, minRatio);
+                if (cmp == 0) {
+                    minRatioPositions.add(i);
+                } else if (cmp < 0) {
+                    minRatio = ratio;
+                    minRatioPositions.clear();
+                    minRatioPositions.add(i);
+                }
+            }
+        }
+
+        if (minRatioPositions.size() == 0) {
+            return null;
+        } else if (minRatioPositions.size() > 1) {
+            // there's a degeneracy as indicated by a tie in the minimum ratio test
+
+            // 1. check if there's an artificial variable that can be forced out of the basis
+            if (tableau.getNumArtificialVariables() > 0) {
+                for (Integer row : minRatioPositions) {
+                    for (int i = 0; i < tableau.getNumArtificialVariables(); i++) {
+                        int column = i + tableau.getArtificialVariableOffset();
+                        final double entry = tableau.getEntry(row, column);
+                        if (Precision.equals(entry, 1d, maxUlps) && row.equals(tableau.getBasicRow(column))) {
+                            return row;
+                        }
+                    }
+                }
+            }
+
+            // 2. apply Bland's rule to prevent cycling:
+            //    take the row for which the corresponding basic variable has the smallest index
+            //
+            // see http://www.stanford.edu/class/msande310/blandrule.pdf
+            // see http://en.wikipedia.org/wiki/Bland%27s_rule (not equivalent to the above paper)
+
+            Integer minRow = null;
+            int minIndex = tableau.getWidth();
+            for (Integer row : minRatioPositions) {
+                final int basicVar = tableau.getBasicVariable(row);
+                if (basicVar < minIndex) {
+                    minIndex = basicVar;
+                    minRow = row;
+                }
+            }
+            return minRow;
+        }
+        return minRatioPositions.get(0);
+    }
+
+    /**
+     * Runs one iteration of the Simplex method on the given model.
+     *
+     * @param tableau Simple tableau for the problem.
+     * @throws TooManyIterationsException if the allowed number of iterations has been exhausted.
+     * @throws UnboundedSolutionException if the model is found not to have a bounded solution.
+     */
+    protected void doIteration(final SimplexTableau tableau)
+        throws TooManyIterationsException,
+               UnboundedSolutionException {
+
+        incrementIterationCount();
+
+        Integer pivotCol = getPivotColumn(tableau);
+        Integer pivotRow = getPivotRow(tableau, pivotCol);
+        if (pivotRow == null) {
+            throw new UnboundedSolutionException();
+        }
+
+        tableau.performRowOperations(pivotCol, pivotRow);
+    }
+
+    /**
+     * Solves Phase 1 of the Simplex method.
+     *
+     * @param tableau Simple tableau for the problem.
+     * @throws TooManyIterationsException if the allowed number of iterations has been exhausted.
+     * @throws UnboundedSolutionException if the model is found not to have a bounded solution.
+     * @throws NoFeasibleSolutionException if there is no feasible solution?
+     */
+    protected void solvePhase1(final SimplexTableau tableau)
+        throws TooManyIterationsException,
+               UnboundedSolutionException,
+               NoFeasibleSolutionException {
+
+        // make sure we're in Phase 1
+        if (tableau.getNumArtificialVariables() == 0) {
+            return;
+        }
+
+        while (!tableau.isOptimal()) {
+            doIteration(tableau);
+        }
+
+        // if W is not zero then we have no feasible solution
+        if (!Precision.equals(tableau.getEntry(0, tableau.getRhsOffset()), 0d, epsilon)) {
+            throw new NoFeasibleSolutionException();
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public PointValuePair doOptimize()
+        throws TooManyIterationsException,
+               UnboundedSolutionException,
+               NoFeasibleSolutionException {
+
+        // reset the tableau to indicate a non-feasible solution in case
+        // we do not pass phase 1 successfully
+        if (solutionCallback != null) {
+            solutionCallback.setTableau(null);
+        }
+
+        final SimplexTableau tableau =
+            new SimplexTableau(getFunction(),
+                               getConstraints(),
+                               getGoalType(),
+                               isRestrictedToNonNegative(),
+                               epsilon,
+                               maxUlps);
+
+        solvePhase1(tableau);
+        tableau.dropPhase1Objective();
+
+        // after phase 1, we are sure to have a feasible solution
+        if (solutionCallback != null) {
+            solutionCallback.setTableau(tableau);
+        }
+
+        while (!tableau.isOptimal()) {
+            doIteration(tableau);
+        }
+
+        // check that the solution respects the nonNegative restriction in case
+        // the epsilon/cutOff values are too large for the actual linear problem
+        // (e.g. with very small constraint coefficients), the solver might actually
+        // find a non-valid solution (with negative coefficients).
+        final PointValuePair solution = tableau.getSolution();
+        if (isRestrictedToNonNegative()) {
+            final double[] coeff = solution.getPoint();
+            for (int i = 0; i < coeff.length; i++) {
+                if (Precision.compareTo(coeff[i], 0, epsilon) < 0) {
+                    throw new NoFeasibleSolutionException();
+                }
+            }
+        }
+        return solution;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/linear/SimplexTableau.java b/src/main/java/org/apache/commons/math3/optim/linear/SimplexTableau.java
new file mode 100644
index 0000000..31e71d2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/linear/SimplexTableau.java
@@ -0,0 +1,713 @@
+/*
+ * 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.math3.optim.linear;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.commons.math3.linear.Array2DRowRealMatrix;
+import org.apache.commons.math3.linear.MatrixUtils;
+import org.apache.commons.math3.linear.RealVector;
+import org.apache.commons.math3.optim.nonlinear.scalar.GoalType;
+import org.apache.commons.math3.optim.PointValuePair;
+import org.apache.commons.math3.util.Precision;
+
+/**
+ * A tableau for use in the Simplex method.
+ *
+ * <p>
+ * Example:
+ * <pre>
+ *   W |  Z |  x1 |  x2 |  x- | s1 |  s2 |  a1 |  RHS
+ * ---------------------------------------------------
+ *  -1    0    0     0     0     0     0     1     0   &lt;= phase 1 objective
+ *   0    1   -15   -10    0     0     0     0     0   &lt;= phase 2 objective
+ *   0    0    1     0     0     1     0     0     2   &lt;= constraint 1
+ *   0    0    0     1     0     0     1     0     3   &lt;= constraint 2
+ *   0    0    1     1     0     0     0     1     4   &lt;= constraint 3
+ * </pre>
+ * W: Phase 1 objective function</br>
+ * Z: Phase 2 objective function</br>
+ * x1 &amp; x2: Decision variables</br>
+ * x-: Extra decision variable to allow for negative values</br>
+ * s1 &amp; s2: Slack/Surplus variables</br>
+ * a1: Artificial variable</br>
+ * RHS: Right hand side</br>
+ * </p>
+ * @since 2.0
+ */
+class SimplexTableau implements Serializable {
+
+    /** Column label for negative vars. */
+    private static final String NEGATIVE_VAR_COLUMN_LABEL = "x-";
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -1369660067587938365L;
+
+    /** Linear objective function. */
+    private final LinearObjectiveFunction f;
+
+    /** Linear constraints. */
+    private final List<LinearConstraint> constraints;
+
+    /** Whether to restrict the variables to non-negative values. */
+    private final boolean restrictToNonNegative;
+
+    /** The variables each column represents */
+    private final List<String> columnLabels = new ArrayList<String>();
+
+    /** Simple tableau. */
+    private transient Array2DRowRealMatrix tableau;
+
+    /** Number of decision variables. */
+    private final int numDecisionVariables;
+
+    /** Number of slack variables. */
+    private final int numSlackVariables;
+
+    /** Number of artificial variables. */
+    private int numArtificialVariables;
+
+    /** Amount of error to accept when checking for optimality. */
+    private final double epsilon;
+
+    /** Amount of error to accept in floating point comparisons. */
+    private final int maxUlps;
+
+    /** Maps basic variables to row they are basic in. */
+    private int[] basicVariables;
+
+    /** Maps rows to their corresponding basic variables. */
+    private int[] basicRows;
+
+    /**
+     * Builds a tableau for a linear problem.
+     *
+     * @param f Linear objective function.
+     * @param constraints Linear constraints.
+     * @param goalType Optimization goal: either {@link GoalType#MAXIMIZE}
+     * or {@link GoalType#MINIMIZE}.
+     * @param restrictToNonNegative Whether to restrict the variables to non-negative values.
+     * @param epsilon Amount of error to accept when checking for optimality.
+     */
+    SimplexTableau(final LinearObjectiveFunction f,
+                   final Collection<LinearConstraint> constraints,
+                   final GoalType goalType,
+                   final boolean restrictToNonNegative,
+                   final double epsilon) {
+        this(f, constraints, goalType, restrictToNonNegative, epsilon, SimplexSolver.DEFAULT_ULPS);
+    }
+
+    /**
+     * Build a tableau for a linear problem.
+     * @param f linear objective function
+     * @param constraints linear constraints
+     * @param goalType type of optimization goal: either {@link GoalType#MAXIMIZE} or {@link GoalType#MINIMIZE}
+     * @param restrictToNonNegative whether to restrict the variables to non-negative values
+     * @param epsilon amount of error to accept when checking for optimality
+     * @param maxUlps amount of error to accept in floating point comparisons
+     */
+    SimplexTableau(final LinearObjectiveFunction f,
+                   final Collection<LinearConstraint> constraints,
+                   final GoalType goalType,
+                   final boolean restrictToNonNegative,
+                   final double epsilon,
+                   final int maxUlps) {
+        this.f                      = f;
+        this.constraints            = normalizeConstraints(constraints);
+        this.restrictToNonNegative  = restrictToNonNegative;
+        this.epsilon                = epsilon;
+        this.maxUlps                = maxUlps;
+        this.numDecisionVariables   = f.getCoefficients().getDimension() + (restrictToNonNegative ? 0 : 1);
+        this.numSlackVariables      = getConstraintTypeCounts(Relationship.LEQ) +
+                                      getConstraintTypeCounts(Relationship.GEQ);
+        this.numArtificialVariables = getConstraintTypeCounts(Relationship.EQ) +
+                                      getConstraintTypeCounts(Relationship.GEQ);
+        this.tableau = createTableau(goalType == GoalType.MAXIMIZE);
+        // initialize the basic variables for phase 1:
+        //   we know that only slack or artificial variables can be basic
+        initializeBasicVariables(getSlackVariableOffset());
+        initializeColumnLabels();
+    }
+
+    /**
+     * Initialize the labels for the columns.
+     */
+    protected void initializeColumnLabels() {
+      if (getNumObjectiveFunctions() == 2) {
+        columnLabels.add("W");
+      }
+      columnLabels.add("Z");
+      for (int i = 0; i < getOriginalNumDecisionVariables(); i++) {
+        columnLabels.add("x" + i);
+      }
+      if (!restrictToNonNegative) {
+        columnLabels.add(NEGATIVE_VAR_COLUMN_LABEL);
+      }
+      for (int i = 0; i < getNumSlackVariables(); i++) {
+        columnLabels.add("s" + i);
+      }
+      for (int i = 0; i < getNumArtificialVariables(); i++) {
+        columnLabels.add("a" + i);
+      }
+      columnLabels.add("RHS");
+    }
+
+    /**
+     * Create the tableau by itself.
+     * @param maximize if true, goal is to maximize the objective function
+     * @return created tableau
+     */
+    protected Array2DRowRealMatrix createTableau(final boolean maximize) {
+
+        // create a matrix of the correct size
+        int width = numDecisionVariables + numSlackVariables +
+        numArtificialVariables + getNumObjectiveFunctions() + 1; // + 1 is for RHS
+        int height = constraints.size() + getNumObjectiveFunctions();
+        Array2DRowRealMatrix matrix = new Array2DRowRealMatrix(height, width);
+
+        // initialize the objective function rows
+        if (getNumObjectiveFunctions() == 2) {
+            matrix.setEntry(0, 0, -1);
+        }
+
+        int zIndex = (getNumObjectiveFunctions() == 1) ? 0 : 1;
+        matrix.setEntry(zIndex, zIndex, maximize ? 1 : -1);
+        RealVector objectiveCoefficients = maximize ? f.getCoefficients().mapMultiply(-1) : f.getCoefficients();
+        copyArray(objectiveCoefficients.toArray(), matrix.getDataRef()[zIndex]);
+        matrix.setEntry(zIndex, width - 1, maximize ? f.getConstantTerm() : -1 * f.getConstantTerm());
+
+        if (!restrictToNonNegative) {
+            matrix.setEntry(zIndex, getSlackVariableOffset() - 1,
+                            getInvertedCoefficientSum(objectiveCoefficients));
+        }
+
+        // initialize the constraint rows
+        int slackVar = 0;
+        int artificialVar = 0;
+        for (int i = 0; i < constraints.size(); i++) {
+            LinearConstraint constraint = constraints.get(i);
+            int row = getNumObjectiveFunctions() + i;
+
+            // decision variable coefficients
+            copyArray(constraint.getCoefficients().toArray(), matrix.getDataRef()[row]);
+
+            // x-
+            if (!restrictToNonNegative) {
+                matrix.setEntry(row, getSlackVariableOffset() - 1,
+                                getInvertedCoefficientSum(constraint.getCoefficients()));
+            }
+
+            // RHS
+            matrix.setEntry(row, width - 1, constraint.getValue());
+
+            // slack variables
+            if (constraint.getRelationship() == Relationship.LEQ) {
+                matrix.setEntry(row, getSlackVariableOffset() + slackVar++, 1);  // slack
+            } else if (constraint.getRelationship() == Relationship.GEQ) {
+                matrix.setEntry(row, getSlackVariableOffset() + slackVar++, -1); // excess
+            }
+
+            // artificial variables
+            if ((constraint.getRelationship() == Relationship.EQ) ||
+                (constraint.getRelationship() == Relationship.GEQ)) {
+                matrix.setEntry(0, getArtificialVariableOffset() + artificialVar, 1);
+                matrix.setEntry(row, getArtificialVariableOffset() + artificialVar++, 1);
+                matrix.setRowVector(0, matrix.getRowVector(0).subtract(matrix.getRowVector(row)));
+            }
+        }
+
+        return matrix;
+    }
+
+    /**
+     * Get new versions of the constraints which have positive right hand sides.
+     * @param originalConstraints original (not normalized) constraints
+     * @return new versions of the constraints
+     */
+    public List<LinearConstraint> normalizeConstraints(Collection<LinearConstraint> originalConstraints) {
+        List<LinearConstraint> normalized = new ArrayList<LinearConstraint>(originalConstraints.size());
+        for (LinearConstraint constraint : originalConstraints) {
+            normalized.add(normalize(constraint));
+        }
+        return normalized;
+    }
+
+    /**
+     * Get a new equation equivalent to this one with a positive right hand side.
+     * @param constraint reference constraint
+     * @return new equation
+     */
+    private LinearConstraint normalize(final LinearConstraint constraint) {
+        if (constraint.getValue() < 0) {
+            return new LinearConstraint(constraint.getCoefficients().mapMultiply(-1),
+                                        constraint.getRelationship().oppositeRelationship(),
+                                        -1 * constraint.getValue());
+        }
+        return new LinearConstraint(constraint.getCoefficients(),
+                                    constraint.getRelationship(), constraint.getValue());
+    }
+
+    /**
+     * Get the number of objective functions in this tableau.
+     * @return 2 for Phase 1.  1 for Phase 2.
+     */
+    protected final int getNumObjectiveFunctions() {
+        return this.numArtificialVariables > 0 ? 2 : 1;
+    }
+
+    /**
+     * Get a count of constraints corresponding to a specified relationship.
+     * @param relationship relationship to count
+     * @return number of constraint with the specified relationship
+     */
+    private int getConstraintTypeCounts(final Relationship relationship) {
+        int count = 0;
+        for (final LinearConstraint constraint : constraints) {
+            if (constraint.getRelationship() == relationship) {
+                ++count;
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Get the -1 times the sum of all coefficients in the given array.
+     * @param coefficients coefficients to sum
+     * @return the -1 times the sum of all coefficients in the given array.
+     */
+    protected static double getInvertedCoefficientSum(final RealVector coefficients) {
+        double sum = 0;
+        for (double coefficient : coefficients.toArray()) {
+            sum -= coefficient;
+        }
+        return sum;
+    }
+
+    /**
+     * Checks whether the given column is basic.
+     * @param col index of the column to check
+     * @return the row that the variable is basic in.  null if the column is not basic
+     */
+    protected Integer getBasicRow(final int col) {
+        final int row = basicVariables[col];
+        return row == -1 ? null : row;
+    }
+
+    /**
+     * Returns the variable that is basic in this row.
+     * @param row the index of the row to check
+     * @return the variable that is basic for this row.
+     */
+    protected int getBasicVariable(final int row) {
+        return basicRows[row];
+    }
+
+    /**
+     * Initializes the basic variable / row mapping.
+     * @param startColumn the column to start
+     */
+    private void initializeBasicVariables(final int startColumn) {
+        basicVariables = new int[getWidth() - 1];
+        basicRows = new int[getHeight()];
+
+        Arrays.fill(basicVariables, -1);
+
+        for (int i = startColumn; i < getWidth() - 1; i++) {
+            Integer row = findBasicRow(i);
+            if (row != null) {
+                basicVariables[i] = row;
+                basicRows[row] = i;
+            }
+        }
+    }
+
+    /**
+     * Returns the row in which the given column is basic.
+     * @param col index of the column
+     * @return the row that the variable is basic in, or {@code null} if the variable is not basic.
+     */
+    private Integer findBasicRow(final int col) {
+        Integer row = null;
+        for (int i = 0; i < getHeight(); i++) {
+            final double entry = getEntry(i, col);
+            if (Precision.equals(entry, 1d, maxUlps) && (row == null)) {
+                row = i;
+            } else if (!Precision.equals(entry, 0d, maxUlps)) {
+                return null;
+            }
+        }
+        return row;
+    }
+
+    /**
+     * Removes the phase 1 objective function, positive cost non-artificial variables,
+     * and the non-basic artificial variables from this tableau.
+     */
+    protected void dropPhase1Objective() {
+        if (getNumObjectiveFunctions() == 1) {
+            return;
+        }
+
+        final Set<Integer> columnsToDrop = new TreeSet<Integer>();
+        columnsToDrop.add(0);
+
+        // positive cost non-artificial variables
+        for (int i = getNumObjectiveFunctions(); i < getArtificialVariableOffset(); i++) {
+            final double entry = getEntry(0, i);
+            if (Precision.compareTo(entry, 0d, epsilon) > 0) {
+                columnsToDrop.add(i);
+            }
+        }
+
+        // non-basic artificial variables
+        for (int i = 0; i < getNumArtificialVariables(); i++) {
+            int col = i + getArtificialVariableOffset();
+            if (getBasicRow(col) == null) {
+                columnsToDrop.add(col);
+            }
+        }
+
+        final double[][] matrix = new double[getHeight() - 1][getWidth() - columnsToDrop.size()];
+        for (int i = 1; i < getHeight(); i++) {
+            int col = 0;
+            for (int j = 0; j < getWidth(); j++) {
+                if (!columnsToDrop.contains(j)) {
+                    matrix[i - 1][col++] = getEntry(i, j);
+                }
+            }
+        }
+
+        // remove the columns in reverse order so the indices are correct
+        Integer[] drop = columnsToDrop.toArray(new Integer[columnsToDrop.size()]);
+        for (int i = drop.length - 1; i >= 0; i--) {
+            columnLabels.remove((int) drop[i]);
+        }
+
+        this.tableau = new Array2DRowRealMatrix(matrix);
+        this.numArtificialVariables = 0;
+        // need to update the basic variable mappings as row/columns have been dropped
+        initializeBasicVariables(getNumObjectiveFunctions());
+    }
+
+    /**
+     * @param src the source array
+     * @param dest the destination array
+     */
+    private void copyArray(final double[] src, final double[] dest) {
+        System.arraycopy(src, 0, dest, getNumObjectiveFunctions(), src.length);
+    }
+
+    /**
+     * Returns whether the problem is at an optimal state.
+     * @return whether the model has been solved
+     */
+    boolean isOptimal() {
+        final double[] objectiveFunctionRow = getRow(0);
+        final int end = getRhsOffset();
+        for (int i = getNumObjectiveFunctions(); i < end; i++) {
+            final double entry = objectiveFunctionRow[i];
+            if (Precision.compareTo(entry, 0d, epsilon) < 0) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Get the current solution.
+     * @return current solution
+     */
+    protected PointValuePair getSolution() {
+        int negativeVarColumn = columnLabels.indexOf(NEGATIVE_VAR_COLUMN_LABEL);
+        Integer negativeVarBasicRow = negativeVarColumn > 0 ? getBasicRow(negativeVarColumn) : null;
+        double mostNegative = negativeVarBasicRow == null ? 0 : getEntry(negativeVarBasicRow, getRhsOffset());
+
+        final Set<Integer> usedBasicRows = new HashSet<Integer>();
+        final double[] coefficients = new double[getOriginalNumDecisionVariables()];
+        for (int i = 0; i < coefficients.length; i++) {
+            int colIndex = columnLabels.indexOf("x" + i);
+            if (colIndex < 0) {
+                coefficients[i] = 0;
+                continue;
+            }
+            Integer basicRow = getBasicRow(colIndex);
+            if (basicRow != null && basicRow == 0) {
+                // if the basic row is found to be the objective function row
+                // set the coefficient to 0 -> this case handles unconstrained
+                // variables that are still part of the objective function
+                coefficients[i] = 0;
+            } else if (usedBasicRows.contains(basicRow)) {
+                // if multiple variables can take a given value
+                // then we choose the first and set the rest equal to 0
+                coefficients[i] = 0 - (restrictToNonNegative ? 0 : mostNegative);
+            } else {
+                usedBasicRows.add(basicRow);
+                coefficients[i] =
+                    (basicRow == null ? 0 : getEntry(basicRow, getRhsOffset())) -
+                    (restrictToNonNegative ? 0 : mostNegative);
+            }
+        }
+        return new PointValuePair(coefficients, f.value(coefficients));
+    }
+
+    /**
+     * Perform the row operations of the simplex algorithm with the selected
+     * pivot column and row.
+     * @param pivotCol the pivot column
+     * @param pivotRow the pivot row
+     */
+    protected void performRowOperations(int pivotCol, int pivotRow) {
+        // set the pivot element to 1
+        final double pivotVal = getEntry(pivotRow, pivotCol);
+        divideRow(pivotRow, pivotVal);
+
+        // set the rest of the pivot column to 0
+        for (int i = 0; i < getHeight(); i++) {
+            if (i != pivotRow) {
+                final double multiplier = getEntry(i, pivotCol);
+                if (multiplier != 0.0) {
+                    subtractRow(i, pivotRow, multiplier);
+                }
+            }
+        }
+
+        // update the basic variable mappings
+        final int previousBasicVariable = getBasicVariable(pivotRow);
+        basicVariables[previousBasicVariable] = -1;
+        basicVariables[pivotCol] = pivotRow;
+        basicRows[pivotRow] = pivotCol;
+    }
+
+    /**
+     * Divides one row by a given divisor.
+     * <p>
+     * After application of this operation, the following will hold:
+     * <pre>dividendRow = dividendRow / divisor</pre>
+     *
+     * @param dividendRowIndex index of the row
+     * @param divisor value of the divisor
+     */
+    protected void divideRow(final int dividendRowIndex, final double divisor) {
+        final double[] dividendRow = getRow(dividendRowIndex);
+        for (int j = 0; j < getWidth(); j++) {
+            dividendRow[j] /= divisor;
+        }
+    }
+
+    /**
+     * Subtracts a multiple of one row from another.
+     * <p>
+     * After application of this operation, the following will hold:
+     * <pre>minuendRow = minuendRow - multiple * subtrahendRow</pre>
+     *
+     * @param minuendRowIndex row index
+     * @param subtrahendRowIndex row index
+     * @param multiplier multiplication factor
+     */
+    protected void subtractRow(final int minuendRowIndex, final int subtrahendRowIndex, final double multiplier) {
+        final double[] minuendRow = getRow(minuendRowIndex);
+        final double[] subtrahendRow = getRow(subtrahendRowIndex);
+        for (int i = 0; i < getWidth(); i++) {
+            minuendRow[i] -= subtrahendRow[i] * multiplier;
+        }
+    }
+
+    /**
+     * Get the width of the tableau.
+     * @return width of the tableau
+     */
+    protected final int getWidth() {
+        return tableau.getColumnDimension();
+    }
+
+    /**
+     * Get the height of the tableau.
+     * @return height of the tableau
+     */
+    protected final int getHeight() {
+        return tableau.getRowDimension();
+    }
+
+    /**
+     * Get an entry of the tableau.
+     * @param row row index
+     * @param column column index
+     * @return entry at (row, column)
+     */
+    protected final double getEntry(final int row, final int column) {
+        return tableau.getEntry(row, column);
+    }
+
+    /**
+     * Set an entry of the tableau.
+     * @param row row index
+     * @param column column index
+     * @param value for the entry
+     */
+    protected final void setEntry(final int row, final int column, final double value) {
+        tableau.setEntry(row, column, value);
+    }
+
+    /**
+     * Get the offset of the first slack variable.
+     * @return offset of the first slack variable
+     */
+    protected final int getSlackVariableOffset() {
+        return getNumObjectiveFunctions() + numDecisionVariables;
+    }
+
+    /**
+     * Get the offset of the first artificial variable.
+     * @return offset of the first artificial variable
+     */
+    protected final int getArtificialVariableOffset() {
+        return getNumObjectiveFunctions() + numDecisionVariables + numSlackVariables;
+    }
+
+    /**
+     * Get the offset of the right hand side.
+     * @return offset of the right hand side
+     */
+    protected final int getRhsOffset() {
+        return getWidth() - 1;
+    }
+
+    /**
+     * Get the number of decision variables.
+     * <p>
+     * If variables are not restricted to positive values, this will include 1 extra decision variable to represent
+     * the absolute value of the most negative variable.
+     *
+     * @return number of decision variables
+     * @see #getOriginalNumDecisionVariables()
+     */
+    protected final int getNumDecisionVariables() {
+        return numDecisionVariables;
+    }
+
+    /**
+     * Get the original number of decision variables.
+     * @return original number of decision variables
+     * @see #getNumDecisionVariables()
+     */
+    protected final int getOriginalNumDecisionVariables() {
+        return f.getCoefficients().getDimension();
+    }
+
+    /**
+     * Get the number of slack variables.
+     * @return number of slack variables
+     */
+    protected final int getNumSlackVariables() {
+        return numSlackVariables;
+    }
+
+    /**
+     * Get the number of artificial variables.
+     * @return number of artificial variables
+     */
+    protected final int getNumArtificialVariables() {
+        return numArtificialVariables;
+    }
+
+    /**
+     * Get the row from the tableau.
+     * @param row the row index
+     * @return the reference to the underlying row data
+     */
+    protected final double[] getRow(int row) {
+        return tableau.getDataRef()[row];
+    }
+
+    /**
+     * Get the tableau data.
+     * @return tableau data
+     */
+    protected final double[][] getData() {
+        return tableau.getData();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object other) {
+
+      if (this == other) {
+        return true;
+      }
+
+      if (other instanceof SimplexTableau) {
+          SimplexTableau rhs = (SimplexTableau) other;
+          return (restrictToNonNegative  == rhs.restrictToNonNegative) &&
+                 (numDecisionVariables   == rhs.numDecisionVariables) &&
+                 (numSlackVariables      == rhs.numSlackVariables) &&
+                 (numArtificialVariables == rhs.numArtificialVariables) &&
+                 (epsilon                == rhs.epsilon) &&
+                 (maxUlps                == rhs.maxUlps) &&
+                 f.equals(rhs.f) &&
+                 constraints.equals(rhs.constraints) &&
+                 tableau.equals(rhs.tableau);
+      }
+      return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return Boolean.valueOf(restrictToNonNegative).hashCode() ^
+               numDecisionVariables ^
+               numSlackVariables ^
+               numArtificialVariables ^
+               Double.valueOf(epsilon).hashCode() ^
+               maxUlps ^
+               f.hashCode() ^
+               constraints.hashCode() ^
+               tableau.hashCode();
+    }
+
+    /**
+     * Serialize the instance.
+     * @param oos stream where object should be written
+     * @throws IOException if object cannot be written to stream
+     */
+    private void writeObject(ObjectOutputStream oos)
+        throws IOException {
+        oos.defaultWriteObject();
+        MatrixUtils.serializeRealMatrix(tableau, oos);
+    }
+
+    /**
+     * Deserialize the instance.
+     * @param ois stream from which the object should be read
+     * @throws ClassNotFoundException if a class in the stream cannot be found
+     * @throws IOException if object cannot be read from the stream
+     */
+    private void readObject(ObjectInputStream ois)
+      throws ClassNotFoundException, IOException {
+        ois.defaultReadObject();
+        MatrixUtils.deserializeRealMatrix(this, "tableau", ois);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/linear/SolutionCallback.java b/src/main/java/org/apache/commons/math3/optim/linear/SolutionCallback.java
new file mode 100644
index 0000000..24515cc
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/linear/SolutionCallback.java
@@ -0,0 +1,62 @@
+/*
+ * 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.math3.optim.linear;
+
+import org.apache.commons.math3.optim.OptimizationData;
+import org.apache.commons.math3.optim.PointValuePair;
+
+/**
+ * A callback object that can be provided to a linear optimizer to keep track
+ * of the best solution found.
+ *
+ * @since 3.3
+ */
+public class SolutionCallback implements OptimizationData {
+    /** The SimplexTableau used by the SimplexSolver. */
+    private SimplexTableau tableau;
+
+    /**
+     * Set the simplex tableau used during the optimization once a feasible
+     * solution has been found.
+     *
+     * @param tableau the simplex tableau containing a feasible solution
+     */
+    void setTableau(final SimplexTableau tableau) {
+        this.tableau = tableau;
+    }
+
+    /**
+     * Retrieve the best solution found so far.
+     * <p>
+     * <b>Note:</b> the returned solution may not be optimal, e.g. in case
+     * the optimizer did reach the iteration limits.
+     *
+     * @return the best solution found so far by the optimizer, or {@code null} if
+     * no feasible solution could be found
+     */
+    public PointValuePair getSolution() {
+        return tableau != null ? tableau.getSolution() : null;
+    }
+
+    /**
+     * Returns if the found solution is optimal.
+     * @return {@code true} if the solution is optimal, {@code false} otherwise
+     */
+    public boolean isSolutionOptimal() {
+        return tableau != null ? tableau.isOptimal() : false;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/linear/UnboundedSolutionException.java b/src/main/java/org/apache/commons/math3/optim/linear/UnboundedSolutionException.java
new file mode 100644
index 0000000..546cdd2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/linear/UnboundedSolutionException.java
@@ -0,0 +1,37 @@
+/*
+ * 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.math3.optim.linear;
+
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * This class represents exceptions thrown by optimizers when a solution escapes to infinity.
+ *
+ * @since 2.0
+ */
+public class UnboundedSolutionException extends MathIllegalStateException {
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 940539497277290619L;
+
+    /**
+     * Simple constructor using a default message.
+     */
+    public UnboundedSolutionException() {
+        super(LocalizedFormats.UNBOUNDED_SOLUTION);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/linear/package-info.java b/src/main/java/org/apache/commons/math3/optim/linear/package-info.java
new file mode 100644
index 0000000..b900589
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/linear/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/**
+ * Optimization algorithms for linear constrained problems.
+ */
+package org.apache.commons.math3.optim.linear;
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/GoalType.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/GoalType.java
new file mode 100644
index 0000000..c0457b4
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/GoalType.java
@@ -0,0 +1,32 @@
+/*
+ * 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.math3.optim.nonlinear.scalar;
+
+import org.apache.commons.math3.optim.OptimizationData;
+
+/**
+ * Goal type for an optimization problem (minimization or maximization of
+ * a scalar function.
+ *
+ * @since 2.0
+ */
+public enum GoalType implements OptimizationData {
+    /** Maximization. */
+    MAXIMIZE,
+    /** Minimization. */
+    MINIMIZE
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/GradientMultivariateOptimizer.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/GradientMultivariateOptimizer.java
new file mode 100644
index 0000000..38a8bf7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/GradientMultivariateOptimizer.java
@@ -0,0 +1,102 @@
+/*
+ * 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.math3.optim.nonlinear.scalar;
+
+import org.apache.commons.math3.analysis.MultivariateVectorFunction;
+import org.apache.commons.math3.optim.ConvergenceChecker;
+import org.apache.commons.math3.optim.OptimizationData;
+import org.apache.commons.math3.optim.PointValuePair;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+
+/**
+ * Base class for implementing optimizers for multivariate scalar
+ * differentiable functions.
+ * It contains boiler-plate code for dealing with gradient evaluation.
+ *
+ * @since 3.1
+ */
+public abstract class GradientMultivariateOptimizer
+    extends MultivariateOptimizer {
+    /**
+     * Gradient of the objective function.
+     */
+    private MultivariateVectorFunction gradient;
+
+    /**
+     * @param checker Convergence checker.
+     */
+    protected GradientMultivariateOptimizer(ConvergenceChecker<PointValuePair> checker) {
+        super(checker);
+    }
+
+    /**
+     * Compute the gradient vector.
+     *
+     * @param params Point at which the gradient must be evaluated.
+     * @return the gradient at the specified point.
+     */
+    protected double[] computeObjectiveGradient(final double[] params) {
+        return gradient.value(params);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @param optData Optimization data. In addition to those documented in
+     * {@link MultivariateOptimizer#parseOptimizationData(OptimizationData[])
+     * MultivariateOptimizer}, this method will register the following data:
+     * <ul>
+     *  <li>{@link ObjectiveFunctionGradient}</li>
+     * </ul>
+     * @return {@inheritDoc}
+     * @throws TooManyEvaluationsException if the maximal number of
+     * evaluations (of the objective function) is exceeded.
+     */
+    @Override
+    public PointValuePair optimize(OptimizationData... optData)
+        throws TooManyEvaluationsException {
+        // Set up base class and perform computation.
+        return super.optimize(optData);
+    }
+
+    /**
+     * Scans the list of (required and optional) optimization data that
+     * characterize the problem.
+     *
+     * @param optData Optimization data.
+     * The following data will be looked for:
+     * <ul>
+     *  <li>{@link ObjectiveFunctionGradient}</li>
+     * </ul>
+     */
+    @Override
+    protected void parseOptimizationData(OptimizationData... optData) {
+        // Allow base class to register its own data.
+        super.parseOptimizationData(optData);
+
+        // The existing values (as set by the previous call) are reused if
+        // not provided in the argument list.
+        for (OptimizationData data : optData) {
+            if  (data instanceof ObjectiveFunctionGradient) {
+                gradient = ((ObjectiveFunctionGradient) data).getObjectiveFunctionGradient();
+                // If more data must be parsed, this statement _must_ be
+                // changed to "continue".
+                break;
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/LeastSquaresConverter.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/LeastSquaresConverter.java
new file mode 100644
index 0000000..4be1f12
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/LeastSquaresConverter.java
@@ -0,0 +1,186 @@
+/*
+ * 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.math3.optim.nonlinear.scalar;
+
+import org.apache.commons.math3.analysis.MultivariateFunction;
+import org.apache.commons.math3.analysis.MultivariateVectorFunction;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.linear.RealMatrix;
+
+/**
+ * This class converts
+ * {@link MultivariateVectorFunction vectorial objective functions} to
+ * {@link MultivariateFunction scalar objective functions}
+ * when the goal is to minimize them.
+ * <br/>
+ * This class is mostly used when the vectorial objective function represents
+ * a theoretical result computed from a point set applied to a model and
+ * the models point must be adjusted to fit the theoretical result to some
+ * reference observations. The observations may be obtained for example from
+ * physical measurements whether the model is built from theoretical
+ * considerations.
+ * <br/>
+ * This class computes a possibly weighted squared sum of the residuals, which is
+ * a scalar value. The residuals are the difference between the theoretical model
+ * (i.e. the output of the vectorial objective function) and the observations. The
+ * class implements the {@link MultivariateFunction} interface and can therefore be
+ * minimized by any optimizer supporting scalar objectives functions.This is one way
+ * to perform a least square estimation. There are other ways to do this without using
+ * this converter, as some optimization algorithms directly support vectorial objective
+ * functions.
+ * <br/>
+ * This class support combination of residuals with or without weights and correlations.
+  *
+ * @see MultivariateFunction
+ * @see MultivariateVectorFunction
+ * @since 2.0
+ */
+
+public class LeastSquaresConverter implements MultivariateFunction {
+    /** Underlying vectorial function. */
+    private final MultivariateVectorFunction function;
+    /** Observations to be compared to objective function to compute residuals. */
+    private final double[] observations;
+    /** Optional weights for the residuals. */
+    private final double[] weights;
+    /** Optional scaling matrix (weight and correlations) for the residuals. */
+    private final RealMatrix scale;
+
+    /**
+     * Builds a simple converter for uncorrelated residuals with identical
+     * weights.
+     *
+     * @param function vectorial residuals function to wrap
+     * @param observations observations to be compared to objective function to compute residuals
+     */
+    public LeastSquaresConverter(final MultivariateVectorFunction function,
+                                 final double[] observations) {
+        this.function     = function;
+        this.observations = observations.clone();
+        this.weights      = null;
+        this.scale        = null;
+    }
+
+    /**
+     * Builds a simple converter for uncorrelated residuals with the
+     * specified weights.
+     * <p>
+     * The scalar objective function value is computed as:
+     * <pre>
+     * objective = &sum;weight<sub>i</sub>(observation<sub>i</sub>-objective<sub>i</sub>)<sup>2</sup>
+     * </pre>
+     * </p>
+     * <p>
+     * Weights can be used for example to combine residuals with different standard
+     * deviations. As an example, consider a residuals array in which even elements
+     * are angular measurements in degrees with a 0.01&deg; standard deviation and
+     * odd elements are distance measurements in meters with a 15m standard deviation.
+     * In this case, the weights array should be initialized with value
+     * 1.0/(0.01<sup>2</sup>) in the even elements and 1.0/(15.0<sup>2</sup>) in the
+     * odd elements (i.e. reciprocals of variances).
+     * </p>
+     * <p>
+     * The array computed by the objective function, the observations array and the
+     * weights array must have consistent sizes or a {@link DimensionMismatchException}
+     * will be triggered while computing the scalar objective.
+     * </p>
+     *
+     * @param function vectorial residuals function to wrap
+     * @param observations observations to be compared to objective function to compute residuals
+     * @param weights weights to apply to the residuals
+     * @throws DimensionMismatchException if the observations vector and the weights
+     * vector dimensions do not match (objective function dimension is checked only when
+     * the {@link #value(double[])} method is called)
+     */
+    public LeastSquaresConverter(final MultivariateVectorFunction function,
+                                 final double[] observations,
+                                 final double[] weights) {
+        if (observations.length != weights.length) {
+            throw new DimensionMismatchException(observations.length, weights.length);
+        }
+        this.function     = function;
+        this.observations = observations.clone();
+        this.weights      = weights.clone();
+        this.scale        = null;
+    }
+
+    /**
+     * Builds a simple converter for correlated residuals with the
+     * specified weights.
+     * <p>
+     * The scalar objective function value is computed as:
+     * <pre>
+     * objective = y<sup>T</sup>y with y = scale&times;(observation-objective)
+     * </pre>
+     * </p>
+     * <p>
+     * The array computed by the objective function, the observations array and the
+     * the scaling matrix must have consistent sizes or a {@link DimensionMismatchException}
+     * will be triggered while computing the scalar objective.
+     * </p>
+     *
+     * @param function vectorial residuals function to wrap
+     * @param observations observations to be compared to objective function to compute residuals
+     * @param scale scaling matrix
+     * @throws DimensionMismatchException if the observations vector and the scale
+     * matrix dimensions do not match (objective function dimension is checked only when
+     * the {@link #value(double[])} method is called)
+     */
+    public LeastSquaresConverter(final MultivariateVectorFunction function,
+                                 final double[] observations,
+                                 final RealMatrix scale) {
+        if (observations.length != scale.getColumnDimension()) {
+            throw new DimensionMismatchException(observations.length, scale.getColumnDimension());
+        }
+        this.function     = function;
+        this.observations = observations.clone();
+        this.weights      = null;
+        this.scale        = scale.copy();
+    }
+
+    /** {@inheritDoc} */
+    public double value(final double[] point) {
+        // compute residuals
+        final double[] residuals = function.value(point);
+        if (residuals.length != observations.length) {
+            throw new DimensionMismatchException(residuals.length, observations.length);
+        }
+        for (int i = 0; i < residuals.length; ++i) {
+            residuals[i] -= observations[i];
+        }
+
+        // compute sum of squares
+        double sumSquares = 0;
+        if (weights != null) {
+            for (int i = 0; i < residuals.length; ++i) {
+                final double ri = residuals[i];
+                sumSquares +=  weights[i] * ri * ri;
+            }
+        } else if (scale != null) {
+            for (final double yi : scale.operate(residuals)) {
+                sumSquares += yi * yi;
+            }
+        } else {
+            for (final double ri : residuals) {
+                sumSquares += ri * ri;
+            }
+        }
+
+        return sumSquares;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/LineSearch.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/LineSearch.java
new file mode 100644
index 0000000..4a630a2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/LineSearch.java
@@ -0,0 +1,138 @@
+/*
+ * 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.math3.optim.nonlinear.scalar;
+
+import org.apache.commons.math3.optim.univariate.UnivariateOptimizer;
+import org.apache.commons.math3.optim.univariate.BrentOptimizer;
+import org.apache.commons.math3.optim.univariate.BracketFinder;
+import org.apache.commons.math3.optim.univariate.UnivariatePointValuePair;
+import org.apache.commons.math3.optim.univariate.SimpleUnivariateValueChecker;
+import org.apache.commons.math3.optim.univariate.SearchInterval;
+import org.apache.commons.math3.optim.univariate.UnivariateObjectiveFunction;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.optim.MaxEval;
+
+/**
+ * Class for finding the minimum of the objective function along a given
+ * direction.
+ *
+ * @since 3.3
+ */
+public class LineSearch {
+    /**
+     * Value that will pass the precondition check for {@link BrentOptimizer}
+     * but will not pass the convergence check, so that the custom checker
+     * will always decide when to stop the line search.
+     */
+    private static final double REL_TOL_UNUSED = 1e-15;
+    /**
+     * Value that will pass the precondition check for {@link BrentOptimizer}
+     * but will not pass the convergence check, so that the custom checker
+     * will always decide when to stop the line search.
+     */
+    private static final double ABS_TOL_UNUSED = Double.MIN_VALUE;
+    /**
+     * Optimizer used for line search.
+     */
+    private final UnivariateOptimizer lineOptimizer;
+    /**
+     * Automatic bracketing.
+     */
+    private final BracketFinder bracket = new BracketFinder();
+    /**
+     * Extent of the initial interval used to find an interval that
+     * brackets the optimum.
+     */
+    private final double initialBracketingRange;
+    /**
+     * Optimizer on behalf of which the line search must be performed.
+     */
+    private final MultivariateOptimizer mainOptimizer;
+
+    /**
+     * The {@code BrentOptimizer} default stopping criterion uses the
+     * tolerances to check the domain (point) values, not the function
+     * values.
+     * The {@code relativeTolerance} and {@code absoluteTolerance}
+     * arguments are thus passed to a {@link SimpleUnivariateValueChecker
+     * custom checker} that will use the function values.
+     *
+     * @param optimizer Optimizer on behalf of which the line search
+     * be performed.
+     * Its {@link MultivariateOptimizer#computeObjectiveValue(double[])
+     * computeObjectiveValue} method will be called by the
+     * {@link #search(double[],double[]) search} method.
+     * @param relativeTolerance Search will stop when the function relative
+     * difference between successive iterations is below this value.
+     * @param absoluteTolerance Search will stop when the function absolute
+     * difference between successive iterations is below this value.
+     * @param initialBracketingRange Extent of the initial interval used to
+     * find an interval that brackets the optimum.
+     * If the optimized function varies a lot in the vicinity of the optimum,
+     * it may be necessary to provide a value lower than the distance between
+     * successive local minima.
+     */
+    public LineSearch(MultivariateOptimizer optimizer,
+                      double relativeTolerance,
+                      double absoluteTolerance,
+                      double initialBracketingRange) {
+        mainOptimizer = optimizer;
+        lineOptimizer = new BrentOptimizer(REL_TOL_UNUSED,
+                                           ABS_TOL_UNUSED,
+                                           new SimpleUnivariateValueChecker(relativeTolerance,
+                                                                            absoluteTolerance));
+        this.initialBracketingRange = initialBracketingRange;
+    }
+
+    /**
+     * Finds the number {@code alpha} that optimizes
+     * {@code f(startPoint + alpha * direction)}.
+     *
+     * @param startPoint Starting point.
+     * @param direction Search direction.
+     * @return the optimum.
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException
+     * if the number of evaluations is exceeded.
+     */
+    public UnivariatePointValuePair search(final double[] startPoint,
+                                           final double[] direction) {
+        final int n = startPoint.length;
+        final UnivariateFunction f = new UnivariateFunction() {
+                /** {@inheritDoc} */
+                public double value(double alpha) {
+                    final double[] x = new double[n];
+                    for (int i = 0; i < n; i++) {
+                        x[i] = startPoint[i] + alpha * direction[i];
+                    }
+                    final double obj = mainOptimizer.computeObjectiveValue(x);
+                    return obj;
+                }
+            };
+
+        final GoalType goal = mainOptimizer.getGoalType();
+        bracket.search(f, goal, 0, initialBracketingRange);
+        // Passing "MAX_VALUE" as a dummy value because it is the enclosing
+        // class that counts the number of evaluations (and will eventually
+        // generate the exception).
+        return lineOptimizer.optimize(new MaxEval(Integer.MAX_VALUE),
+                                      new UnivariateObjectiveFunction(f),
+                                      goal,
+                                      new SearchInterval(bracket.getLo(),
+                                                         bracket.getHi(),
+                                                         bracket.getMid()));
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/MultiStartMultivariateOptimizer.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/MultiStartMultivariateOptimizer.java
new file mode 100644
index 0000000..86dcd70
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/MultiStartMultivariateOptimizer.java
@@ -0,0 +1,111 @@
+/*
+ * 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.math3.optim.nonlinear.scalar;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Comparator;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.random.RandomVectorGenerator;
+import org.apache.commons.math3.optim.BaseMultiStartMultivariateOptimizer;
+import org.apache.commons.math3.optim.PointValuePair;
+
+/**
+ * Multi-start optimizer.
+ *
+ * This class wraps an optimizer in order to use it several times in
+ * turn with different starting points (trying to avoid being trapped
+ * in a local extremum when looking for a global one).
+ *
+ * @since 3.0
+ */
+public class MultiStartMultivariateOptimizer
+    extends BaseMultiStartMultivariateOptimizer<PointValuePair> {
+    /** Underlying optimizer. */
+    private final MultivariateOptimizer optimizer;
+    /** Found optima. */
+    private final List<PointValuePair> optima = new ArrayList<PointValuePair>();
+
+    /**
+     * Create a multi-start optimizer from a single-start optimizer.
+     *
+     * @param optimizer Single-start optimizer to wrap.
+     * @param starts Number of starts to perform.
+     * If {@code starts == 1}, the result will be same as if {@code optimizer}
+     * is called directly.
+     * @param generator Random vector generator to use for restarts.
+     * @throws NullArgumentException if {@code optimizer} or {@code generator}
+     * is {@code null}.
+     * @throws NotStrictlyPositiveException if {@code starts < 1}.
+     */
+    public MultiStartMultivariateOptimizer(final MultivariateOptimizer optimizer,
+                                           final int starts,
+                                           final RandomVectorGenerator generator)
+        throws NullArgumentException,
+        NotStrictlyPositiveException {
+        super(optimizer, starts, generator);
+        this.optimizer = optimizer;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public PointValuePair[] getOptima() {
+        Collections.sort(optima, getPairComparator());
+        return optima.toArray(new PointValuePair[0]);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void store(PointValuePair optimum) {
+        optima.add(optimum);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void clear() {
+        optima.clear();
+    }
+
+    /**
+     * @return a comparator for sorting the optima.
+     */
+    private Comparator<PointValuePair> getPairComparator() {
+        return new Comparator<PointValuePair>() {
+            /** {@inheritDoc} */
+            public int compare(final PointValuePair o1,
+                               final PointValuePair o2) {
+                if (o1 == null) {
+                    return (o2 == null) ? 0 : 1;
+                } else if (o2 == null) {
+                    return -1;
+                }
+                final double v1 = o1.getValue();
+                final double v2 = o2.getValue();
+                return (optimizer.getGoalType() == GoalType.MINIMIZE) ?
+                    Double.compare(v1, v2) : Double.compare(v2, v1);
+            }
+        };
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/MultivariateFunctionMappingAdapter.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/MultivariateFunctionMappingAdapter.java
new file mode 100644
index 0000000..3c5127c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/MultivariateFunctionMappingAdapter.java
@@ -0,0 +1,294 @@
+/*
+ * 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.math3.optim.nonlinear.scalar;
+
+import org.apache.commons.math3.analysis.MultivariateFunction;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.function.Logit;
+import org.apache.commons.math3.analysis.function.Sigmoid;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * <p>Adapter for mapping bounded {@link MultivariateFunction} to unbounded ones.</p>
+ *
+ * <p>
+ * This adapter can be used to wrap functions subject to simple bounds on
+ * parameters so they can be used by optimizers that do <em>not</em> directly
+ * support simple bounds.
+ * </p>
+ * <p>
+ * The principle is that the user function that will be wrapped will see its
+ * parameters bounded as required, i.e when its {@code value} method is called
+ * with argument array {@code point}, the elements array will fulfill requirement
+ * {@code lower[i] <= point[i] <= upper[i]} for all i. Some of the components
+ * may be unbounded or bounded only on one side if the corresponding bound is
+ * set to an infinite value. The optimizer will not manage the user function by
+ * itself, but it will handle this adapter and it is this adapter that will take
+ * care the bounds are fulfilled. The adapter {@link #value(double[])} method will
+ * be called by the optimizer with unbound parameters, and the adapter will map
+ * the unbounded value to the bounded range using appropriate functions like
+ * {@link Sigmoid} for double bounded elements for example.
+ * </p>
+ * <p>
+ * As the optimizer sees only unbounded parameters, it should be noted that the
+ * start point or simplex expected by the optimizer should be unbounded, so the
+ * user is responsible for converting his bounded point to unbounded by calling
+ * {@link #boundedToUnbounded(double[])} before providing them to the optimizer.
+ * For the same reason, the point returned by the {@link
+ * org.apache.commons.math3.optimization.BaseMultivariateOptimizer#optimize(int,
+ * MultivariateFunction, org.apache.commons.math3.optimization.GoalType, double[])}
+ * method is unbounded. So to convert this point to bounded, users must call
+ * {@link #unboundedToBounded(double[])} by themselves!</p>
+ * <p>
+ * This adapter is only a poor man solution to simple bounds optimization constraints
+ * that can be used with simple optimizers like
+ * {@link org.apache.commons.math3.optim.nonlinear.scalar.noderiv.SimplexOptimizer
+ * SimplexOptimizer}.
+ * A better solution is to use an optimizer that directly supports simple bounds like
+ * {@link org.apache.commons.math3.optim.nonlinear.scalar.noderiv.CMAESOptimizer
+ * CMAESOptimizer} or
+ * {@link org.apache.commons.math3.optim.nonlinear.scalar.noderiv.BOBYQAOptimizer
+ * BOBYQAOptimizer}.
+ * One caveat of this poor-man's solution is that behavior near the bounds may be
+ * numerically unstable as bounds are mapped from infinite values.
+ * Another caveat is that convergence values are evaluated by the optimizer with
+ * respect to unbounded variables, so there will be scales differences when
+ * converted to bounded variables.
+ * </p>
+ *
+ * @see MultivariateFunctionPenaltyAdapter
+ *
+ * @since 3.0
+ */
+public class MultivariateFunctionMappingAdapter
+    implements MultivariateFunction {
+    /** Underlying bounded function. */
+    private final MultivariateFunction bounded;
+    /** Mapping functions. */
+    private final Mapper[] mappers;
+
+    /** Simple constructor.
+     * @param bounded bounded function
+     * @param lower lower bounds for each element of the input parameters array
+     * (some elements may be set to {@code Double.NEGATIVE_INFINITY} for
+     * unbounded values)
+     * @param upper upper bounds for each element of the input parameters array
+     * (some elements may be set to {@code Double.POSITIVE_INFINITY} for
+     * unbounded values)
+     * @exception DimensionMismatchException if lower and upper bounds are not
+     * consistent, either according to dimension or to values
+     */
+    public MultivariateFunctionMappingAdapter(final MultivariateFunction bounded,
+                                              final double[] lower, final double[] upper) {
+        // safety checks
+        MathUtils.checkNotNull(lower);
+        MathUtils.checkNotNull(upper);
+        if (lower.length != upper.length) {
+            throw new DimensionMismatchException(lower.length, upper.length);
+        }
+        for (int i = 0; i < lower.length; ++i) {
+            // note the following test is written in such a way it also fails for NaN
+            if (!(upper[i] >= lower[i])) {
+                throw new NumberIsTooSmallException(upper[i], lower[i], true);
+            }
+        }
+
+        this.bounded = bounded;
+        this.mappers = new Mapper[lower.length];
+        for (int i = 0; i < mappers.length; ++i) {
+            if (Double.isInfinite(lower[i])) {
+                if (Double.isInfinite(upper[i])) {
+                    // element is unbounded, no transformation is needed
+                    mappers[i] = new NoBoundsMapper();
+                } else {
+                    // element is simple-bounded on the upper side
+                    mappers[i] = new UpperBoundMapper(upper[i]);
+                }
+            } else {
+                if (Double.isInfinite(upper[i])) {
+                    // element is simple-bounded on the lower side
+                    mappers[i] = new LowerBoundMapper(lower[i]);
+                } else {
+                    // element is double-bounded
+                    mappers[i] = new LowerUpperBoundMapper(lower[i], upper[i]);
+                }
+            }
+        }
+    }
+
+    /**
+     * Maps an array from unbounded to bounded.
+     *
+     * @param point Unbounded values.
+     * @return the bounded values.
+     */
+    public double[] unboundedToBounded(double[] point) {
+        // Map unbounded input point to bounded point.
+        final double[] mapped = new double[mappers.length];
+        for (int i = 0; i < mappers.length; ++i) {
+            mapped[i] = mappers[i].unboundedToBounded(point[i]);
+        }
+
+        return mapped;
+    }
+
+    /**
+     * Maps an array from bounded to unbounded.
+     *
+     * @param point Bounded values.
+     * @return the unbounded values.
+     */
+    public double[] boundedToUnbounded(double[] point) {
+        // Map bounded input point to unbounded point.
+        final double[] mapped = new double[mappers.length];
+        for (int i = 0; i < mappers.length; ++i) {
+            mapped[i] = mappers[i].boundedToUnbounded(point[i]);
+        }
+
+        return mapped;
+    }
+
+    /**
+     * Compute the underlying function value from an unbounded point.
+     * <p>
+     * This method simply bounds the unbounded point using the mappings
+     * set up at construction and calls the underlying function using
+     * the bounded point.
+     * </p>
+     * @param point unbounded value
+     * @return underlying function value
+     * @see #unboundedToBounded(double[])
+     */
+    public double value(double[] point) {
+        return bounded.value(unboundedToBounded(point));
+    }
+
+    /** Mapping interface. */
+    private interface Mapper {
+        /**
+         * Maps a value from unbounded to bounded.
+         *
+         * @param y Unbounded value.
+         * @return the bounded value.
+         */
+        double unboundedToBounded(double y);
+
+        /**
+         * Maps a value from bounded to unbounded.
+         *
+         * @param x Bounded value.
+         * @return the unbounded value.
+         */
+        double boundedToUnbounded(double x);
+    }
+
+    /** Local class for no bounds mapping. */
+    private static class NoBoundsMapper implements Mapper {
+        /** {@inheritDoc} */
+        public double unboundedToBounded(final double y) {
+            return y;
+        }
+
+        /** {@inheritDoc} */
+        public double boundedToUnbounded(final double x) {
+            return x;
+        }
+    }
+
+    /** Local class for lower bounds mapping. */
+    private static class LowerBoundMapper implements Mapper {
+        /** Low bound. */
+        private final double lower;
+
+        /**
+         * Simple constructor.
+         *
+         * @param lower lower bound
+         */
+        LowerBoundMapper(final double lower) {
+            this.lower = lower;
+        }
+
+        /** {@inheritDoc} */
+        public double unboundedToBounded(final double y) {
+            return lower + FastMath.exp(y);
+        }
+
+        /** {@inheritDoc} */
+        public double boundedToUnbounded(final double x) {
+            return FastMath.log(x - lower);
+        }
+
+    }
+
+    /** Local class for upper bounds mapping. */
+    private static class UpperBoundMapper implements Mapper {
+
+        /** Upper bound. */
+        private final double upper;
+
+        /** Simple constructor.
+         * @param upper upper bound
+         */
+        UpperBoundMapper(final double upper) {
+            this.upper = upper;
+        }
+
+        /** {@inheritDoc} */
+        public double unboundedToBounded(final double y) {
+            return upper - FastMath.exp(-y);
+        }
+
+        /** {@inheritDoc} */
+        public double boundedToUnbounded(final double x) {
+            return -FastMath.log(upper - x);
+        }
+
+    }
+
+    /** Local class for lower and bounds mapping. */
+    private static class LowerUpperBoundMapper implements Mapper {
+        /** Function from unbounded to bounded. */
+        private final UnivariateFunction boundingFunction;
+        /** Function from bounded to unbounded. */
+        private final UnivariateFunction unboundingFunction;
+
+        /**
+         * Simple constructor.
+         *
+         * @param lower lower bound
+         * @param upper upper bound
+         */
+        LowerUpperBoundMapper(final double lower, final double upper) {
+            boundingFunction   = new Sigmoid(lower, upper);
+            unboundingFunction = new Logit(lower, upper);
+        }
+
+        /** {@inheritDoc} */
+        public double unboundedToBounded(final double y) {
+            return boundingFunction.value(y);
+        }
+
+        /** {@inheritDoc} */
+        public double boundedToUnbounded(final double x) {
+            return unboundingFunction.value(x);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/MultivariateFunctionPenaltyAdapter.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/MultivariateFunctionPenaltyAdapter.java
new file mode 100644
index 0000000..931f17f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/MultivariateFunctionPenaltyAdapter.java
@@ -0,0 +1,186 @@
+/*
+ * 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.math3.optim.nonlinear.scalar;
+
+import org.apache.commons.math3.analysis.MultivariateFunction;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * <p>Adapter extending bounded {@link MultivariateFunction} to an unbouded
+ * domain using a penalty function.</p>
+ *
+ * <p>
+ * This adapter can be used to wrap functions subject to simple bounds on
+ * parameters so they can be used by optimizers that do <em>not</em> directly
+ * support simple bounds.
+ * </p>
+ * <p>
+ * The principle is that the user function that will be wrapped will see its
+ * parameters bounded as required, i.e when its {@code value} method is called
+ * with argument array {@code point}, the elements array will fulfill requirement
+ * {@code lower[i] <= point[i] <= upper[i]} for all i. Some of the components
+ * may be unbounded or bounded only on one side if the corresponding bound is
+ * set to an infinite value. The optimizer will not manage the user function by
+ * itself, but it will handle this adapter and it is this adapter that will take
+ * care the bounds are fulfilled. The adapter {@link #value(double[])} method will
+ * be called by the optimizer with unbound parameters, and the adapter will check
+ * if the parameters is within range or not. If it is in range, then the underlying
+ * user function will be called, and if it is not the value of a penalty function
+ * will be returned instead.
+ * </p>
+ * <p>
+ * This adapter is only a poor-man's solution to simple bounds optimization
+ * constraints that can be used with simple optimizers like
+ * {@link org.apache.commons.math3.optim.nonlinear.scalar.noderiv.SimplexOptimizer
+ * SimplexOptimizer}.
+ * A better solution is to use an optimizer that directly supports simple bounds like
+ * {@link org.apache.commons.math3.optim.nonlinear.scalar.noderiv.CMAESOptimizer
+ * CMAESOptimizer} or
+ * {@link org.apache.commons.math3.optim.nonlinear.scalar.noderiv.BOBYQAOptimizer
+ * BOBYQAOptimizer}.
+ * One caveat of this poor-man's solution is that if start point or start simplex
+ * is completely outside of the allowed range, only the penalty function is used,
+ * and the optimizer may converge without ever entering the range.
+ * </p>
+ *
+ * @see MultivariateFunctionMappingAdapter
+ *
+ * @since 3.0
+ */
+public class MultivariateFunctionPenaltyAdapter
+    implements MultivariateFunction {
+    /** Underlying bounded function. */
+    private final MultivariateFunction bounded;
+    /** Lower bounds. */
+    private final double[] lower;
+    /** Upper bounds. */
+    private final double[] upper;
+    /** Penalty offset. */
+    private final double offset;
+    /** Penalty scales. */
+    private final double[] scale;
+
+    /**
+     * Simple constructor.
+     * <p>
+     * When the optimizer provided points are out of range, the value of the
+     * penalty function will be used instead of the value of the underlying
+     * function. In order for this penalty to be effective in rejecting this
+     * point during the optimization process, the penalty function value should
+     * be defined with care. This value is computed as:
+     * <pre>
+     *   penalty(point) = offset + &sum;<sub>i</sub>[scale[i] * &radic;|point[i]-boundary[i]|]
+     * </pre>
+     * where indices i correspond to all the components that violates their boundaries.
+     * </p>
+     * <p>
+     * So when attempting a function minimization, offset should be larger than
+     * the maximum expected value of the underlying function and scale components
+     * should all be positive. When attempting a function maximization, offset
+     * should be lesser than the minimum expected value of the underlying function
+     * and scale components should all be negative.
+     * minimization, and lesser than the minimum expected value of the underlying
+     * function when attempting maximization.
+     * </p>
+     * <p>
+     * These choices for the penalty function have two properties. First, all out
+     * of range points will return a function value that is worse than the value
+     * returned by any in range point. Second, the penalty is worse for large
+     * boundaries violation than for small violations, so the optimizer has an hint
+     * about the direction in which it should search for acceptable points.
+     * </p>
+     * @param bounded bounded function
+     * @param lower lower bounds for each element of the input parameters array
+     * (some elements may be set to {@code Double.NEGATIVE_INFINITY} for
+     * unbounded values)
+     * @param upper upper bounds for each element of the input parameters array
+     * (some elements may be set to {@code Double.POSITIVE_INFINITY} for
+     * unbounded values)
+     * @param offset base offset of the penalty function
+     * @param scale scale of the penalty function
+     * @exception DimensionMismatchException if lower bounds, upper bounds and
+     * scales are not consistent, either according to dimension or to bounadary
+     * values
+     */
+    public MultivariateFunctionPenaltyAdapter(final MultivariateFunction bounded,
+                                              final double[] lower, final double[] upper,
+                                              final double offset, final double[] scale) {
+
+        // safety checks
+        MathUtils.checkNotNull(lower);
+        MathUtils.checkNotNull(upper);
+        MathUtils.checkNotNull(scale);
+        if (lower.length != upper.length) {
+            throw new DimensionMismatchException(lower.length, upper.length);
+        }
+        if (lower.length != scale.length) {
+            throw new DimensionMismatchException(lower.length, scale.length);
+        }
+        for (int i = 0; i < lower.length; ++i) {
+            // note the following test is written in such a way it also fails for NaN
+            if (!(upper[i] >= lower[i])) {
+                throw new NumberIsTooSmallException(upper[i], lower[i], true);
+            }
+        }
+
+        this.bounded = bounded;
+        this.lower   = lower.clone();
+        this.upper   = upper.clone();
+        this.offset  = offset;
+        this.scale   = scale.clone();
+    }
+
+    /**
+     * Computes the underlying function value from an unbounded point.
+     * <p>
+     * This method simply returns the value of the underlying function
+     * if the unbounded point already fulfills the bounds, and compute
+     * a replacement value using the offset and scale if bounds are
+     * violated, without calling the function at all.
+     * </p>
+     * @param point unbounded point
+     * @return either underlying function value or penalty function value
+     */
+    public double value(double[] point) {
+
+        for (int i = 0; i < scale.length; ++i) {
+            if ((point[i] < lower[i]) || (point[i] > upper[i])) {
+                // bound violation starting at this component
+                double sum = 0;
+                for (int j = i; j < scale.length; ++j) {
+                    final double overshoot;
+                    if (point[j] < lower[j]) {
+                        overshoot = scale[j] * (lower[j] - point[j]);
+                    } else if (point[j] > upper[j]) {
+                        overshoot = scale[j] * (point[j] - upper[j]);
+                    } else {
+                        overshoot = 0;
+                    }
+                    sum += FastMath.sqrt(overshoot);
+                }
+                return offset + sum;
+            }
+        }
+
+        // all boundaries are fulfilled, we are in the expected
+        // domain of the underlying function
+        return bounded.value(point);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/MultivariateOptimizer.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/MultivariateOptimizer.java
new file mode 100644
index 0000000..bc0bec9
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/MultivariateOptimizer.java
@@ -0,0 +1,117 @@
+/*
+ * 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.math3.optim.nonlinear.scalar;
+
+import org.apache.commons.math3.analysis.MultivariateFunction;
+import org.apache.commons.math3.optim.BaseMultivariateOptimizer;
+import org.apache.commons.math3.optim.OptimizationData;
+import org.apache.commons.math3.optim.ConvergenceChecker;
+import org.apache.commons.math3.optim.PointValuePair;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+
+/**
+ * Base class for a multivariate scalar function optimizer.
+ *
+ * @since 3.1
+ */
+public abstract class MultivariateOptimizer
+    extends BaseMultivariateOptimizer<PointValuePair> {
+    /** Objective function. */
+    private MultivariateFunction function;
+    /** Type of optimization. */
+    private GoalType goal;
+
+    /**
+     * @param checker Convergence checker.
+     */
+    protected MultivariateOptimizer(ConvergenceChecker<PointValuePair> checker) {
+        super(checker);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @param optData Optimization data. In addition to those documented in
+     * {@link BaseMultivariateOptimizer#parseOptimizationData(OptimizationData[])
+     * BaseMultivariateOptimizer}, this method will register the following data:
+     * <ul>
+     *  <li>{@link ObjectiveFunction}</li>
+     *  <li>{@link GoalType}</li>
+     * </ul>
+     * @return {@inheritDoc}
+     * @throws TooManyEvaluationsException if the maximal number of
+     * evaluations is exceeded.
+     */
+    @Override
+    public PointValuePair optimize(OptimizationData... optData)
+        throws TooManyEvaluationsException {
+        // Set up base class and perform computation.
+        return super.optimize(optData);
+    }
+
+    /**
+     * Scans the list of (required and optional) optimization data that
+     * characterize the problem.
+     *
+     * @param optData Optimization data.
+     * The following data will be looked for:
+     * <ul>
+     *  <li>{@link ObjectiveFunction}</li>
+     *  <li>{@link GoalType}</li>
+     * </ul>
+     */
+    @Override
+    protected void parseOptimizationData(OptimizationData... optData) {
+        // Allow base class to register its own data.
+        super.parseOptimizationData(optData);
+
+        // The existing values (as set by the previous call) are reused if
+        // not provided in the argument list.
+        for (OptimizationData data : optData) {
+            if (data instanceof GoalType) {
+                goal = (GoalType) data;
+                continue;
+            }
+            if (data instanceof ObjectiveFunction) {
+                function = ((ObjectiveFunction) data).getObjectiveFunction();
+                continue;
+            }
+        }
+    }
+
+    /**
+     * @return the optimization type.
+     */
+    public GoalType getGoalType() {
+        return goal;
+    }
+
+    /**
+     * Computes the objective function value.
+     * This method <em>must</em> be called by subclasses to enforce the
+     * evaluation counter limit.
+     *
+     * @param params Point at which the objective function must be evaluated.
+     * @return the objective function value at the specified point.
+     * @throws TooManyEvaluationsException if the maximal number of
+     * evaluations is exceeded.
+     */
+    public double computeObjectiveValue(double[] params) {
+        super.incrementEvaluationCount();
+        return function.value(params);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/ObjectiveFunction.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/ObjectiveFunction.java
new file mode 100644
index 0000000..643cc03
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/ObjectiveFunction.java
@@ -0,0 +1,46 @@
+/*
+ * 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.math3.optim.nonlinear.scalar;
+
+import org.apache.commons.math3.analysis.MultivariateFunction;
+import org.apache.commons.math3.optim.OptimizationData;
+
+/**
+ * Scalar function to be optimized.
+ *
+ * @since 3.1
+ */
+public class ObjectiveFunction implements OptimizationData {
+    /** Function to be optimized. */
+    private final MultivariateFunction function;
+
+    /**
+     * @param f Function to be optimized.
+     */
+    public ObjectiveFunction(MultivariateFunction f) {
+        function = f;
+    }
+
+    /**
+     * Gets the function to be optimized.
+     *
+     * @return the objective function.
+     */
+    public MultivariateFunction getObjectiveFunction() {
+        return function;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/ObjectiveFunctionGradient.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/ObjectiveFunctionGradient.java
new file mode 100644
index 0000000..2fcf2ee
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/ObjectiveFunctionGradient.java
@@ -0,0 +1,46 @@
+/*
+ * 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.math3.optim.nonlinear.scalar;
+
+import org.apache.commons.math3.analysis.MultivariateVectorFunction;
+import org.apache.commons.math3.optim.OptimizationData;
+
+/**
+ * Gradient of the scalar function to be optimized.
+ *
+ * @since 3.1
+ */
+public class ObjectiveFunctionGradient implements OptimizationData {
+    /** Function to be optimized. */
+    private final MultivariateVectorFunction gradient;
+
+    /**
+     * @param g Gradient of the function to be optimized.
+     */
+    public ObjectiveFunctionGradient(MultivariateVectorFunction g) {
+        gradient = g;
+    }
+
+    /**
+     * Gets the gradient of the function to be optimized.
+     *
+     * @return the objective function gradient.
+     */
+    public MultivariateVectorFunction getObjectiveFunctionGradient() {
+        return gradient;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/gradient/NonLinearConjugateGradientOptimizer.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/gradient/NonLinearConjugateGradientOptimizer.java
new file mode 100644
index 0000000..9074122
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/gradient/NonLinearConjugateGradientOptimizer.java
@@ -0,0 +1,415 @@
+/*
+ * 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.math3.optim.nonlinear.scalar.gradient;
+
+import org.apache.commons.math3.analysis.solvers.UnivariateSolver;
+import org.apache.commons.math3.exception.MathInternalError;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+import org.apache.commons.math3.exception.MathUnsupportedOperationException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.optim.OptimizationData;
+import org.apache.commons.math3.optim.PointValuePair;
+import org.apache.commons.math3.optim.ConvergenceChecker;
+import org.apache.commons.math3.optim.nonlinear.scalar.GoalType;
+import org.apache.commons.math3.optim.nonlinear.scalar.GradientMultivariateOptimizer;
+import org.apache.commons.math3.optim.nonlinear.scalar.LineSearch;
+
+
+/**
+ * Non-linear conjugate gradient optimizer.
+ * <br/>
+ * This class supports both the Fletcher-Reeves and the Polak-Ribière
+ * update formulas for the conjugate search directions.
+ * It also supports optional preconditioning.
+ * <br/>
+ * Constraints are not supported: the call to
+ * {@link #optimize(OptimizationData[]) optimize} will throw
+ * {@link MathUnsupportedOperationException} if bounds are passed to it.
+ *
+ * @since 2.0
+ */
+public class NonLinearConjugateGradientOptimizer
+    extends GradientMultivariateOptimizer {
+    /** Update formula for the beta parameter. */
+    private final Formula updateFormula;
+    /** Preconditioner (may be null). */
+    private final Preconditioner preconditioner;
+    /** Line search algorithm. */
+    private final LineSearch line;
+
+    /**
+     * Available choices of update formulas for the updating the parameter
+     * that is used to compute the successive conjugate search directions.
+     * For non-linear conjugate gradients, there are
+     * two formulas:
+     * <ul>
+     *   <li>Fletcher-Reeves formula</li>
+     *   <li>Polak-Ribière formula</li>
+     * </ul>
+     *
+     * On the one hand, the Fletcher-Reeves formula is guaranteed to converge
+     * if the start point is close enough of the optimum whether the
+     * Polak-Ribière formula may not converge in rare cases. On the
+     * other hand, the Polak-Ribière formula is often faster when it
+     * does converge. Polak-Ribière is often used.
+     *
+     * @since 2.0
+     */
+    public enum Formula {
+        /** Fletcher-Reeves formula. */
+        FLETCHER_REEVES,
+        /** Polak-Ribière formula. */
+        POLAK_RIBIERE
+    }
+
+    /**
+     * The initial step is a factor with respect to the search direction
+     * (which itself is roughly related to the gradient of the function).
+     * <br/>
+     * It is used to find an interval that brackets the optimum in line
+     * search.
+     *
+     * @since 3.1
+     * @deprecated As of v3.3, this class is not used anymore.
+     * This setting is replaced by the {@code initialBracketingRange}
+     * argument to the new constructors.
+     */
+    @Deprecated
+    public static class BracketingStep implements OptimizationData {
+        /** Initial step. */
+        private final double initialStep;
+
+        /**
+         * @param step Initial step for the bracket search.
+         */
+        public BracketingStep(double step) {
+            initialStep = step;
+        }
+
+        /**
+         * Gets the initial step.
+         *
+         * @return the initial step.
+         */
+        public double getBracketingStep() {
+            return initialStep;
+        }
+    }
+
+    /**
+     * Constructor with default tolerances for the line search (1e-8) and
+     * {@link IdentityPreconditioner preconditioner}.
+     *
+     * @param updateFormula formula to use for updating the &beta; parameter,
+     * must be one of {@link Formula#FLETCHER_REEVES} or
+     * {@link Formula#POLAK_RIBIERE}.
+     * @param checker Convergence checker.
+     */
+    public NonLinearConjugateGradientOptimizer(final Formula updateFormula,
+                                               ConvergenceChecker<PointValuePair> checker) {
+        this(updateFormula,
+             checker,
+             1e-8,
+             1e-8,
+             1e-8,
+             new IdentityPreconditioner());
+    }
+
+    /**
+     * Constructor with default {@link IdentityPreconditioner preconditioner}.
+     *
+     * @param updateFormula formula to use for updating the &beta; parameter,
+     * must be one of {@link Formula#FLETCHER_REEVES} or
+     * {@link Formula#POLAK_RIBIERE}.
+     * @param checker Convergence checker.
+     * @param lineSearchSolver Solver to use during line search.
+     * @deprecated as of 3.3. Please use
+     * {@link #NonLinearConjugateGradientOptimizer(Formula,ConvergenceChecker,double,double,double)} instead.
+     */
+    @Deprecated
+    public NonLinearConjugateGradientOptimizer(final Formula updateFormula,
+                                               ConvergenceChecker<PointValuePair> checker,
+                                               final UnivariateSolver lineSearchSolver) {
+        this(updateFormula,
+             checker,
+             lineSearchSolver,
+             new IdentityPreconditioner());
+    }
+
+    /**
+     * Constructor with default {@link IdentityPreconditioner preconditioner}.
+     *
+     * @param updateFormula formula to use for updating the &beta; parameter,
+     * must be one of {@link Formula#FLETCHER_REEVES} or
+     * {@link Formula#POLAK_RIBIERE}.
+     * @param checker Convergence checker.
+     * @param relativeTolerance Relative threshold for line search.
+     * @param absoluteTolerance Absolute threshold for line search.
+     * @param initialBracketingRange Extent of the initial interval used to
+     * find an interval that brackets the optimum in order to perform the
+     * line search.
+     *
+     * @see LineSearch#LineSearch(MultivariateOptimizer,double,double,double)
+     * @since 3.3
+     */
+    public NonLinearConjugateGradientOptimizer(final Formula updateFormula,
+                                               ConvergenceChecker<PointValuePair> checker,
+                                               double relativeTolerance,
+                                               double absoluteTolerance,
+                                               double initialBracketingRange) {
+        this(updateFormula,
+             checker,
+             relativeTolerance,
+             absoluteTolerance,
+             initialBracketingRange,
+             new IdentityPreconditioner());
+    }
+
+    /**
+     * @param updateFormula formula to use for updating the &beta; parameter,
+     * must be one of {@link Formula#FLETCHER_REEVES} or
+     * {@link Formula#POLAK_RIBIERE}.
+     * @param checker Convergence checker.
+     * @param lineSearchSolver Solver to use during line search.
+     * @param preconditioner Preconditioner.
+     * @deprecated as of 3.3. Please use
+     * {@link #NonLinearConjugateGradientOptimizer(Formula,ConvergenceChecker,double,double,double,Preconditioner)} instead.
+     */
+    @Deprecated
+    public NonLinearConjugateGradientOptimizer(final Formula updateFormula,
+                                               ConvergenceChecker<PointValuePair> checker,
+                                               final UnivariateSolver lineSearchSolver,
+                                               final Preconditioner preconditioner) {
+        this(updateFormula,
+             checker,
+             lineSearchSolver.getRelativeAccuracy(),
+             lineSearchSolver.getAbsoluteAccuracy(),
+             lineSearchSolver.getAbsoluteAccuracy(),
+             preconditioner);
+    }
+
+    /**
+     * @param updateFormula formula to use for updating the &beta; parameter,
+     * must be one of {@link Formula#FLETCHER_REEVES} or
+     * {@link Formula#POLAK_RIBIERE}.
+     * @param checker Convergence checker.
+     * @param preconditioner Preconditioner.
+     * @param relativeTolerance Relative threshold for line search.
+     * @param absoluteTolerance Absolute threshold for line search.
+     * @param initialBracketingRange Extent of the initial interval used to
+     * find an interval that brackets the optimum in order to perform the
+     * line search.
+     *
+     * @see LineSearch#LineSearch(MultivariateOptimizer,double,double,double)
+     * @since 3.3
+     */
+    public NonLinearConjugateGradientOptimizer(final Formula updateFormula,
+                                               ConvergenceChecker<PointValuePair> checker,
+                                               double relativeTolerance,
+                                               double absoluteTolerance,
+                                               double initialBracketingRange,
+                                               final Preconditioner preconditioner) {
+        super(checker);
+
+        this.updateFormula = updateFormula;
+        this.preconditioner = preconditioner;
+        line = new LineSearch(this,
+                              relativeTolerance,
+                              absoluteTolerance,
+                              initialBracketingRange);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public PointValuePair optimize(OptimizationData... optData)
+        throws TooManyEvaluationsException {
+        // Set up base class and perform computation.
+        return super.optimize(optData);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected PointValuePair doOptimize() {
+        final ConvergenceChecker<PointValuePair> checker = getConvergenceChecker();
+        final double[] point = getStartPoint();
+        final GoalType goal = getGoalType();
+        final int n = point.length;
+        double[] r = computeObjectiveGradient(point);
+        if (goal == GoalType.MINIMIZE) {
+            for (int i = 0; i < n; i++) {
+                r[i] = -r[i];
+            }
+        }
+
+        // Initial search direction.
+        double[] steepestDescent = preconditioner.precondition(point, r);
+        double[] searchDirection = steepestDescent.clone();
+
+        double delta = 0;
+        for (int i = 0; i < n; ++i) {
+            delta += r[i] * searchDirection[i];
+        }
+
+        PointValuePair current = null;
+        while (true) {
+            incrementIterationCount();
+
+            final double objective = computeObjectiveValue(point);
+            PointValuePair previous = current;
+            current = new PointValuePair(point, objective);
+            if (previous != null && checker.converged(getIterations(), previous, current)) {
+                // We have found an optimum.
+                return current;
+            }
+
+            final double step = line.search(point, searchDirection).getPoint();
+
+            // Validate new point.
+            for (int i = 0; i < point.length; ++i) {
+                point[i] += step * searchDirection[i];
+            }
+
+            r = computeObjectiveGradient(point);
+            if (goal == GoalType.MINIMIZE) {
+                for (int i = 0; i < n; ++i) {
+                    r[i] = -r[i];
+                }
+            }
+
+            // Compute beta.
+            final double deltaOld = delta;
+            final double[] newSteepestDescent = preconditioner.precondition(point, r);
+            delta = 0;
+            for (int i = 0; i < n; ++i) {
+                delta += r[i] * newSteepestDescent[i];
+            }
+
+            final double beta;
+            switch (updateFormula) {
+            case FLETCHER_REEVES:
+                beta = delta / deltaOld;
+                break;
+            case POLAK_RIBIERE:
+                double deltaMid = 0;
+                for (int i = 0; i < r.length; ++i) {
+                    deltaMid += r[i] * steepestDescent[i];
+                }
+                beta = (delta - deltaMid) / deltaOld;
+                break;
+            default:
+                // Should never happen.
+                throw new MathInternalError();
+            }
+            steepestDescent = newSteepestDescent;
+
+            // Compute conjugate search direction.
+            if (getIterations() % n == 0 ||
+                beta < 0) {
+                // Break conjugation: reset search direction.
+                searchDirection = steepestDescent.clone();
+            } else {
+                // Compute new conjugate search direction.
+                for (int i = 0; i < n; ++i) {
+                    searchDirection[i] = steepestDescent[i] + beta * searchDirection[i];
+                }
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void parseOptimizationData(OptimizationData... optData) {
+        // Allow base class to register its own data.
+        super.parseOptimizationData(optData);
+
+        checkParameters();
+    }
+
+    /** Default identity preconditioner. */
+    public static class IdentityPreconditioner implements Preconditioner {
+        /** {@inheritDoc} */
+        public double[] precondition(double[] variables, double[] r) {
+            return r.clone();
+        }
+    }
+
+    // Class is not used anymore (cf. MATH-1092). However, it might
+    // be interesting to create a class similar to "LineSearch", but
+    // that will take advantage that the model's gradient is available.
+//     /**
+//      * Internal class for line search.
+//      * <p>
+//      * The function represented by this class is the dot product of
+//      * the objective function gradient and the search direction. Its
+//      * value is zero when the gradient is orthogonal to the search
+//      * direction, i.e. when the objective function value is a local
+//      * extremum along the search direction.
+//      * </p>
+//      */
+//     private class LineSearchFunction implements UnivariateFunction {
+//         /** Current point. */
+//         private final double[] currentPoint;
+//         /** Search direction. */
+//         private final double[] searchDirection;
+
+//         /**
+//          * @param point Current point.
+//          * @param direction Search direction.
+//          */
+//         public LineSearchFunction(double[] point,
+//                                   double[] direction) {
+//             currentPoint = point.clone();
+//             searchDirection = direction.clone();
+//         }
+
+//         /** {@inheritDoc} */
+//         public double value(double x) {
+//             // current point in the search direction
+//             final double[] shiftedPoint = currentPoint.clone();
+//             for (int i = 0; i < shiftedPoint.length; ++i) {
+//                 shiftedPoint[i] += x * searchDirection[i];
+//             }
+
+//             // gradient of the objective function
+//             final double[] gradient = computeObjectiveGradient(shiftedPoint);
+
+//             // dot product with the search direction
+//             double dotProduct = 0;
+//             for (int i = 0; i < gradient.length; ++i) {
+//                 dotProduct += gradient[i] * searchDirection[i];
+//             }
+
+//             return dotProduct;
+//         }
+//     }
+
+    /**
+     * @throws MathUnsupportedOperationException if bounds were passed to the
+     * {@link #optimize(OptimizationData[]) optimize} method.
+     */
+    private void checkParameters() {
+        if (getLowerBound() != null ||
+            getUpperBound() != null) {
+            throw new MathUnsupportedOperationException(LocalizedFormats.CONSTRAINT);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/gradient/Preconditioner.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/gradient/Preconditioner.java
new file mode 100644
index 0000000..3c0f8fb
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/gradient/Preconditioner.java
@@ -0,0 +1,44 @@
+/*
+ * 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.math3.optim.nonlinear.scalar.gradient;
+
+/**
+ * This interface represents a preconditioner for differentiable scalar
+ * objective function optimizers.
+ * @since 2.0
+ */
+public interface Preconditioner {
+    /**
+     * Precondition a search direction.
+     * <p>
+     * The returned preconditioned search direction must be computed fast or
+     * the algorithm performances will drop drastically. A classical approach
+     * is to compute only the diagonal elements of the hessian and to divide
+     * the raw search direction by these elements if they are all positive.
+     * If at least one of them is negative, it is safer to return a clone of
+     * the raw search direction as if the hessian was the identity matrix. The
+     * rationale for this simplified choice is that a negative diagonal element
+     * means the current point is far from the optimum and preconditioning will
+     * not be efficient anyway in this case.
+     * </p>
+     * @param point current point at which the search direction was computed
+     * @param r raw search direction (i.e. opposite of the gradient)
+     * @return approximation of H<sup>-1</sup>r where H is the objective function hessian
+     */
+    double[] precondition(double[] point, double[] r);
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/gradient/package-info.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/gradient/package-info.java
new file mode 100644
index 0000000..9dd9c5a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/gradient/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/**
+ * This package provides optimization algorithms that require derivatives.
+ */
+package org.apache.commons.math3.optim.nonlinear.scalar.gradient;
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/AbstractSimplex.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/AbstractSimplex.java
new file mode 100644
index 0000000..e959787
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/AbstractSimplex.java
@@ -0,0 +1,345 @@
+/*
+ * 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.math3.optim.nonlinear.scalar.noderiv;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+import org.apache.commons.math3.analysis.MultivariateFunction;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.ZeroException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.optim.PointValuePair;
+import org.apache.commons.math3.optim.OptimizationData;
+
+/**
+ * This class implements the simplex concept.
+ * It is intended to be used in conjunction with {@link SimplexOptimizer}.
+ * <br/>
+ * The initial configuration of the simplex is set by the constructors
+ * {@link #AbstractSimplex(double[])} or {@link #AbstractSimplex(double[][])}.
+ * The other {@link #AbstractSimplex(int) constructor} will set all steps
+ * to 1, thus building a default configuration from a unit hypercube.
+ * <br/>
+ * Users <em>must</em> call the {@link #build(double[]) build} method in order
+ * to create the data structure that will be acted on by the other methods of
+ * this class.
+ *
+ * @see SimplexOptimizer
+ * @since 3.0
+ */
+public abstract class AbstractSimplex implements OptimizationData {
+    /** Simplex. */
+    private PointValuePair[] simplex;
+    /** Start simplex configuration. */
+    private double[][] startConfiguration;
+    /** Simplex dimension (must be equal to {@code simplex.length - 1}). */
+    private final int dimension;
+
+    /**
+     * Build a unit hypercube simplex.
+     *
+     * @param n Dimension of the simplex.
+     */
+    protected AbstractSimplex(int n) {
+        this(n, 1d);
+    }
+
+    /**
+     * Build a hypercube simplex with the given side length.
+     *
+     * @param n Dimension of the simplex.
+     * @param sideLength Length of the sides of the hypercube.
+     */
+    protected AbstractSimplex(int n,
+                              double sideLength) {
+        this(createHypercubeSteps(n, sideLength));
+    }
+
+    /**
+     * The start configuration for simplex is built from a box parallel to
+     * the canonical axes of the space. The simplex is the subset of vertices
+     * of a box parallel to the canonical axes. It is built as the path followed
+     * while traveling from one vertex of the box to the diagonally opposite
+     * vertex moving only along the box edges. The first vertex of the box will
+     * be located at the start point of the optimization.
+     * As an example, in dimension 3 a simplex has 4 vertices. Setting the
+     * steps to (1, 10, 2) and the start point to (1, 1, 1) would imply the
+     * start simplex would be: { (1, 1, 1), (2, 1, 1), (2, 11, 1), (2, 11, 3) }.
+     * The first vertex would be set to the start point at (1, 1, 1) and the
+     * last vertex would be set to the diagonally opposite vertex at (2, 11, 3).
+     *
+     * @param steps Steps along the canonical axes representing box edges. They
+     * may be negative but not zero.
+     * @throws NullArgumentException if {@code steps} is {@code null}.
+     * @throws ZeroException if one of the steps is zero.
+     */
+    protected AbstractSimplex(final double[] steps) {
+        if (steps == null) {
+            throw new NullArgumentException();
+        }
+        if (steps.length == 0) {
+            throw new ZeroException();
+        }
+        dimension = steps.length;
+
+        // Only the relative position of the n final vertices with respect
+        // to the first one are stored.
+        startConfiguration = new double[dimension][dimension];
+        for (int i = 0; i < dimension; i++) {
+            final double[] vertexI = startConfiguration[i];
+            for (int j = 0; j < i + 1; j++) {
+                if (steps[j] == 0) {
+                    throw new ZeroException(LocalizedFormats.EQUAL_VERTICES_IN_SIMPLEX);
+                }
+                System.arraycopy(steps, 0, vertexI, 0, j + 1);
+            }
+        }
+    }
+
+    /**
+     * The real initial simplex will be set up by moving the reference
+     * simplex such that its first point is located at the start point of the
+     * optimization.
+     *
+     * @param referenceSimplex Reference simplex.
+     * @throws NotStrictlyPositiveException if the reference simplex does not
+     * contain at least one point.
+     * @throws DimensionMismatchException if there is a dimension mismatch
+     * in the reference simplex.
+     * @throws IllegalArgumentException if one of its vertices is duplicated.
+     */
+    protected AbstractSimplex(final double[][] referenceSimplex) {
+        if (referenceSimplex.length <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.SIMPLEX_NEED_ONE_POINT,
+                                                   referenceSimplex.length);
+        }
+        dimension = referenceSimplex.length - 1;
+
+        // Only the relative position of the n final vertices with respect
+        // to the first one are stored.
+        startConfiguration = new double[dimension][dimension];
+        final double[] ref0 = referenceSimplex[0];
+
+        // Loop over vertices.
+        for (int i = 0; i < referenceSimplex.length; i++) {
+            final double[] refI = referenceSimplex[i];
+
+            // Safety checks.
+            if (refI.length != dimension) {
+                throw new DimensionMismatchException(refI.length, dimension);
+            }
+            for (int j = 0; j < i; j++) {
+                final double[] refJ = referenceSimplex[j];
+                boolean allEquals = true;
+                for (int k = 0; k < dimension; k++) {
+                    if (refI[k] != refJ[k]) {
+                        allEquals = false;
+                        break;
+                    }
+                }
+                if (allEquals) {
+                    throw new MathIllegalArgumentException(LocalizedFormats.EQUAL_VERTICES_IN_SIMPLEX,
+                                                           i, j);
+                }
+            }
+
+            // Store vertex i position relative to vertex 0 position.
+            if (i > 0) {
+                final double[] confI = startConfiguration[i - 1];
+                for (int k = 0; k < dimension; k++) {
+                    confI[k] = refI[k] - ref0[k];
+                }
+            }
+        }
+    }
+
+    /**
+     * Get simplex dimension.
+     *
+     * @return the dimension of the simplex.
+     */
+    public int getDimension() {
+        return dimension;
+    }
+
+    /**
+     * Get simplex size.
+     * After calling the {@link #build(double[]) build} method, this method will
+     * will be equivalent to {@code getDimension() + 1}.
+     *
+     * @return the size of the simplex.
+     */
+    public int getSize() {
+        return simplex.length;
+    }
+
+    /**
+     * Compute the next simplex of the algorithm.
+     *
+     * @param evaluationFunction Evaluation function.
+     * @param comparator Comparator to use to sort simplex vertices from best
+     * to worst.
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException
+     * if the algorithm fails to converge.
+     */
+    public abstract void iterate(final MultivariateFunction evaluationFunction,
+                                 final Comparator<PointValuePair> comparator);
+
+    /**
+     * Build an initial simplex.
+     *
+     * @param startPoint First point of the simplex.
+     * @throws DimensionMismatchException if the start point does not match
+     * simplex dimension.
+     */
+    public void build(final double[] startPoint) {
+        if (dimension != startPoint.length) {
+            throw new DimensionMismatchException(dimension, startPoint.length);
+        }
+
+        // Set first vertex.
+        simplex = new PointValuePair[dimension + 1];
+        simplex[0] = new PointValuePair(startPoint, Double.NaN);
+
+        // Set remaining vertices.
+        for (int i = 0; i < dimension; i++) {
+            final double[] confI = startConfiguration[i];
+            final double[] vertexI = new double[dimension];
+            for (int k = 0; k < dimension; k++) {
+                vertexI[k] = startPoint[k] + confI[k];
+            }
+            simplex[i + 1] = new PointValuePair(vertexI, Double.NaN);
+        }
+    }
+
+    /**
+     * Evaluate all the non-evaluated points of the simplex.
+     *
+     * @param evaluationFunction Evaluation function.
+     * @param comparator Comparator to use to sort simplex vertices from best to worst.
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException
+     * if the maximal number of evaluations is exceeded.
+     */
+    public void evaluate(final MultivariateFunction evaluationFunction,
+                         final Comparator<PointValuePair> comparator) {
+        // Evaluate the objective function at all non-evaluated simplex points.
+        for (int i = 0; i < simplex.length; i++) {
+            final PointValuePair vertex = simplex[i];
+            final double[] point = vertex.getPointRef();
+            if (Double.isNaN(vertex.getValue())) {
+                simplex[i] = new PointValuePair(point, evaluationFunction.value(point), false);
+            }
+        }
+
+        // Sort the simplex from best to worst.
+        Arrays.sort(simplex, comparator);
+    }
+
+    /**
+     * Replace the worst point of the simplex by a new point.
+     *
+     * @param pointValuePair Point to insert.
+     * @param comparator Comparator to use for sorting the simplex vertices
+     * from best to worst.
+     */
+    protected void replaceWorstPoint(PointValuePair pointValuePair,
+                                     final Comparator<PointValuePair> comparator) {
+        for (int i = 0; i < dimension; i++) {
+            if (comparator.compare(simplex[i], pointValuePair) > 0) {
+                PointValuePair tmp = simplex[i];
+                simplex[i] = pointValuePair;
+                pointValuePair = tmp;
+            }
+        }
+        simplex[dimension] = pointValuePair;
+    }
+
+    /**
+     * Get the points of the simplex.
+     *
+     * @return all the simplex points.
+     */
+    public PointValuePair[] getPoints() {
+        final PointValuePair[] copy = new PointValuePair[simplex.length];
+        System.arraycopy(simplex, 0, copy, 0, simplex.length);
+        return copy;
+    }
+
+    /**
+     * Get the simplex point stored at the requested {@code index}.
+     *
+     * @param index Location.
+     * @return the point at location {@code index}.
+     */
+    public PointValuePair getPoint(int index) {
+        if (index < 0 ||
+            index >= simplex.length) {
+            throw new OutOfRangeException(index, 0, simplex.length - 1);
+        }
+        return simplex[index];
+    }
+
+    /**
+     * Store a new point at location {@code index}.
+     * Note that no deep-copy of {@code point} is performed.
+     *
+     * @param index Location.
+     * @param point New value.
+     */
+    protected void setPoint(int index, PointValuePair point) {
+        if (index < 0 ||
+            index >= simplex.length) {
+            throw new OutOfRangeException(index, 0, simplex.length - 1);
+        }
+        simplex[index] = point;
+    }
+
+    /**
+     * Replace all points.
+     * Note that no deep-copy of {@code points} is performed.
+     *
+     * @param points New Points.
+     */
+    protected void setPoints(PointValuePair[] points) {
+        if (points.length != simplex.length) {
+            throw new DimensionMismatchException(points.length, simplex.length);
+        }
+        simplex = points;
+    }
+
+    /**
+     * Create steps for a unit hypercube.
+     *
+     * @param n Dimension of the hypercube.
+     * @param sideLength Length of the sides of the hypercube.
+     * @return the steps.
+     */
+    private static double[] createHypercubeSteps(int n,
+                                                 double sideLength) {
+        final double[] steps = new double[n];
+        for (int i = 0; i < n; i++) {
+            steps[i] = sideLength;
+        }
+        return steps;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/BOBYQAOptimizer.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/BOBYQAOptimizer.java
new file mode 100644
index 0000000..e5bf39f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/BOBYQAOptimizer.java
@@ -0,0 +1,2475 @@
+/*
+ * 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.
+ */
+// CHECKSTYLE: stop all
+package org.apache.commons.math3.optim.nonlinear.scalar.noderiv;
+
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.linear.Array2DRowRealMatrix;
+import org.apache.commons.math3.linear.ArrayRealVector;
+import org.apache.commons.math3.linear.RealVector;
+import org.apache.commons.math3.optim.nonlinear.scalar.GoalType;
+import org.apache.commons.math3.optim.PointValuePair;
+import org.apache.commons.math3.optim.nonlinear.scalar.MultivariateOptimizer;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Powell's BOBYQA algorithm. This implementation is translated and
+ * adapted from the Fortran version available
+ * <a href="http://plato.asu.edu/ftp/other_software/bobyqa.zip">here</a>.
+ * See <a href="http://www.optimization-online.org/DB_HTML/2010/05/2616.html">
+ * this paper</a> for an introduction.
+ * <br/>
+ * BOBYQA is particularly well suited for high dimensional problems
+ * where derivatives are not available. In most cases it outperforms the
+ * {@link PowellOptimizer} significantly. Stochastic algorithms like
+ * {@link CMAESOptimizer} succeed more often than BOBYQA, but are more
+ * expensive. BOBYQA could also be considered as a replacement of any
+ * derivative-based optimizer when the derivatives are approximated by
+ * finite differences.
+ *
+ * @since 3.0
+ */
+public class BOBYQAOptimizer
+    extends MultivariateOptimizer {
+    /** Minimum dimension of the problem: {@value} */
+    public static final int MINIMUM_PROBLEM_DIMENSION = 2;
+    /** Default value for {@link #initialTrustRegionRadius}: {@value} . */
+    public static final double DEFAULT_INITIAL_RADIUS = 10.0;
+    /** Default value for {@link #stoppingTrustRegionRadius}: {@value} . */
+    public static final double DEFAULT_STOPPING_RADIUS = 1E-8;
+    /** Constant 0. */
+    private static final double ZERO = 0d;
+    /** Constant 1. */
+    private static final double ONE = 1d;
+    /** Constant 2. */
+    private static final double TWO = 2d;
+    /** Constant 10. */
+    private static final double TEN = 10d;
+    /** Constant 16. */
+    private static final double SIXTEEN = 16d;
+    /** Constant 250. */
+    private static final double TWO_HUNDRED_FIFTY = 250d;
+    /** Constant -1. */
+    private static final double MINUS_ONE = -ONE;
+    /** Constant 1/2. */
+    private static final double HALF = ONE / 2;
+    /** Constant 1/4. */
+    private static final double ONE_OVER_FOUR = ONE / 4;
+    /** Constant 1/8. */
+    private static final double ONE_OVER_EIGHT = ONE / 8;
+    /** Constant 1/10. */
+    private static final double ONE_OVER_TEN = ONE / 10;
+    /** Constant 1/1000. */
+    private static final double ONE_OVER_A_THOUSAND = ONE / 1000;
+
+    /**
+     * numberOfInterpolationPoints XXX
+     */
+    private final int numberOfInterpolationPoints;
+    /**
+     * initialTrustRegionRadius XXX
+     */
+    private double initialTrustRegionRadius;
+    /**
+     * stoppingTrustRegionRadius XXX
+     */
+    private final double stoppingTrustRegionRadius;
+    /** Goal type (minimize or maximize). */
+    private boolean isMinimize;
+    /**
+     * Current best values for the variables to be optimized.
+     * The vector will be changed in-place to contain the values of the least
+     * calculated objective function values.
+     */
+    private ArrayRealVector currentBest;
+    /** Differences between the upper and lower bounds. */
+    private double[] boundDifference;
+    /**
+     * Index of the interpolation point at the trust region center.
+     */
+    private int trustRegionCenterInterpolationPointIndex;
+    /**
+     * Last <em>n</em> columns of matrix H (where <em>n</em> is the dimension
+     * of the problem).
+     * XXX "bmat" in the original code.
+     */
+    private Array2DRowRealMatrix bMatrix;
+    /**
+     * Factorization of the leading <em>npt</em> square submatrix of H, this
+     * factorization being Z Z<sup>T</sup>, which provides both the correct
+     * rank and positive semi-definiteness.
+     * XXX "zmat" in the original code.
+     */
+    private Array2DRowRealMatrix zMatrix;
+    /**
+     * Coordinates of the interpolation points relative to {@link #originShift}.
+     * XXX "xpt" in the original code.
+     */
+    private Array2DRowRealMatrix interpolationPoints;
+    /**
+     * Shift of origin that should reduce the contributions from rounding
+     * errors to values of the model and Lagrange functions.
+     * XXX "xbase" in the original code.
+     */
+    private ArrayRealVector originShift;
+    /**
+     * Values of the objective function at the interpolation points.
+     * XXX "fval" in the original code.
+     */
+    private ArrayRealVector fAtInterpolationPoints;
+    /**
+     * Displacement from {@link #originShift} of the trust region center.
+     * XXX "xopt" in the original code.
+     */
+    private ArrayRealVector trustRegionCenterOffset;
+    /**
+     * Gradient of the quadratic model at {@link #originShift} +
+     * {@link #trustRegionCenterOffset}.
+     * XXX "gopt" in the original code.
+     */
+    private ArrayRealVector gradientAtTrustRegionCenter;
+    /**
+     * Differences {@link #getLowerBound()} - {@link #originShift}.
+     * All the components of every {@link #trustRegionCenterOffset} are going
+     * to satisfy the bounds<br/>
+     * {@link #getLowerBound() lowerBound}<sub>i</sub> &le;
+     * {@link #trustRegionCenterOffset}<sub>i</sub>,<br/>
+     * with appropriate equalities when {@link #trustRegionCenterOffset} is
+     * on a constraint boundary.
+     * XXX "sl" in the original code.
+     */
+    private ArrayRealVector lowerDifference;
+    /**
+     * Differences {@link #getUpperBound()} - {@link #originShift}
+     * All the components of every {@link #trustRegionCenterOffset} are going
+     * to satisfy the bounds<br/>
+     *  {@link #trustRegionCenterOffset}<sub>i</sub> &le;
+     *  {@link #getUpperBound() upperBound}<sub>i</sub>,<br/>
+     * with appropriate equalities when {@link #trustRegionCenterOffset} is
+     * on a constraint boundary.
+     * XXX "su" in the original code.
+     */
+    private ArrayRealVector upperDifference;
+    /**
+     * Parameters of the implicit second derivatives of the quadratic model.
+     * XXX "pq" in the original code.
+     */
+    private ArrayRealVector modelSecondDerivativesParameters;
+    /**
+     * Point chosen by function {@link #trsbox(double,ArrayRealVector,
+     * ArrayRealVector, ArrayRealVector,ArrayRealVector,ArrayRealVector) trsbox}
+     * or {@link #altmov(int,double) altmov}.
+     * Usually {@link #originShift} + {@link #newPoint} is the vector of
+     * variables for the next evaluation of the objective function.
+     * It also satisfies the constraints indicated in {@link #lowerDifference}
+     * and {@link #upperDifference}.
+     * XXX "xnew" in the original code.
+     */
+    private ArrayRealVector newPoint;
+    /**
+     * Alternative to {@link #newPoint}, chosen by
+     * {@link #altmov(int,double) altmov}.
+     * It may replace {@link #newPoint} in order to increase the denominator
+     * in the {@link #update(double, double, int) updating procedure}.
+     * XXX "xalt" in the original code.
+     */
+    private ArrayRealVector alternativeNewPoint;
+    /**
+     * Trial step from {@link #trustRegionCenterOffset} which is usually
+     * {@link #newPoint} - {@link #trustRegionCenterOffset}.
+     * XXX "d__" in the original code.
+     */
+    private ArrayRealVector trialStepPoint;
+    /**
+     * Values of the Lagrange functions at a new point.
+     * XXX "vlag" in the original code.
+     */
+    private ArrayRealVector lagrangeValuesAtNewPoint;
+    /**
+     * Explicit second derivatives of the quadratic model.
+     * XXX "hq" in the original code.
+     */
+    private ArrayRealVector modelSecondDerivativesValues;
+
+    /**
+     * @param numberOfInterpolationPoints Number of interpolation conditions.
+     * For a problem of dimension {@code n}, its value must be in the interval
+     * {@code [n+2, (n+1)(n+2)/2]}.
+     * Choices that exceed {@code 2n+1} are not recommended.
+     */
+    public BOBYQAOptimizer(int numberOfInterpolationPoints) {
+        this(numberOfInterpolationPoints,
+             DEFAULT_INITIAL_RADIUS,
+             DEFAULT_STOPPING_RADIUS);
+    }
+
+    /**
+     * @param numberOfInterpolationPoints Number of interpolation conditions.
+     * For a problem of dimension {@code n}, its value must be in the interval
+     * {@code [n+2, (n+1)(n+2)/2]}.
+     * Choices that exceed {@code 2n+1} are not recommended.
+     * @param initialTrustRegionRadius Initial trust region radius.
+     * @param stoppingTrustRegionRadius Stopping trust region radius.
+     */
+    public BOBYQAOptimizer(int numberOfInterpolationPoints,
+                           double initialTrustRegionRadius,
+                           double stoppingTrustRegionRadius) {
+        super(null); // No custom convergence criterion.
+        this.numberOfInterpolationPoints = numberOfInterpolationPoints;
+        this.initialTrustRegionRadius = initialTrustRegionRadius;
+        this.stoppingTrustRegionRadius = stoppingTrustRegionRadius;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected PointValuePair doOptimize() {
+        final double[] lowerBound = getLowerBound();
+        final double[] upperBound = getUpperBound();
+
+        // Validity checks.
+        setup(lowerBound, upperBound);
+
+        isMinimize = (getGoalType() == GoalType.MINIMIZE);
+        currentBest = new ArrayRealVector(getStartPoint());
+
+        final double value = bobyqa(lowerBound, upperBound);
+
+        return new PointValuePair(currentBest.getDataRef(),
+                                  isMinimize ? value : -value);
+    }
+
+    /**
+     *     This subroutine seeks the least value of a function of many variables,
+     *     by applying a trust region method that forms quadratic models by
+     *     interpolation. There is usually some freedom in the interpolation
+     *     conditions, which is taken up by minimizing the Frobenius norm of
+     *     the change to the second derivative of the model, beginning with the
+     *     zero matrix. The values of the variables are constrained by upper and
+     *     lower bounds. The arguments of the subroutine are as follows.
+     *
+     *     N must be set to the number of variables and must be at least two.
+     *     NPT is the number of interpolation conditions. Its value must be in
+     *       the interval [N+2,(N+1)(N+2)/2]. Choices that exceed 2*N+1 are not
+     *       recommended.
+     *     Initial values of the variables must be set in X(1),X(2),...,X(N). They
+     *       will be changed to the values that give the least calculated F.
+     *     For I=1,2,...,N, XL(I) and XU(I) must provide the lower and upper
+     *       bounds, respectively, on X(I). The construction of quadratic models
+     *       requires XL(I) to be strictly less than XU(I) for each I. Further,
+     *       the contribution to a model from changes to the I-th variable is
+     *       damaged severely by rounding errors if XU(I)-XL(I) is too small.
+     *     RHOBEG and RHOEND must be set to the initial and final values of a trust
+     *       region radius, so both must be positive with RHOEND no greater than
+     *       RHOBEG. Typically, RHOBEG should be about one tenth of the greatest
+     *       expected change to a variable, while RHOEND should indicate the
+     *       accuracy that is required in the final values of the variables. An
+     *       error return occurs if any of the differences XU(I)-XL(I), I=1,...,N,
+     *       is less than 2*RHOBEG.
+     *     MAXFUN must be set to an upper bound on the number of calls of CALFUN.
+     *     The array W will be used for working space. Its length must be at least
+     *       (NPT+5)*(NPT+N)+3*N*(N+5)/2.
+     *
+     * @param lowerBound Lower bounds.
+     * @param upperBound Upper bounds.
+     * @return the value of the objective at the optimum.
+     */
+    private double bobyqa(double[] lowerBound,
+                          double[] upperBound) {
+        printMethod(); // XXX
+
+        final int n = currentBest.getDimension();
+
+        // Return if there is insufficient space between the bounds. Modify the
+        // initial X if necessary in order to avoid conflicts between the bounds
+        // and the construction of the first quadratic model. The lower and upper
+        // bounds on moves from the updated X are set now, in the ISL and ISU
+        // partitions of W, in order to provide useful and exact information about
+        // components of X that become within distance RHOBEG from their bounds.
+
+        for (int j = 0; j < n; j++) {
+            final double boundDiff = boundDifference[j];
+            lowerDifference.setEntry(j, lowerBound[j] - currentBest.getEntry(j));
+            upperDifference.setEntry(j, upperBound[j] - currentBest.getEntry(j));
+            if (lowerDifference.getEntry(j) >= -initialTrustRegionRadius) {
+                if (lowerDifference.getEntry(j) >= ZERO) {
+                    currentBest.setEntry(j, lowerBound[j]);
+                    lowerDifference.setEntry(j, ZERO);
+                    upperDifference.setEntry(j, boundDiff);
+                } else {
+                    currentBest.setEntry(j, lowerBound[j] + initialTrustRegionRadius);
+                    lowerDifference.setEntry(j, -initialTrustRegionRadius);
+                    // Computing MAX
+                    final double deltaOne = upperBound[j] - currentBest.getEntry(j);
+                    upperDifference.setEntry(j, FastMath.max(deltaOne, initialTrustRegionRadius));
+                }
+            } else if (upperDifference.getEntry(j) <= initialTrustRegionRadius) {
+                if (upperDifference.getEntry(j) <= ZERO) {
+                    currentBest.setEntry(j, upperBound[j]);
+                    lowerDifference.setEntry(j, -boundDiff);
+                    upperDifference.setEntry(j, ZERO);
+                } else {
+                    currentBest.setEntry(j, upperBound[j] - initialTrustRegionRadius);
+                    // Computing MIN
+                    final double deltaOne = lowerBound[j] - currentBest.getEntry(j);
+                    final double deltaTwo = -initialTrustRegionRadius;
+                    lowerDifference.setEntry(j, FastMath.min(deltaOne, deltaTwo));
+                    upperDifference.setEntry(j, initialTrustRegionRadius);
+                }
+            }
+        }
+
+        // Make the call of BOBYQB.
+
+        return bobyqb(lowerBound, upperBound);
+    } // bobyqa
+
+    // ----------------------------------------------------------------------------------------
+
+    /**
+     *     The arguments N, NPT, X, XL, XU, RHOBEG, RHOEND, IPRINT and MAXFUN
+     *       are identical to the corresponding arguments in SUBROUTINE BOBYQA.
+     *     XBASE holds a shift of origin that should reduce the contributions
+     *       from rounding errors to values of the model and Lagrange functions.
+     *     XPT is a two-dimensional array that holds the coordinates of the
+     *       interpolation points relative to XBASE.
+     *     FVAL holds the values of F at the interpolation points.
+     *     XOPT is set to the displacement from XBASE of the trust region centre.
+     *     GOPT holds the gradient of the quadratic model at XBASE+XOPT.
+     *     HQ holds the explicit second derivatives of the quadratic model.
+     *     PQ contains the parameters of the implicit second derivatives of the
+     *       quadratic model.
+     *     BMAT holds the last N columns of H.
+     *     ZMAT holds the factorization of the leading NPT by NPT submatrix of H,
+     *       this factorization being ZMAT times ZMAT^T, which provides both the
+     *       correct rank and positive semi-definiteness.
+     *     NDIM is the first dimension of BMAT and has the value NPT+N.
+     *     SL and SU hold the differences XL-XBASE and XU-XBASE, respectively.
+     *       All the components of every XOPT are going to satisfy the bounds
+     *       SL(I) .LEQ. XOPT(I) .LEQ. SU(I), with appropriate equalities when
+     *       XOPT is on a constraint boundary.
+     *     XNEW is chosen by SUBROUTINE TRSBOX or ALTMOV. Usually XBASE+XNEW is the
+     *       vector of variables for the next call of CALFUN. XNEW also satisfies
+     *       the SL and SU constraints in the way that has just been mentioned.
+     *     XALT is an alternative to XNEW, chosen by ALTMOV, that may replace XNEW
+     *       in order to increase the denominator in the updating of UPDATE.
+     *     D is reserved for a trial step from XOPT, which is usually XNEW-XOPT.
+     *     VLAG contains the values of the Lagrange functions at a new point X.
+     *       They are part of a product that requires VLAG to be of length NDIM.
+     *     W is a one-dimensional array that is used for working space. Its length
+     *       must be at least 3*NDIM = 3*(NPT+N).
+     *
+     * @param lowerBound Lower bounds.
+     * @param upperBound Upper bounds.
+     * @return the value of the objective at the optimum.
+     */
+    private double bobyqb(double[] lowerBound,
+                          double[] upperBound) {
+        printMethod(); // XXX
+
+        final int n = currentBest.getDimension();
+        final int npt = numberOfInterpolationPoints;
+        final int np = n + 1;
+        final int nptm = npt - np;
+        final int nh = n * np / 2;
+
+        final ArrayRealVector work1 = new ArrayRealVector(n);
+        final ArrayRealVector work2 = new ArrayRealVector(npt);
+        final ArrayRealVector work3 = new ArrayRealVector(npt);
+
+        double cauchy = Double.NaN;
+        double alpha = Double.NaN;
+        double dsq = Double.NaN;
+        double crvmin = Double.NaN;
+
+        // Set some constants.
+        // Parameter adjustments
+
+        // Function Body
+
+        // The call of PRELIM sets the elements of XBASE, XPT, FVAL, GOPT, HQ, PQ,
+        // BMAT and ZMAT for the first iteration, with the corresponding values of
+        // of NF and KOPT, which are the number of calls of CALFUN so far and the
+        // index of the interpolation point at the trust region centre. Then the
+        // initial XOPT is set too. The branch to label 720 occurs if MAXFUN is
+        // less than NPT. GOPT will be updated if KOPT is different from KBASE.
+
+        trustRegionCenterInterpolationPointIndex = 0;
+
+        prelim(lowerBound, upperBound);
+        double xoptsq = ZERO;
+        for (int i = 0; i < n; i++) {
+            trustRegionCenterOffset.setEntry(i, interpolationPoints.getEntry(trustRegionCenterInterpolationPointIndex, i));
+            // Computing 2nd power
+            final double deltaOne = trustRegionCenterOffset.getEntry(i);
+            xoptsq += deltaOne * deltaOne;
+        }
+        double fsave = fAtInterpolationPoints.getEntry(0);
+        final int kbase = 0;
+
+        // Complete the settings that are required for the iterative procedure.
+
+        int ntrits = 0;
+        int itest = 0;
+        int knew = 0;
+        int nfsav = getEvaluations();
+        double rho = initialTrustRegionRadius;
+        double delta = rho;
+        double diffa = ZERO;
+        double diffb = ZERO;
+        double diffc = ZERO;
+        double f = ZERO;
+        double beta = ZERO;
+        double adelt = ZERO;
+        double denom = ZERO;
+        double ratio = ZERO;
+        double dnorm = ZERO;
+        double scaden = ZERO;
+        double biglsq = ZERO;
+        double distsq = ZERO;
+
+        // Update GOPT if necessary before the first iteration and after each
+        // call of RESCUE that makes a call of CALFUN.
+
+        int state = 20;
+        for(;;) {
+        switch (state) {
+        case 20: {
+            printState(20); // XXX
+            if (trustRegionCenterInterpolationPointIndex != kbase) {
+                int ih = 0;
+                for (int j = 0; j < n; j++) {
+                    for (int i = 0; i <= j; i++) {
+                        if (i < j) {
+                            gradientAtTrustRegionCenter.setEntry(j, gradientAtTrustRegionCenter.getEntry(j) + modelSecondDerivativesValues.getEntry(ih) * trustRegionCenterOffset.getEntry(i));
+                        }
+                        gradientAtTrustRegionCenter.setEntry(i, gradientAtTrustRegionCenter.getEntry(i) + modelSecondDerivativesValues.getEntry(ih) * trustRegionCenterOffset.getEntry(j));
+                        ih++;
+                    }
+                }
+                if (getEvaluations() > npt) {
+                    for (int k = 0; k < npt; k++) {
+                        double temp = ZERO;
+                        for (int j = 0; j < n; j++) {
+                            temp += interpolationPoints.getEntry(k, j) * trustRegionCenterOffset.getEntry(j);
+                        }
+                        temp *= modelSecondDerivativesParameters.getEntry(k);
+                        for (int i = 0; i < n; i++) {
+                            gradientAtTrustRegionCenter.setEntry(i, gradientAtTrustRegionCenter.getEntry(i) + temp * interpolationPoints.getEntry(k, i));
+                        }
+                    }
+                    // throw new PathIsExploredException(); // XXX
+                }
+            }
+
+            // Generate the next point in the trust region that provides a small value
+            // of the quadratic model subject to the constraints on the variables.
+            // The int NTRITS is set to the number "trust region" iterations that
+            // have occurred since the last "alternative" iteration. If the length
+            // of XNEW-XOPT is less than HALF*RHO, however, then there is a branch to
+            // label 650 or 680 with NTRITS=-1, instead of calculating F at XNEW.
+
+        }
+        case 60: {
+            printState(60); // XXX
+            final ArrayRealVector gnew = new ArrayRealVector(n);
+            final ArrayRealVector xbdi = new ArrayRealVector(n);
+            final ArrayRealVector s = new ArrayRealVector(n);
+            final ArrayRealVector hs = new ArrayRealVector(n);
+            final ArrayRealVector hred = new ArrayRealVector(n);
+
+            final double[] dsqCrvmin = trsbox(delta, gnew, xbdi, s,
+                                              hs, hred);
+            dsq = dsqCrvmin[0];
+            crvmin = dsqCrvmin[1];
+
+            // Computing MIN
+            double deltaOne = delta;
+            double deltaTwo = FastMath.sqrt(dsq);
+            dnorm = FastMath.min(deltaOne, deltaTwo);
+            if (dnorm < HALF * rho) {
+                ntrits = -1;
+                // Computing 2nd power
+                deltaOne = TEN * rho;
+                distsq = deltaOne * deltaOne;
+                if (getEvaluations() <= nfsav + 2) {
+                    state = 650; break;
+                }
+
+                // The following choice between labels 650 and 680 depends on whether or
+                // not our work with the current RHO seems to be complete. Either RHO is
+                // decreased or termination occurs if the errors in the quadratic model at
+                // the last three interpolation points compare favourably with predictions
+                // of likely improvements to the model within distance HALF*RHO of XOPT.
+
+                // Computing MAX
+                deltaOne = FastMath.max(diffa, diffb);
+                final double errbig = FastMath.max(deltaOne, diffc);
+                final double frhosq = rho * ONE_OVER_EIGHT * rho;
+                if (crvmin > ZERO &&
+                    errbig > frhosq * crvmin) {
+                    state = 650; break;
+                }
+                final double bdtol = errbig / rho;
+                for (int j = 0; j < n; j++) {
+                    double bdtest = bdtol;
+                    if (newPoint.getEntry(j) == lowerDifference.getEntry(j)) {
+                        bdtest = work1.getEntry(j);
+                    }
+                    if (newPoint.getEntry(j) == upperDifference.getEntry(j)) {
+                        bdtest = -work1.getEntry(j);
+                    }
+                    if (bdtest < bdtol) {
+                        double curv = modelSecondDerivativesValues.getEntry((j + j * j) / 2);
+                        for (int k = 0; k < npt; k++) {
+                            // Computing 2nd power
+                            final double d1 = interpolationPoints.getEntry(k, j);
+                            curv += modelSecondDerivativesParameters.getEntry(k) * (d1 * d1);
+                        }
+                        bdtest += HALF * curv * rho;
+                        if (bdtest < bdtol) {
+                            state = 650; break;
+                        }
+                        // throw new PathIsExploredException(); // XXX
+                    }
+                }
+                state = 680; break;
+            }
+            ++ntrits;
+
+            // Severe cancellation is likely to occur if XOPT is too far from XBASE.
+            // If the following test holds, then XBASE is shifted so that XOPT becomes
+            // zero. The appropriate changes are made to BMAT and to the second
+            // derivatives of the current model, beginning with the changes to BMAT
+            // that do not depend on ZMAT. VLAG is used temporarily for working space.
+
+        }
+        case 90: {
+            printState(90); // XXX
+            if (dsq <= xoptsq * ONE_OVER_A_THOUSAND) {
+                final double fracsq = xoptsq * ONE_OVER_FOUR;
+                double sumpq = ZERO;
+                // final RealVector sumVector
+                //     = new ArrayRealVector(npt, -HALF * xoptsq).add(interpolationPoints.operate(trustRegionCenter));
+                for (int k = 0; k < npt; k++) {
+                    sumpq += modelSecondDerivativesParameters.getEntry(k);
+                    double sum = -HALF * xoptsq;
+                    for (int i = 0; i < n; i++) {
+                        sum += interpolationPoints.getEntry(k, i) * trustRegionCenterOffset.getEntry(i);
+                    }
+                    // sum = sumVector.getEntry(k); // XXX "testAckley" and "testDiffPow" fail.
+                    work2.setEntry(k, sum);
+                    final double temp = fracsq - HALF * sum;
+                    for (int i = 0; i < n; i++) {
+                        work1.setEntry(i, bMatrix.getEntry(k, i));
+                        lagrangeValuesAtNewPoint.setEntry(i, sum * interpolationPoints.getEntry(k, i) + temp * trustRegionCenterOffset.getEntry(i));
+                        final int ip = npt + i;
+                        for (int j = 0; j <= i; j++) {
+                            bMatrix.setEntry(ip, j,
+                                          bMatrix.getEntry(ip, j)
+                                          + work1.getEntry(i) * lagrangeValuesAtNewPoint.getEntry(j)
+                                          + lagrangeValuesAtNewPoint.getEntry(i) * work1.getEntry(j));
+                        }
+                    }
+                }
+
+                // Then the revisions of BMAT that depend on ZMAT are calculated.
+
+                for (int m = 0; m < nptm; m++) {
+                    double sumz = ZERO;
+                    double sumw = ZERO;
+                    for (int k = 0; k < npt; k++) {
+                        sumz += zMatrix.getEntry(k, m);
+                        lagrangeValuesAtNewPoint.setEntry(k, work2.getEntry(k) * zMatrix.getEntry(k, m));
+                        sumw += lagrangeValuesAtNewPoint.getEntry(k);
+                    }
+                    for (int j = 0; j < n; j++) {
+                        double sum = (fracsq * sumz - HALF * sumw) * trustRegionCenterOffset.getEntry(j);
+                        for (int k = 0; k < npt; k++) {
+                            sum += lagrangeValuesAtNewPoint.getEntry(k) * interpolationPoints.getEntry(k, j);
+                        }
+                        work1.setEntry(j, sum);
+                        for (int k = 0; k < npt; k++) {
+                            bMatrix.setEntry(k, j,
+                                          bMatrix.getEntry(k, j)
+                                          + sum * zMatrix.getEntry(k, m));
+                        }
+                    }
+                    for (int i = 0; i < n; i++) {
+                        final int ip = i + npt;
+                        final double temp = work1.getEntry(i);
+                        for (int j = 0; j <= i; j++) {
+                            bMatrix.setEntry(ip, j,
+                                          bMatrix.getEntry(ip, j)
+                                          + temp * work1.getEntry(j));
+                        }
+                    }
+                }
+
+                // The following instructions complete the shift, including the changes
+                // to the second derivative parameters of the quadratic model.
+
+                int ih = 0;
+                for (int j = 0; j < n; j++) {
+                    work1.setEntry(j, -HALF * sumpq * trustRegionCenterOffset.getEntry(j));
+                    for (int k = 0; k < npt; k++) {
+                        work1.setEntry(j, work1.getEntry(j) + modelSecondDerivativesParameters.getEntry(k) * interpolationPoints.getEntry(k, j));
+                        interpolationPoints.setEntry(k, j, interpolationPoints.getEntry(k, j) - trustRegionCenterOffset.getEntry(j));
+                    }
+                    for (int i = 0; i <= j; i++) {
+                         modelSecondDerivativesValues.setEntry(ih,
+                                    modelSecondDerivativesValues.getEntry(ih)
+                                    + work1.getEntry(i) * trustRegionCenterOffset.getEntry(j)
+                                    + trustRegionCenterOffset.getEntry(i) * work1.getEntry(j));
+                        bMatrix.setEntry(npt + i, j, bMatrix.getEntry(npt + j, i));
+                        ih++;
+                    }
+                }
+                for (int i = 0; i < n; i++) {
+                    originShift.setEntry(i, originShift.getEntry(i) + trustRegionCenterOffset.getEntry(i));
+                    newPoint.setEntry(i, newPoint.getEntry(i) - trustRegionCenterOffset.getEntry(i));
+                    lowerDifference.setEntry(i, lowerDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i));
+                    upperDifference.setEntry(i, upperDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i));
+                    trustRegionCenterOffset.setEntry(i, ZERO);
+                }
+                xoptsq = ZERO;
+            }
+            if (ntrits == 0) {
+                state = 210; break;
+            }
+            state = 230; break;
+
+            // XBASE is also moved to XOPT by a call of RESCUE. This calculation is
+            // more expensive than the previous shift, because new matrices BMAT and
+            // ZMAT are generated from scratch, which may include the replacement of
+            // interpolation points whose positions seem to be causing near linear
+            // dependence in the interpolation conditions. Therefore RESCUE is called
+            // only if rounding errors have reduced by at least a factor of two the
+            // denominator of the formula for updating the H matrix. It provides a
+            // useful safeguard, but is not invoked in most applications of BOBYQA.
+
+        }
+        case 210: {
+            printState(210); // XXX
+            // Pick two alternative vectors of variables, relative to XBASE, that
+            // are suitable as new positions of the KNEW-th interpolation point.
+            // Firstly, XNEW is set to the point on a line through XOPT and another
+            // interpolation point that minimizes the predicted value of the next
+            // denominator, subject to ||XNEW - XOPT|| .LEQ. ADELT and to the SL
+            // and SU bounds. Secondly, XALT is set to the best feasible point on
+            // a constrained version of the Cauchy step of the KNEW-th Lagrange
+            // function, the corresponding value of the square of this function
+            // being returned in CAUCHY. The choice between these alternatives is
+            // going to be made when the denominator is calculated.
+
+            final double[] alphaCauchy = altmov(knew, adelt);
+            alpha = alphaCauchy[0];
+            cauchy = alphaCauchy[1];
+
+            for (int i = 0; i < n; i++) {
+                trialStepPoint.setEntry(i, newPoint.getEntry(i) - trustRegionCenterOffset.getEntry(i));
+            }
+
+            // Calculate VLAG and BETA for the current choice of D. The scalar
+            // product of D with XPT(K,.) is going to be held in W(NPT+K) for
+            // use when VQUAD is calculated.
+
+        }
+        case 230: {
+            printState(230); // XXX
+            for (int k = 0; k < npt; k++) {
+                double suma = ZERO;
+                double sumb = ZERO;
+                double sum = ZERO;
+                for (int j = 0; j < n; j++) {
+                    suma += interpolationPoints.getEntry(k, j) * trialStepPoint.getEntry(j);
+                    sumb += interpolationPoints.getEntry(k, j) * trustRegionCenterOffset.getEntry(j);
+                    sum += bMatrix.getEntry(k, j) * trialStepPoint.getEntry(j);
+                }
+                work3.setEntry(k, suma * (HALF * suma + sumb));
+                lagrangeValuesAtNewPoint.setEntry(k, sum);
+                work2.setEntry(k, suma);
+            }
+            beta = ZERO;
+            for (int m = 0; m < nptm; m++) {
+                double sum = ZERO;
+                for (int k = 0; k < npt; k++) {
+                    sum += zMatrix.getEntry(k, m) * work3.getEntry(k);
+                }
+                beta -= sum * sum;
+                for (int k = 0; k < npt; k++) {
+                    lagrangeValuesAtNewPoint.setEntry(k, lagrangeValuesAtNewPoint.getEntry(k) + sum * zMatrix.getEntry(k, m));
+                }
+            }
+            dsq = ZERO;
+            double bsum = ZERO;
+            double dx = ZERO;
+            for (int j = 0; j < n; j++) {
+                // Computing 2nd power
+                final double d1 = trialStepPoint.getEntry(j);
+                dsq += d1 * d1;
+                double sum = ZERO;
+                for (int k = 0; k < npt; k++) {
+                    sum += work3.getEntry(k) * bMatrix.getEntry(k, j);
+                }
+                bsum += sum * trialStepPoint.getEntry(j);
+                final int jp = npt + j;
+                for (int i = 0; i < n; i++) {
+                    sum += bMatrix.getEntry(jp, i) * trialStepPoint.getEntry(i);
+                }
+                lagrangeValuesAtNewPoint.setEntry(jp, sum);
+                bsum += sum * trialStepPoint.getEntry(j);
+                dx += trialStepPoint.getEntry(j) * trustRegionCenterOffset.getEntry(j);
+            }
+
+            beta = dx * dx + dsq * (xoptsq + dx + dx + HALF * dsq) + beta - bsum; // Original
+            // beta += dx * dx + dsq * (xoptsq + dx + dx + HALF * dsq) - bsum; // XXX "testAckley" and "testDiffPow" fail.
+            // beta = dx * dx + dsq * (xoptsq + 2 * dx + HALF * dsq) + beta - bsum; // XXX "testDiffPow" fails.
+
+            lagrangeValuesAtNewPoint.setEntry(trustRegionCenterInterpolationPointIndex,
+                          lagrangeValuesAtNewPoint.getEntry(trustRegionCenterInterpolationPointIndex) + ONE);
+
+            // If NTRITS is zero, the denominator may be increased by replacing
+            // the step D of ALTMOV by a Cauchy step. Then RESCUE may be called if
+            // rounding errors have damaged the chosen denominator.
+
+            if (ntrits == 0) {
+                // Computing 2nd power
+                final double d1 = lagrangeValuesAtNewPoint.getEntry(knew);
+                denom = d1 * d1 + alpha * beta;
+                if (denom < cauchy && cauchy > ZERO) {
+                    for (int i = 0; i < n; i++) {
+                        newPoint.setEntry(i, alternativeNewPoint.getEntry(i));
+                        trialStepPoint.setEntry(i, newPoint.getEntry(i) - trustRegionCenterOffset.getEntry(i));
+                    }
+                    cauchy = ZERO; // XXX Useful statement?
+                    state = 230; break;
+                }
+                // Alternatively, if NTRITS is positive, then set KNEW to the index of
+                // the next interpolation point to be deleted to make room for a trust
+                // region step. Again RESCUE may be called if rounding errors have damaged_
+                // the chosen denominator, which is the reason for attempting to select
+                // KNEW before calculating the next value of the objective function.
+
+            } else {
+                final double delsq = delta * delta;
+                scaden = ZERO;
+                biglsq = ZERO;
+                knew = 0;
+                for (int k = 0; k < npt; k++) {
+                    if (k == trustRegionCenterInterpolationPointIndex) {
+                        continue;
+                    }
+                    double hdiag = ZERO;
+                    for (int m = 0; m < nptm; m++) {
+                        // Computing 2nd power
+                        final double d1 = zMatrix.getEntry(k, m);
+                        hdiag += d1 * d1;
+                    }
+                    // Computing 2nd power
+                    final double d2 = lagrangeValuesAtNewPoint.getEntry(k);
+                    final double den = beta * hdiag + d2 * d2;
+                    distsq = ZERO;
+                    for (int j = 0; j < n; j++) {
+                        // Computing 2nd power
+                        final double d3 = interpolationPoints.getEntry(k, j) - trustRegionCenterOffset.getEntry(j);
+                        distsq += d3 * d3;
+                    }
+                    // Computing MAX
+                    // Computing 2nd power
+                    final double d4 = distsq / delsq;
+                    final double temp = FastMath.max(ONE, d4 * d4);
+                    if (temp * den > scaden) {
+                        scaden = temp * den;
+                        knew = k;
+                        denom = den;
+                    }
+                    // Computing MAX
+                    // Computing 2nd power
+                    final double d5 = lagrangeValuesAtNewPoint.getEntry(k);
+                    biglsq = FastMath.max(biglsq, temp * (d5 * d5));
+                }
+            }
+
+            // Put the variables for the next calculation of the objective function
+            //   in XNEW, with any adjustments for the bounds.
+
+            // Calculate the value of the objective function at XBASE+XNEW, unless
+            //   the limit on the number of calculations of F has been reached.
+
+        }
+        case 360: {
+            printState(360); // XXX
+            for (int i = 0; i < n; i++) {
+                // Computing MIN
+                // Computing MAX
+                final double d3 = lowerBound[i];
+                final double d4 = originShift.getEntry(i) + newPoint.getEntry(i);
+                final double d1 = FastMath.max(d3, d4);
+                final double d2 = upperBound[i];
+                currentBest.setEntry(i, FastMath.min(d1, d2));
+                if (newPoint.getEntry(i) == lowerDifference.getEntry(i)) {
+                    currentBest.setEntry(i, lowerBound[i]);
+                }
+                if (newPoint.getEntry(i) == upperDifference.getEntry(i)) {
+                    currentBest.setEntry(i, upperBound[i]);
+                }
+            }
+
+            f = computeObjectiveValue(currentBest.toArray());
+
+            if (!isMinimize) {
+                f = -f;
+            }
+            if (ntrits == -1) {
+                fsave = f;
+                state = 720; break;
+            }
+
+            // Use the quadratic model to predict the change in F due to the step D,
+            //   and set DIFF to the error of this prediction.
+
+            final double fopt = fAtInterpolationPoints.getEntry(trustRegionCenterInterpolationPointIndex);
+            double vquad = ZERO;
+            int ih = 0;
+            for (int j = 0; j < n; j++) {
+                vquad += trialStepPoint.getEntry(j) * gradientAtTrustRegionCenter.getEntry(j);
+                for (int i = 0; i <= j; i++) {
+                    double temp = trialStepPoint.getEntry(i) * trialStepPoint.getEntry(j);
+                    if (i == j) {
+                        temp *= HALF;
+                    }
+                    vquad += modelSecondDerivativesValues.getEntry(ih) * temp;
+                    ih++;
+               }
+            }
+            for (int k = 0; k < npt; k++) {
+                // Computing 2nd power
+                final double d1 = work2.getEntry(k);
+                final double d2 = d1 * d1; // "d1" must be squared first to prevent test failures.
+                vquad += HALF * modelSecondDerivativesParameters.getEntry(k) * d2;
+            }
+            final double diff = f - fopt - vquad;
+            diffc = diffb;
+            diffb = diffa;
+            diffa = FastMath.abs(diff);
+            if (dnorm > rho) {
+                nfsav = getEvaluations();
+            }
+
+            // Pick the next value of DELTA after a trust region step.
+
+            if (ntrits > 0) {
+                if (vquad >= ZERO) {
+                    throw new MathIllegalStateException(LocalizedFormats.TRUST_REGION_STEP_FAILED, vquad);
+                }
+                ratio = (f - fopt) / vquad;
+                final double hDelta = HALF * delta;
+                if (ratio <= ONE_OVER_TEN) {
+                    // Computing MIN
+                    delta = FastMath.min(hDelta, dnorm);
+                } else if (ratio <= .7) {
+                    // Computing MAX
+                    delta = FastMath.max(hDelta, dnorm);
+                } else {
+                    // Computing MAX
+                    delta = FastMath.max(hDelta, 2 * dnorm);
+                }
+                if (delta <= rho * 1.5) {
+                    delta = rho;
+                }
+
+                // Recalculate KNEW and DENOM if the new F is less than FOPT.
+
+                if (f < fopt) {
+                    final int ksav = knew;
+                    final double densav = denom;
+                    final double delsq = delta * delta;
+                    scaden = ZERO;
+                    biglsq = ZERO;
+                    knew = 0;
+                    for (int k = 0; k < npt; k++) {
+                        double hdiag = ZERO;
+                        for (int m = 0; m < nptm; m++) {
+                            // Computing 2nd power
+                            final double d1 = zMatrix.getEntry(k, m);
+                            hdiag += d1 * d1;
+                        }
+                        // Computing 2nd power
+                        final double d1 = lagrangeValuesAtNewPoint.getEntry(k);
+                        final double den = beta * hdiag + d1 * d1;
+                        distsq = ZERO;
+                        for (int j = 0; j < n; j++) {
+                            // Computing 2nd power
+                            final double d2 = interpolationPoints.getEntry(k, j) - newPoint.getEntry(j);
+                            distsq += d2 * d2;
+                        }
+                        // Computing MAX
+                        // Computing 2nd power
+                        final double d3 = distsq / delsq;
+                        final double temp = FastMath.max(ONE, d3 * d3);
+                        if (temp * den > scaden) {
+                            scaden = temp * den;
+                            knew = k;
+                            denom = den;
+                        }
+                        // Computing MAX
+                        // Computing 2nd power
+                        final double d4 = lagrangeValuesAtNewPoint.getEntry(k);
+                        final double d5 = temp * (d4 * d4);
+                        biglsq = FastMath.max(biglsq, d5);
+                    }
+                    if (scaden <= HALF * biglsq) {
+                        knew = ksav;
+                        denom = densav;
+                    }
+                }
+            }
+
+            // Update BMAT and ZMAT, so that the KNEW-th interpolation point can be
+            // moved. Also update the second derivative terms of the model.
+
+            update(beta, denom, knew);
+
+            ih = 0;
+            final double pqold = modelSecondDerivativesParameters.getEntry(knew);
+            modelSecondDerivativesParameters.setEntry(knew, ZERO);
+            for (int i = 0; i < n; i++) {
+                final double temp = pqold * interpolationPoints.getEntry(knew, i);
+                for (int j = 0; j <= i; j++) {
+                    modelSecondDerivativesValues.setEntry(ih, modelSecondDerivativesValues.getEntry(ih) + temp * interpolationPoints.getEntry(knew, j));
+                    ih++;
+                }
+            }
+            for (int m = 0; m < nptm; m++) {
+                final double temp = diff * zMatrix.getEntry(knew, m);
+                for (int k = 0; k < npt; k++) {
+                    modelSecondDerivativesParameters.setEntry(k, modelSecondDerivativesParameters.getEntry(k) + temp * zMatrix.getEntry(k, m));
+                }
+            }
+
+            // Include the new interpolation point, and make the changes to GOPT at
+            // the old XOPT that are caused by the updating of the quadratic model.
+
+            fAtInterpolationPoints.setEntry(knew,  f);
+            for (int i = 0; i < n; i++) {
+                interpolationPoints.setEntry(knew, i, newPoint.getEntry(i));
+                work1.setEntry(i, bMatrix.getEntry(knew, i));
+            }
+            for (int k = 0; k < npt; k++) {
+                double suma = ZERO;
+                for (int m = 0; m < nptm; m++) {
+                    suma += zMatrix.getEntry(knew, m) * zMatrix.getEntry(k, m);
+                }
+                double sumb = ZERO;
+                for (int j = 0; j < n; j++) {
+                    sumb += interpolationPoints.getEntry(k, j) * trustRegionCenterOffset.getEntry(j);
+                }
+                final double temp = suma * sumb;
+                for (int i = 0; i < n; i++) {
+                    work1.setEntry(i, work1.getEntry(i) + temp * interpolationPoints.getEntry(k, i));
+                }
+            }
+            for (int i = 0; i < n; i++) {
+                gradientAtTrustRegionCenter.setEntry(i, gradientAtTrustRegionCenter.getEntry(i) + diff * work1.getEntry(i));
+            }
+
+            // Update XOPT, GOPT and KOPT if the new calculated F is less than FOPT.
+
+            if (f < fopt) {
+                trustRegionCenterInterpolationPointIndex = knew;
+                xoptsq = ZERO;
+                ih = 0;
+                for (int j = 0; j < n; j++) {
+                    trustRegionCenterOffset.setEntry(j, newPoint.getEntry(j));
+                    // Computing 2nd power
+                    final double d1 = trustRegionCenterOffset.getEntry(j);
+                    xoptsq += d1 * d1;
+                    for (int i = 0; i <= j; i++) {
+                        if (i < j) {
+                            gradientAtTrustRegionCenter.setEntry(j, gradientAtTrustRegionCenter.getEntry(j) + modelSecondDerivativesValues.getEntry(ih) * trialStepPoint.getEntry(i));
+                        }
+                        gradientAtTrustRegionCenter.setEntry(i, gradientAtTrustRegionCenter.getEntry(i) + modelSecondDerivativesValues.getEntry(ih) * trialStepPoint.getEntry(j));
+                        ih++;
+                    }
+                }
+                for (int k = 0; k < npt; k++) {
+                    double temp = ZERO;
+                    for (int j = 0; j < n; j++) {
+                        temp += interpolationPoints.getEntry(k, j) * trialStepPoint.getEntry(j);
+                    }
+                    temp *= modelSecondDerivativesParameters.getEntry(k);
+                    for (int i = 0; i < n; i++) {
+                        gradientAtTrustRegionCenter.setEntry(i, gradientAtTrustRegionCenter.getEntry(i) + temp * interpolationPoints.getEntry(k, i));
+                    }
+                }
+            }
+
+            // Calculate the parameters of the least Frobenius norm interpolant to
+            // the current data, the gradient of this interpolant at XOPT being put
+            // into VLAG(NPT+I), I=1,2,...,N.
+
+            if (ntrits > 0) {
+                for (int k = 0; k < npt; k++) {
+                    lagrangeValuesAtNewPoint.setEntry(k, fAtInterpolationPoints.getEntry(k) - fAtInterpolationPoints.getEntry(trustRegionCenterInterpolationPointIndex));
+                    work3.setEntry(k, ZERO);
+                }
+                for (int j = 0; j < nptm; j++) {
+                    double sum = ZERO;
+                    for (int k = 0; k < npt; k++) {
+                        sum += zMatrix.getEntry(k, j) * lagrangeValuesAtNewPoint.getEntry(k);
+                    }
+                    for (int k = 0; k < npt; k++) {
+                        work3.setEntry(k, work3.getEntry(k) + sum * zMatrix.getEntry(k, j));
+                    }
+                }
+                for (int k = 0; k < npt; k++) {
+                    double sum = ZERO;
+                    for (int j = 0; j < n; j++) {
+                        sum += interpolationPoints.getEntry(k, j) * trustRegionCenterOffset.getEntry(j);
+                    }
+                    work2.setEntry(k, work3.getEntry(k));
+                    work3.setEntry(k, sum * work3.getEntry(k));
+                }
+                double gqsq = ZERO;
+                double gisq = ZERO;
+                for (int i = 0; i < n; i++) {
+                    double sum = ZERO;
+                    for (int k = 0; k < npt; k++) {
+                        sum += bMatrix.getEntry(k, i) *
+                            lagrangeValuesAtNewPoint.getEntry(k) + interpolationPoints.getEntry(k, i) * work3.getEntry(k);
+                    }
+                    if (trustRegionCenterOffset.getEntry(i) == lowerDifference.getEntry(i)) {
+                        // Computing MIN
+                        // Computing 2nd power
+                        final double d1 = FastMath.min(ZERO, gradientAtTrustRegionCenter.getEntry(i));
+                        gqsq += d1 * d1;
+                        // Computing 2nd power
+                        final double d2 = FastMath.min(ZERO, sum);
+                        gisq += d2 * d2;
+                    } else if (trustRegionCenterOffset.getEntry(i) == upperDifference.getEntry(i)) {
+                        // Computing MAX
+                        // Computing 2nd power
+                        final double d1 = FastMath.max(ZERO, gradientAtTrustRegionCenter.getEntry(i));
+                        gqsq += d1 * d1;
+                        // Computing 2nd power
+                        final double d2 = FastMath.max(ZERO, sum);
+                        gisq += d2 * d2;
+                    } else {
+                        // Computing 2nd power
+                        final double d1 = gradientAtTrustRegionCenter.getEntry(i);
+                        gqsq += d1 * d1;
+                        gisq += sum * sum;
+                    }
+                    lagrangeValuesAtNewPoint.setEntry(npt + i, sum);
+                }
+
+                // Test whether to replace the new quadratic model by the least Frobenius
+                // norm interpolant, making the replacement if the test is satisfied.
+
+                ++itest;
+                if (gqsq < TEN * gisq) {
+                    itest = 0;
+                }
+                if (itest >= 3) {
+                    for (int i = 0, max = FastMath.max(npt, nh); i < max; i++) {
+                        if (i < n) {
+                            gradientAtTrustRegionCenter.setEntry(i, lagrangeValuesAtNewPoint.getEntry(npt + i));
+                        }
+                        if (i < npt) {
+                            modelSecondDerivativesParameters.setEntry(i, work2.getEntry(i));
+                        }
+                        if (i < nh) {
+                            modelSecondDerivativesValues.setEntry(i, ZERO);
+                        }
+                        itest = 0;
+                    }
+                }
+            }
+
+            // If a trust region step has provided a sufficient decrease in F, then
+            // branch for another trust region calculation. The case NTRITS=0 occurs
+            // when the new interpolation point was reached by an alternative step.
+
+            if (ntrits == 0) {
+                state = 60; break;
+            }
+            if (f <= fopt + ONE_OVER_TEN * vquad) {
+                state = 60; break;
+            }
+
+            // Alternatively, find out if the interpolation points are close enough
+            //   to the best point so far.
+
+            // Computing MAX
+            // Computing 2nd power
+            final double d1 = TWO * delta;
+            // Computing 2nd power
+            final double d2 = TEN * rho;
+            distsq = FastMath.max(d1 * d1, d2 * d2);
+        }
+        case 650: {
+            printState(650); // XXX
+            knew = -1;
+            for (int k = 0; k < npt; k++) {
+                double sum = ZERO;
+                for (int j = 0; j < n; j++) {
+                    // Computing 2nd power
+                    final double d1 = interpolationPoints.getEntry(k, j) - trustRegionCenterOffset.getEntry(j);
+                    sum += d1 * d1;
+                }
+                if (sum > distsq) {
+                    knew = k;
+                    distsq = sum;
+                }
+            }
+
+            // If KNEW is positive, then ALTMOV finds alternative new positions for
+            // the KNEW-th interpolation point within distance ADELT of XOPT. It is
+            // reached via label 90. Otherwise, there is a branch to label 60 for
+            // another trust region iteration, unless the calculations with the
+            // current RHO are complete.
+
+            if (knew >= 0) {
+                final double dist = FastMath.sqrt(distsq);
+                if (ntrits == -1) {
+                    // Computing MIN
+                    delta = FastMath.min(ONE_OVER_TEN * delta, HALF * dist);
+                    if (delta <= rho * 1.5) {
+                        delta = rho;
+                    }
+                }
+                ntrits = 0;
+                // Computing MAX
+                // Computing MIN
+                final double d1 = FastMath.min(ONE_OVER_TEN * dist, delta);
+                adelt = FastMath.max(d1, rho);
+                dsq = adelt * adelt;
+                state = 90; break;
+            }
+            if (ntrits == -1) {
+                state = 680; break;
+            }
+            if (ratio > ZERO) {
+                state = 60; break;
+            }
+            if (FastMath.max(delta, dnorm) > rho) {
+                state = 60; break;
+            }
+
+            // The calculations with the current value of RHO are complete. Pick the
+            //   next values of RHO and DELTA.
+        }
+        case 680: {
+            printState(680); // XXX
+            if (rho > stoppingTrustRegionRadius) {
+                delta = HALF * rho;
+                ratio = rho / stoppingTrustRegionRadius;
+                if (ratio <= SIXTEEN) {
+                    rho = stoppingTrustRegionRadius;
+                } else if (ratio <= TWO_HUNDRED_FIFTY) {
+                    rho = FastMath.sqrt(ratio) * stoppingTrustRegionRadius;
+                } else {
+                    rho *= ONE_OVER_TEN;
+                }
+                delta = FastMath.max(delta, rho);
+                ntrits = 0;
+                nfsav = getEvaluations();
+                state = 60; break;
+            }
+
+            // Return from the calculation, after another Newton-Raphson step, if
+            //   it is too short to have been tried before.
+
+            if (ntrits == -1) {
+                state = 360; break;
+            }
+        }
+        case 720: {
+            printState(720); // XXX
+            if (fAtInterpolationPoints.getEntry(trustRegionCenterInterpolationPointIndex) <= fsave) {
+                for (int i = 0; i < n; i++) {
+                    // Computing MIN
+                    // Computing MAX
+                    final double d3 = lowerBound[i];
+                    final double d4 = originShift.getEntry(i) + trustRegionCenterOffset.getEntry(i);
+                    final double d1 = FastMath.max(d3, d4);
+                    final double d2 = upperBound[i];
+                    currentBest.setEntry(i, FastMath.min(d1, d2));
+                    if (trustRegionCenterOffset.getEntry(i) == lowerDifference.getEntry(i)) {
+                        currentBest.setEntry(i, lowerBound[i]);
+                    }
+                    if (trustRegionCenterOffset.getEntry(i) == upperDifference.getEntry(i)) {
+                        currentBest.setEntry(i, upperBound[i]);
+                    }
+                }
+                f = fAtInterpolationPoints.getEntry(trustRegionCenterInterpolationPointIndex);
+            }
+            return f;
+        }
+        default: {
+            throw new MathIllegalStateException(LocalizedFormats.SIMPLE_MESSAGE, "bobyqb");
+        }}}
+    } // bobyqb
+
+    // ----------------------------------------------------------------------------------------
+
+    /**
+     *     The arguments N, NPT, XPT, XOPT, BMAT, ZMAT, NDIM, SL and SU all have
+     *       the same meanings as the corresponding arguments of BOBYQB.
+     *     KOPT is the index of the optimal interpolation point.
+     *     KNEW is the index of the interpolation point that is going to be moved.
+     *     ADELT is the current trust region bound.
+     *     XNEW will be set to a suitable new position for the interpolation point
+     *       XPT(KNEW,.). Specifically, it satisfies the SL, SU and trust region
+     *       bounds and it should provide a large denominator in the next call of
+     *       UPDATE. The step XNEW-XOPT from XOPT is restricted to moves along the
+     *       straight lines through XOPT and another interpolation point.
+     *     XALT also provides a large value of the modulus of the KNEW-th Lagrange
+     *       function subject to the constraints that have been mentioned, its main
+     *       difference from XNEW being that XALT-XOPT is a constrained version of
+     *       the Cauchy step within the trust region. An exception is that XALT is
+     *       not calculated if all components of GLAG (see below) are zero.
+     *     ALPHA will be set to the KNEW-th diagonal element of the H matrix.
+     *     CAUCHY will be set to the square of the KNEW-th Lagrange function at
+     *       the step XALT-XOPT from XOPT for the vector XALT that is returned,
+     *       except that CAUCHY is set to zero if XALT is not calculated.
+     *     GLAG is a working space vector of length N for the gradient of the
+     *       KNEW-th Lagrange function at XOPT.
+     *     HCOL is a working space vector of length NPT for the second derivative
+     *       coefficients of the KNEW-th Lagrange function.
+     *     W is a working space vector of length 2N that is going to hold the
+     *       constrained Cauchy step from XOPT of the Lagrange function, followed
+     *       by the downhill version of XALT when the uphill step is calculated.
+     *
+     *     Set the first NPT components of W to the leading elements of the
+     *     KNEW-th column of the H matrix.
+     * @param knew
+     * @param adelt
+     */
+    private double[] altmov(
+            int knew,
+            double adelt
+    ) {
+        printMethod(); // XXX
+
+        final int n = currentBest.getDimension();
+        final int npt = numberOfInterpolationPoints;
+
+        final ArrayRealVector glag = new ArrayRealVector(n);
+        final ArrayRealVector hcol = new ArrayRealVector(npt);
+
+        final ArrayRealVector work1 = new ArrayRealVector(n);
+        final ArrayRealVector work2 = new ArrayRealVector(n);
+
+        for (int k = 0; k < npt; k++) {
+            hcol.setEntry(k, ZERO);
+        }
+        for (int j = 0, max = npt - n - 1; j < max; j++) {
+            final double tmp = zMatrix.getEntry(knew, j);
+            for (int k = 0; k < npt; k++) {
+                hcol.setEntry(k, hcol.getEntry(k) + tmp * zMatrix.getEntry(k, j));
+            }
+        }
+        final double alpha = hcol.getEntry(knew);
+        final double ha = HALF * alpha;
+
+        // Calculate the gradient of the KNEW-th Lagrange function at XOPT.
+
+        for (int i = 0; i < n; i++) {
+            glag.setEntry(i, bMatrix.getEntry(knew, i));
+        }
+        for (int k = 0; k < npt; k++) {
+            double tmp = ZERO;
+            for (int j = 0; j < n; j++) {
+                tmp += interpolationPoints.getEntry(k, j) * trustRegionCenterOffset.getEntry(j);
+            }
+            tmp *= hcol.getEntry(k);
+            for (int i = 0; i < n; i++) {
+                glag.setEntry(i, glag.getEntry(i) + tmp * interpolationPoints.getEntry(k, i));
+            }
+        }
+
+        // Search for a large denominator along the straight lines through XOPT
+        // and another interpolation point. SLBD and SUBD will be lower and upper
+        // bounds on the step along each of these lines in turn. PREDSQ will be
+        // set to the square of the predicted denominator for each line. PRESAV
+        // will be set to the largest admissible value of PREDSQ that occurs.
+
+        double presav = ZERO;
+        double step = Double.NaN;
+        int ksav = 0;
+        int ibdsav = 0;
+        double stpsav = 0;
+        for (int k = 0; k < npt; k++) {
+            if (k == trustRegionCenterInterpolationPointIndex) {
+                continue;
+            }
+            double dderiv = ZERO;
+            double distsq = ZERO;
+            for (int i = 0; i < n; i++) {
+                final double tmp = interpolationPoints.getEntry(k, i) - trustRegionCenterOffset.getEntry(i);
+                dderiv += glag.getEntry(i) * tmp;
+                distsq += tmp * tmp;
+            }
+            double subd = adelt / FastMath.sqrt(distsq);
+            double slbd = -subd;
+            int ilbd = 0;
+            int iubd = 0;
+            final double sumin = FastMath.min(ONE, subd);
+
+            // Revise SLBD and SUBD if necessary because of the bounds in SL and SU.
+
+            for (int i = 0; i < n; i++) {
+                final double tmp = interpolationPoints.getEntry(k, i) - trustRegionCenterOffset.getEntry(i);
+                if (tmp > ZERO) {
+                    if (slbd * tmp < lowerDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)) {
+                        slbd = (lowerDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)) / tmp;
+                        ilbd = -i - 1;
+                    }
+                    if (subd * tmp > upperDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)) {
+                        // Computing MAX
+                        subd = FastMath.max(sumin,
+                                            (upperDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)) / tmp);
+                        iubd = i + 1;
+                    }
+                } else if (tmp < ZERO) {
+                    if (slbd * tmp > upperDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)) {
+                        slbd = (upperDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)) / tmp;
+                        ilbd = i + 1;
+                    }
+                    if (subd * tmp < lowerDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)) {
+                        // Computing MAX
+                        subd = FastMath.max(sumin,
+                                            (lowerDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)) / tmp);
+                        iubd = -i - 1;
+                    }
+                }
+            }
+
+            // Seek a large modulus of the KNEW-th Lagrange function when the index
+            // of the other interpolation point on the line through XOPT is KNEW.
+
+            step = slbd;
+            int isbd = ilbd;
+            double vlag = Double.NaN;
+            if (k == knew) {
+                final double diff = dderiv - ONE;
+                vlag = slbd * (dderiv - slbd * diff);
+                final double d1 = subd * (dderiv - subd * diff);
+                if (FastMath.abs(d1) > FastMath.abs(vlag)) {
+                    step = subd;
+                    vlag = d1;
+                    isbd = iubd;
+                }
+                final double d2 = HALF * dderiv;
+                final double d3 = d2 - diff * slbd;
+                final double d4 = d2 - diff * subd;
+                if (d3 * d4 < ZERO) {
+                    final double d5 = d2 * d2 / diff;
+                    if (FastMath.abs(d5) > FastMath.abs(vlag)) {
+                        step = d2 / diff;
+                        vlag = d5;
+                        isbd = 0;
+                    }
+                }
+
+                // Search along each of the other lines through XOPT and another point.
+
+            } else {
+                vlag = slbd * (ONE - slbd);
+                final double tmp = subd * (ONE - subd);
+                if (FastMath.abs(tmp) > FastMath.abs(vlag)) {
+                    step = subd;
+                    vlag = tmp;
+                    isbd = iubd;
+                }
+                if (subd > HALF && FastMath.abs(vlag) < ONE_OVER_FOUR) {
+                    step = HALF;
+                    vlag = ONE_OVER_FOUR;
+                    isbd = 0;
+                }
+                vlag *= dderiv;
+            }
+
+            // Calculate PREDSQ for the current line search and maintain PRESAV.
+
+            final double tmp = step * (ONE - step) * distsq;
+            final double predsq = vlag * vlag * (vlag * vlag + ha * tmp * tmp);
+            if (predsq > presav) {
+                presav = predsq;
+                ksav = k;
+                stpsav = step;
+                ibdsav = isbd;
+            }
+        }
+
+        // Construct XNEW in a way that satisfies the bound constraints exactly.
+
+        for (int i = 0; i < n; i++) {
+            final double tmp = trustRegionCenterOffset.getEntry(i) + stpsav * (interpolationPoints.getEntry(ksav, i) - trustRegionCenterOffset.getEntry(i));
+            newPoint.setEntry(i, FastMath.max(lowerDifference.getEntry(i),
+                                              FastMath.min(upperDifference.getEntry(i), tmp)));
+        }
+        if (ibdsav < 0) {
+            newPoint.setEntry(-ibdsav - 1, lowerDifference.getEntry(-ibdsav - 1));
+        }
+        if (ibdsav > 0) {
+            newPoint.setEntry(ibdsav - 1, upperDifference.getEntry(ibdsav - 1));
+        }
+
+        // Prepare for the iterative method that assembles the constrained Cauchy
+        // step in W. The sum of squares of the fixed components of W is formed in
+        // WFIXSQ, and the free components of W are set to BIGSTP.
+
+        final double bigstp = adelt + adelt;
+        int iflag = 0;
+        double cauchy = Double.NaN;
+        double csave = ZERO;
+        while (true) {
+            double wfixsq = ZERO;
+            double ggfree = ZERO;
+            for (int i = 0; i < n; i++) {
+                final double glagValue = glag.getEntry(i);
+                work1.setEntry(i, ZERO);
+                if (FastMath.min(trustRegionCenterOffset.getEntry(i) - lowerDifference.getEntry(i), glagValue) > ZERO ||
+                    FastMath.max(trustRegionCenterOffset.getEntry(i) - upperDifference.getEntry(i), glagValue) < ZERO) {
+                    work1.setEntry(i, bigstp);
+                    // Computing 2nd power
+                    ggfree += glagValue * glagValue;
+                }
+            }
+            if (ggfree == ZERO) {
+                return new double[] { alpha, ZERO };
+            }
+
+            // Investigate whether more components of W can be fixed.
+            final double tmp1 = adelt * adelt - wfixsq;
+            if (tmp1 > ZERO) {
+                step = FastMath.sqrt(tmp1 / ggfree);
+                ggfree = ZERO;
+                for (int i = 0; i < n; i++) {
+                    if (work1.getEntry(i) == bigstp) {
+                        final double tmp2 = trustRegionCenterOffset.getEntry(i) - step * glag.getEntry(i);
+                        if (tmp2 <= lowerDifference.getEntry(i)) {
+                            work1.setEntry(i, lowerDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i));
+                            // Computing 2nd power
+                            final double d1 = work1.getEntry(i);
+                            wfixsq += d1 * d1;
+                        } else if (tmp2 >= upperDifference.getEntry(i)) {
+                            work1.setEntry(i, upperDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i));
+                            // Computing 2nd power
+                            final double d1 = work1.getEntry(i);
+                            wfixsq += d1 * d1;
+                        } else {
+                            // Computing 2nd power
+                            final double d1 = glag.getEntry(i);
+                            ggfree += d1 * d1;
+                        }
+                    }
+                }
+            }
+
+            // Set the remaining free components of W and all components of XALT,
+            // except that W may be scaled later.
+
+            double gw = ZERO;
+            for (int i = 0; i < n; i++) {
+                final double glagValue = glag.getEntry(i);
+                if (work1.getEntry(i) == bigstp) {
+                    work1.setEntry(i, -step * glagValue);
+                    final double min = FastMath.min(upperDifference.getEntry(i),
+                                                    trustRegionCenterOffset.getEntry(i) + work1.getEntry(i));
+                    alternativeNewPoint.setEntry(i, FastMath.max(lowerDifference.getEntry(i), min));
+                } else if (work1.getEntry(i) == ZERO) {
+                    alternativeNewPoint.setEntry(i, trustRegionCenterOffset.getEntry(i));
+                } else if (glagValue > ZERO) {
+                    alternativeNewPoint.setEntry(i, lowerDifference.getEntry(i));
+                } else {
+                    alternativeNewPoint.setEntry(i, upperDifference.getEntry(i));
+                }
+                gw += glagValue * work1.getEntry(i);
+            }
+
+            // Set CURV to the curvature of the KNEW-th Lagrange function along W.
+            // Scale W by a factor less than one if that can reduce the modulus of
+            // the Lagrange function at XOPT+W. Set CAUCHY to the final value of
+            // the square of this function.
+
+            double curv = ZERO;
+            for (int k = 0; k < npt; k++) {
+                double tmp = ZERO;
+                for (int j = 0; j < n; j++) {
+                    tmp += interpolationPoints.getEntry(k, j) * work1.getEntry(j);
+                }
+                curv += hcol.getEntry(k) * tmp * tmp;
+            }
+            if (iflag == 1) {
+                curv = -curv;
+            }
+            if (curv > -gw &&
+                curv < -gw * (ONE + FastMath.sqrt(TWO))) {
+                final double scale = -gw / curv;
+                for (int i = 0; i < n; i++) {
+                    final double tmp = trustRegionCenterOffset.getEntry(i) + scale * work1.getEntry(i);
+                    alternativeNewPoint.setEntry(i, FastMath.max(lowerDifference.getEntry(i),
+                                                    FastMath.min(upperDifference.getEntry(i), tmp)));
+                }
+                // Computing 2nd power
+                final double d1 = HALF * gw * scale;
+                cauchy = d1 * d1;
+            } else {
+                // Computing 2nd power
+                final double d1 = gw + HALF * curv;
+                cauchy = d1 * d1;
+            }
+
+            // If IFLAG is zero, then XALT is calculated as before after reversing
+            // the sign of GLAG. Thus two XALT vectors become available. The one that
+            // is chosen is the one that gives the larger value of CAUCHY.
+
+            if (iflag == 0) {
+                for (int i = 0; i < n; i++) {
+                    glag.setEntry(i, -glag.getEntry(i));
+                    work2.setEntry(i, alternativeNewPoint.getEntry(i));
+                }
+                csave = cauchy;
+                iflag = 1;
+            } else {
+                break;
+            }
+        }
+        if (csave > cauchy) {
+            for (int i = 0; i < n; i++) {
+                alternativeNewPoint.setEntry(i, work2.getEntry(i));
+            }
+            cauchy = csave;
+        }
+
+        return new double[] { alpha, cauchy };
+    } // altmov
+
+    // ----------------------------------------------------------------------------------------
+
+    /**
+     *     SUBROUTINE PRELIM sets the elements of XBASE, XPT, FVAL, GOPT, HQ, PQ,
+     *     BMAT and ZMAT for the first iteration, and it maintains the values of
+     *     NF and KOPT. The vector X is also changed by PRELIM.
+     *
+     *     The arguments N, NPT, X, XL, XU, RHOBEG, IPRINT and MAXFUN are the
+     *       same as the corresponding arguments in SUBROUTINE BOBYQA.
+     *     The arguments XBASE, XPT, FVAL, HQ, PQ, BMAT, ZMAT, NDIM, SL and SU
+     *       are the same as the corresponding arguments in BOBYQB, the elements
+     *       of SL and SU being set in BOBYQA.
+     *     GOPT is usually the gradient of the quadratic model at XOPT+XBASE, but
+     *       it is set by PRELIM to the gradient of the quadratic model at XBASE.
+     *       If XOPT is nonzero, BOBYQB will change it to its usual value later.
+     *     NF is maintaned as the number of calls of CALFUN so far.
+     *     KOPT will be such that the least calculated value of F so far is at
+     *       the point XPT(KOPT,.)+XBASE in the space of the variables.
+     *
+     * @param lowerBound Lower bounds.
+     * @param upperBound Upper bounds.
+     */
+    private void prelim(double[] lowerBound,
+                        double[] upperBound) {
+        printMethod(); // XXX
+
+        final int n = currentBest.getDimension();
+        final int npt = numberOfInterpolationPoints;
+        final int ndim = bMatrix.getRowDimension();
+
+        final double rhosq = initialTrustRegionRadius * initialTrustRegionRadius;
+        final double recip = 1d / rhosq;
+        final int np = n + 1;
+
+        // Set XBASE to the initial vector of variables, and set the initial
+        // elements of XPT, BMAT, HQ, PQ and ZMAT to zero.
+
+        for (int j = 0; j < n; j++) {
+            originShift.setEntry(j, currentBest.getEntry(j));
+            for (int k = 0; k < npt; k++) {
+                interpolationPoints.setEntry(k, j, ZERO);
+            }
+            for (int i = 0; i < ndim; i++) {
+                bMatrix.setEntry(i, j, ZERO);
+            }
+        }
+        for (int i = 0, max = n * np / 2; i < max; i++) {
+            modelSecondDerivativesValues.setEntry(i, ZERO);
+        }
+        for (int k = 0; k < npt; k++) {
+            modelSecondDerivativesParameters.setEntry(k, ZERO);
+            for (int j = 0, max = npt - np; j < max; j++) {
+                zMatrix.setEntry(k, j, ZERO);
+            }
+        }
+
+        // Begin the initialization procedure. NF becomes one more than the number
+        // of function values so far. The coordinates of the displacement of the
+        // next initial interpolation point from XBASE are set in XPT(NF+1,.).
+
+        int ipt = 0;
+        int jpt = 0;
+        double fbeg = Double.NaN;
+        do {
+            final int nfm = getEvaluations();
+            final int nfx = nfm - n;
+            final int nfmm = nfm - 1;
+            final int nfxm = nfx - 1;
+            double stepa = 0;
+            double stepb = 0;
+            if (nfm <= 2 * n) {
+                if (nfm >= 1 &&
+                    nfm <= n) {
+                    stepa = initialTrustRegionRadius;
+                    if (upperDifference.getEntry(nfmm) == ZERO) {
+                        stepa = -stepa;
+                        // throw new PathIsExploredException(); // XXX
+                    }
+                    interpolationPoints.setEntry(nfm, nfmm, stepa);
+                } else if (nfm > n) {
+                    stepa = interpolationPoints.getEntry(nfx, nfxm);
+                    stepb = -initialTrustRegionRadius;
+                    if (lowerDifference.getEntry(nfxm) == ZERO) {
+                        stepb = FastMath.min(TWO * initialTrustRegionRadius, upperDifference.getEntry(nfxm));
+                        // throw new PathIsExploredException(); // XXX
+                    }
+                    if (upperDifference.getEntry(nfxm) == ZERO) {
+                        stepb = FastMath.max(-TWO * initialTrustRegionRadius, lowerDifference.getEntry(nfxm));
+                        // throw new PathIsExploredException(); // XXX
+                    }
+                    interpolationPoints.setEntry(nfm, nfxm, stepb);
+                }
+            } else {
+                final int tmp1 = (nfm - np) / n;
+                jpt = nfm - tmp1 * n - n;
+                ipt = jpt + tmp1;
+                if (ipt > n) {
+                    final int tmp2 = jpt;
+                    jpt = ipt - n;
+                    ipt = tmp2;
+//                     throw new PathIsExploredException(); // XXX
+                }
+                final int iptMinus1 = ipt - 1;
+                final int jptMinus1 = jpt - 1;
+                interpolationPoints.setEntry(nfm, iptMinus1, interpolationPoints.getEntry(ipt, iptMinus1));
+                interpolationPoints.setEntry(nfm, jptMinus1, interpolationPoints.getEntry(jpt, jptMinus1));
+            }
+
+            // Calculate the next value of F. The least function value so far and
+            // its index are required.
+
+            for (int j = 0; j < n; j++) {
+                currentBest.setEntry(j, FastMath.min(FastMath.max(lowerBound[j],
+                                                                  originShift.getEntry(j) + interpolationPoints.getEntry(nfm, j)),
+                                                     upperBound[j]));
+                if (interpolationPoints.getEntry(nfm, j) == lowerDifference.getEntry(j)) {
+                    currentBest.setEntry(j, lowerBound[j]);
+                }
+                if (interpolationPoints.getEntry(nfm, j) == upperDifference.getEntry(j)) {
+                    currentBest.setEntry(j, upperBound[j]);
+                }
+            }
+
+            final double objectiveValue = computeObjectiveValue(currentBest.toArray());
+            final double f = isMinimize ? objectiveValue : -objectiveValue;
+            final int numEval = getEvaluations(); // nfm + 1
+            fAtInterpolationPoints.setEntry(nfm, f);
+
+            if (numEval == 1) {
+                fbeg = f;
+                trustRegionCenterInterpolationPointIndex = 0;
+            } else if (f < fAtInterpolationPoints.getEntry(trustRegionCenterInterpolationPointIndex)) {
+                trustRegionCenterInterpolationPointIndex = nfm;
+            }
+
+            // Set the nonzero initial elements of BMAT and the quadratic model in the
+            // cases when NF is at most 2*N+1. If NF exceeds N+1, then the positions
+            // of the NF-th and (NF-N)-th interpolation points may be switched, in
+            // order that the function value at the first of them contributes to the
+            // off-diagonal second derivative terms of the initial quadratic model.
+
+            if (numEval <= 2 * n + 1) {
+                if (numEval >= 2 &&
+                    numEval <= n + 1) {
+                    gradientAtTrustRegionCenter.setEntry(nfmm, (f - fbeg) / stepa);
+                    if (npt < numEval + n) {
+                        final double oneOverStepA = ONE / stepa;
+                        bMatrix.setEntry(0, nfmm, -oneOverStepA);
+                        bMatrix.setEntry(nfm, nfmm, oneOverStepA);
+                        bMatrix.setEntry(npt + nfmm, nfmm, -HALF * rhosq);
+                        // throw new PathIsExploredException(); // XXX
+                    }
+                } else if (numEval >= n + 2) {
+                    final int ih = nfx * (nfx + 1) / 2 - 1;
+                    final double tmp = (f - fbeg) / stepb;
+                    final double diff = stepb - stepa;
+                    modelSecondDerivativesValues.setEntry(ih, TWO * (tmp - gradientAtTrustRegionCenter.getEntry(nfxm)) / diff);
+                    gradientAtTrustRegionCenter.setEntry(nfxm, (gradientAtTrustRegionCenter.getEntry(nfxm) * stepb - tmp * stepa) / diff);
+                    if (stepa * stepb < ZERO && f < fAtInterpolationPoints.getEntry(nfm - n)) {
+                        fAtInterpolationPoints.setEntry(nfm, fAtInterpolationPoints.getEntry(nfm - n));
+                        fAtInterpolationPoints.setEntry(nfm - n, f);
+                        if (trustRegionCenterInterpolationPointIndex == nfm) {
+                            trustRegionCenterInterpolationPointIndex = nfm - n;
+                        }
+                        interpolationPoints.setEntry(nfm - n, nfxm, stepb);
+                        interpolationPoints.setEntry(nfm, nfxm, stepa);
+                    }
+                    bMatrix.setEntry(0, nfxm, -(stepa + stepb) / (stepa * stepb));
+                    bMatrix.setEntry(nfm, nfxm, -HALF / interpolationPoints.getEntry(nfm - n, nfxm));
+                    bMatrix.setEntry(nfm - n, nfxm,
+                                  -bMatrix.getEntry(0, nfxm) - bMatrix.getEntry(nfm, nfxm));
+                    zMatrix.setEntry(0, nfxm, FastMath.sqrt(TWO) / (stepa * stepb));
+                    zMatrix.setEntry(nfm, nfxm, FastMath.sqrt(HALF) / rhosq);
+                    // zMatrix.setEntry(nfm, nfxm, FastMath.sqrt(HALF) * recip); // XXX "testAckley" and "testDiffPow" fail.
+                    zMatrix.setEntry(nfm - n, nfxm,
+                                  -zMatrix.getEntry(0, nfxm) - zMatrix.getEntry(nfm, nfxm));
+                }
+
+                // Set the off-diagonal second derivatives of the Lagrange functions and
+                // the initial quadratic model.
+
+            } else {
+                zMatrix.setEntry(0, nfxm, recip);
+                zMatrix.setEntry(nfm, nfxm, recip);
+                zMatrix.setEntry(ipt, nfxm, -recip);
+                zMatrix.setEntry(jpt, nfxm, -recip);
+
+                final int ih = ipt * (ipt - 1) / 2 + jpt - 1;
+                final double tmp = interpolationPoints.getEntry(nfm, ipt - 1) * interpolationPoints.getEntry(nfm, jpt - 1);
+                modelSecondDerivativesValues.setEntry(ih, (fbeg - fAtInterpolationPoints.getEntry(ipt) - fAtInterpolationPoints.getEntry(jpt) + f) / tmp);
+//                 throw new PathIsExploredException(); // XXX
+            }
+        } while (getEvaluations() < npt);
+    } // prelim
+
+
+    // ----------------------------------------------------------------------------------------
+
+    /**
+     *     A version of the truncated conjugate gradient is applied. If a line
+     *     search is restricted by a constraint, then the procedure is restarted,
+     *     the values of the variables that are at their bounds being fixed. If
+     *     the trust region boundary is reached, then further changes may be made
+     *     to D, each one being in the two dimensional space that is spanned
+     *     by the current D and the gradient of Q at XOPT+D, staying on the trust
+     *     region boundary. Termination occurs when the reduction in Q seems to
+     *     be close to the greatest reduction that can be achieved.
+     *     The arguments N, NPT, XPT, XOPT, GOPT, HQ, PQ, SL and SU have the same
+     *       meanings as the corresponding arguments of BOBYQB.
+     *     DELTA is the trust region radius for the present calculation, which
+     *       seeks a small value of the quadratic model within distance DELTA of
+     *       XOPT subject to the bounds on the variables.
+     *     XNEW will be set to a new vector of variables that is approximately
+     *       the one that minimizes the quadratic model within the trust region
+     *       subject to the SL and SU constraints on the variables. It satisfies
+     *       as equations the bounds that become active during the calculation.
+     *     D is the calculated trial step from XOPT, generated iteratively from an
+     *       initial value of zero. Thus XNEW is XOPT+D after the final iteration.
+     *     GNEW holds the gradient of the quadratic model at XOPT+D. It is updated
+     *       when D is updated.
+     *     xbdi.get( is a working space vector. For I=1,2,...,N, the element xbdi.get((I) is
+     *       set to -1.0, 0.0, or 1.0, the value being nonzero if and only if the
+     *       I-th variable has become fixed at a bound, the bound being SL(I) or
+     *       SU(I) in the case xbdi.get((I)=-1.0 or xbdi.get((I)=1.0, respectively. This
+     *       information is accumulated during the construction of XNEW.
+     *     The arrays S, HS and HRED are also used for working space. They hold the
+     *       current search direction, and the changes in the gradient of Q along S
+     *       and the reduced D, respectively, where the reduced D is the same as D,
+     *       except that the components of the fixed variables are zero.
+     *     DSQ will be set to the square of the length of XNEW-XOPT.
+     *     CRVMIN is set to zero if D reaches the trust region boundary. Otherwise
+     *       it is set to the least curvature of H that occurs in the conjugate
+     *       gradient searches that are not restricted by any constraints. The
+     *       value CRVMIN=-1.0D0 is set, however, if all of these searches are
+     *       constrained.
+     * @param delta
+     * @param gnew
+     * @param xbdi
+     * @param s
+     * @param hs
+     * @param hred
+     */
+    private double[] trsbox(
+            double delta,
+            ArrayRealVector gnew,
+            ArrayRealVector xbdi,
+            ArrayRealVector s,
+            ArrayRealVector hs,
+            ArrayRealVector hred
+    ) {
+        printMethod(); // XXX
+
+        final int n = currentBest.getDimension();
+        final int npt = numberOfInterpolationPoints;
+
+        double dsq = Double.NaN;
+        double crvmin = Double.NaN;
+
+        // Local variables
+        double ds;
+        int iu;
+        double dhd, dhs, cth, shs, sth, ssq, beta=0, sdec, blen;
+        int iact = -1;
+        int nact = 0;
+        double angt = 0, qred;
+        int isav;
+        double temp = 0, xsav = 0, xsum = 0, angbd = 0, dredg = 0, sredg = 0;
+        int iterc;
+        double resid = 0, delsq = 0, ggsav = 0, tempa = 0, tempb = 0,
+        redmax = 0, dredsq = 0, redsav = 0, gredsq = 0, rednew = 0;
+        int itcsav = 0;
+        double rdprev = 0, rdnext = 0, stplen = 0, stepsq = 0;
+        int itermax = 0;
+
+        // Set some constants.
+
+        // Function Body
+
+        // The sign of GOPT(I) gives the sign of the change to the I-th variable
+        // that will reduce Q from its value at XOPT. Thus xbdi.get((I) shows whether
+        // or not to fix the I-th variable at one of its bounds initially, with
+        // NACT being set to the number of fixed variables. D and GNEW are also
+        // set for the first iteration. DELSQ is the upper bound on the sum of
+        // squares of the free variables. QRED is the reduction in Q so far.
+
+        iterc = 0;
+        nact = 0;
+        for (int i = 0; i < n; i++) {
+            xbdi.setEntry(i, ZERO);
+            if (trustRegionCenterOffset.getEntry(i) <= lowerDifference.getEntry(i)) {
+                if (gradientAtTrustRegionCenter.getEntry(i) >= ZERO) {
+                    xbdi.setEntry(i, MINUS_ONE);
+                }
+            } else if (trustRegionCenterOffset.getEntry(i) >= upperDifference.getEntry(i) &&
+                    gradientAtTrustRegionCenter.getEntry(i) <= ZERO) {
+                xbdi.setEntry(i, ONE);
+            }
+            if (xbdi.getEntry(i) != ZERO) {
+                ++nact;
+            }
+            trialStepPoint.setEntry(i, ZERO);
+            gnew.setEntry(i, gradientAtTrustRegionCenter.getEntry(i));
+        }
+        delsq = delta * delta;
+        qred = ZERO;
+        crvmin = MINUS_ONE;
+
+        // Set the next search direction of the conjugate gradient method. It is
+        // the steepest descent direction initially and when the iterations are
+        // restarted because a variable has just been fixed by a bound, and of
+        // course the components of the fixed variables are zero. ITERMAX is an
+        // upper bound on the indices of the conjugate gradient iterations.
+
+        int state = 20;
+        for(;;) {
+            switch (state) {
+        case 20: {
+            printState(20); // XXX
+            beta = ZERO;
+        }
+        case 30: {
+            printState(30); // XXX
+            stepsq = ZERO;
+            for (int i = 0; i < n; i++) {
+                if (xbdi.getEntry(i) != ZERO) {
+                    s.setEntry(i, ZERO);
+                } else if (beta == ZERO) {
+                    s.setEntry(i, -gnew.getEntry(i));
+                } else {
+                    s.setEntry(i, beta * s.getEntry(i) - gnew.getEntry(i));
+                }
+                // Computing 2nd power
+                final double d1 = s.getEntry(i);
+                stepsq += d1 * d1;
+            }
+            if (stepsq == ZERO) {
+                state = 190; break;
+            }
+            if (beta == ZERO) {
+                gredsq = stepsq;
+                itermax = iterc + n - nact;
+            }
+            if (gredsq * delsq <= qred * 1e-4 * qred) {
+                state = 190; break;
+            }
+
+            // Multiply the search direction by the second derivative matrix of Q and
+            // calculate some scalars for the choice of steplength. Then set BLEN to
+            // the length of the the step to the trust region boundary and STPLEN to
+            // the steplength, ignoring the simple bounds.
+
+            state = 210; break;
+        }
+        case 50: {
+            printState(50); // XXX
+            resid = delsq;
+            ds = ZERO;
+            shs = ZERO;
+            for (int i = 0; i < n; i++) {
+                if (xbdi.getEntry(i) == ZERO) {
+                    // Computing 2nd power
+                    final double d1 = trialStepPoint.getEntry(i);
+                    resid -= d1 * d1;
+                    ds += s.getEntry(i) * trialStepPoint.getEntry(i);
+                    shs += s.getEntry(i) * hs.getEntry(i);
+                }
+            }
+            if (resid <= ZERO) {
+                state = 90; break;
+            }
+            temp = FastMath.sqrt(stepsq * resid + ds * ds);
+            if (ds < ZERO) {
+                blen = (temp - ds) / stepsq;
+            } else {
+                blen = resid / (temp + ds);
+            }
+            stplen = blen;
+            if (shs > ZERO) {
+                // Computing MIN
+                stplen = FastMath.min(blen, gredsq / shs);
+            }
+
+            // Reduce STPLEN if necessary in order to preserve the simple bounds,
+            // letting IACT be the index of the new constrained variable.
+
+            iact = -1;
+            for (int i = 0; i < n; i++) {
+                if (s.getEntry(i) != ZERO) {
+                    xsum = trustRegionCenterOffset.getEntry(i) + trialStepPoint.getEntry(i);
+                    if (s.getEntry(i) > ZERO) {
+                        temp = (upperDifference.getEntry(i) - xsum) / s.getEntry(i);
+                    } else {
+                        temp = (lowerDifference.getEntry(i) - xsum) / s.getEntry(i);
+                    }
+                    if (temp < stplen) {
+                        stplen = temp;
+                        iact = i;
+                    }
+                }
+            }
+
+            // Update CRVMIN, GNEW and D. Set SDEC to the decrease that occurs in Q.
+
+            sdec = ZERO;
+            if (stplen > ZERO) {
+                ++iterc;
+                temp = shs / stepsq;
+                if (iact == -1 && temp > ZERO) {
+                    crvmin = FastMath.min(crvmin,temp);
+                    if (crvmin == MINUS_ONE) {
+                        crvmin = temp;
+                    }
+                }
+                ggsav = gredsq;
+                gredsq = ZERO;
+                for (int i = 0; i < n; i++) {
+                    gnew.setEntry(i, gnew.getEntry(i) + stplen * hs.getEntry(i));
+                    if (xbdi.getEntry(i) == ZERO) {
+                        // Computing 2nd power
+                        final double d1 = gnew.getEntry(i);
+                        gredsq += d1 * d1;
+                    }
+                    trialStepPoint.setEntry(i, trialStepPoint.getEntry(i) + stplen * s.getEntry(i));
+                }
+                // Computing MAX
+                final double d1 = stplen * (ggsav - HALF * stplen * shs);
+                sdec = FastMath.max(d1, ZERO);
+                qred += sdec;
+            }
+
+            // Restart the conjugate gradient method if it has hit a new bound.
+
+            if (iact >= 0) {
+                ++nact;
+                xbdi.setEntry(iact, ONE);
+                if (s.getEntry(iact) < ZERO) {
+                    xbdi.setEntry(iact, MINUS_ONE);
+                }
+                // Computing 2nd power
+                final double d1 = trialStepPoint.getEntry(iact);
+                delsq -= d1 * d1;
+                if (delsq <= ZERO) {
+                    state = 190; break;
+                }
+                state = 20; break;
+            }
+
+            // If STPLEN is less than BLEN, then either apply another conjugate
+            // gradient iteration or RETURN.
+
+            if (stplen < blen) {
+                if (iterc == itermax) {
+                    state = 190; break;
+                }
+                if (sdec <= qred * .01) {
+                    state = 190; break;
+                }
+                beta = gredsq / ggsav;
+                state = 30; break;
+            }
+        }
+        case 90: {
+            printState(90); // XXX
+            crvmin = ZERO;
+
+            // Prepare for the alternative iteration by calculating some scalars
+            // and by multiplying the reduced D by the second derivative matrix of
+            // Q, where S holds the reduced D in the call of GGMULT.
+
+        }
+        case 100: {
+            printState(100); // XXX
+            if (nact >= n - 1) {
+                state = 190; break;
+            }
+            dredsq = ZERO;
+            dredg = ZERO;
+            gredsq = ZERO;
+            for (int i = 0; i < n; i++) {
+                if (xbdi.getEntry(i) == ZERO) {
+                    // Computing 2nd power
+                    double d1 = trialStepPoint.getEntry(i);
+                    dredsq += d1 * d1;
+                    dredg += trialStepPoint.getEntry(i) * gnew.getEntry(i);
+                    // Computing 2nd power
+                    d1 = gnew.getEntry(i);
+                    gredsq += d1 * d1;
+                    s.setEntry(i, trialStepPoint.getEntry(i));
+                } else {
+                    s.setEntry(i, ZERO);
+                }
+            }
+            itcsav = iterc;
+            state = 210; break;
+            // Let the search direction S be a linear combination of the reduced D
+            // and the reduced G that is orthogonal to the reduced D.
+        }
+        case 120: {
+            printState(120); // XXX
+            ++iterc;
+            temp = gredsq * dredsq - dredg * dredg;
+            if (temp <= qred * 1e-4 * qred) {
+                state = 190; break;
+            }
+            temp = FastMath.sqrt(temp);
+            for (int i = 0; i < n; i++) {
+                if (xbdi.getEntry(i) == ZERO) {
+                    s.setEntry(i, (dredg * trialStepPoint.getEntry(i) - dredsq * gnew.getEntry(i)) / temp);
+                } else {
+                    s.setEntry(i, ZERO);
+                }
+            }
+            sredg = -temp;
+
+            // By considering the simple bounds on the variables, calculate an upper
+            // bound on the tangent of half the angle of the alternative iteration,
+            // namely ANGBD, except that, if already a free variable has reached a
+            // bound, there is a branch back to label 100 after fixing that variable.
+
+            angbd = ONE;
+            iact = -1;
+            for (int i = 0; i < n; i++) {
+                if (xbdi.getEntry(i) == ZERO) {
+                    tempa = trustRegionCenterOffset.getEntry(i) + trialStepPoint.getEntry(i) - lowerDifference.getEntry(i);
+                    tempb = upperDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i) - trialStepPoint.getEntry(i);
+                    if (tempa <= ZERO) {
+                        ++nact;
+                        xbdi.setEntry(i, MINUS_ONE);
+                        state = 100; break;
+                    } else if (tempb <= ZERO) {
+                        ++nact;
+                        xbdi.setEntry(i, ONE);
+                        state = 100; break;
+                    }
+                    // Computing 2nd power
+                    double d1 = trialStepPoint.getEntry(i);
+                    // Computing 2nd power
+                    double d2 = s.getEntry(i);
+                    ssq = d1 * d1 + d2 * d2;
+                    // Computing 2nd power
+                    d1 = trustRegionCenterOffset.getEntry(i) - lowerDifference.getEntry(i);
+                    temp = ssq - d1 * d1;
+                    if (temp > ZERO) {
+                        temp = FastMath.sqrt(temp) - s.getEntry(i);
+                        if (angbd * temp > tempa) {
+                            angbd = tempa / temp;
+                            iact = i;
+                            xsav = MINUS_ONE;
+                        }
+                    }
+                    // Computing 2nd power
+                    d1 = upperDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i);
+                    temp = ssq - d1 * d1;
+                    if (temp > ZERO) {
+                        temp = FastMath.sqrt(temp) + s.getEntry(i);
+                        if (angbd * temp > tempb) {
+                            angbd = tempb / temp;
+                            iact = i;
+                            xsav = ONE;
+                        }
+                    }
+                }
+            }
+
+            // Calculate HHD and some curvatures for the alternative iteration.
+
+            state = 210; break;
+        }
+        case 150: {
+            printState(150); // XXX
+            shs = ZERO;
+            dhs = ZERO;
+            dhd = ZERO;
+            for (int i = 0; i < n; i++) {
+                if (xbdi.getEntry(i) == ZERO) {
+                    shs += s.getEntry(i) * hs.getEntry(i);
+                    dhs += trialStepPoint.getEntry(i) * hs.getEntry(i);
+                    dhd += trialStepPoint.getEntry(i) * hred.getEntry(i);
+                }
+            }
+
+            // Seek the greatest reduction in Q for a range of equally spaced values
+            // of ANGT in [0,ANGBD], where ANGT is the tangent of half the angle of
+            // the alternative iteration.
+
+            redmax = ZERO;
+            isav = -1;
+            redsav = ZERO;
+            iu = (int) (angbd * 17. + 3.1);
+            for (int i = 0; i < iu; i++) {
+                angt = angbd * i / iu;
+                sth = (angt + angt) / (ONE + angt * angt);
+                temp = shs + angt * (angt * dhd - dhs - dhs);
+                rednew = sth * (angt * dredg - sredg - HALF * sth * temp);
+                if (rednew > redmax) {
+                    redmax = rednew;
+                    isav = i;
+                    rdprev = redsav;
+                } else if (i == isav + 1) {
+                    rdnext = rednew;
+                }
+                redsav = rednew;
+            }
+
+            // Return if the reduction is zero. Otherwise, set the sine and cosine
+            // of the angle of the alternative iteration, and calculate SDEC.
+
+            if (isav < 0) {
+                state = 190; break;
+            }
+            if (isav < iu) {
+                temp = (rdnext - rdprev) / (redmax + redmax - rdprev - rdnext);
+                angt = angbd * (isav + HALF * temp) / iu;
+            }
+            cth = (ONE - angt * angt) / (ONE + angt * angt);
+            sth = (angt + angt) / (ONE + angt * angt);
+            temp = shs + angt * (angt * dhd - dhs - dhs);
+            sdec = sth * (angt * dredg - sredg - HALF * sth * temp);
+            if (sdec <= ZERO) {
+                state = 190; break;
+            }
+
+            // Update GNEW, D and HRED. If the angle of the alternative iteration
+            // is restricted by a bound on a free variable, that variable is fixed
+            // at the bound.
+
+            dredg = ZERO;
+            gredsq = ZERO;
+            for (int i = 0; i < n; i++) {
+                gnew.setEntry(i, gnew.getEntry(i) + (cth - ONE) * hred.getEntry(i) + sth * hs.getEntry(i));
+                if (xbdi.getEntry(i) == ZERO) {
+                    trialStepPoint.setEntry(i, cth * trialStepPoint.getEntry(i) + sth * s.getEntry(i));
+                    dredg += trialStepPoint.getEntry(i) * gnew.getEntry(i);
+                    // Computing 2nd power
+                    final double d1 = gnew.getEntry(i);
+                    gredsq += d1 * d1;
+                }
+                hred.setEntry(i, cth * hred.getEntry(i) + sth * hs.getEntry(i));
+            }
+            qred += sdec;
+            if (iact >= 0 && isav == iu) {
+                ++nact;
+                xbdi.setEntry(iact, xsav);
+                state = 100; break;
+            }
+
+            // If SDEC is sufficiently small, then RETURN after setting XNEW to
+            // XOPT+D, giving careful attention to the bounds.
+
+            if (sdec > qred * .01) {
+                state = 120; break;
+            }
+        }
+        case 190: {
+            printState(190); // XXX
+            dsq = ZERO;
+            for (int i = 0; i < n; i++) {
+                // Computing MAX
+                // Computing MIN
+                final double min = FastMath.min(trustRegionCenterOffset.getEntry(i) + trialStepPoint.getEntry(i),
+                                            upperDifference.getEntry(i));
+                newPoint.setEntry(i, FastMath.max(min, lowerDifference.getEntry(i)));
+                if (xbdi.getEntry(i) == MINUS_ONE) {
+                    newPoint.setEntry(i, lowerDifference.getEntry(i));
+                }
+                if (xbdi.getEntry(i) == ONE) {
+                    newPoint.setEntry(i, upperDifference.getEntry(i));
+                }
+                trialStepPoint.setEntry(i, newPoint.getEntry(i) - trustRegionCenterOffset.getEntry(i));
+                // Computing 2nd power
+                final double d1 = trialStepPoint.getEntry(i);
+                dsq += d1 * d1;
+            }
+            return new double[] { dsq, crvmin };
+            // The following instructions multiply the current S-vector by the second
+            // derivative matrix of the quadratic model, putting the product in HS.
+            // They are reached from three different parts of the software above and
+            // they can be regarded as an external subroutine.
+        }
+        case 210: {
+            printState(210); // XXX
+            int ih = 0;
+            for (int j = 0; j < n; j++) {
+                hs.setEntry(j, ZERO);
+                for (int i = 0; i <= j; i++) {
+                    if (i < j) {
+                        hs.setEntry(j, hs.getEntry(j) + modelSecondDerivativesValues.getEntry(ih) * s.getEntry(i));
+                    }
+                    hs.setEntry(i, hs.getEntry(i) + modelSecondDerivativesValues.getEntry(ih) * s.getEntry(j));
+                    ih++;
+                }
+            }
+            final RealVector tmp = interpolationPoints.operate(s).ebeMultiply(modelSecondDerivativesParameters);
+            for (int k = 0; k < npt; k++) {
+                if (modelSecondDerivativesParameters.getEntry(k) != ZERO) {
+                    for (int i = 0; i < n; i++) {
+                        hs.setEntry(i, hs.getEntry(i) + tmp.getEntry(k) * interpolationPoints.getEntry(k, i));
+                    }
+                }
+            }
+            if (crvmin != ZERO) {
+                state = 50; break;
+            }
+            if (iterc > itcsav) {
+                state = 150; break;
+            }
+            for (int i = 0; i < n; i++) {
+                hred.setEntry(i, hs.getEntry(i));
+            }
+            state = 120; break;
+        }
+        default: {
+            throw new MathIllegalStateException(LocalizedFormats.SIMPLE_MESSAGE, "trsbox");
+        }}
+        }
+    } // trsbox
+
+    // ----------------------------------------------------------------------------------------
+
+    /**
+     *     The arrays BMAT and ZMAT are updated, as required by the new position
+     *     of the interpolation point that has the index KNEW. The vector VLAG has
+     *     N+NPT components, set on entry to the first NPT and last N components
+     *     of the product Hw in equation (4.11) of the Powell (2006) paper on
+     *     NEWUOA. Further, BETA is set on entry to the value of the parameter
+     *     with that name, and DENOM is set to the denominator of the updating
+     *     formula. Elements of ZMAT may be treated as zero if their moduli are
+     *     at most ZTEST. The first NDIM elements of W are used for working space.
+     * @param beta
+     * @param denom
+     * @param knew
+     */
+    private void update(
+            double beta,
+            double denom,
+            int knew
+    ) {
+        printMethod(); // XXX
+
+        final int n = currentBest.getDimension();
+        final int npt = numberOfInterpolationPoints;
+        final int nptm = npt - n - 1;
+
+        // XXX Should probably be split into two arrays.
+        final ArrayRealVector work = new ArrayRealVector(npt + n);
+
+        double ztest = ZERO;
+        for (int k = 0; k < npt; k++) {
+            for (int j = 0; j < nptm; j++) {
+                // Computing MAX
+                ztest = FastMath.max(ztest, FastMath.abs(zMatrix.getEntry(k, j)));
+            }
+        }
+        ztest *= 1e-20;
+
+        // Apply the rotations that put zeros in the KNEW-th row of ZMAT.
+
+        for (int j = 1; j < nptm; j++) {
+            final double d1 = zMatrix.getEntry(knew, j);
+            if (FastMath.abs(d1) > ztest) {
+                // Computing 2nd power
+                final double d2 = zMatrix.getEntry(knew, 0);
+                // Computing 2nd power
+                final double d3 = zMatrix.getEntry(knew, j);
+                final double d4 = FastMath.sqrt(d2 * d2 + d3 * d3);
+                final double d5 = zMatrix.getEntry(knew, 0) / d4;
+                final double d6 = zMatrix.getEntry(knew, j) / d4;
+                for (int i = 0; i < npt; i++) {
+                    final double d7 = d5 * zMatrix.getEntry(i, 0) + d6 * zMatrix.getEntry(i, j);
+                    zMatrix.setEntry(i, j, d5 * zMatrix.getEntry(i, j) - d6 * zMatrix.getEntry(i, 0));
+                    zMatrix.setEntry(i, 0, d7);
+                }
+            }
+            zMatrix.setEntry(knew, j, ZERO);
+        }
+
+        // Put the first NPT components of the KNEW-th column of HLAG into W,
+        // and calculate the parameters of the updating formula.
+
+        for (int i = 0; i < npt; i++) {
+            work.setEntry(i, zMatrix.getEntry(knew, 0) * zMatrix.getEntry(i, 0));
+        }
+        final double alpha = work.getEntry(knew);
+        final double tau = lagrangeValuesAtNewPoint.getEntry(knew);
+        lagrangeValuesAtNewPoint.setEntry(knew, lagrangeValuesAtNewPoint.getEntry(knew) - ONE);
+
+        // Complete the updating of ZMAT.
+
+        final double sqrtDenom = FastMath.sqrt(denom);
+        final double d1 = tau / sqrtDenom;
+        final double d2 = zMatrix.getEntry(knew, 0) / sqrtDenom;
+        for (int i = 0; i < npt; i++) {
+            zMatrix.setEntry(i, 0,
+                          d1 * zMatrix.getEntry(i, 0) - d2 * lagrangeValuesAtNewPoint.getEntry(i));
+        }
+
+        // Finally, update the matrix BMAT.
+
+        for (int j = 0; j < n; j++) {
+            final int jp = npt + j;
+            work.setEntry(jp, bMatrix.getEntry(knew, j));
+            final double d3 = (alpha * lagrangeValuesAtNewPoint.getEntry(jp) - tau * work.getEntry(jp)) / denom;
+            final double d4 = (-beta * work.getEntry(jp) - tau * lagrangeValuesAtNewPoint.getEntry(jp)) / denom;
+            for (int i = 0; i <= jp; i++) {
+                bMatrix.setEntry(i, j,
+                              bMatrix.getEntry(i, j) + d3 * lagrangeValuesAtNewPoint.getEntry(i) + d4 * work.getEntry(i));
+                if (i >= npt) {
+                    bMatrix.setEntry(jp, (i - npt), bMatrix.getEntry(i, j));
+                }
+            }
+        }
+    } // update
+
+    /**
+     * Performs validity checks.
+     *
+     * @param lowerBound Lower bounds (constraints) of the objective variables.
+     * @param upperBound Upperer bounds (constraints) of the objective variables.
+     */
+    private void setup(double[] lowerBound,
+                       double[] upperBound) {
+        printMethod(); // XXX
+
+        double[] init = getStartPoint();
+        final int dimension = init.length;
+
+        // Check problem dimension.
+        if (dimension < MINIMUM_PROBLEM_DIMENSION) {
+            throw new NumberIsTooSmallException(dimension, MINIMUM_PROBLEM_DIMENSION, true);
+        }
+        // Check number of interpolation points.
+        final int[] nPointsInterval = { dimension + 2, (dimension + 2) * (dimension + 1) / 2 };
+        if (numberOfInterpolationPoints < nPointsInterval[0] ||
+            numberOfInterpolationPoints > nPointsInterval[1]) {
+            throw new OutOfRangeException(LocalizedFormats.NUMBER_OF_INTERPOLATION_POINTS,
+                                          numberOfInterpolationPoints,
+                                          nPointsInterval[0],
+                                          nPointsInterval[1]);
+        }
+
+        // Initialize bound differences.
+        boundDifference = new double[dimension];
+
+        double requiredMinDiff = 2 * initialTrustRegionRadius;
+        double minDiff = Double.POSITIVE_INFINITY;
+        for (int i = 0; i < dimension; i++) {
+            boundDifference[i] = upperBound[i] - lowerBound[i];
+            minDiff = FastMath.min(minDiff, boundDifference[i]);
+        }
+        if (minDiff < requiredMinDiff) {
+            initialTrustRegionRadius = minDiff / 3.0;
+        }
+
+        // Initialize the data structures used by the "bobyqa" method.
+        bMatrix = new Array2DRowRealMatrix(dimension + numberOfInterpolationPoints,
+                                           dimension);
+        zMatrix = new Array2DRowRealMatrix(numberOfInterpolationPoints,
+                                           numberOfInterpolationPoints - dimension - 1);
+        interpolationPoints = new Array2DRowRealMatrix(numberOfInterpolationPoints,
+                                                       dimension);
+        originShift = new ArrayRealVector(dimension);
+        fAtInterpolationPoints = new ArrayRealVector(numberOfInterpolationPoints);
+        trustRegionCenterOffset = new ArrayRealVector(dimension);
+        gradientAtTrustRegionCenter = new ArrayRealVector(dimension);
+        lowerDifference = new ArrayRealVector(dimension);
+        upperDifference = new ArrayRealVector(dimension);
+        modelSecondDerivativesParameters = new ArrayRealVector(numberOfInterpolationPoints);
+        newPoint = new ArrayRealVector(dimension);
+        alternativeNewPoint = new ArrayRealVector(dimension);
+        trialStepPoint = new ArrayRealVector(dimension);
+        lagrangeValuesAtNewPoint = new ArrayRealVector(dimension + numberOfInterpolationPoints);
+        modelSecondDerivativesValues = new ArrayRealVector(dimension * (dimension + 1) / 2);
+    }
+
+    // XXX utility for figuring out call sequence.
+    private static String caller(int n) {
+        final Throwable t = new Throwable();
+        final StackTraceElement[] elements = t.getStackTrace();
+        final StackTraceElement e = elements[n];
+        return e.getMethodName() + " (at line " + e.getLineNumber() + ")";
+    }
+    // XXX utility for figuring out call sequence.
+    private static void printState(int s) {
+        //        System.out.println(caller(2) + ": state " + s);
+    }
+    // XXX utility for figuring out call sequence.
+    private static void printMethod() {
+        //        System.out.println(caller(2));
+    }
+
+    /**
+     * Marker for code paths that are not explored with the current unit tests.
+     * If the path becomes explored, it should just be removed from the code.
+     */
+    private static class PathIsExploredException extends RuntimeException {
+        /** Serializable UID. */
+        private static final long serialVersionUID = 745350979634801853L;
+
+        /** Message string. */
+        private static final String PATH_IS_EXPLORED
+            = "If this exception is thrown, just remove it from the code";
+
+        PathIsExploredException() {
+            super(PATH_IS_EXPLORED + " " + BOBYQAOptimizer.caller(3));
+        }
+    }
+}
+//CHECKSTYLE: resume all
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/CMAESOptimizer.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/CMAESOptimizer.java
new file mode 100644
index 0000000..13566be
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/CMAESOptimizer.java
@@ -0,0 +1,1354 @@
+/*
+ * 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.math3.optim.nonlinear.scalar.noderiv;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+import org.apache.commons.math3.linear.Array2DRowRealMatrix;
+import org.apache.commons.math3.linear.EigenDecomposition;
+import org.apache.commons.math3.linear.MatrixUtils;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.optim.ConvergenceChecker;
+import org.apache.commons.math3.optim.OptimizationData;
+import org.apache.commons.math3.optim.nonlinear.scalar.GoalType;
+import org.apache.commons.math3.optim.PointValuePair;
+import org.apache.commons.math3.optim.nonlinear.scalar.MultivariateOptimizer;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * An implementation of the active Covariance Matrix Adaptation Evolution Strategy (CMA-ES)
+ * for non-linear, non-convex, non-smooth, global function minimization.
+ * <p>
+ * The CMA-Evolution Strategy (CMA-ES) is a reliable stochastic optimization method
+ * which should be applied if derivative-based methods, e.g. quasi-Newton BFGS or
+ * conjugate gradient, fail due to a rugged search landscape (e.g. noise, local
+ * optima, outlier, etc.) of the objective function. Like a
+ * quasi-Newton method, the CMA-ES learns and applies a variable metric
+ * on the underlying search space. Unlike a quasi-Newton method, the
+ * CMA-ES neither estimates nor uses gradients, making it considerably more
+ * reliable in terms of finding a good, or even close to optimal, solution.
+ * <p>
+ * In general, on smooth objective functions the CMA-ES is roughly ten times
+ * slower than BFGS (counting objective function evaluations, no gradients provided).
+ * For up to <math>N=10</math> variables also the derivative-free simplex
+ * direct search method (Nelder and Mead) can be faster, but it is
+ * far less reliable than CMA-ES.
+ * <p>
+ * The CMA-ES is particularly well suited for non-separable
+ * and/or badly conditioned problems. To observe the advantage of CMA compared
+ * to a conventional evolution strategy, it will usually take about
+ * <math>30 N</math> function evaluations. On difficult problems the complete
+ * optimization (a single run) is expected to take <em>roughly</em> between
+ * <math>30 N</math> and <math>300 N<sup>2</sup></math>
+ * function evaluations.
+ * <p>
+ * This implementation is translated and adapted from the Matlab version
+ * of the CMA-ES algorithm as implemented in module {@code cmaes.m} version 3.51.
+ * <p>
+ * For more information, please refer to the following links:
+ * <ul>
+ *  <li><a href="http://www.lri.fr/~hansen/cmaes.m">Matlab code</a></li>
+ *  <li><a href="http://www.lri.fr/~hansen/cmaesintro.html">Introduction to CMA-ES</a></li>
+ *  <li><a href="http://en.wikipedia.org/wiki/CMA-ES">Wikipedia</a></li>
+ * </ul>
+ *
+ * @since 3.0
+ */
+public class CMAESOptimizer
+    extends MultivariateOptimizer {
+    // global search parameters
+    /**
+     * Population size, offspring number. The primary strategy parameter to play
+     * with, which can be increased from its default value. Increasing the
+     * population size improves global search properties in exchange to speed.
+     * Speed decreases, as a rule, at most linearly with increasing population
+     * size. It is advisable to begin with the default small population size.
+     */
+    private int lambda; // population size
+    /**
+     * Covariance update mechanism, default is active CMA. isActiveCMA = true
+     * turns on "active CMA" with a negative update of the covariance matrix and
+     * checks for positive definiteness. OPTS.CMA.active = 2 does not check for
+     * pos. def. and is numerically faster. Active CMA usually speeds up the
+     * adaptation.
+     */
+    private final boolean isActiveCMA;
+    /**
+     * Determines how often a new random offspring is generated in case it is
+     * not feasible / beyond the defined limits, default is 0.
+     */
+    private final int checkFeasableCount;
+    /**
+     * @see Sigma
+     */
+    private double[] inputSigma;
+    /** Number of objective variables/problem dimension */
+    private int dimension;
+    /**
+     * Defines the number of initial iterations, where the covariance matrix
+     * remains diagonal and the algorithm has internally linear time complexity.
+     * diagonalOnly = 1 means keeping the covariance matrix always diagonal and
+     * this setting also exhibits linear space complexity. This can be
+     * particularly useful for dimension > 100.
+     * @see <a href="http://hal.archives-ouvertes.fr/inria-00287367/en">A Simple Modification in CMA-ES</a>
+     */
+    private int diagonalOnly;
+    /** Number of objective variables/problem dimension */
+    private boolean isMinimize = true;
+    /** Indicates whether statistic data is collected. */
+    private final boolean generateStatistics;
+
+    // termination criteria
+    /** Maximal number of iterations allowed. */
+    private final int maxIterations;
+    /** Limit for fitness value. */
+    private final double stopFitness;
+    /** Stop if x-changes larger stopTolUpX. */
+    private double stopTolUpX;
+    /** Stop if x-change smaller stopTolX. */
+    private double stopTolX;
+    /** Stop if fun-changes smaller stopTolFun. */
+    private double stopTolFun;
+    /** Stop if back fun-changes smaller stopTolHistFun. */
+    private double stopTolHistFun;
+
+    // selection strategy parameters
+    /** Number of parents/points for recombination. */
+    private int mu; //
+    /** log(mu + 0.5), stored for efficiency. */
+    private double logMu2;
+    /** Array for weighted recombination. */
+    private RealMatrix weights;
+    /** Variance-effectiveness of sum w_i x_i. */
+    private double mueff; //
+
+    // dynamic strategy parameters and constants
+    /** Overall standard deviation - search volume. */
+    private double sigma;
+    /** Cumulation constant. */
+    private double cc;
+    /** Cumulation constant for step-size. */
+    private double cs;
+    /** Damping for step-size. */
+    private double damps;
+    /** Learning rate for rank-one update. */
+    private double ccov1;
+    /** Learning rate for rank-mu update' */
+    private double ccovmu;
+    /** Expectation of ||N(0,I)|| == norm(randn(N,1)). */
+    private double chiN;
+    /** Learning rate for rank-one update - diagonalOnly */
+    private double ccov1Sep;
+    /** Learning rate for rank-mu update - diagonalOnly */
+    private double ccovmuSep;
+
+    // CMA internal values - updated each generation
+    /** Objective variables. */
+    private RealMatrix xmean;
+    /** Evolution path. */
+    private RealMatrix pc;
+    /** Evolution path for sigma. */
+    private RealMatrix ps;
+    /** Norm of ps, stored for efficiency. */
+    private double normps;
+    /** Coordinate system. */
+    private RealMatrix B;
+    /** Scaling. */
+    private RealMatrix D;
+    /** B*D, stored for efficiency. */
+    private RealMatrix BD;
+    /** Diagonal of sqrt(D), stored for efficiency. */
+    private RealMatrix diagD;
+    /** Covariance matrix. */
+    private RealMatrix C;
+    /** Diagonal of C, used for diagonalOnly. */
+    private RealMatrix diagC;
+    /** Number of iterations already performed. */
+    private int iterations;
+
+    /** History queue of best values. */
+    private double[] fitnessHistory;
+    /** Size of history queue of best values. */
+    private int historySize;
+
+    /** Random generator. */
+    private final RandomGenerator random;
+
+    /** History of sigma values. */
+    private final List<Double> statisticsSigmaHistory = new ArrayList<Double>();
+    /** History of mean matrix. */
+    private final List<RealMatrix> statisticsMeanHistory = new ArrayList<RealMatrix>();
+    /** History of fitness values. */
+    private final List<Double> statisticsFitnessHistory = new ArrayList<Double>();
+    /** History of D matrix. */
+    private final List<RealMatrix> statisticsDHistory = new ArrayList<RealMatrix>();
+
+    /**
+     * @param maxIterations Maximal number of iterations.
+     * @param stopFitness Whether to stop if objective function value is smaller than
+     * {@code stopFitness}.
+     * @param isActiveCMA Chooses the covariance matrix update method.
+     * @param diagonalOnly Number of initial iterations, where the covariance matrix
+     * remains diagonal.
+     * @param checkFeasableCount Determines how often new random objective variables are
+     * generated in case they are out of bounds.
+     * @param random Random generator.
+     * @param generateStatistics Whether statistic data is collected.
+     * @param checker Convergence checker.
+     *
+     * @since 3.1
+     */
+    public CMAESOptimizer(int maxIterations,
+                          double stopFitness,
+                          boolean isActiveCMA,
+                          int diagonalOnly,
+                          int checkFeasableCount,
+                          RandomGenerator random,
+                          boolean generateStatistics,
+                          ConvergenceChecker<PointValuePair> checker) {
+        super(checker);
+        this.maxIterations = maxIterations;
+        this.stopFitness = stopFitness;
+        this.isActiveCMA = isActiveCMA;
+        this.diagonalOnly = diagonalOnly;
+        this.checkFeasableCount = checkFeasableCount;
+        this.random = random;
+        this.generateStatistics = generateStatistics;
+    }
+
+    /**
+     * @return History of sigma values.
+     */
+    public List<Double> getStatisticsSigmaHistory() {
+        return statisticsSigmaHistory;
+    }
+
+    /**
+     * @return History of mean matrix.
+     */
+    public List<RealMatrix> getStatisticsMeanHistory() {
+        return statisticsMeanHistory;
+    }
+
+    /**
+     * @return History of fitness values.
+     */
+    public List<Double> getStatisticsFitnessHistory() {
+        return statisticsFitnessHistory;
+    }
+
+    /**
+     * @return History of D matrix.
+     */
+    public List<RealMatrix> getStatisticsDHistory() {
+        return statisticsDHistory;
+    }
+
+    /**
+     * Input sigma values.
+     * They define the initial coordinate-wise standard deviations for
+     * sampling new search points around the initial guess.
+     * It is suggested to set them to the estimated distance from the
+     * initial to the desired optimum.
+     * Small values induce the search to be more local (and very small
+     * values are more likely to find a local optimum close to the initial
+     * guess).
+     * Too small values might however lead to early termination.
+     */
+    public static class Sigma implements OptimizationData {
+        /** Sigma values. */
+        private final double[] sigma;
+
+        /**
+         * @param s Sigma values.
+         * @throws NotPositiveException if any of the array entries is smaller
+         * than zero.
+         */
+        public Sigma(double[] s)
+            throws NotPositiveException {
+            for (int i = 0; i < s.length; i++) {
+                if (s[i] < 0) {
+                    throw new NotPositiveException(s[i]);
+                }
+            }
+
+            sigma = s.clone();
+        }
+
+        /**
+         * @return the sigma values.
+         */
+        public double[] getSigma() {
+            return sigma.clone();
+        }
+    }
+
+    /**
+     * Population size.
+     * The number of offspring is the primary strategy parameter.
+     * In the absence of better clues, a good default could be an
+     * integer close to {@code 4 + 3 ln(n)}, where {@code n} is the
+     * number of optimized parameters.
+     * Increasing the population size improves global search properties
+     * at the expense of speed (which in general decreases at most
+     * linearly with increasing population size).
+     */
+    public static class PopulationSize implements OptimizationData {
+        /** Population size. */
+        private final int lambda;
+
+        /**
+         * @param size Population size.
+         * @throws NotStrictlyPositiveException if {@code size <= 0}.
+         */
+        public PopulationSize(int size)
+            throws NotStrictlyPositiveException {
+            if (size <= 0) {
+                throw new NotStrictlyPositiveException(size);
+            }
+            lambda = size;
+        }
+
+        /**
+         * @return the population size.
+         */
+        public int getPopulationSize() {
+            return lambda;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @param optData Optimization data. In addition to those documented in
+     * {@link MultivariateOptimizer#parseOptimizationData(OptimizationData[])
+     * MultivariateOptimizer}, this method will register the following data:
+     * <ul>
+     *  <li>{@link Sigma}</li>
+     *  <li>{@link PopulationSize}</li>
+     * </ul>
+     * @return {@inheritDoc}
+     * @throws TooManyEvaluationsException if the maximal number of
+     * evaluations is exceeded.
+     * @throws DimensionMismatchException if the initial guess, target, and weight
+     * arguments have inconsistent dimensions.
+     */
+    @Override
+    public PointValuePair optimize(OptimizationData... optData)
+        throws TooManyEvaluationsException,
+               DimensionMismatchException {
+        // Set up base class and perform computation.
+        return super.optimize(optData);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected PointValuePair doOptimize() {
+         // -------------------- Initialization --------------------------------
+        isMinimize = getGoalType().equals(GoalType.MINIMIZE);
+        final FitnessFunction fitfun = new FitnessFunction();
+        final double[] guess = getStartPoint();
+        // number of objective variables/problem dimension
+        dimension = guess.length;
+        initializeCMA(guess);
+        iterations = 0;
+        ValuePenaltyPair valuePenalty = fitfun.value(guess);
+        double bestValue = valuePenalty.value+valuePenalty.penalty;
+        push(fitnessHistory, bestValue);
+        PointValuePair optimum
+            = new PointValuePair(getStartPoint(),
+                                 isMinimize ? bestValue : -bestValue);
+        PointValuePair lastResult = null;
+
+        // -------------------- Generation Loop --------------------------------
+
+        generationLoop:
+        for (iterations = 1; iterations <= maxIterations; iterations++) {
+            incrementIterationCount();
+
+            // Generate and evaluate lambda offspring
+            final RealMatrix arz = randn1(dimension, lambda);
+            final RealMatrix arx = zeros(dimension, lambda);
+            final double[] fitness = new double[lambda];
+            final ValuePenaltyPair[] valuePenaltyPairs = new ValuePenaltyPair[lambda];
+            // generate random offspring
+            for (int k = 0; k < lambda; k++) {
+                RealMatrix arxk = null;
+                for (int i = 0; i < checkFeasableCount + 1; i++) {
+                    if (diagonalOnly <= 0) {
+                        arxk = xmean.add(BD.multiply(arz.getColumnMatrix(k))
+                                         .scalarMultiply(sigma)); // m + sig * Normal(0,C)
+                    } else {
+                        arxk = xmean.add(times(diagD,arz.getColumnMatrix(k))
+                                         .scalarMultiply(sigma));
+                    }
+                    if (i >= checkFeasableCount ||
+                        fitfun.isFeasible(arxk.getColumn(0))) {
+                        break;
+                    }
+                    // regenerate random arguments for row
+                    arz.setColumn(k, randn(dimension));
+                }
+                copyColumn(arxk, 0, arx, k);
+                try {
+                    valuePenaltyPairs[k] = fitfun.value(arx.getColumn(k)); // compute fitness
+                } catch (TooManyEvaluationsException e) {
+                    break generationLoop;
+                }
+            }
+
+            // Compute fitnesses by adding value and penalty after scaling by value range.
+            double valueRange = valueRange(valuePenaltyPairs);
+            for (int iValue=0;iValue<valuePenaltyPairs.length;iValue++) {
+                 fitness[iValue] = valuePenaltyPairs[iValue].value + valuePenaltyPairs[iValue].penalty*valueRange;
+            }
+
+            // Sort by fitness and compute weighted mean into xmean
+            final int[] arindex = sortedIndices(fitness);
+            // Calculate new xmean, this is selection and recombination
+            final RealMatrix xold = xmean; // for speed up of Eq. (2) and (3)
+            final RealMatrix bestArx = selectColumns(arx, MathArrays.copyOf(arindex, mu));
+            xmean = bestArx.multiply(weights);
+            final RealMatrix bestArz = selectColumns(arz, MathArrays.copyOf(arindex, mu));
+            final RealMatrix zmean = bestArz.multiply(weights);
+            final boolean hsig = updateEvolutionPaths(zmean, xold);
+            if (diagonalOnly <= 0) {
+                updateCovariance(hsig, bestArx, arz, arindex, xold);
+            } else {
+                updateCovarianceDiagonalOnly(hsig, bestArz);
+            }
+            // Adapt step size sigma - Eq. (5)
+            sigma *= FastMath.exp(FastMath.min(1, (normps/chiN - 1) * cs / damps));
+            final double bestFitness = fitness[arindex[0]];
+            final double worstFitness = fitness[arindex[arindex.length - 1]];
+            if (bestValue > bestFitness) {
+                bestValue = bestFitness;
+                lastResult = optimum;
+                optimum = new PointValuePair(fitfun.repair(bestArx.getColumn(0)),
+                                             isMinimize ? bestFitness : -bestFitness);
+                if (getConvergenceChecker() != null && lastResult != null &&
+                    getConvergenceChecker().converged(iterations, optimum, lastResult)) {
+                    break generationLoop;
+                }
+            }
+            // handle termination criteria
+            // Break, if fitness is good enough
+            if (stopFitness != 0 && bestFitness < (isMinimize ? stopFitness : -stopFitness)) {
+                break generationLoop;
+            }
+            final double[] sqrtDiagC = sqrt(diagC).getColumn(0);
+            final double[] pcCol = pc.getColumn(0);
+            for (int i = 0; i < dimension; i++) {
+                if (sigma * FastMath.max(FastMath.abs(pcCol[i]), sqrtDiagC[i]) > stopTolX) {
+                    break;
+                }
+                if (i >= dimension - 1) {
+                    break generationLoop;
+                }
+            }
+            for (int i = 0; i < dimension; i++) {
+                if (sigma * sqrtDiagC[i] > stopTolUpX) {
+                    break generationLoop;
+                }
+            }
+            final double historyBest = min(fitnessHistory);
+            final double historyWorst = max(fitnessHistory);
+            if (iterations > 2 &&
+                FastMath.max(historyWorst, worstFitness) -
+                FastMath.min(historyBest, bestFitness) < stopTolFun) {
+                break generationLoop;
+            }
+            if (iterations > fitnessHistory.length &&
+                historyWorst - historyBest < stopTolHistFun) {
+                break generationLoop;
+            }
+            // condition number of the covariance matrix exceeds 1e14
+            if (max(diagD) / min(diagD) > 1e7) {
+                break generationLoop;
+            }
+            // user defined termination
+            if (getConvergenceChecker() != null) {
+                final PointValuePair current
+                    = new PointValuePair(bestArx.getColumn(0),
+                                         isMinimize ? bestFitness : -bestFitness);
+                if (lastResult != null &&
+                    getConvergenceChecker().converged(iterations, current, lastResult)) {
+                    break generationLoop;
+                    }
+                lastResult = current;
+            }
+            // Adjust step size in case of equal function values (flat fitness)
+            if (bestValue == fitness[arindex[(int)(0.1+lambda/4.)]]) {
+                sigma *= FastMath.exp(0.2 + cs / damps);
+            }
+            if (iterations > 2 && FastMath.max(historyWorst, bestFitness) -
+                FastMath.min(historyBest, bestFitness) == 0) {
+                sigma *= FastMath.exp(0.2 + cs / damps);
+            }
+            // store best in history
+            push(fitnessHistory,bestFitness);
+            if (generateStatistics) {
+                statisticsSigmaHistory.add(sigma);
+                statisticsFitnessHistory.add(bestFitness);
+                statisticsMeanHistory.add(xmean.transpose());
+                statisticsDHistory.add(diagD.transpose().scalarMultiply(1E5));
+            }
+        }
+        return optimum;
+    }
+
+    /**
+     * Scans the list of (required and optional) optimization data that
+     * characterize the problem.
+     *
+     * @param optData Optimization data. The following data will be looked for:
+     * <ul>
+     *  <li>{@link Sigma}</li>
+     *  <li>{@link PopulationSize}</li>
+     * </ul>
+     */
+    @Override
+    protected void parseOptimizationData(OptimizationData... optData) {
+        // Allow base class to register its own data.
+        super.parseOptimizationData(optData);
+
+        // The existing values (as set by the previous call) are reused if
+        // not provided in the argument list.
+        for (OptimizationData data : optData) {
+            if (data instanceof Sigma) {
+                inputSigma = ((Sigma) data).getSigma();
+                continue;
+            }
+            if (data instanceof PopulationSize) {
+                lambda = ((PopulationSize) data).getPopulationSize();
+                continue;
+            }
+        }
+
+        checkParameters();
+    }
+
+    /**
+     * Checks dimensions and values of boundaries and inputSigma if defined.
+     */
+    private void checkParameters() {
+        final double[] init = getStartPoint();
+        final double[] lB = getLowerBound();
+        final double[] uB = getUpperBound();
+
+        if (inputSigma != null) {
+            if (inputSigma.length != init.length) {
+                throw new DimensionMismatchException(inputSigma.length, init.length);
+            }
+            for (int i = 0; i < init.length; i++) {
+                if (inputSigma[i] > uB[i] - lB[i]) {
+                    throw new OutOfRangeException(inputSigma[i], 0, uB[i] - lB[i]);
+                }
+            }
+        }
+    }
+
+    /**
+     * Initialization of the dynamic search parameters
+     *
+     * @param guess Initial guess for the arguments of the fitness function.
+     */
+    private void initializeCMA(double[] guess) {
+        if (lambda <= 0) {
+            throw new NotStrictlyPositiveException(lambda);
+        }
+        // initialize sigma
+        final double[][] sigmaArray = new double[guess.length][1];
+        for (int i = 0; i < guess.length; i++) {
+            sigmaArray[i][0] = inputSigma[i];
+        }
+        final RealMatrix insigma = new Array2DRowRealMatrix(sigmaArray, false);
+        sigma = max(insigma); // overall standard deviation
+
+        // initialize termination criteria
+        stopTolUpX = 1e3 * max(insigma);
+        stopTolX = 1e-11 * max(insigma);
+        stopTolFun = 1e-12;
+        stopTolHistFun = 1e-13;
+
+        // initialize selection strategy parameters
+        mu = lambda / 2; // number of parents/points for recombination
+        logMu2 = FastMath.log(mu + 0.5);
+        weights = log(sequence(1, mu, 1)).scalarMultiply(-1).scalarAdd(logMu2);
+        double sumw = 0;
+        double sumwq = 0;
+        for (int i = 0; i < mu; i++) {
+            double w = weights.getEntry(i, 0);
+            sumw += w;
+            sumwq += w * w;
+        }
+        weights = weights.scalarMultiply(1 / sumw);
+        mueff = sumw * sumw / sumwq; // variance-effectiveness of sum w_i x_i
+
+        // initialize dynamic strategy parameters and constants
+        cc = (4 + mueff / dimension) /
+                (dimension + 4 + 2 * mueff / dimension);
+        cs = (mueff + 2) / (dimension + mueff + 3.);
+        damps = (1 + 2 * FastMath.max(0, FastMath.sqrt((mueff - 1) /
+                                                       (dimension + 1)) - 1)) *
+            FastMath.max(0.3,
+                         1 - dimension / (1e-6 + maxIterations)) + cs; // minor increment
+        ccov1 = 2 / ((dimension + 1.3) * (dimension + 1.3) + mueff);
+        ccovmu = FastMath.min(1 - ccov1, 2 * (mueff - 2 + 1 / mueff) /
+                              ((dimension + 2) * (dimension + 2) + mueff));
+        ccov1Sep = FastMath.min(1, ccov1 * (dimension + 1.5) / 3);
+        ccovmuSep = FastMath.min(1 - ccov1, ccovmu * (dimension + 1.5) / 3);
+        chiN = FastMath.sqrt(dimension) *
+                (1 - 1 / ((double) 4 * dimension) + 1 / ((double) 21 * dimension * dimension));
+        // intialize CMA internal values - updated each generation
+        xmean = MatrixUtils.createColumnRealMatrix(guess); // objective variables
+        diagD = insigma.scalarMultiply(1 / sigma);
+        diagC = square(diagD);
+        pc = zeros(dimension, 1); // evolution paths for C and sigma
+        ps = zeros(dimension, 1); // B defines the coordinate system
+        normps = ps.getFrobeniusNorm();
+
+        B = eye(dimension, dimension);
+        D = ones(dimension, 1); // diagonal D defines the scaling
+        BD = times(B, repmat(diagD.transpose(), dimension, 1));
+        C = B.multiply(diag(square(D)).multiply(B.transpose())); // covariance
+        historySize = 10 + (int) (3 * 10 * dimension / (double) lambda);
+        fitnessHistory = new double[historySize]; // history of fitness values
+        for (int i = 0; i < historySize; i++) {
+            fitnessHistory[i] = Double.MAX_VALUE;
+        }
+    }
+
+    /**
+     * Update of the evolution paths ps and pc.
+     *
+     * @param zmean Weighted row matrix of the gaussian random numbers generating
+     * the current offspring.
+     * @param xold xmean matrix of the previous generation.
+     * @return hsig flag indicating a small correction.
+     */
+    private boolean updateEvolutionPaths(RealMatrix zmean, RealMatrix xold) {
+        ps = ps.scalarMultiply(1 - cs).add(
+                B.multiply(zmean).scalarMultiply(
+                        FastMath.sqrt(cs * (2 - cs) * mueff)));
+        normps = ps.getFrobeniusNorm();
+        final boolean hsig = normps /
+            FastMath.sqrt(1 - FastMath.pow(1 - cs, 2 * iterations)) /
+            chiN < 1.4 + 2 / ((double) dimension + 1);
+        pc = pc.scalarMultiply(1 - cc);
+        if (hsig) {
+            pc = pc.add(xmean.subtract(xold).scalarMultiply(FastMath.sqrt(cc * (2 - cc) * mueff) / sigma));
+        }
+        return hsig;
+    }
+
+    /**
+     * Update of the covariance matrix C for diagonalOnly > 0
+     *
+     * @param hsig Flag indicating a small correction.
+     * @param bestArz Fitness-sorted matrix of the gaussian random values of the
+     * current offspring.
+     */
+    private void updateCovarianceDiagonalOnly(boolean hsig,
+                                              final RealMatrix bestArz) {
+        // minor correction if hsig==false
+        double oldFac = hsig ? 0 : ccov1Sep * cc * (2 - cc);
+        oldFac += 1 - ccov1Sep - ccovmuSep;
+        diagC = diagC.scalarMultiply(oldFac) // regard old matrix
+            .add(square(pc).scalarMultiply(ccov1Sep)) // plus rank one update
+            .add((times(diagC, square(bestArz).multiply(weights))) // plus rank mu update
+                 .scalarMultiply(ccovmuSep));
+        diagD = sqrt(diagC); // replaces eig(C)
+        if (diagonalOnly > 1 &&
+            iterations > diagonalOnly) {
+            // full covariance matrix from now on
+            diagonalOnly = 0;
+            B = eye(dimension, dimension);
+            BD = diag(diagD);
+            C = diag(diagC);
+        }
+    }
+
+    /**
+     * Update of the covariance matrix C.
+     *
+     * @param hsig Flag indicating a small correction.
+     * @param bestArx Fitness-sorted matrix of the argument vectors producing the
+     * current offspring.
+     * @param arz Unsorted matrix containing the gaussian random values of the
+     * current offspring.
+     * @param arindex Indices indicating the fitness-order of the current offspring.
+     * @param xold xmean matrix of the previous generation.
+     */
+    private void updateCovariance(boolean hsig, final RealMatrix bestArx,
+                                  final RealMatrix arz, final int[] arindex,
+                                  final RealMatrix xold) {
+        double negccov = 0;
+        if (ccov1 + ccovmu > 0) {
+            final RealMatrix arpos = bestArx.subtract(repmat(xold, 1, mu))
+                .scalarMultiply(1 / sigma); // mu difference vectors
+            final RealMatrix roneu = pc.multiply(pc.transpose())
+                .scalarMultiply(ccov1); // rank one update
+            // minor correction if hsig==false
+            double oldFac = hsig ? 0 : ccov1 * cc * (2 - cc);
+            oldFac += 1 - ccov1 - ccovmu;
+            if (isActiveCMA) {
+                // Adapt covariance matrix C active CMA
+                negccov = (1 - ccovmu) * 0.25 * mueff /
+                    (FastMath.pow(dimension + 2, 1.5) + 2 * mueff);
+                // keep at least 0.66 in all directions, small popsize are most
+                // critical
+                final double negminresidualvariance = 0.66;
+                // where to make up for the variance loss
+                final double negalphaold = 0.5;
+                // prepare vectors, compute negative updating matrix Cneg
+                final int[] arReverseIndex = reverse(arindex);
+                RealMatrix arzneg = selectColumns(arz, MathArrays.copyOf(arReverseIndex, mu));
+                RealMatrix arnorms = sqrt(sumRows(square(arzneg)));
+                final int[] idxnorms = sortedIndices(arnorms.getRow(0));
+                final RealMatrix arnormsSorted = selectColumns(arnorms, idxnorms);
+                final int[] idxReverse = reverse(idxnorms);
+                final RealMatrix arnormsReverse = selectColumns(arnorms, idxReverse);
+                arnorms = divide(arnormsReverse, arnormsSorted);
+                final int[] idxInv = inverse(idxnorms);
+                final RealMatrix arnormsInv = selectColumns(arnorms, idxInv);
+                // check and set learning rate negccov
+                final double negcovMax = (1 - negminresidualvariance) /
+                    square(arnormsInv).multiply(weights).getEntry(0, 0);
+                if (negccov > negcovMax) {
+                    negccov = negcovMax;
+                }
+                arzneg = times(arzneg, repmat(arnormsInv, dimension, 1));
+                final RealMatrix artmp = BD.multiply(arzneg);
+                final RealMatrix Cneg = artmp.multiply(diag(weights)).multiply(artmp.transpose());
+                oldFac += negalphaold * negccov;
+                C = C.scalarMultiply(oldFac)
+                    .add(roneu) // regard old matrix
+                    .add(arpos.scalarMultiply( // plus rank one update
+                                              ccovmu + (1 - negalphaold) * negccov) // plus rank mu update
+                         .multiply(times(repmat(weights, 1, dimension),
+                                         arpos.transpose())))
+                    .subtract(Cneg.scalarMultiply(negccov));
+            } else {
+                // Adapt covariance matrix C - nonactive
+                C = C.scalarMultiply(oldFac) // regard old matrix
+                    .add(roneu) // plus rank one update
+                    .add(arpos.scalarMultiply(ccovmu) // plus rank mu update
+                         .multiply(times(repmat(weights, 1, dimension),
+                                         arpos.transpose())));
+            }
+        }
+        updateBD(negccov);
+    }
+
+    /**
+     * Update B and D from C.
+     *
+     * @param negccov Negative covariance factor.
+     */
+    private void updateBD(double negccov) {
+        if (ccov1 + ccovmu + negccov > 0 &&
+            (iterations % 1. / (ccov1 + ccovmu + negccov) / dimension / 10.) < 1) {
+            // to achieve O(N^2)
+            C = triu(C, 0).add(triu(C, 1).transpose());
+            // enforce symmetry to prevent complex numbers
+            final EigenDecomposition eig = new EigenDecomposition(C);
+            B = eig.getV(); // eigen decomposition, B==normalized eigenvectors
+            D = eig.getD();
+            diagD = diag(D);
+            if (min(diagD) <= 0) {
+                for (int i = 0; i < dimension; i++) {
+                    if (diagD.getEntry(i, 0) < 0) {
+                        diagD.setEntry(i, 0, 0);
+                    }
+                }
+                final double tfac = max(diagD) / 1e14;
+                C = C.add(eye(dimension, dimension).scalarMultiply(tfac));
+                diagD = diagD.add(ones(dimension, 1).scalarMultiply(tfac));
+            }
+            if (max(diagD) > 1e14 * min(diagD)) {
+                final double tfac = max(diagD) / 1e14 - min(diagD);
+                C = C.add(eye(dimension, dimension).scalarMultiply(tfac));
+                diagD = diagD.add(ones(dimension, 1).scalarMultiply(tfac));
+            }
+            diagC = diag(C);
+            diagD = sqrt(diagD); // D contains standard deviations now
+            BD = times(B, repmat(diagD.transpose(), dimension, 1)); // O(n^2)
+        }
+    }
+
+    /**
+     * Pushes the current best fitness value in a history queue.
+     *
+     * @param vals History queue.
+     * @param val Current best fitness value.
+     */
+    private static void push(double[] vals, double val) {
+        for (int i = vals.length-1; i > 0; i--) {
+            vals[i] = vals[i-1];
+        }
+        vals[0] = val;
+    }
+
+    /**
+     * Sorts fitness values.
+     *
+     * @param doubles Array of values to be sorted.
+     * @return a sorted array of indices pointing into doubles.
+     */
+    private int[] sortedIndices(final double[] doubles) {
+        final DoubleIndex[] dis = new DoubleIndex[doubles.length];
+        for (int i = 0; i < doubles.length; i++) {
+            dis[i] = new DoubleIndex(doubles[i], i);
+        }
+        Arrays.sort(dis);
+        final int[] indices = new int[doubles.length];
+        for (int i = 0; i < doubles.length; i++) {
+            indices[i] = dis[i].index;
+        }
+        return indices;
+    }
+   /**
+     * Get range of values.
+     *
+     * @param vpPairs Array of valuePenaltyPairs to get range from.
+     * @return a double equal to maximum value minus minimum value.
+     */
+    private double valueRange(final ValuePenaltyPair[] vpPairs) {
+        double max = Double.NEGATIVE_INFINITY;
+        double min = Double.MAX_VALUE;
+        for (ValuePenaltyPair vpPair:vpPairs) {
+            if (vpPair.value > max) {
+                max = vpPair.value;
+            }
+            if (vpPair.value < min) {
+                min = vpPair.value;
+            }
+        }
+        return max-min;
+    }
+
+    /**
+     * Used to sort fitness values. Sorting is always in lower value first
+     * order.
+     */
+    private static class DoubleIndex implements Comparable<DoubleIndex> {
+        /** Value to compare. */
+        private final double value;
+        /** Index into sorted array. */
+        private final int index;
+
+        /**
+         * @param value Value to compare.
+         * @param index Index into sorted array.
+         */
+        DoubleIndex(double value, int index) {
+            this.value = value;
+            this.index = index;
+        }
+
+        /** {@inheritDoc} */
+        public int compareTo(DoubleIndex o) {
+            return Double.compare(value, o.value);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public boolean equals(Object other) {
+
+            if (this == other) {
+                return true;
+            }
+
+            if (other instanceof DoubleIndex) {
+                return Double.compare(value, ((DoubleIndex) other).value) == 0;
+            }
+
+            return false;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public int hashCode() {
+            long bits = Double.doubleToLongBits(value);
+            return (int) ((1438542 ^ (bits >>> 32) ^ bits) & 0xffffffff);
+        }
+    }
+    /**
+     * Stores the value and penalty (for repair of out of bounds point).
+     */
+    private static class ValuePenaltyPair {
+        /** Objective function value. */
+        private double value;
+        /** Penalty value for repair of out out of bounds points. */
+        private double penalty;
+
+        /**
+         * @param value Function value.
+         * @param penalty Out-of-bounds penalty.
+        */
+        ValuePenaltyPair(final double value, final double penalty) {
+            this.value   = value;
+            this.penalty = penalty;
+        }
+    }
+
+
+    /**
+     * Normalizes fitness values to the range [0,1]. Adds a penalty to the
+     * fitness value if out of range.
+     */
+    private class FitnessFunction {
+        /**
+         * Flag indicating whether the objective variables are forced into their
+         * bounds if defined
+         */
+        private final boolean isRepairMode;
+
+        /** Simple constructor.
+         */
+        FitnessFunction() {
+            isRepairMode = true;
+        }
+
+        /**
+         * @param point Normalized objective variables.
+         * @return the objective value + penalty for violated bounds.
+         */
+        public ValuePenaltyPair value(final double[] point) {
+            double value;
+            double penalty=0.0;
+            if (isRepairMode) {
+                double[] repaired = repair(point);
+                value = CMAESOptimizer.this.computeObjectiveValue(repaired);
+                penalty =  penalty(point, repaired);
+            } else {
+                value = CMAESOptimizer.this.computeObjectiveValue(point);
+            }
+            value = isMinimize ? value : -value;
+            penalty = isMinimize ? penalty : -penalty;
+            return new ValuePenaltyPair(value,penalty);
+        }
+
+        /**
+         * @param x Normalized objective variables.
+         * @return {@code true} if in bounds.
+         */
+        public boolean isFeasible(final double[] x) {
+            final double[] lB = CMAESOptimizer.this.getLowerBound();
+            final double[] uB = CMAESOptimizer.this.getUpperBound();
+
+            for (int i = 0; i < x.length; i++) {
+                if (x[i] < lB[i]) {
+                    return false;
+                }
+                if (x[i] > uB[i]) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        /**
+         * @param x Normalized objective variables.
+         * @return the repaired (i.e. all in bounds) objective variables.
+         */
+        private double[] repair(final double[] x) {
+            final double[] lB = CMAESOptimizer.this.getLowerBound();
+            final double[] uB = CMAESOptimizer.this.getUpperBound();
+
+            final double[] repaired = new double[x.length];
+            for (int i = 0; i < x.length; i++) {
+                if (x[i] < lB[i]) {
+                    repaired[i] = lB[i];
+                } else if (x[i] > uB[i]) {
+                    repaired[i] = uB[i];
+                } else {
+                    repaired[i] = x[i];
+                }
+            }
+            return repaired;
+        }
+
+        /**
+         * @param x Normalized objective variables.
+         * @param repaired Repaired objective variables.
+         * @return Penalty value according to the violation of the bounds.
+         */
+        private double penalty(final double[] x, final double[] repaired) {
+            double penalty = 0;
+            for (int i = 0; i < x.length; i++) {
+                double diff = FastMath.abs(x[i] - repaired[i]);
+                penalty += diff;
+            }
+            return isMinimize ? penalty : -penalty;
+        }
+    }
+
+    // -----Matrix utility functions similar to the Matlab build in functions------
+
+    /**
+     * @param m Input matrix
+     * @return Matrix representing the element-wise logarithm of m.
+     */
+    private static RealMatrix log(final RealMatrix m) {
+        final double[][] d = new double[m.getRowDimension()][m.getColumnDimension()];
+        for (int r = 0; r < m.getRowDimension(); r++) {
+            for (int c = 0; c < m.getColumnDimension(); c++) {
+                d[r][c] = FastMath.log(m.getEntry(r, c));
+            }
+        }
+        return new Array2DRowRealMatrix(d, false);
+    }
+
+    /**
+     * @param m Input matrix.
+     * @return Matrix representing the element-wise square root of m.
+     */
+    private static RealMatrix sqrt(final RealMatrix m) {
+        final double[][] d = new double[m.getRowDimension()][m.getColumnDimension()];
+        for (int r = 0; r < m.getRowDimension(); r++) {
+            for (int c = 0; c < m.getColumnDimension(); c++) {
+                d[r][c] = FastMath.sqrt(m.getEntry(r, c));
+            }
+        }
+        return new Array2DRowRealMatrix(d, false);
+    }
+
+    /**
+     * @param m Input matrix.
+     * @return Matrix representing the element-wise square of m.
+     */
+    private static RealMatrix square(final RealMatrix m) {
+        final double[][] d = new double[m.getRowDimension()][m.getColumnDimension()];
+        for (int r = 0; r < m.getRowDimension(); r++) {
+            for (int c = 0; c < m.getColumnDimension(); c++) {
+                double e = m.getEntry(r, c);
+                d[r][c] = e * e;
+            }
+        }
+        return new Array2DRowRealMatrix(d, false);
+    }
+
+    /**
+     * @param m Input matrix 1.
+     * @param n Input matrix 2.
+     * @return the matrix where the elements of m and n are element-wise multiplied.
+     */
+    private static RealMatrix times(final RealMatrix m, final RealMatrix n) {
+        final double[][] d = new double[m.getRowDimension()][m.getColumnDimension()];
+        for (int r = 0; r < m.getRowDimension(); r++) {
+            for (int c = 0; c < m.getColumnDimension(); c++) {
+                d[r][c] = m.getEntry(r, c) * n.getEntry(r, c);
+            }
+        }
+        return new Array2DRowRealMatrix(d, false);
+    }
+
+    /**
+     * @param m Input matrix 1.
+     * @param n Input matrix 2.
+     * @return Matrix where the elements of m and n are element-wise divided.
+     */
+    private static RealMatrix divide(final RealMatrix m, final RealMatrix n) {
+        final double[][] d = new double[m.getRowDimension()][m.getColumnDimension()];
+        for (int r = 0; r < m.getRowDimension(); r++) {
+            for (int c = 0; c < m.getColumnDimension(); c++) {
+                d[r][c] = m.getEntry(r, c) / n.getEntry(r, c);
+            }
+        }
+        return new Array2DRowRealMatrix(d, false);
+    }
+
+    /**
+     * @param m Input matrix.
+     * @param cols Columns to select.
+     * @return Matrix representing the selected columns.
+     */
+    private static RealMatrix selectColumns(final RealMatrix m, final int[] cols) {
+        final double[][] d = new double[m.getRowDimension()][cols.length];
+        for (int r = 0; r < m.getRowDimension(); r++) {
+            for (int c = 0; c < cols.length; c++) {
+                d[r][c] = m.getEntry(r, cols[c]);
+            }
+        }
+        return new Array2DRowRealMatrix(d, false);
+    }
+
+    /**
+     * @param m Input matrix.
+     * @param k Diagonal position.
+     * @return Upper triangular part of matrix.
+     */
+    private static RealMatrix triu(final RealMatrix m, int k) {
+        final double[][] d = new double[m.getRowDimension()][m.getColumnDimension()];
+        for (int r = 0; r < m.getRowDimension(); r++) {
+            for (int c = 0; c < m.getColumnDimension(); c++) {
+                d[r][c] = r <= c - k ? m.getEntry(r, c) : 0;
+            }
+        }
+        return new Array2DRowRealMatrix(d, false);
+    }
+
+    /**
+     * @param m Input matrix.
+     * @return Row matrix representing the sums of the rows.
+     */
+    private static RealMatrix sumRows(final RealMatrix m) {
+        final double[][] d = new double[1][m.getColumnDimension()];
+        for (int c = 0; c < m.getColumnDimension(); c++) {
+            double sum = 0;
+            for (int r = 0; r < m.getRowDimension(); r++) {
+                sum += m.getEntry(r, c);
+            }
+            d[0][c] = sum;
+        }
+        return new Array2DRowRealMatrix(d, false);
+    }
+
+    /**
+     * @param m Input matrix.
+     * @return the diagonal n-by-n matrix if m is a column matrix or the column
+     * matrix representing the diagonal if m is a n-by-n matrix.
+     */
+    private static RealMatrix diag(final RealMatrix m) {
+        if (m.getColumnDimension() == 1) {
+            final double[][] d = new double[m.getRowDimension()][m.getRowDimension()];
+            for (int i = 0; i < m.getRowDimension(); i++) {
+                d[i][i] = m.getEntry(i, 0);
+            }
+            return new Array2DRowRealMatrix(d, false);
+        } else {
+            final double[][] d = new double[m.getRowDimension()][1];
+            for (int i = 0; i < m.getColumnDimension(); i++) {
+                d[i][0] = m.getEntry(i, i);
+            }
+            return new Array2DRowRealMatrix(d, false);
+        }
+    }
+
+    /**
+     * Copies a column from m1 to m2.
+     *
+     * @param m1 Source matrix.
+     * @param col1 Source column.
+     * @param m2 Target matrix.
+     * @param col2 Target column.
+     */
+    private static void copyColumn(final RealMatrix m1, int col1,
+                                   RealMatrix m2, int col2) {
+        for (int i = 0; i < m1.getRowDimension(); i++) {
+            m2.setEntry(i, col2, m1.getEntry(i, col1));
+        }
+    }
+
+    /**
+     * @param n Number of rows.
+     * @param m Number of columns.
+     * @return n-by-m matrix filled with 1.
+     */
+    private static RealMatrix ones(int n, int m) {
+        final double[][] d = new double[n][m];
+        for (int r = 0; r < n; r++) {
+            Arrays.fill(d[r], 1);
+        }
+        return new Array2DRowRealMatrix(d, false);
+    }
+
+    /**
+     * @param n Number of rows.
+     * @param m Number of columns.
+     * @return n-by-m matrix of 0 values out of diagonal, and 1 values on
+     * the diagonal.
+     */
+    private static RealMatrix eye(int n, int m) {
+        final double[][] d = new double[n][m];
+        for (int r = 0; r < n; r++) {
+            if (r < m) {
+                d[r][r] = 1;
+            }
+        }
+        return new Array2DRowRealMatrix(d, false);
+    }
+
+    /**
+     * @param n Number of rows.
+     * @param m Number of columns.
+     * @return n-by-m matrix of zero values.
+     */
+    private static RealMatrix zeros(int n, int m) {
+        return new Array2DRowRealMatrix(n, m);
+    }
+
+    /**
+     * @param mat Input matrix.
+     * @param n Number of row replicates.
+     * @param m Number of column replicates.
+     * @return a matrix which replicates the input matrix in both directions.
+     */
+    private static RealMatrix repmat(final RealMatrix mat, int n, int m) {
+        final int rd = mat.getRowDimension();
+        final int cd = mat.getColumnDimension();
+        final double[][] d = new double[n * rd][m * cd];
+        for (int r = 0; r < n * rd; r++) {
+            for (int c = 0; c < m * cd; c++) {
+                d[r][c] = mat.getEntry(r % rd, c % cd);
+            }
+        }
+        return new Array2DRowRealMatrix(d, false);
+    }
+
+    /**
+     * @param start Start value.
+     * @param end End value.
+     * @param step Step size.
+     * @return a sequence as column matrix.
+     */
+    private static RealMatrix sequence(double start, double end, double step) {
+        final int size = (int) ((end - start) / step + 1);
+        final double[][] d = new double[size][1];
+        double value = start;
+        for (int r = 0; r < size; r++) {
+            d[r][0] = value;
+            value += step;
+        }
+        return new Array2DRowRealMatrix(d, false);
+    }
+
+    /**
+     * @param m Input matrix.
+     * @return the maximum of the matrix element values.
+     */
+    private static double max(final RealMatrix m) {
+        double max = -Double.MAX_VALUE;
+        for (int r = 0; r < m.getRowDimension(); r++) {
+            for (int c = 0; c < m.getColumnDimension(); c++) {
+                double e = m.getEntry(r, c);
+                if (max < e) {
+                    max = e;
+                }
+            }
+        }
+        return max;
+    }
+
+    /**
+     * @param m Input matrix.
+     * @return the minimum of the matrix element values.
+     */
+    private static double min(final RealMatrix m) {
+        double min = Double.MAX_VALUE;
+        for (int r = 0; r < m.getRowDimension(); r++) {
+            for (int c = 0; c < m.getColumnDimension(); c++) {
+                double e = m.getEntry(r, c);
+                if (min > e) {
+                    min = e;
+                }
+            }
+        }
+        return min;
+    }
+
+    /**
+     * @param m Input array.
+     * @return the maximum of the array values.
+     */
+    private static double max(final double[] m) {
+        double max = -Double.MAX_VALUE;
+        for (int r = 0; r < m.length; r++) {
+            if (max < m[r]) {
+                max = m[r];
+            }
+        }
+        return max;
+    }
+
+    /**
+     * @param m Input array.
+     * @return the minimum of the array values.
+     */
+    private static double min(final double[] m) {
+        double min = Double.MAX_VALUE;
+        for (int r = 0; r < m.length; r++) {
+            if (min > m[r]) {
+                min = m[r];
+            }
+        }
+        return min;
+    }
+
+    /**
+     * @param indices Input index array.
+     * @return the inverse of the mapping defined by indices.
+     */
+    private static int[] inverse(final int[] indices) {
+        final int[] inverse = new int[indices.length];
+        for (int i = 0; i < indices.length; i++) {
+            inverse[indices[i]] = i;
+        }
+        return inverse;
+    }
+
+    /**
+     * @param indices Input index array.
+     * @return the indices in inverse order (last is first).
+     */
+    private static int[] reverse(final int[] indices) {
+        final int[] reverse = new int[indices.length];
+        for (int i = 0; i < indices.length; i++) {
+            reverse[i] = indices[indices.length - i - 1];
+        }
+        return reverse;
+    }
+
+    /**
+     * @param size Length of random array.
+     * @return an array of Gaussian random numbers.
+     */
+    private double[] randn(int size) {
+        final double[] randn = new double[size];
+        for (int i = 0; i < size; i++) {
+            randn[i] = random.nextGaussian();
+        }
+        return randn;
+    }
+
+    /**
+     * @param size Number of rows.
+     * @param popSize Population size.
+     * @return a 2-dimensional matrix of Gaussian random numbers.
+     */
+    private RealMatrix randn1(int size, int popSize) {
+        final double[][] d = new double[size][popSize];
+        for (int r = 0; r < size; r++) {
+            for (int c = 0; c < popSize; c++) {
+                d[r][c] = random.nextGaussian();
+            }
+        }
+        return new Array2DRowRealMatrix(d, false);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/MultiDirectionalSimplex.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/MultiDirectionalSimplex.java
new file mode 100644
index 0000000..7ee3acf
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/MultiDirectionalSimplex.java
@@ -0,0 +1,215 @@
+/*
+ * 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.math3.optim.nonlinear.scalar.noderiv;
+
+import java.util.Comparator;
+
+import org.apache.commons.math3.analysis.MultivariateFunction;
+import org.apache.commons.math3.optim.PointValuePair;
+
+/**
+ * This class implements the multi-directional direct search method.
+ *
+ * @since 3.0
+ */
+public class MultiDirectionalSimplex extends AbstractSimplex {
+    /** Default value for {@link #khi}: {@value}. */
+    private static final double DEFAULT_KHI = 2;
+    /** Default value for {@link #gamma}: {@value}. */
+    private static final double DEFAULT_GAMMA = 0.5;
+    /** Expansion coefficient. */
+    private final double khi;
+    /** Contraction coefficient. */
+    private final double gamma;
+
+    /**
+     * Build a multi-directional simplex with default coefficients.
+     * The default values are 2.0 for khi and 0.5 for gamma.
+     *
+     * @param n Dimension of the simplex.
+     */
+    public MultiDirectionalSimplex(final int n) {
+        this(n, 1d);
+    }
+
+    /**
+     * Build a multi-directional simplex with default coefficients.
+     * The default values are 2.0 for khi and 0.5 for gamma.
+     *
+     * @param n Dimension of the simplex.
+     * @param sideLength Length of the sides of the default (hypercube)
+     * simplex. See {@link AbstractSimplex#AbstractSimplex(int,double)}.
+     */
+    public MultiDirectionalSimplex(final int n, double sideLength) {
+        this(n, sideLength, DEFAULT_KHI, DEFAULT_GAMMA);
+    }
+
+    /**
+     * Build a multi-directional simplex with specified coefficients.
+     *
+     * @param n Dimension of the simplex. See
+     * {@link AbstractSimplex#AbstractSimplex(int,double)}.
+     * @param khi Expansion coefficient.
+     * @param gamma Contraction coefficient.
+     */
+    public MultiDirectionalSimplex(final int n,
+                                   final double khi, final double gamma) {
+        this(n, 1d, khi, gamma);
+    }
+
+    /**
+     * Build a multi-directional simplex with specified coefficients.
+     *
+     * @param n Dimension of the simplex. See
+     * {@link AbstractSimplex#AbstractSimplex(int,double)}.
+     * @param sideLength Length of the sides of the default (hypercube)
+     * simplex. See {@link AbstractSimplex#AbstractSimplex(int,double)}.
+     * @param khi Expansion coefficient.
+     * @param gamma Contraction coefficient.
+     */
+    public MultiDirectionalSimplex(final int n, double sideLength,
+                                   final double khi, final double gamma) {
+        super(n, sideLength);
+
+        this.khi   = khi;
+        this.gamma = gamma;
+    }
+
+    /**
+     * Build a multi-directional simplex with default coefficients.
+     * The default values are 2.0 for khi and 0.5 for gamma.
+     *
+     * @param steps Steps along the canonical axes representing box edges.
+     * They may be negative but not zero. See
+     */
+    public MultiDirectionalSimplex(final double[] steps) {
+        this(steps, DEFAULT_KHI, DEFAULT_GAMMA);
+    }
+
+    /**
+     * Build a multi-directional simplex with specified coefficients.
+     *
+     * @param steps Steps along the canonical axes representing box edges.
+     * They may be negative but not zero. See
+     * {@link AbstractSimplex#AbstractSimplex(double[])}.
+     * @param khi Expansion coefficient.
+     * @param gamma Contraction coefficient.
+     */
+    public MultiDirectionalSimplex(final double[] steps,
+                                   final double khi, final double gamma) {
+        super(steps);
+
+        this.khi   = khi;
+        this.gamma = gamma;
+    }
+
+    /**
+     * Build a multi-directional simplex with default coefficients.
+     * The default values are 2.0 for khi and 0.5 for gamma.
+     *
+     * @param referenceSimplex Reference simplex. See
+     * {@link AbstractSimplex#AbstractSimplex(double[][])}.
+     */
+    public MultiDirectionalSimplex(final double[][] referenceSimplex) {
+        this(referenceSimplex, DEFAULT_KHI, DEFAULT_GAMMA);
+    }
+
+    /**
+     * Build a multi-directional simplex with specified coefficients.
+     *
+     * @param referenceSimplex Reference simplex. See
+     * {@link AbstractSimplex#AbstractSimplex(double[][])}.
+     * @param khi Expansion coefficient.
+     * @param gamma Contraction coefficient.
+     * @throws org.apache.commons.math3.exception.NotStrictlyPositiveException
+     * if the reference simplex does not contain at least one point.
+     * @throws org.apache.commons.math3.exception.DimensionMismatchException
+     * if there is a dimension mismatch in the reference simplex.
+     */
+    public MultiDirectionalSimplex(final double[][] referenceSimplex,
+                                   final double khi, final double gamma) {
+        super(referenceSimplex);
+
+        this.khi   = khi;
+        this.gamma = gamma;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void iterate(final MultivariateFunction evaluationFunction,
+                        final Comparator<PointValuePair> comparator) {
+        // Save the original simplex.
+        final PointValuePair[] original = getPoints();
+        final PointValuePair best = original[0];
+
+        // Perform a reflection step.
+        final PointValuePair reflected = evaluateNewSimplex(evaluationFunction,
+                                                                original, 1, comparator);
+        if (comparator.compare(reflected, best) < 0) {
+            // Compute the expanded simplex.
+            final PointValuePair[] reflectedSimplex = getPoints();
+            final PointValuePair expanded = evaluateNewSimplex(evaluationFunction,
+                                                                   original, khi, comparator);
+            if (comparator.compare(reflected, expanded) <= 0) {
+                // Keep the reflected simplex.
+                setPoints(reflectedSimplex);
+            }
+            // Keep the expanded simplex.
+            return;
+        }
+
+        // Compute the contracted simplex.
+        evaluateNewSimplex(evaluationFunction, original, gamma, comparator);
+
+    }
+
+    /**
+     * Compute and evaluate a new simplex.
+     *
+     * @param evaluationFunction Evaluation function.
+     * @param original Original simplex (to be preserved).
+     * @param coeff Linear coefficient.
+     * @param comparator Comparator to use to sort simplex vertices from best
+     * to poorest.
+     * @return the best point in the transformed simplex.
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException
+     * if the maximal number of evaluations is exceeded.
+     */
+    private PointValuePair evaluateNewSimplex(final MultivariateFunction evaluationFunction,
+                                                  final PointValuePair[] original,
+                                                  final double coeff,
+                                                  final Comparator<PointValuePair> comparator) {
+        final double[] xSmallest = original[0].getPointRef();
+        // Perform a linear transformation on all the simplex points,
+        // except the first one.
+        setPoint(0, original[0]);
+        final int dim = getDimension();
+        for (int i = 1; i < getSize(); i++) {
+            final double[] xOriginal = original[i].getPointRef();
+            final double[] xTransformed = new double[dim];
+            for (int j = 0; j < dim; j++) {
+                xTransformed[j] = xSmallest[j] + coeff * (xSmallest[j] - xOriginal[j]);
+            }
+            setPoint(i, new PointValuePair(xTransformed, Double.NaN, false));
+        }
+
+        // Evaluate the simplex.
+        evaluate(evaluationFunction, comparator);
+
+        return getPoint(0);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/NelderMeadSimplex.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/NelderMeadSimplex.java
new file mode 100644
index 0000000..f7015ed
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/NelderMeadSimplex.java
@@ -0,0 +1,280 @@
+/*
+ * 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.math3.optim.nonlinear.scalar.noderiv;
+
+import java.util.Comparator;
+
+import org.apache.commons.math3.optim.PointValuePair;
+import org.apache.commons.math3.analysis.MultivariateFunction;
+
+/**
+ * This class implements the Nelder-Mead simplex algorithm.
+ *
+ * @since 3.0
+ */
+public class NelderMeadSimplex extends AbstractSimplex {
+    /** Default value for {@link #rho}: {@value}. */
+    private static final double DEFAULT_RHO = 1;
+    /** Default value for {@link #khi}: {@value}. */
+    private static final double DEFAULT_KHI = 2;
+    /** Default value for {@link #gamma}: {@value}. */
+    private static final double DEFAULT_GAMMA = 0.5;
+    /** Default value for {@link #sigma}: {@value}. */
+    private static final double DEFAULT_SIGMA = 0.5;
+    /** Reflection coefficient. */
+    private final double rho;
+    /** Expansion coefficient. */
+    private final double khi;
+    /** Contraction coefficient. */
+    private final double gamma;
+    /** Shrinkage coefficient. */
+    private final double sigma;
+
+    /**
+     * Build a Nelder-Mead simplex with default coefficients.
+     * The default coefficients are 1.0 for rho, 2.0 for khi and 0.5
+     * for both gamma and sigma.
+     *
+     * @param n Dimension of the simplex.
+     */
+    public NelderMeadSimplex(final int n) {
+        this(n, 1d);
+    }
+
+    /**
+     * Build a Nelder-Mead simplex with default coefficients.
+     * The default coefficients are 1.0 for rho, 2.0 for khi and 0.5
+     * for both gamma and sigma.
+     *
+     * @param n Dimension of the simplex.
+     * @param sideLength Length of the sides of the default (hypercube)
+     * simplex. See {@link AbstractSimplex#AbstractSimplex(int,double)}.
+     */
+    public NelderMeadSimplex(final int n, double sideLength) {
+        this(n, sideLength,
+             DEFAULT_RHO, DEFAULT_KHI, DEFAULT_GAMMA, DEFAULT_SIGMA);
+    }
+
+    /**
+     * Build a Nelder-Mead simplex with specified coefficients.
+     *
+     * @param n Dimension of the simplex. See
+     * {@link AbstractSimplex#AbstractSimplex(int,double)}.
+     * @param sideLength Length of the sides of the default (hypercube)
+     * simplex. See {@link AbstractSimplex#AbstractSimplex(int,double)}.
+     * @param rho Reflection coefficient.
+     * @param khi Expansion coefficient.
+     * @param gamma Contraction coefficient.
+     * @param sigma Shrinkage coefficient.
+     */
+    public NelderMeadSimplex(final int n, double sideLength,
+                             final double rho, final double khi,
+                             final double gamma, final double sigma) {
+        super(n, sideLength);
+
+        this.rho = rho;
+        this.khi = khi;
+        this.gamma = gamma;
+        this.sigma = sigma;
+    }
+
+    /**
+     * Build a Nelder-Mead simplex with specified coefficients.
+     *
+     * @param n Dimension of the simplex. See
+     * {@link AbstractSimplex#AbstractSimplex(int)}.
+     * @param rho Reflection coefficient.
+     * @param khi Expansion coefficient.
+     * @param gamma Contraction coefficient.
+     * @param sigma Shrinkage coefficient.
+     */
+    public NelderMeadSimplex(final int n,
+                             final double rho, final double khi,
+                             final double gamma, final double sigma) {
+        this(n, 1d, rho, khi, gamma, sigma);
+    }
+
+    /**
+     * Build a Nelder-Mead simplex with default coefficients.
+     * The default coefficients are 1.0 for rho, 2.0 for khi and 0.5
+     * for both gamma and sigma.
+     *
+     * @param steps Steps along the canonical axes representing box edges.
+     * They may be negative but not zero. See
+     */
+    public NelderMeadSimplex(final double[] steps) {
+        this(steps, DEFAULT_RHO, DEFAULT_KHI, DEFAULT_GAMMA, DEFAULT_SIGMA);
+    }
+
+    /**
+     * Build a Nelder-Mead simplex with specified coefficients.
+     *
+     * @param steps Steps along the canonical axes representing box edges.
+     * They may be negative but not zero. See
+     * {@link AbstractSimplex#AbstractSimplex(double[])}.
+     * @param rho Reflection coefficient.
+     * @param khi Expansion coefficient.
+     * @param gamma Contraction coefficient.
+     * @param sigma Shrinkage coefficient.
+     * @throws IllegalArgumentException if one of the steps is zero.
+     */
+    public NelderMeadSimplex(final double[] steps,
+                             final double rho, final double khi,
+                             final double gamma, final double sigma) {
+        super(steps);
+
+        this.rho = rho;
+        this.khi = khi;
+        this.gamma = gamma;
+        this.sigma = sigma;
+    }
+
+    /**
+     * Build a Nelder-Mead simplex with default coefficients.
+     * The default coefficients are 1.0 for rho, 2.0 for khi and 0.5
+     * for both gamma and sigma.
+     *
+     * @param referenceSimplex Reference simplex. See
+     * {@link AbstractSimplex#AbstractSimplex(double[][])}.
+     */
+    public NelderMeadSimplex(final double[][] referenceSimplex) {
+        this(referenceSimplex, DEFAULT_RHO, DEFAULT_KHI, DEFAULT_GAMMA, DEFAULT_SIGMA);
+    }
+
+    /**
+     * Build a Nelder-Mead simplex with specified coefficients.
+     *
+     * @param referenceSimplex Reference simplex. See
+     * {@link AbstractSimplex#AbstractSimplex(double[][])}.
+     * @param rho Reflection coefficient.
+     * @param khi Expansion coefficient.
+     * @param gamma Contraction coefficient.
+     * @param sigma Shrinkage coefficient.
+     * @throws org.apache.commons.math3.exception.NotStrictlyPositiveException
+     * if the reference simplex does not contain at least one point.
+     * @throws org.apache.commons.math3.exception.DimensionMismatchException
+     * if there is a dimension mismatch in the reference simplex.
+     */
+    public NelderMeadSimplex(final double[][] referenceSimplex,
+                             final double rho, final double khi,
+                             final double gamma, final double sigma) {
+        super(referenceSimplex);
+
+        this.rho = rho;
+        this.khi = khi;
+        this.gamma = gamma;
+        this.sigma = sigma;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void iterate(final MultivariateFunction evaluationFunction,
+                        final Comparator<PointValuePair> comparator) {
+        // The simplex has n + 1 points if dimension is n.
+        final int n = getDimension();
+
+        // Interesting values.
+        final PointValuePair best = getPoint(0);
+        final PointValuePair secondBest = getPoint(n - 1);
+        final PointValuePair worst = getPoint(n);
+        final double[] xWorst = worst.getPointRef();
+
+        // Compute the centroid of the best vertices (dismissing the worst
+        // point at index n).
+        final double[] centroid = new double[n];
+        for (int i = 0; i < n; i++) {
+            final double[] x = getPoint(i).getPointRef();
+            for (int j = 0; j < n; j++) {
+                centroid[j] += x[j];
+            }
+        }
+        final double scaling = 1.0 / n;
+        for (int j = 0; j < n; j++) {
+            centroid[j] *= scaling;
+        }
+
+        // compute the reflection point
+        final double[] xR = new double[n];
+        for (int j = 0; j < n; j++) {
+            xR[j] = centroid[j] + rho * (centroid[j] - xWorst[j]);
+        }
+        final PointValuePair reflected
+            = new PointValuePair(xR, evaluationFunction.value(xR), false);
+
+        if (comparator.compare(best, reflected) <= 0 &&
+            comparator.compare(reflected, secondBest) < 0) {
+            // Accept the reflected point.
+            replaceWorstPoint(reflected, comparator);
+        } else if (comparator.compare(reflected, best) < 0) {
+            // Compute the expansion point.
+            final double[] xE = new double[n];
+            for (int j = 0; j < n; j++) {
+                xE[j] = centroid[j] + khi * (xR[j] - centroid[j]);
+            }
+            final PointValuePair expanded
+                = new PointValuePair(xE, evaluationFunction.value(xE), false);
+
+            if (comparator.compare(expanded, reflected) < 0) {
+                // Accept the expansion point.
+                replaceWorstPoint(expanded, comparator);
+            } else {
+                // Accept the reflected point.
+                replaceWorstPoint(reflected, comparator);
+            }
+        } else {
+            if (comparator.compare(reflected, worst) < 0) {
+                // Perform an outside contraction.
+                final double[] xC = new double[n];
+                for (int j = 0; j < n; j++) {
+                    xC[j] = centroid[j] + gamma * (xR[j] - centroid[j]);
+                }
+                final PointValuePair outContracted
+                    = new PointValuePair(xC, evaluationFunction.value(xC), false);
+                if (comparator.compare(outContracted, reflected) <= 0) {
+                    // Accept the contraction point.
+                    replaceWorstPoint(outContracted, comparator);
+                    return;
+                }
+            } else {
+                // Perform an inside contraction.
+                final double[] xC = new double[n];
+                for (int j = 0; j < n; j++) {
+                    xC[j] = centroid[j] - gamma * (centroid[j] - xWorst[j]);
+                }
+                final PointValuePair inContracted
+                    = new PointValuePair(xC, evaluationFunction.value(xC), false);
+
+                if (comparator.compare(inContracted, worst) < 0) {
+                    // Accept the contraction point.
+                    replaceWorstPoint(inContracted, comparator);
+                    return;
+                }
+            }
+
+            // Perform a shrink.
+            final double[] xSmallest = getPoint(0).getPointRef();
+            for (int i = 1; i <= n; i++) {
+                final double[] x = getPoint(i).getPoint();
+                for (int j = 0; j < n; j++) {
+                    x[j] = xSmallest[j] + sigma * (x[j] - xSmallest[j]);
+                }
+                setPoint(i, new PointValuePair(x, Double.NaN, false));
+            }
+            evaluate(evaluationFunction, comparator);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/PowellOptimizer.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/PowellOptimizer.java
new file mode 100644
index 0000000..afa8426
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/PowellOptimizer.java
@@ -0,0 +1,299 @@
+/*
+ * 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.math3.optim.nonlinear.scalar.noderiv;
+
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.MathUnsupportedOperationException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.optim.nonlinear.scalar.GoalType;
+import org.apache.commons.math3.optim.PointValuePair;
+import org.apache.commons.math3.optim.ConvergenceChecker;
+import org.apache.commons.math3.optim.nonlinear.scalar.MultivariateOptimizer;
+import org.apache.commons.math3.optim.nonlinear.scalar.LineSearch;
+import org.apache.commons.math3.optim.univariate.UnivariatePointValuePair;
+
+/**
+ * Powell's algorithm.
+ * This code is translated and adapted from the Python version of this
+ * algorithm (as implemented in module {@code optimize.py} v0.5 of
+ * <em>SciPy</em>).
+ * <br/>
+ * The default stopping criterion is based on the differences of the
+ * function value between two successive iterations. It is however possible
+ * to define a custom convergence checker that might terminate the algorithm
+ * earlier.
+ * <br/>
+ * Line search is performed by the {@link LineSearch} class.
+ * <br/>
+ * Constraints are not supported: the call to
+ * {@link #optimize(OptimizationData[]) optimize} will throw
+ * {@link MathUnsupportedOperationException} if bounds are passed to it.
+ * In order to impose simple constraints, the objective function must be
+ * wrapped in an adapter like
+ * {@link org.apache.commons.math3.optim.nonlinear.scalar.MultivariateFunctionMappingAdapter
+ * MultivariateFunctionMappingAdapter} or
+ * {@link org.apache.commons.math3.optim.nonlinear.scalar.MultivariateFunctionPenaltyAdapter
+ * MultivariateFunctionPenaltyAdapter}.
+ *
+ * @since 2.2
+ */
+public class PowellOptimizer
+    extends MultivariateOptimizer {
+    /**
+     * Minimum relative tolerance.
+     */
+    private static final double MIN_RELATIVE_TOLERANCE = 2 * FastMath.ulp(1d);
+    /**
+     * Relative threshold.
+     */
+    private final double relativeThreshold;
+    /**
+     * Absolute threshold.
+     */
+    private final double absoluteThreshold;
+    /**
+     * Line search.
+     */
+    private final LineSearch line;
+
+    /**
+     * This constructor allows to specify a user-defined convergence checker,
+     * in addition to the parameters that control the default convergence
+     * checking procedure.
+     * <br/>
+     * The internal line search tolerances are set to the square-root of their
+     * corresponding value in the multivariate optimizer.
+     *
+     * @param rel Relative threshold.
+     * @param abs Absolute threshold.
+     * @param checker Convergence checker.
+     * @throws NotStrictlyPositiveException if {@code abs <= 0}.
+     * @throws NumberIsTooSmallException if {@code rel < 2 * Math.ulp(1d)}.
+     */
+    public PowellOptimizer(double rel,
+                           double abs,
+                           ConvergenceChecker<PointValuePair> checker) {
+        this(rel, abs, FastMath.sqrt(rel), FastMath.sqrt(abs), checker);
+    }
+
+    /**
+     * This constructor allows to specify a user-defined convergence checker,
+     * in addition to the parameters that control the default convergence
+     * checking procedure and the line search tolerances.
+     *
+     * @param rel Relative threshold for this optimizer.
+     * @param abs Absolute threshold for this optimizer.
+     * @param lineRel Relative threshold for the internal line search optimizer.
+     * @param lineAbs Absolute threshold for the internal line search optimizer.
+     * @param checker Convergence checker.
+     * @throws NotStrictlyPositiveException if {@code abs <= 0}.
+     * @throws NumberIsTooSmallException if {@code rel < 2 * Math.ulp(1d)}.
+     */
+    public PowellOptimizer(double rel,
+                           double abs,
+                           double lineRel,
+                           double lineAbs,
+                           ConvergenceChecker<PointValuePair> checker) {
+        super(checker);
+
+        if (rel < MIN_RELATIVE_TOLERANCE) {
+            throw new NumberIsTooSmallException(rel, MIN_RELATIVE_TOLERANCE, true);
+        }
+        if (abs <= 0) {
+            throw new NotStrictlyPositiveException(abs);
+        }
+        relativeThreshold = rel;
+        absoluteThreshold = abs;
+
+        // Create the line search optimizer.
+        line = new LineSearch(this,
+                              lineRel,
+                              lineAbs,
+                              1d);
+    }
+
+    /**
+     * The parameters control the default convergence checking procedure.
+     * <br/>
+     * The internal line search tolerances are set to the square-root of their
+     * corresponding value in the multivariate optimizer.
+     *
+     * @param rel Relative threshold.
+     * @param abs Absolute threshold.
+     * @throws NotStrictlyPositiveException if {@code abs <= 0}.
+     * @throws NumberIsTooSmallException if {@code rel < 2 * Math.ulp(1d)}.
+     */
+    public PowellOptimizer(double rel,
+                           double abs) {
+        this(rel, abs, null);
+    }
+
+    /**
+     * Builds an instance with the default convergence checking procedure.
+     *
+     * @param rel Relative threshold.
+     * @param abs Absolute threshold.
+     * @param lineRel Relative threshold for the internal line search optimizer.
+     * @param lineAbs Absolute threshold for the internal line search optimizer.
+     * @throws NotStrictlyPositiveException if {@code abs <= 0}.
+     * @throws NumberIsTooSmallException if {@code rel < 2 * Math.ulp(1d)}.
+     */
+    public PowellOptimizer(double rel,
+                           double abs,
+                           double lineRel,
+                           double lineAbs) {
+        this(rel, abs, lineRel, lineAbs, null);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected PointValuePair doOptimize() {
+        checkParameters();
+
+        final GoalType goal = getGoalType();
+        final double[] guess = getStartPoint();
+        final int n = guess.length;
+
+        final double[][] direc = new double[n][n];
+        for (int i = 0; i < n; i++) {
+            direc[i][i] = 1;
+        }
+
+        final ConvergenceChecker<PointValuePair> checker
+            = getConvergenceChecker();
+
+        double[] x = guess;
+        double fVal = computeObjectiveValue(x);
+        double[] x1 = x.clone();
+        while (true) {
+            incrementIterationCount();
+
+            double fX = fVal;
+            double fX2 = 0;
+            double delta = 0;
+            int bigInd = 0;
+            double alphaMin = 0;
+
+            for (int i = 0; i < n; i++) {
+                final double[] d = MathArrays.copyOf(direc[i]);
+
+                fX2 = fVal;
+
+                final UnivariatePointValuePair optimum = line.search(x, d);
+                fVal = optimum.getValue();
+                alphaMin = optimum.getPoint();
+                final double[][] result = newPointAndDirection(x, d, alphaMin);
+                x = result[0];
+
+                if ((fX2 - fVal) > delta) {
+                    delta = fX2 - fVal;
+                    bigInd = i;
+                }
+            }
+
+            // Default convergence check.
+            boolean stop = 2 * (fX - fVal) <=
+                (relativeThreshold * (FastMath.abs(fX) + FastMath.abs(fVal)) +
+                 absoluteThreshold);
+
+            final PointValuePair previous = new PointValuePair(x1, fX);
+            final PointValuePair current = new PointValuePair(x, fVal);
+            if (!stop && checker != null) { // User-defined stopping criteria.
+                stop = checker.converged(getIterations(), previous, current);
+            }
+            if (stop) {
+                if (goal == GoalType.MINIMIZE) {
+                    return (fVal < fX) ? current : previous;
+                } else {
+                    return (fVal > fX) ? current : previous;
+                }
+            }
+
+            final double[] d = new double[n];
+            final double[] x2 = new double[n];
+            for (int i = 0; i < n; i++) {
+                d[i] = x[i] - x1[i];
+                x2[i] = 2 * x[i] - x1[i];
+            }
+
+            x1 = x.clone();
+            fX2 = computeObjectiveValue(x2);
+
+            if (fX > fX2) {
+                double t = 2 * (fX + fX2 - 2 * fVal);
+                double temp = fX - fVal - delta;
+                t *= temp * temp;
+                temp = fX - fX2;
+                t -= delta * temp * temp;
+
+                if (t < 0.0) {
+                    final UnivariatePointValuePair optimum = line.search(x, d);
+                    fVal = optimum.getValue();
+                    alphaMin = optimum.getPoint();
+                    final double[][] result = newPointAndDirection(x, d, alphaMin);
+                    x = result[0];
+
+                    final int lastInd = n - 1;
+                    direc[bigInd] = direc[lastInd];
+                    direc[lastInd] = result[1];
+                }
+            }
+        }
+    }
+
+    /**
+     * Compute a new point (in the original space) and a new direction
+     * vector, resulting from the line search.
+     *
+     * @param p Point used in the line search.
+     * @param d Direction used in the line search.
+     * @param optimum Optimum found by the line search.
+     * @return a 2-element array containing the new point (at index 0) and
+     * the new direction (at index 1).
+     */
+    private double[][] newPointAndDirection(double[] p,
+                                            double[] d,
+                                            double optimum) {
+        final int n = p.length;
+        final double[] nP = new double[n];
+        final double[] nD = new double[n];
+        for (int i = 0; i < n; i++) {
+            nD[i] = d[i] * optimum;
+            nP[i] = p[i] + nD[i];
+        }
+
+        final double[][] result = new double[2][];
+        result[0] = nP;
+        result[1] = nD;
+
+        return result;
+    }
+
+    /**
+     * @throws MathUnsupportedOperationException if bounds were passed to the
+     * {@link #optimize(OptimizationData[]) optimize} method.
+     */
+    private void checkParameters() {
+        if (getLowerBound() != null ||
+            getUpperBound() != null) {
+            throw new MathUnsupportedOperationException(LocalizedFormats.CONSTRAINT);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/SimplexOptimizer.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/SimplexOptimizer.java
new file mode 100644
index 0000000..4bb6b64
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/SimplexOptimizer.java
@@ -0,0 +1,222 @@
+/*
+ * 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.math3.optim.nonlinear.scalar.noderiv;
+
+import java.util.Comparator;
+import org.apache.commons.math3.analysis.MultivariateFunction;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.MathUnsupportedOperationException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.optim.nonlinear.scalar.GoalType;
+import org.apache.commons.math3.optim.ConvergenceChecker;
+import org.apache.commons.math3.optim.PointValuePair;
+import org.apache.commons.math3.optim.SimpleValueChecker;
+import org.apache.commons.math3.optim.OptimizationData;
+import org.apache.commons.math3.optim.nonlinear.scalar.MultivariateOptimizer;
+
+/**
+ * This class implements simplex-based direct search optimization.
+ *
+ * <p>
+ *  Direct search methods only use objective function values, they do
+ *  not need derivatives and don't either try to compute approximation
+ *  of the derivatives. According to a 1996 paper by Margaret H. Wright
+ *  (<a href="http://cm.bell-labs.com/cm/cs/doc/96/4-02.ps.gz">Direct
+ *  Search Methods: Once Scorned, Now Respectable</a>), they are used
+ *  when either the computation of the derivative is impossible (noisy
+ *  functions, unpredictable discontinuities) or difficult (complexity,
+ *  computation cost). In the first cases, rather than an optimum, a
+ *  <em>not too bad</em> point is desired. In the latter cases, an
+ *  optimum is desired but cannot be reasonably found. In all cases
+ *  direct search methods can be useful.
+ * </p>
+ * <p>
+ *  Simplex-based direct search methods are based on comparison of
+ *  the objective function values at the vertices of a simplex (which is a
+ *  set of n+1 points in dimension n) that is updated by the algorithms
+ *  steps.
+ * <p>
+ * <p>
+ *  The simplex update procedure ({@link NelderMeadSimplex} or
+ * {@link MultiDirectionalSimplex})  must be passed to the
+ * {@code optimize} method.
+ * </p>
+ * <p>
+ *  Each call to {@code optimize} will re-use the start configuration of
+ *  the current simplex and move it such that its first vertex is at the
+ *  provided start point of the optimization.
+ *  If the {@code optimize} method is called to solve a different problem
+ *  and the number of parameters change, the simplex must be re-initialized
+ *  to one with the appropriate dimensions.
+ * </p>
+ * <p>
+ *  Convergence is checked by providing the <em>worst</em> points of
+ *  previous and current simplex to the convergence checker, not the best
+ *  ones.
+ * </p>
+ * <p>
+ *  This simplex optimizer implementation does not directly support constrained
+ *  optimization with simple bounds; so, for such optimizations, either a more
+ *  dedicated algorithm must be used like
+ *  {@link CMAESOptimizer} or {@link BOBYQAOptimizer}, or the objective
+ *  function must be wrapped in an adapter like
+ *  {@link org.apache.commons.math3.optim.nonlinear.scalar.MultivariateFunctionMappingAdapter
+ *  MultivariateFunctionMappingAdapter} or
+ *  {@link org.apache.commons.math3.optim.nonlinear.scalar.MultivariateFunctionPenaltyAdapter
+ *  MultivariateFunctionPenaltyAdapter}.
+ *  <br/>
+ *  The call to {@link #optimize(OptimizationData[]) optimize} will throw
+ *  {@link MathUnsupportedOperationException} if bounds are passed to it.
+ * </p>
+ *
+ * @since 3.0
+ */
+public class SimplexOptimizer extends MultivariateOptimizer {
+    /** Simplex update rule. */
+    private AbstractSimplex simplex;
+
+    /**
+     * @param checker Convergence checker.
+     */
+    public SimplexOptimizer(ConvergenceChecker<PointValuePair> checker) {
+        super(checker);
+    }
+
+    /**
+     * @param rel Relative threshold.
+     * @param abs Absolute threshold.
+     */
+    public SimplexOptimizer(double rel, double abs) {
+        this(new SimpleValueChecker(rel, abs));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @param optData Optimization data. In addition to those documented in
+     * {@link MultivariateOptimizer#parseOptimizationData(OptimizationData[])
+     * MultivariateOptimizer}, this method will register the following data:
+     * <ul>
+     *  <li>{@link AbstractSimplex}</li>
+     * </ul>
+     * @return {@inheritDoc}
+     */
+    @Override
+    public PointValuePair optimize(OptimizationData... optData) {
+        // Set up base class and perform computation.
+        return super.optimize(optData);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected PointValuePair doOptimize() {
+        checkParameters();
+
+        // Indirect call to "computeObjectiveValue" in order to update the
+        // evaluations counter.
+        final MultivariateFunction evalFunc
+            = new MultivariateFunction() {
+                /** {@inheritDoc} */
+                public double value(double[] point) {
+                    return computeObjectiveValue(point);
+                }
+            };
+
+        final boolean isMinim = getGoalType() == GoalType.MINIMIZE;
+        final Comparator<PointValuePair> comparator
+            = new Comparator<PointValuePair>() {
+            /** {@inheritDoc} */
+            public int compare(final PointValuePair o1,
+                               final PointValuePair o2) {
+                final double v1 = o1.getValue();
+                final double v2 = o2.getValue();
+                return isMinim ? Double.compare(v1, v2) : Double.compare(v2, v1);
+            }
+        };
+
+        // Initialize search.
+        simplex.build(getStartPoint());
+        simplex.evaluate(evalFunc, comparator);
+
+        PointValuePair[] previous = null;
+        int iteration = 0;
+        final ConvergenceChecker<PointValuePair> checker = getConvergenceChecker();
+        while (true) {
+            if (getIterations() > 0) {
+                boolean converged = true;
+                for (int i = 0; i < simplex.getSize(); i++) {
+                    PointValuePair prev = previous[i];
+                    converged = converged &&
+                        checker.converged(iteration, prev, simplex.getPoint(i));
+                }
+                if (converged) {
+                    // We have found an optimum.
+                    return simplex.getPoint(0);
+                }
+            }
+
+            // We still need to search.
+            previous = simplex.getPoints();
+            simplex.iterate(evalFunc, comparator);
+
+            incrementIterationCount();
+        }
+    }
+
+    /**
+     * Scans the list of (required and optional) optimization data that
+     * characterize the problem.
+     *
+     * @param optData Optimization data.
+     * The following data will be looked for:
+     * <ul>
+     *  <li>{@link AbstractSimplex}</li>
+     * </ul>
+     */
+    @Override
+    protected void parseOptimizationData(OptimizationData... optData) {
+        // Allow base class to register its own data.
+        super.parseOptimizationData(optData);
+
+        // The existing values (as set by the previous call) are reused if
+        // not provided in the argument list.
+        for (OptimizationData data : optData) {
+            if (data instanceof AbstractSimplex) {
+                simplex = (AbstractSimplex) data;
+                // If more data must be parsed, this statement _must_ be
+                // changed to "continue".
+                break;
+            }
+        }
+    }
+
+    /**
+     * @throws MathUnsupportedOperationException if bounds were passed to the
+     * {@link #optimize(OptimizationData[]) optimize} method.
+     * @throws NullArgumentException if no initial simplex was passed to the
+     * {@link #optimize(OptimizationData[]) optimize} method.
+     */
+    private void checkParameters() {
+        if (simplex == null) {
+            throw new NullArgumentException();
+        }
+        if (getLowerBound() != null ||
+            getUpperBound() != null) {
+            throw new MathUnsupportedOperationException(LocalizedFormats.CONSTRAINT);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/package-info.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/package-info.java
new file mode 100644
index 0000000..4afeb50
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/noderiv/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/**
+ * This package provides optimization algorithms that do not require derivatives.
+ */
+package org.apache.commons.math3.optim.nonlinear.scalar.noderiv;
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/package-info.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/package-info.java
new file mode 100644
index 0000000..d65533a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/scalar/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/**
+ * Algorithms for optimizing a scalar function.
+ */
+package org.apache.commons.math3.optim.nonlinear.scalar;
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/JacobianMultivariateVectorOptimizer.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/JacobianMultivariateVectorOptimizer.java
new file mode 100644
index 0000000..52372c8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/JacobianMultivariateVectorOptimizer.java
@@ -0,0 +1,116 @@
+/*
+ * 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.math3.optim.nonlinear.vector;
+
+import org.apache.commons.math3.analysis.MultivariateMatrixFunction;
+import org.apache.commons.math3.optim.ConvergenceChecker;
+import org.apache.commons.math3.optim.OptimizationData;
+import org.apache.commons.math3.optim.PointVectorValuePair;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+
+/**
+ * Base class for implementing optimizers for multivariate vector
+ * differentiable functions.
+ * It contains boiler-plate code for dealing with Jacobian evaluation.
+ * It assumes that the rows of the Jacobian matrix iterate on the model
+ * functions while the columns iterate on the parameters; thus, the numbers
+ * of rows is equal to the dimension of the {@link Target} while the
+ * number of columns is equal to the dimension of the
+ * {@link org.apache.commons.math3.optim.InitialGuess InitialGuess}.
+ *
+ * @since 3.1
+ * @deprecated All classes and interfaces in this package are deprecated.
+ * The optimizers that were provided here were moved to the
+ * {@link org.apache.commons.math3.fitting.leastsquares} package
+ * (cf. MATH-1008).
+ */
+@Deprecated
+public abstract class JacobianMultivariateVectorOptimizer
+    extends MultivariateVectorOptimizer {
+    /**
+     * Jacobian of the model function.
+     */
+    private MultivariateMatrixFunction jacobian;
+
+    /**
+     * @param checker Convergence checker.
+     */
+    protected JacobianMultivariateVectorOptimizer(ConvergenceChecker<PointVectorValuePair> checker) {
+        super(checker);
+    }
+
+    /**
+     * Computes the Jacobian matrix.
+     *
+     * @param params Point at which the Jacobian must be evaluated.
+     * @return the Jacobian at the specified point.
+     */
+    protected double[][] computeJacobian(final double[] params) {
+        return jacobian.value(params);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @param optData Optimization data. In addition to those documented in
+     * {@link MultivariateVectorOptimizer#optimize(OptimizationData...)}
+     * MultivariateOptimizer}, this method will register the following data:
+     * <ul>
+     *  <li>{@link ModelFunctionJacobian}</li>
+     * </ul>
+     * @return {@inheritDoc}
+     * @throws TooManyEvaluationsException if the maximal number of
+     * evaluations is exceeded.
+     * @throws DimensionMismatchException if the initial guess, target, and weight
+     * arguments have inconsistent dimensions.
+     */
+    @Override
+    public PointVectorValuePair optimize(OptimizationData... optData)
+        throws TooManyEvaluationsException,
+               DimensionMismatchException {
+        // Set up base class and perform computation.
+        return super.optimize(optData);
+    }
+
+    /**
+     * Scans the list of (required and optional) optimization data that
+     * characterize the problem.
+     *
+     * @param optData Optimization data.
+     * The following data will be looked for:
+     * <ul>
+     *  <li>{@link ModelFunctionJacobian}</li>
+     * </ul>
+     */
+    @Override
+    protected void parseOptimizationData(OptimizationData... optData) {
+        // Allow base class to register its own data.
+        super.parseOptimizationData(optData);
+
+        // The existing values (as set by the previous call) are reused if
+        // not provided in the argument list.
+        for (OptimizationData data : optData) {
+            if (data instanceof ModelFunctionJacobian) {
+                jacobian = ((ModelFunctionJacobian) data).getModelFunctionJacobian();
+                // If more data must be parsed, this statement _must_ be
+                // changed to "continue".
+                break;
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/ModelFunction.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/ModelFunction.java
new file mode 100644
index 0000000..73de7d6
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/ModelFunction.java
@@ -0,0 +1,51 @@
+/*
+ * 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.math3.optim.nonlinear.vector;
+
+import org.apache.commons.math3.analysis.MultivariateVectorFunction;
+import org.apache.commons.math3.optim.OptimizationData;
+
+/**
+ * Model (vector) function to be optimized.
+ *
+ * @since 3.1
+ * @deprecated All classes and interfaces in this package are deprecated.
+ * The optimizers that were provided here were moved to the
+ * {@link org.apache.commons.math3.fitting.leastsquares} package
+ * (cf. MATH-1008).
+ */
+@Deprecated
+public class ModelFunction implements OptimizationData {
+    /** Function to be optimized. */
+    private final MultivariateVectorFunction model;
+
+    /**
+     * @param m Model function to be optimized.
+     */
+    public ModelFunction(MultivariateVectorFunction m) {
+        model = m;
+    }
+
+    /**
+     * Gets the model function to be optimized.
+     *
+     * @return the model function.
+     */
+    public MultivariateVectorFunction getModelFunction() {
+        return model;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/ModelFunctionJacobian.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/ModelFunctionJacobian.java
new file mode 100644
index 0000000..72ea4ae
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/ModelFunctionJacobian.java
@@ -0,0 +1,51 @@
+/*
+ * 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.math3.optim.nonlinear.vector;
+
+import org.apache.commons.math3.analysis.MultivariateMatrixFunction;
+import org.apache.commons.math3.optim.OptimizationData;
+
+/**
+ * Jacobian of the model (vector) function to be optimized.
+ *
+ * @since 3.1
+ * @deprecated All classes and interfaces in this package are deprecated.
+ * The optimizers that were provided here were moved to the
+ * {@link org.apache.commons.math3.fitting.leastsquares} package
+ * (cf. MATH-1008).
+ */
+@Deprecated
+public class ModelFunctionJacobian implements OptimizationData {
+    /** Function to be optimized. */
+    private final MultivariateMatrixFunction jacobian;
+
+    /**
+     * @param j Jacobian of the model function to be optimized.
+     */
+    public ModelFunctionJacobian(MultivariateMatrixFunction j) {
+        jacobian = j;
+    }
+
+    /**
+     * Gets the Jacobian of the model function to be optimized.
+     *
+     * @return the model function Jacobian.
+     */
+    public MultivariateMatrixFunction getModelFunctionJacobian() {
+        return jacobian;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/MultiStartMultivariateVectorOptimizer.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/MultiStartMultivariateVectorOptimizer.java
new file mode 100644
index 0000000..2cebf79
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/MultiStartMultivariateVectorOptimizer.java
@@ -0,0 +1,124 @@
+/*
+ * 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.math3.optim.nonlinear.vector;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Comparator;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.linear.RealVector;
+import org.apache.commons.math3.linear.ArrayRealVector;
+import org.apache.commons.math3.random.RandomVectorGenerator;
+import org.apache.commons.math3.optim.BaseMultiStartMultivariateOptimizer;
+import org.apache.commons.math3.optim.PointVectorValuePair;
+
+/**
+ * Multi-start optimizer for a (vector) model function.
+ *
+ * This class wraps an optimizer in order to use it several times in
+ * turn with different starting points (trying to avoid being trapped
+ * in a local extremum when looking for a global one).
+ *
+ * @since 3.0
+ */
+@Deprecated
+public class MultiStartMultivariateVectorOptimizer
+    extends BaseMultiStartMultivariateOptimizer<PointVectorValuePair> {
+    /** Underlying optimizer. */
+    private final MultivariateVectorOptimizer optimizer;
+    /** Found optima. */
+    private final List<PointVectorValuePair> optima = new ArrayList<PointVectorValuePair>();
+
+    /**
+     * Create a multi-start optimizer from a single-start optimizer.
+     *
+     * @param optimizer Single-start optimizer to wrap.
+     * @param starts Number of starts to perform.
+     * If {@code starts == 1}, the result will be same as if {@code optimizer}
+     * is called directly.
+     * @param generator Random vector generator to use for restarts.
+     * @throws NullArgumentException if {@code optimizer} or {@code generator}
+     * is {@code null}.
+     * @throws NotStrictlyPositiveException if {@code starts < 1}.
+     */
+    public MultiStartMultivariateVectorOptimizer(final MultivariateVectorOptimizer optimizer,
+                                                 final int starts,
+                                                 final RandomVectorGenerator generator)
+        throws NullArgumentException,
+        NotStrictlyPositiveException {
+        super(optimizer, starts, generator);
+        this.optimizer = optimizer;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public PointVectorValuePair[] getOptima() {
+        Collections.sort(optima, getPairComparator());
+        return optima.toArray(new PointVectorValuePair[0]);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void store(PointVectorValuePair optimum) {
+        optima.add(optimum);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void clear() {
+        optima.clear();
+    }
+
+    /**
+     * @return a comparator for sorting the optima.
+     */
+    private Comparator<PointVectorValuePair> getPairComparator() {
+        return new Comparator<PointVectorValuePair>() {
+            /** Observed value to be matched. */
+            private final RealVector target = new ArrayRealVector(optimizer.getTarget(), false);
+            /** Observations weights. */
+            private final RealMatrix weight = optimizer.getWeight();
+
+            /** {@inheritDoc} */
+            public int compare(final PointVectorValuePair o1,
+                               final PointVectorValuePair o2) {
+                if (o1 == null) {
+                    return (o2 == null) ? 0 : 1;
+                } else if (o2 == null) {
+                    return -1;
+                }
+                return Double.compare(weightedResidual(o1),
+                                      weightedResidual(o2));
+            }
+
+            private double weightedResidual(final PointVectorValuePair pv) {
+                final RealVector v = new ArrayRealVector(pv.getValueRef(), false);
+                final RealVector r = target.subtract(v);
+                return r.dotProduct(weight.operate(r));
+            }
+        };
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/MultivariateVectorOptimizer.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/MultivariateVectorOptimizer.java
new file mode 100644
index 0000000..c79defa
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/MultivariateVectorOptimizer.java
@@ -0,0 +1,167 @@
+/*
+ * 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.math3.optim.nonlinear.vector;
+
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.analysis.MultivariateVectorFunction;
+import org.apache.commons.math3.optim.OptimizationData;
+import org.apache.commons.math3.optim.BaseMultivariateOptimizer;
+import org.apache.commons.math3.optim.ConvergenceChecker;
+import org.apache.commons.math3.optim.PointVectorValuePair;
+import org.apache.commons.math3.linear.RealMatrix;
+
+/**
+ * Base class for a multivariate vector function optimizer.
+ *
+ * @since 3.1
+ */
+@Deprecated
+public abstract class MultivariateVectorOptimizer
+    extends BaseMultivariateOptimizer<PointVectorValuePair> {
+    /** Target values for the model function at optimum. */
+    private double[] target;
+    /** Weight matrix. */
+    private RealMatrix weightMatrix;
+    /** Model function. */
+    private MultivariateVectorFunction model;
+
+    /**
+     * @param checker Convergence checker.
+     */
+    protected MultivariateVectorOptimizer(ConvergenceChecker<PointVectorValuePair> checker) {
+        super(checker);
+    }
+
+    /**
+     * Computes the objective function value.
+     * This method <em>must</em> be called by subclasses to enforce the
+     * evaluation counter limit.
+     *
+     * @param params Point at which the objective function must be evaluated.
+     * @return the objective function value at the specified point.
+     * @throws TooManyEvaluationsException if the maximal number of evaluations
+     * (of the model vector function) is exceeded.
+     */
+    protected double[] computeObjectiveValue(double[] params) {
+        super.incrementEvaluationCount();
+        return model.value(params);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @param optData Optimization data. In addition to those documented in
+     * {@link BaseMultivariateOptimizer#parseOptimizationData(OptimizationData[])
+     * BaseMultivariateOptimizer}, this method will register the following data:
+     * <ul>
+     *  <li>{@link Target}</li>
+     *  <li>{@link Weight}</li>
+     *  <li>{@link ModelFunction}</li>
+     * </ul>
+     * @return {@inheritDoc}
+     * @throws TooManyEvaluationsException if the maximal number of
+     * evaluations is exceeded.
+     * @throws DimensionMismatchException if the initial guess, target, and weight
+     * arguments have inconsistent dimensions.
+     */
+    @Override
+    public PointVectorValuePair optimize(OptimizationData... optData)
+        throws TooManyEvaluationsException,
+               DimensionMismatchException {
+        // Set up base class and perform computation.
+        return super.optimize(optData);
+    }
+
+    /**
+     * Gets the weight matrix of the observations.
+     *
+     * @return the weight matrix.
+     */
+    public RealMatrix getWeight() {
+        return weightMatrix.copy();
+    }
+    /**
+     * Gets the observed values to be matched by the objective vector
+     * function.
+     *
+     * @return the target values.
+     */
+    public double[] getTarget() {
+        return target.clone();
+    }
+
+    /**
+     * Gets the number of observed values.
+     *
+     * @return the length of the target vector.
+     */
+    public int getTargetSize() {
+        return target.length;
+    }
+
+    /**
+     * Scans the list of (required and optional) optimization data that
+     * characterize the problem.
+     *
+     * @param optData Optimization data. The following data will be looked for:
+     * <ul>
+     *  <li>{@link Target}</li>
+     *  <li>{@link Weight}</li>
+     *  <li>{@link ModelFunction}</li>
+     * </ul>
+     */
+    @Override
+    protected void parseOptimizationData(OptimizationData... optData) {
+        // Allow base class to register its own data.
+        super.parseOptimizationData(optData);
+
+        // The existing values (as set by the previous call) are reused if
+        // not provided in the argument list.
+        for (OptimizationData data : optData) {
+            if (data instanceof ModelFunction) {
+                model = ((ModelFunction) data).getModelFunction();
+                continue;
+            }
+            if (data instanceof Target) {
+                target = ((Target) data).getTarget();
+                continue;
+            }
+            if (data instanceof Weight) {
+                weightMatrix = ((Weight) data).getWeight();
+                continue;
+            }
+        }
+
+        // Check input consistency.
+        checkParameters();
+    }
+
+    /**
+     * Check parameters consistency.
+     *
+     * @throws DimensionMismatchException if {@link #target} and
+     * {@link #weightMatrix} have inconsistent dimensions.
+     */
+    private void checkParameters() {
+        if (target.length != weightMatrix.getColumnDimension()) {
+            throw new DimensionMismatchException(target.length,
+                                                 weightMatrix.getColumnDimension());
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/Target.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/Target.java
new file mode 100644
index 0000000..cd387d5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/Target.java
@@ -0,0 +1,54 @@
+/*
+ * 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.math3.optim.nonlinear.vector;
+
+import org.apache.commons.math3.optim.OptimizationData;
+
+/**
+ * Target of the optimization procedure.
+ * They are the values which the objective vector function must reproduce
+ * When the parameters of the model have been optimized.
+ * <br/>
+ * Immutable class.
+ *
+ * @since 3.1
+ * @deprecated All classes and interfaces in this package are deprecated.
+ * The optimizers that were provided here were moved to the
+ * {@link org.apache.commons.math3.fitting.leastsquares} package
+ * (cf. MATH-1008).
+ */
+@Deprecated
+public class Target implements OptimizationData {
+    /** Target values (of the objective vector function). */
+    private final double[] target;
+
+    /**
+     * @param observations Target values.
+     */
+    public Target(double[] observations) {
+        target = observations.clone();
+    }
+
+    /**
+     * Gets the initial guess.
+     *
+     * @return the initial guess.
+     */
+    public double[] getTarget() {
+        return target.clone();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/Weight.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/Weight.java
new file mode 100644
index 0000000..4d51cd7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/Weight.java
@@ -0,0 +1,71 @@
+/*
+ * 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.math3.optim.nonlinear.vector;
+
+import org.apache.commons.math3.optim.OptimizationData;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.linear.DiagonalMatrix;
+import org.apache.commons.math3.linear.NonSquareMatrixException;
+
+/**
+ * Weight matrix of the residuals between model and observations.
+ * <br/>
+ * Immutable class.
+ *
+ * @since 3.1
+ * @deprecated All classes and interfaces in this package are deprecated.
+ * The optimizers that were provided here were moved to the
+ * {@link org.apache.commons.math3.fitting.leastsquares} package
+ * (cf. MATH-1008).
+ */
+@Deprecated
+public class Weight implements OptimizationData {
+    /** Weight matrix. */
+    private final RealMatrix weightMatrix;
+
+    /**
+     * Creates a diagonal weight matrix.
+     *
+     * @param weight List of the values of the diagonal.
+     */
+    public Weight(double[] weight) {
+        weightMatrix = new DiagonalMatrix(weight);
+    }
+
+    /**
+     * @param weight Weight matrix.
+     * @throws NonSquareMatrixException if the argument is not
+     * a square matrix.
+     */
+    public Weight(RealMatrix weight) {
+        if (weight.getColumnDimension() != weight.getRowDimension()) {
+            throw new NonSquareMatrixException(weight.getColumnDimension(),
+                                               weight.getRowDimension());
+        }
+
+        weightMatrix = weight.copy();
+    }
+
+    /**
+     * Gets the initial guess.
+     *
+     * @return the initial guess.
+     */
+    public RealMatrix getWeight() {
+        return weightMatrix.copy();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/jacobian/AbstractLeastSquaresOptimizer.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/jacobian/AbstractLeastSquaresOptimizer.java
new file mode 100644
index 0000000..67682eb
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/jacobian/AbstractLeastSquaresOptimizer.java
@@ -0,0 +1,281 @@
+/*
+ * 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.math3.optim.nonlinear.vector.jacobian;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+import org.apache.commons.math3.linear.ArrayRealVector;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.linear.DiagonalMatrix;
+import org.apache.commons.math3.linear.DecompositionSolver;
+import org.apache.commons.math3.linear.MatrixUtils;
+import org.apache.commons.math3.linear.QRDecomposition;
+import org.apache.commons.math3.linear.EigenDecomposition;
+import org.apache.commons.math3.optim.OptimizationData;
+import org.apache.commons.math3.optim.ConvergenceChecker;
+import org.apache.commons.math3.optim.PointVectorValuePair;
+import org.apache.commons.math3.optim.nonlinear.vector.Weight;
+import org.apache.commons.math3.optim.nonlinear.vector.JacobianMultivariateVectorOptimizer;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Base class for implementing least-squares optimizers.
+ * It provides methods for error estimation.
+ *
+ * @since 3.1
+ * @deprecated All classes and interfaces in this package are deprecated.
+ * The optimizers that were provided here were moved to the
+ * {@link org.apache.commons.math3.fitting.leastsquares} package
+ * (cf. MATH-1008).
+ */
+@Deprecated
+public abstract class AbstractLeastSquaresOptimizer
+    extends JacobianMultivariateVectorOptimizer {
+    /** Square-root of the weight matrix. */
+    private RealMatrix weightMatrixSqrt;
+    /** Cost value (square root of the sum of the residuals). */
+    private double cost;
+
+    /**
+     * @param checker Convergence checker.
+     */
+    protected AbstractLeastSquaresOptimizer(ConvergenceChecker<PointVectorValuePair> checker) {
+        super(checker);
+    }
+
+    /**
+     * Computes the weighted Jacobian matrix.
+     *
+     * @param params Model parameters at which to compute the Jacobian.
+     * @return the weighted Jacobian: W<sup>1/2</sup> J.
+     * @throws DimensionMismatchException if the Jacobian dimension does not
+     * match problem dimension.
+     */
+    protected RealMatrix computeWeightedJacobian(double[] params) {
+        return weightMatrixSqrt.multiply(MatrixUtils.createRealMatrix(computeJacobian(params)));
+    }
+
+    /**
+     * Computes the cost.
+     *
+     * @param residuals Residuals.
+     * @return the cost.
+     * @see #computeResiduals(double[])
+     */
+    protected double computeCost(double[] residuals) {
+        final ArrayRealVector r = new ArrayRealVector(residuals);
+        return FastMath.sqrt(r.dotProduct(getWeight().operate(r)));
+    }
+
+    /**
+     * Gets the root-mean-square (RMS) value.
+     *
+     * The RMS the root of the arithmetic mean of the square of all weighted
+     * residuals.
+     * This is related to the criterion that is minimized by the optimizer
+     * as follows: If <em>c</em> if the criterion, and <em>n</em> is the
+     * number of measurements, then the RMS is <em>sqrt (c/n)</em>.
+     *
+     * @return the RMS value.
+     */
+    public double getRMS() {
+        return FastMath.sqrt(getChiSquare() / getTargetSize());
+    }
+
+    /**
+     * Get a Chi-Square-like value assuming the N residuals follow N
+     * distinct normal distributions centered on 0 and whose variances are
+     * the reciprocal of the weights.
+     * @return chi-square value
+     */
+    public double getChiSquare() {
+        return cost * cost;
+    }
+
+    /**
+     * Gets the square-root of the weight matrix.
+     *
+     * @return the square-root of the weight matrix.
+     */
+    public RealMatrix getWeightSquareRoot() {
+        return weightMatrixSqrt.copy();
+    }
+
+    /**
+     * Sets the cost.
+     *
+     * @param cost Cost value.
+     */
+    protected void setCost(double cost) {
+        this.cost = cost;
+    }
+
+    /**
+     * Get the covariance matrix of the optimized parameters.
+     * <br/>
+     * Note that this operation involves the inversion of the
+     * <code>J<sup>T</sup>J</code> matrix, where {@code J} is the
+     * Jacobian matrix.
+     * The {@code threshold} parameter is a way for the caller to specify
+     * that the result of this computation should be considered meaningless,
+     * and thus trigger an exception.
+     *
+     * @param params Model parameters.
+     * @param threshold Singularity threshold.
+     * @return the covariance matrix.
+     * @throws org.apache.commons.math3.linear.SingularMatrixException
+     * if the covariance matrix cannot be computed (singular problem).
+     */
+    public double[][] computeCovariances(double[] params,
+                                         double threshold) {
+        // Set up the Jacobian.
+        final RealMatrix j = computeWeightedJacobian(params);
+
+        // Compute transpose(J)J.
+        final RealMatrix jTj = j.transpose().multiply(j);
+
+        // Compute the covariances matrix.
+        final DecompositionSolver solver
+            = new QRDecomposition(jTj, threshold).getSolver();
+        return solver.getInverse().getData();
+    }
+
+    /**
+     * Computes an estimate of the standard deviation of the parameters. The
+     * returned values are the square root of the diagonal coefficients of the
+     * covariance matrix, {@code sd(a[i]) ~= sqrt(C[i][i])}, where {@code a[i]}
+     * is the optimized value of the {@code i}-th parameter, and {@code C} is
+     * the covariance matrix.
+     *
+     * @param params Model parameters.
+     * @param covarianceSingularityThreshold Singularity threshold (see
+     * {@link #computeCovariances(double[],double) computeCovariances}).
+     * @return an estimate of the standard deviation of the optimized parameters
+     * @throws org.apache.commons.math3.linear.SingularMatrixException
+     * if the covariance matrix cannot be computed.
+     */
+    public double[] computeSigma(double[] params,
+                                 double covarianceSingularityThreshold) {
+        final int nC = params.length;
+        final double[] sig = new double[nC];
+        final double[][] cov = computeCovariances(params, covarianceSingularityThreshold);
+        for (int i = 0; i < nC; ++i) {
+            sig[i] = FastMath.sqrt(cov[i][i]);
+        }
+        return sig;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @param optData Optimization data. In addition to those documented in
+     * {@link JacobianMultivariateVectorOptimizer#parseOptimizationData(OptimizationData[])
+     * JacobianMultivariateVectorOptimizer}, this method will register the following data:
+     * <ul>
+     *  <li>{@link org.apache.commons.math3.optim.nonlinear.vector.Weight}</li>
+     * </ul>
+     * @return {@inheritDoc}
+     * @throws TooManyEvaluationsException if the maximal number of
+     * evaluations is exceeded.
+     * @throws DimensionMismatchException if the initial guess, target, and weight
+     * arguments have inconsistent dimensions.
+     */
+    @Override
+    public PointVectorValuePair optimize(OptimizationData... optData)
+        throws TooManyEvaluationsException {
+        // Set up base class and perform computation.
+        return super.optimize(optData);
+    }
+
+    /**
+     * Computes the residuals.
+     * The residual is the difference between the observed (target)
+     * values and the model (objective function) value.
+     * There is one residual for each element of the vector-valued
+     * function.
+     *
+     * @param objectiveValue Value of the the objective function. This is
+     * the value returned from a call to
+     * {@link #computeObjectiveValue(double[]) computeObjectiveValue}
+     * (whose array argument contains the model parameters).
+     * @return the residuals.
+     * @throws DimensionMismatchException if {@code params} has a wrong
+     * length.
+     */
+    protected double[] computeResiduals(double[] objectiveValue) {
+        final double[] target = getTarget();
+        if (objectiveValue.length != target.length) {
+            throw new DimensionMismatchException(target.length,
+                                                 objectiveValue.length);
+        }
+
+        final double[] residuals = new double[target.length];
+        for (int i = 0; i < target.length; i++) {
+            residuals[i] = target[i] - objectiveValue[i];
+        }
+
+        return residuals;
+    }
+
+    /**
+     * Scans the list of (required and optional) optimization data that
+     * characterize the problem.
+     * If the weight matrix is specified, the {@link #weightMatrixSqrt}
+     * field is recomputed.
+     *
+     * @param optData Optimization data. The following data will be looked for:
+     * <ul>
+     *  <li>{@link Weight}</li>
+     * </ul>
+     */
+    @Override
+    protected void parseOptimizationData(OptimizationData... optData) {
+        // Allow base class to register its own data.
+        super.parseOptimizationData(optData);
+
+        // The existing values (as set by the previous call) are reused if
+        // not provided in the argument list.
+        for (OptimizationData data : optData) {
+            if (data instanceof Weight) {
+                weightMatrixSqrt = squareRoot(((Weight) data).getWeight());
+                // If more data must be parsed, this statement _must_ be
+                // changed to "continue".
+                break;
+            }
+        }
+    }
+
+    /**
+     * Computes the square-root of the weight matrix.
+     *
+     * @param m Symmetric, positive-definite (weight) matrix.
+     * @return the square-root of the weight matrix.
+     */
+    private RealMatrix squareRoot(RealMatrix m) {
+        if (m instanceof DiagonalMatrix) {
+            final int dim = m.getRowDimension();
+            final RealMatrix sqrtM = new DiagonalMatrix(dim);
+            for (int i = 0; i < dim; i++) {
+                sqrtM.setEntry(i, i, FastMath.sqrt(m.getEntry(i, i)));
+            }
+            return sqrtM;
+        } else {
+            final EigenDecomposition dec = new EigenDecomposition(m);
+            return dec.getSquareRoot();
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/jacobian/GaussNewtonOptimizer.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/jacobian/GaussNewtonOptimizer.java
new file mode 100644
index 0000000..0668475
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/jacobian/GaussNewtonOptimizer.java
@@ -0,0 +1,183 @@
+/*
+ * 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.math3.optim.nonlinear.vector.jacobian;
+
+import org.apache.commons.math3.exception.ConvergenceException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.MathInternalError;
+import org.apache.commons.math3.exception.MathUnsupportedOperationException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.linear.ArrayRealVector;
+import org.apache.commons.math3.linear.BlockRealMatrix;
+import org.apache.commons.math3.linear.DecompositionSolver;
+import org.apache.commons.math3.linear.LUDecomposition;
+import org.apache.commons.math3.linear.QRDecomposition;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.linear.SingularMatrixException;
+import org.apache.commons.math3.optim.ConvergenceChecker;
+import org.apache.commons.math3.optim.PointVectorValuePair;
+
+/**
+ * Gauss-Newton least-squares solver.
+ * <br/>
+ * Constraints are not supported: the call to
+ * {@link #optimize(OptimizationData[]) optimize} will throw
+ * {@link MathUnsupportedOperationException} if bounds are passed to it.
+ *
+ * <p>
+ * This class solve a least-square problem by solving the normal equations
+ * of the linearized problem at each iteration. Either LU decomposition or
+ * QR decomposition can be used to solve the normal equations. LU decomposition
+ * is faster but QR decomposition is more robust for difficult problems.
+ * </p>
+ *
+ * @since 2.0
+ * @deprecated All classes and interfaces in this package are deprecated.
+ * The optimizers that were provided here were moved to the
+ * {@link org.apache.commons.math3.fitting.leastsquares} package
+ * (cf. MATH-1008).
+ */
+@Deprecated
+public class GaussNewtonOptimizer extends AbstractLeastSquaresOptimizer {
+    /** Indicator for using LU decomposition. */
+    private final boolean useLU;
+
+    /**
+     * Simple constructor with default settings.
+     * The normal equations will be solved using LU decomposition.
+     *
+     * @param checker Convergence checker.
+     */
+    public GaussNewtonOptimizer(ConvergenceChecker<PointVectorValuePair> checker) {
+        this(true, checker);
+    }
+
+    /**
+     * @param useLU If {@code true}, the normal equations will be solved
+     * using LU decomposition, otherwise they will be solved using QR
+     * decomposition.
+     * @param checker Convergence checker.
+     */
+    public GaussNewtonOptimizer(final boolean useLU,
+                                ConvergenceChecker<PointVectorValuePair> checker) {
+        super(checker);
+        this.useLU = useLU;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public PointVectorValuePair doOptimize() {
+        checkParameters();
+
+        final ConvergenceChecker<PointVectorValuePair> checker
+            = getConvergenceChecker();
+
+        // Computation will be useless without a checker (see "for-loop").
+        if (checker == null) {
+            throw new NullArgumentException();
+        }
+
+        final double[] targetValues = getTarget();
+        final int nR = targetValues.length; // Number of observed data.
+
+        final RealMatrix weightMatrix = getWeight();
+        // Diagonal of the weight matrix.
+        final double[] residualsWeights = new double[nR];
+        for (int i = 0; i < nR; i++) {
+            residualsWeights[i] = weightMatrix.getEntry(i, i);
+        }
+
+        final double[] currentPoint = getStartPoint();
+        final int nC = currentPoint.length;
+
+        // iterate until convergence is reached
+        PointVectorValuePair current = null;
+        for (boolean converged = false; !converged;) {
+            incrementIterationCount();
+
+            // evaluate the objective function and its jacobian
+            PointVectorValuePair previous = current;
+            // Value of the objective function at "currentPoint".
+            final double[] currentObjective = computeObjectiveValue(currentPoint);
+            final double[] currentResiduals = computeResiduals(currentObjective);
+            final RealMatrix weightedJacobian = computeWeightedJacobian(currentPoint);
+            current = new PointVectorValuePair(currentPoint, currentObjective);
+
+            // build the linear problem
+            final double[]   b = new double[nC];
+            final double[][] a = new double[nC][nC];
+            for (int i = 0; i < nR; ++i) {
+
+                final double[] grad   = weightedJacobian.getRow(i);
+                final double weight   = residualsWeights[i];
+                final double residual = currentResiduals[i];
+
+                // compute the normal equation
+                final double wr = weight * residual;
+                for (int j = 0; j < nC; ++j) {
+                    b[j] += wr * grad[j];
+                }
+
+                // build the contribution matrix for measurement i
+                for (int k = 0; k < nC; ++k) {
+                    double[] ak = a[k];
+                    double wgk = weight * grad[k];
+                    for (int l = 0; l < nC; ++l) {
+                        ak[l] += wgk * grad[l];
+                    }
+                }
+            }
+
+            // Check convergence.
+            if (previous != null) {
+                converged = checker.converged(getIterations(), previous, current);
+                if (converged) {
+                    setCost(computeCost(currentResiduals));
+                    return current;
+                }
+            }
+
+            try {
+                // solve the linearized least squares problem
+                RealMatrix mA = new BlockRealMatrix(a);
+                DecompositionSolver solver = useLU ?
+                        new LUDecomposition(mA).getSolver() :
+                        new QRDecomposition(mA).getSolver();
+                final double[] dX = solver.solve(new ArrayRealVector(b, false)).toArray();
+                // update the estimated parameters
+                for (int i = 0; i < nC; ++i) {
+                    currentPoint[i] += dX[i];
+                }
+            } catch (SingularMatrixException e) {
+                throw new ConvergenceException(LocalizedFormats.UNABLE_TO_SOLVE_SINGULAR_PROBLEM);
+            }
+        }
+        // Must never happen.
+        throw new MathInternalError();
+    }
+
+    /**
+     * @throws MathUnsupportedOperationException if bounds were passed to the
+     * {@link #optimize(OptimizationData[]) optimize} method.
+     */
+    private void checkParameters() {
+        if (getLowerBound() != null ||
+            getUpperBound() != null) {
+            throw new MathUnsupportedOperationException(LocalizedFormats.CONSTRAINT);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/jacobian/LevenbergMarquardtOptimizer.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/jacobian/LevenbergMarquardtOptimizer.java
new file mode 100644
index 0000000..05be0d0
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/jacobian/LevenbergMarquardtOptimizer.java
@@ -0,0 +1,961 @@
+/*
+ * 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.math3.optim.nonlinear.vector.jacobian;
+
+import java.util.Arrays;
+import org.apache.commons.math3.exception.ConvergenceException;
+import org.apache.commons.math3.exception.MathUnsupportedOperationException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.optim.PointVectorValuePair;
+import org.apache.commons.math3.optim.ConvergenceChecker;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.util.Precision;
+import org.apache.commons.math3.util.FastMath;
+
+
+/**
+ * This class solves a least-squares problem using the Levenberg-Marquardt
+ * algorithm.
+ * <br/>
+ * Constraints are not supported: the call to
+ * {@link #optimize(OptimizationData[]) optimize} will throw
+ * {@link MathUnsupportedOperationException} if bounds are passed to it.
+ *
+ * <p>This implementation <em>should</em> work even for over-determined systems
+ * (i.e. systems having more point than equations). Over-determined systems
+ * are solved by ignoring the point which have the smallest impact according
+ * to their jacobian column norm. Only the rank of the matrix and some loop bounds
+ * are changed to implement this.</p>
+ *
+ * <p>The resolution engine is a simple translation of the MINPACK <a
+ * href="http://www.netlib.org/minpack/lmder.f">lmder</a> routine with minor
+ * changes. The changes include the over-determined resolution, the use of
+ * inherited convergence checker and the Q.R. decomposition which has been
+ * rewritten following the algorithm described in the
+ * P. Lascaux and R. Theodor book <i>Analyse num&eacute;rique matricielle
+ * appliqu&eacute;e &agrave; l'art de l'ing&eacute;nieur</i>, Masson 1986.</p>
+ * <p>The authors of the original fortran version are:
+ * <ul>
+ * <li>Argonne National Laboratory. MINPACK project. March 1980</li>
+ * <li>Burton S. Garbow</li>
+ * <li>Kenneth E. Hillstrom</li>
+ * <li>Jorge J. More</li>
+ * </ul>
+ * The redistribution policy for MINPACK is available <a
+ * href="http://www.netlib.org/minpack/disclaimer">here</a>, for convenience, it
+ * is reproduced below.</p>
+ *
+ * <table border="0" width="80%" cellpadding="10" align="center" bgcolor="#E0E0E0">
+ * <tr><td>
+ *    Minpack Copyright Notice (1999) University of Chicago.
+ *    All rights reserved
+ * </td></tr>
+ * <tr><td>
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * <ol>
+ *  <li>Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.</li>
+ * <li>Redistributions in binary form must reproduce the above
+ *     copyright notice, this list of conditions and the following
+ *     disclaimer in the documentation and/or other materials provided
+ *     with the distribution.</li>
+ * <li>The end-user documentation included with the redistribution, if any,
+ *     must include the following acknowledgment:
+ *     <code>This product includes software developed by the University of
+ *           Chicago, as Operator of Argonne National Laboratory.</code>
+ *     Alternately, this acknowledgment may appear in the software itself,
+ *     if and wherever such third-party acknowledgments normally appear.</li>
+ * <li><strong>WARRANTY DISCLAIMER. THE SOFTWARE IS SUPPLIED "AS IS"
+ *     WITHOUT WARRANTY OF ANY KIND. THE COPYRIGHT HOLDER, THE
+ *     UNITED STATES, THE UNITED STATES DEPARTMENT OF ENERGY, AND
+ *     THEIR EMPLOYEES: (1) DISCLAIM ANY WARRANTIES, EXPRESS OR
+ *     IMPLIED, INCLUDING BUT NOT LIMITED TO ANY IMPLIED WARRANTIES
+ *     OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE
+ *     OR NON-INFRINGEMENT, (2) DO NOT ASSUME ANY LEGAL LIABILITY
+ *     OR RESPONSIBILITY FOR THE ACCURACY, COMPLETENESS, OR
+ *     USEFULNESS OF THE SOFTWARE, (3) DO NOT REPRESENT THAT USE OF
+ *     THE SOFTWARE WOULD NOT INFRINGE PRIVATELY OWNED RIGHTS, (4)
+ *     DO NOT WARRANT THAT THE SOFTWARE WILL FUNCTION
+ *     UNINTERRUPTED, THAT IT IS ERROR-FREE OR THAT ANY ERRORS WILL
+ *     BE CORRECTED.</strong></li>
+ * <li><strong>LIMITATION OF LIABILITY. IN NO EVENT WILL THE COPYRIGHT
+ *     HOLDER, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF
+ *     ENERGY, OR THEIR EMPLOYEES: BE LIABLE FOR ANY INDIRECT,
+ *     INCIDENTAL, CONSEQUENTIAL, SPECIAL OR PUNITIVE DAMAGES OF
+ *     ANY KIND OR NATURE, INCLUDING BUT NOT LIMITED TO LOSS OF
+ *     PROFITS OR LOSS OF DATA, FOR ANY REASON WHATSOEVER, WHETHER
+ *     SUCH LIABILITY IS ASSERTED ON THE BASIS OF CONTRACT, TORT
+ *     (INCLUDING NEGLIGENCE OR STRICT LIABILITY), OR OTHERWISE,
+ *     EVEN IF ANY OF SAID PARTIES HAS BEEN WARNED OF THE
+ *     POSSIBILITY OF SUCH LOSS OR DAMAGES.</strong></li>
+ * <ol></td></tr>
+ * </table>
+ *
+ * @since 2.0
+ * @deprecated All classes and interfaces in this package are deprecated.
+ * The optimizers that were provided here were moved to the
+ * {@link org.apache.commons.math3.fitting.leastsquares} package
+ * (cf. MATH-1008).
+ */
+@Deprecated
+public class LevenbergMarquardtOptimizer
+    extends AbstractLeastSquaresOptimizer {
+    /** Twice the "epsilon machine". */
+    private static final double TWO_EPS = 2 * Precision.EPSILON;
+    /** Number of solved point. */
+    private int solvedCols;
+    /** Diagonal elements of the R matrix in the Q.R. decomposition. */
+    private double[] diagR;
+    /** Norms of the columns of the jacobian matrix. */
+    private double[] jacNorm;
+    /** Coefficients of the Householder transforms vectors. */
+    private double[] beta;
+    /** Columns permutation array. */
+    private int[] permutation;
+    /** Rank of the jacobian matrix. */
+    private int rank;
+    /** Levenberg-Marquardt parameter. */
+    private double lmPar;
+    /** Parameters evolution direction associated with lmPar. */
+    private double[] lmDir;
+    /** Positive input variable used in determining the initial step bound. */
+    private final double initialStepBoundFactor;
+    /** Desired relative error in the sum of squares. */
+    private final double costRelativeTolerance;
+    /**  Desired relative error in the approximate solution parameters. */
+    private final double parRelativeTolerance;
+    /** Desired max cosine on the orthogonality between the function vector
+     * and the columns of the jacobian. */
+    private final double orthoTolerance;
+    /** Threshold for QR ranking. */
+    private final double qrRankingThreshold;
+    /** Weighted residuals. */
+    private double[] weightedResidual;
+    /** Weighted Jacobian. */
+    private double[][] weightedJacobian;
+
+    /**
+     * Build an optimizer for least squares problems with default values
+     * for all the tuning parameters (see the {@link
+     * #LevenbergMarquardtOptimizer(double,double,double,double,double)
+     * other contructor}.
+     * The default values for the algorithm settings are:
+     * <ul>
+     *  <li>Initial step bound factor: 100</li>
+     *  <li>Cost relative tolerance: 1e-10</li>
+     *  <li>Parameters relative tolerance: 1e-10</li>
+     *  <li>Orthogonality tolerance: 1e-10</li>
+     *  <li>QR ranking threshold: {@link Precision#SAFE_MIN}</li>
+     * </ul>
+     */
+    public LevenbergMarquardtOptimizer() {
+        this(100, 1e-10, 1e-10, 1e-10, Precision.SAFE_MIN);
+    }
+
+    /**
+     * Constructor that allows the specification of a custom convergence
+     * checker.
+     * Note that all the usual convergence checks will be <em>disabled</em>.
+     * The default values for the algorithm settings are:
+     * <ul>
+     *  <li>Initial step bound factor: 100</li>
+     *  <li>Cost relative tolerance: 1e-10</li>
+     *  <li>Parameters relative tolerance: 1e-10</li>
+     *  <li>Orthogonality tolerance: 1e-10</li>
+     *  <li>QR ranking threshold: {@link Precision#SAFE_MIN}</li>
+     * </ul>
+     *
+     * @param checker Convergence checker.
+     */
+    public LevenbergMarquardtOptimizer(ConvergenceChecker<PointVectorValuePair> checker) {
+        this(100, checker, 1e-10, 1e-10, 1e-10, Precision.SAFE_MIN);
+    }
+
+    /**
+     * Constructor that allows the specification of a custom convergence
+     * checker, in addition to the standard ones.
+     *
+     * @param initialStepBoundFactor Positive input variable used in
+     * determining the initial step bound. This bound is set to the
+     * product of initialStepBoundFactor and the euclidean norm of
+     * {@code diag * x} if non-zero, or else to {@code initialStepBoundFactor}
+     * itself. In most cases factor should lie in the interval
+     * {@code (0.1, 100.0)}. {@code 100} is a generally recommended value.
+     * @param checker Convergence checker.
+     * @param costRelativeTolerance Desired relative error in the sum of
+     * squares.
+     * @param parRelativeTolerance Desired relative error in the approximate
+     * solution parameters.
+     * @param orthoTolerance Desired max cosine on the orthogonality between
+     * the function vector and the columns of the Jacobian.
+     * @param threshold Desired threshold for QR ranking. If the squared norm
+     * of a column vector is smaller or equal to this threshold during QR
+     * decomposition, it is considered to be a zero vector and hence the rank
+     * of the matrix is reduced.
+     */
+    public LevenbergMarquardtOptimizer(double initialStepBoundFactor,
+                                       ConvergenceChecker<PointVectorValuePair> checker,
+                                       double costRelativeTolerance,
+                                       double parRelativeTolerance,
+                                       double orthoTolerance,
+                                       double threshold) {
+        super(checker);
+        this.initialStepBoundFactor = initialStepBoundFactor;
+        this.costRelativeTolerance = costRelativeTolerance;
+        this.parRelativeTolerance = parRelativeTolerance;
+        this.orthoTolerance = orthoTolerance;
+        this.qrRankingThreshold = threshold;
+    }
+
+    /**
+     * Build an optimizer for least squares problems with default values
+     * for some of the tuning parameters (see the {@link
+     * #LevenbergMarquardtOptimizer(double,double,double,double,double)
+     * other contructor}.
+     * The default values for the algorithm settings are:
+     * <ul>
+     *  <li>Initial step bound factor}: 100</li>
+     *  <li>QR ranking threshold}: {@link Precision#SAFE_MIN}</li>
+     * </ul>
+     *
+     * @param costRelativeTolerance Desired relative error in the sum of
+     * squares.
+     * @param parRelativeTolerance Desired relative error in the approximate
+     * solution parameters.
+     * @param orthoTolerance Desired max cosine on the orthogonality between
+     * the function vector and the columns of the Jacobian.
+     */
+    public LevenbergMarquardtOptimizer(double costRelativeTolerance,
+                                       double parRelativeTolerance,
+                                       double orthoTolerance) {
+        this(100,
+             costRelativeTolerance, parRelativeTolerance, orthoTolerance,
+             Precision.SAFE_MIN);
+    }
+
+    /**
+     * The arguments control the behaviour of the default convergence checking
+     * procedure.
+     * Additional criteria can defined through the setting of a {@link
+     * ConvergenceChecker}.
+     *
+     * @param initialStepBoundFactor Positive input variable used in
+     * determining the initial step bound. This bound is set to the
+     * product of initialStepBoundFactor and the euclidean norm of
+     * {@code diag * x} if non-zero, or else to {@code initialStepBoundFactor}
+     * itself. In most cases factor should lie in the interval
+     * {@code (0.1, 100.0)}. {@code 100} is a generally recommended value.
+     * @param costRelativeTolerance Desired relative error in the sum of
+     * squares.
+     * @param parRelativeTolerance Desired relative error in the approximate
+     * solution parameters.
+     * @param orthoTolerance Desired max cosine on the orthogonality between
+     * the function vector and the columns of the Jacobian.
+     * @param threshold Desired threshold for QR ranking. If the squared norm
+     * of a column vector is smaller or equal to this threshold during QR
+     * decomposition, it is considered to be a zero vector and hence the rank
+     * of the matrix is reduced.
+     */
+    public LevenbergMarquardtOptimizer(double initialStepBoundFactor,
+                                       double costRelativeTolerance,
+                                       double parRelativeTolerance,
+                                       double orthoTolerance,
+                                       double threshold) {
+        super(null); // No custom convergence criterion.
+        this.initialStepBoundFactor = initialStepBoundFactor;
+        this.costRelativeTolerance = costRelativeTolerance;
+        this.parRelativeTolerance = parRelativeTolerance;
+        this.orthoTolerance = orthoTolerance;
+        this.qrRankingThreshold = threshold;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected PointVectorValuePair doOptimize() {
+        checkParameters();
+
+        final int nR = getTarget().length; // Number of observed data.
+        final double[] currentPoint = getStartPoint();
+        final int nC = currentPoint.length; // Number of parameters.
+
+        // arrays shared with the other private methods
+        solvedCols  = FastMath.min(nR, nC);
+        diagR       = new double[nC];
+        jacNorm     = new double[nC];
+        beta        = new double[nC];
+        permutation = new int[nC];
+        lmDir       = new double[nC];
+
+        // local point
+        double   delta   = 0;
+        double   xNorm   = 0;
+        double[] diag    = new double[nC];
+        double[] oldX    = new double[nC];
+        double[] oldRes  = new double[nR];
+        double[] oldObj  = new double[nR];
+        double[] qtf     = new double[nR];
+        double[] work1   = new double[nC];
+        double[] work2   = new double[nC];
+        double[] work3   = new double[nC];
+
+        final RealMatrix weightMatrixSqrt = getWeightSquareRoot();
+
+        // Evaluate the function at the starting point and calculate its norm.
+        double[] currentObjective = computeObjectiveValue(currentPoint);
+        double[] currentResiduals = computeResiduals(currentObjective);
+        PointVectorValuePair current = new PointVectorValuePair(currentPoint, currentObjective);
+        double currentCost = computeCost(currentResiduals);
+
+        // Outer loop.
+        lmPar = 0;
+        boolean firstIteration = true;
+        final ConvergenceChecker<PointVectorValuePair> checker = getConvergenceChecker();
+        while (true) {
+            incrementIterationCount();
+
+            final PointVectorValuePair previous = current;
+
+            // QR decomposition of the jacobian matrix
+            qrDecomposition(computeWeightedJacobian(currentPoint));
+
+            weightedResidual = weightMatrixSqrt.operate(currentResiduals);
+            for (int i = 0; i < nR; i++) {
+                qtf[i] = weightedResidual[i];
+            }
+
+            // compute Qt.res
+            qTy(qtf);
+
+            // now we don't need Q anymore,
+            // so let jacobian contain the R matrix with its diagonal elements
+            for (int k = 0; k < solvedCols; ++k) {
+                int pk = permutation[k];
+                weightedJacobian[k][pk] = diagR[pk];
+            }
+
+            if (firstIteration) {
+                // scale the point according to the norms of the columns
+                // of the initial jacobian
+                xNorm = 0;
+                for (int k = 0; k < nC; ++k) {
+                    double dk = jacNorm[k];
+                    if (dk == 0) {
+                        dk = 1.0;
+                    }
+                    double xk = dk * currentPoint[k];
+                    xNorm  += xk * xk;
+                    diag[k] = dk;
+                }
+                xNorm = FastMath.sqrt(xNorm);
+
+                // initialize the step bound delta
+                delta = (xNorm == 0) ? initialStepBoundFactor : (initialStepBoundFactor * xNorm);
+            }
+
+            // check orthogonality between function vector and jacobian columns
+            double maxCosine = 0;
+            if (currentCost != 0) {
+                for (int j = 0; j < solvedCols; ++j) {
+                    int    pj = permutation[j];
+                    double s  = jacNorm[pj];
+                    if (s != 0) {
+                        double sum = 0;
+                        for (int i = 0; i <= j; ++i) {
+                            sum += weightedJacobian[i][pj] * qtf[i];
+                        }
+                        maxCosine = FastMath.max(maxCosine, FastMath.abs(sum) / (s * currentCost));
+                    }
+                }
+            }
+            if (maxCosine <= orthoTolerance) {
+                // Convergence has been reached.
+                setCost(currentCost);
+                return current;
+            }
+
+            // rescale if necessary
+            for (int j = 0; j < nC; ++j) {
+                diag[j] = FastMath.max(diag[j], jacNorm[j]);
+            }
+
+            // Inner loop.
+            for (double ratio = 0; ratio < 1.0e-4;) {
+
+                // save the state
+                for (int j = 0; j < solvedCols; ++j) {
+                    int pj = permutation[j];
+                    oldX[pj] = currentPoint[pj];
+                }
+                final double previousCost = currentCost;
+                double[] tmpVec = weightedResidual;
+                weightedResidual = oldRes;
+                oldRes    = tmpVec;
+                tmpVec    = currentObjective;
+                currentObjective = oldObj;
+                oldObj    = tmpVec;
+
+                // determine the Levenberg-Marquardt parameter
+                determineLMParameter(qtf, delta, diag, work1, work2, work3);
+
+                // compute the new point and the norm of the evolution direction
+                double lmNorm = 0;
+                for (int j = 0; j < solvedCols; ++j) {
+                    int pj = permutation[j];
+                    lmDir[pj] = -lmDir[pj];
+                    currentPoint[pj] = oldX[pj] + lmDir[pj];
+                    double s = diag[pj] * lmDir[pj];
+                    lmNorm  += s * s;
+                }
+                lmNorm = FastMath.sqrt(lmNorm);
+                // on the first iteration, adjust the initial step bound.
+                if (firstIteration) {
+                    delta = FastMath.min(delta, lmNorm);
+                }
+
+                // Evaluate the function at x + p and calculate its norm.
+                currentObjective = computeObjectiveValue(currentPoint);
+                currentResiduals = computeResiduals(currentObjective);
+                current = new PointVectorValuePair(currentPoint, currentObjective);
+                currentCost = computeCost(currentResiduals);
+
+                // compute the scaled actual reduction
+                double actRed = -1.0;
+                if (0.1 * currentCost < previousCost) {
+                    double r = currentCost / previousCost;
+                    actRed = 1.0 - r * r;
+                }
+
+                // compute the scaled predicted reduction
+                // and the scaled directional derivative
+                for (int j = 0; j < solvedCols; ++j) {
+                    int pj = permutation[j];
+                    double dirJ = lmDir[pj];
+                    work1[j] = 0;
+                    for (int i = 0; i <= j; ++i) {
+                        work1[i] += weightedJacobian[i][pj] * dirJ;
+                    }
+                }
+                double coeff1 = 0;
+                for (int j = 0; j < solvedCols; ++j) {
+                    coeff1 += work1[j] * work1[j];
+                }
+                double pc2 = previousCost * previousCost;
+                coeff1 /= pc2;
+                double coeff2 = lmPar * lmNorm * lmNorm / pc2;
+                double preRed = coeff1 + 2 * coeff2;
+                double dirDer = -(coeff1 + coeff2);
+
+                // ratio of the actual to the predicted reduction
+                ratio = (preRed == 0) ? 0 : (actRed / preRed);
+
+                // update the step bound
+                if (ratio <= 0.25) {
+                    double tmp =
+                        (actRed < 0) ? (0.5 * dirDer / (dirDer + 0.5 * actRed)) : 0.5;
+                        if ((0.1 * currentCost >= previousCost) || (tmp < 0.1)) {
+                            tmp = 0.1;
+                        }
+                        delta = tmp * FastMath.min(delta, 10.0 * lmNorm);
+                        lmPar /= tmp;
+                } else if ((lmPar == 0) || (ratio >= 0.75)) {
+                    delta = 2 * lmNorm;
+                    lmPar *= 0.5;
+                }
+
+                // test for successful iteration.
+                if (ratio >= 1.0e-4) {
+                    // successful iteration, update the norm
+                    firstIteration = false;
+                    xNorm = 0;
+                    for (int k = 0; k < nC; ++k) {
+                        double xK = diag[k] * currentPoint[k];
+                        xNorm += xK * xK;
+                    }
+                    xNorm = FastMath.sqrt(xNorm);
+
+                    // tests for convergence.
+                    if (checker != null && checker.converged(getIterations(), previous, current)) {
+                        setCost(currentCost);
+                        return current;
+                    }
+                } else {
+                    // failed iteration, reset the previous values
+                    currentCost = previousCost;
+                    for (int j = 0; j < solvedCols; ++j) {
+                        int pj = permutation[j];
+                        currentPoint[pj] = oldX[pj];
+                    }
+                    tmpVec    = weightedResidual;
+                    weightedResidual = oldRes;
+                    oldRes    = tmpVec;
+                    tmpVec    = currentObjective;
+                    currentObjective = oldObj;
+                    oldObj    = tmpVec;
+                    // Reset "current" to previous values.
+                    current = new PointVectorValuePair(currentPoint, currentObjective);
+                }
+
+                // Default convergence criteria.
+                if ((FastMath.abs(actRed) <= costRelativeTolerance &&
+                     preRed <= costRelativeTolerance &&
+                     ratio <= 2.0) ||
+                    delta <= parRelativeTolerance * xNorm) {
+                    setCost(currentCost);
+                    return current;
+                }
+
+                // tests for termination and stringent tolerances
+                if (FastMath.abs(actRed) <= TWO_EPS &&
+                    preRed <= TWO_EPS &&
+                    ratio <= 2.0) {
+                    throw new ConvergenceException(LocalizedFormats.TOO_SMALL_COST_RELATIVE_TOLERANCE,
+                                                   costRelativeTolerance);
+                } else if (delta <= TWO_EPS * xNorm) {
+                    throw new ConvergenceException(LocalizedFormats.TOO_SMALL_PARAMETERS_RELATIVE_TOLERANCE,
+                                                   parRelativeTolerance);
+                } else if (maxCosine <= TWO_EPS) {
+                    throw new ConvergenceException(LocalizedFormats.TOO_SMALL_ORTHOGONALITY_TOLERANCE,
+                                                   orthoTolerance);
+                }
+            }
+        }
+    }
+
+    /**
+     * Determine the Levenberg-Marquardt parameter.
+     * <p>This implementation is a translation in Java of the MINPACK
+     * <a href="http://www.netlib.org/minpack/lmpar.f">lmpar</a>
+     * routine.</p>
+     * <p>This method sets the lmPar and lmDir attributes.</p>
+     * <p>The authors of the original fortran function are:</p>
+     * <ul>
+     *   <li>Argonne National Laboratory. MINPACK project. March 1980</li>
+     *   <li>Burton  S. Garbow</li>
+     *   <li>Kenneth E. Hillstrom</li>
+     *   <li>Jorge   J. More</li>
+     * </ul>
+     * <p>Luc Maisonobe did the Java translation.</p>
+     *
+     * @param qy array containing qTy
+     * @param delta upper bound on the euclidean norm of diagR * lmDir
+     * @param diag diagonal matrix
+     * @param work1 work array
+     * @param work2 work array
+     * @param work3 work array
+     */
+    private void determineLMParameter(double[] qy, double delta, double[] diag,
+                                      double[] work1, double[] work2, double[] work3) {
+        final int nC = weightedJacobian[0].length;
+
+        // compute and store in x the gauss-newton direction, if the
+        // jacobian is rank-deficient, obtain a least squares solution
+        for (int j = 0; j < rank; ++j) {
+            lmDir[permutation[j]] = qy[j];
+        }
+        for (int j = rank; j < nC; ++j) {
+            lmDir[permutation[j]] = 0;
+        }
+        for (int k = rank - 1; k >= 0; --k) {
+            int pk = permutation[k];
+            double ypk = lmDir[pk] / diagR[pk];
+            for (int i = 0; i < k; ++i) {
+                lmDir[permutation[i]] -= ypk * weightedJacobian[i][pk];
+            }
+            lmDir[pk] = ypk;
+        }
+
+        // evaluate the function at the origin, and test
+        // for acceptance of the Gauss-Newton direction
+        double dxNorm = 0;
+        for (int j = 0; j < solvedCols; ++j) {
+            int pj = permutation[j];
+            double s = diag[pj] * lmDir[pj];
+            work1[pj] = s;
+            dxNorm += s * s;
+        }
+        dxNorm = FastMath.sqrt(dxNorm);
+        double fp = dxNorm - delta;
+        if (fp <= 0.1 * delta) {
+            lmPar = 0;
+            return;
+        }
+
+        // if the jacobian is not rank deficient, the Newton step provides
+        // a lower bound, parl, for the zero of the function,
+        // otherwise set this bound to zero
+        double sum2;
+        double parl = 0;
+        if (rank == solvedCols) {
+            for (int j = 0; j < solvedCols; ++j) {
+                int pj = permutation[j];
+                work1[pj] *= diag[pj] / dxNorm;
+            }
+            sum2 = 0;
+            for (int j = 0; j < solvedCols; ++j) {
+                int pj = permutation[j];
+                double sum = 0;
+                for (int i = 0; i < j; ++i) {
+                    sum += weightedJacobian[i][pj] * work1[permutation[i]];
+                }
+                double s = (work1[pj] - sum) / diagR[pj];
+                work1[pj] = s;
+                sum2 += s * s;
+            }
+            parl = fp / (delta * sum2);
+        }
+
+        // calculate an upper bound, paru, for the zero of the function
+        sum2 = 0;
+        for (int j = 0; j < solvedCols; ++j) {
+            int pj = permutation[j];
+            double sum = 0;
+            for (int i = 0; i <= j; ++i) {
+                sum += weightedJacobian[i][pj] * qy[i];
+            }
+            sum /= diag[pj];
+            sum2 += sum * sum;
+        }
+        double gNorm = FastMath.sqrt(sum2);
+        double paru = gNorm / delta;
+        if (paru == 0) {
+            paru = Precision.SAFE_MIN / FastMath.min(delta, 0.1);
+        }
+
+        // if the input par lies outside of the interval (parl,paru),
+        // set par to the closer endpoint
+        lmPar = FastMath.min(paru, FastMath.max(lmPar, parl));
+        if (lmPar == 0) {
+            lmPar = gNorm / dxNorm;
+        }
+
+        for (int countdown = 10; countdown >= 0; --countdown) {
+
+            // evaluate the function at the current value of lmPar
+            if (lmPar == 0) {
+                lmPar = FastMath.max(Precision.SAFE_MIN, 0.001 * paru);
+            }
+            double sPar = FastMath.sqrt(lmPar);
+            for (int j = 0; j < solvedCols; ++j) {
+                int pj = permutation[j];
+                work1[pj] = sPar * diag[pj];
+            }
+            determineLMDirection(qy, work1, work2, work3);
+
+            dxNorm = 0;
+            for (int j = 0; j < solvedCols; ++j) {
+                int pj = permutation[j];
+                double s = diag[pj] * lmDir[pj];
+                work3[pj] = s;
+                dxNorm += s * s;
+            }
+            dxNorm = FastMath.sqrt(dxNorm);
+            double previousFP = fp;
+            fp = dxNorm - delta;
+
+            // if the function is small enough, accept the current value
+            // of lmPar, also test for the exceptional cases where parl is zero
+            if ((FastMath.abs(fp) <= 0.1 * delta) ||
+                    ((parl == 0) && (fp <= previousFP) && (previousFP < 0))) {
+                return;
+            }
+
+            // compute the Newton correction
+            for (int j = 0; j < solvedCols; ++j) {
+                int pj = permutation[j];
+                work1[pj] = work3[pj] * diag[pj] / dxNorm;
+            }
+            for (int j = 0; j < solvedCols; ++j) {
+                int pj = permutation[j];
+                work1[pj] /= work2[j];
+                double tmp = work1[pj];
+                for (int i = j + 1; i < solvedCols; ++i) {
+                    work1[permutation[i]] -= weightedJacobian[i][pj] * tmp;
+                }
+            }
+            sum2 = 0;
+            for (int j = 0; j < solvedCols; ++j) {
+                double s = work1[permutation[j]];
+                sum2 += s * s;
+            }
+            double correction = fp / (delta * sum2);
+
+            // depending on the sign of the function, update parl or paru.
+            if (fp > 0) {
+                parl = FastMath.max(parl, lmPar);
+            } else if (fp < 0) {
+                paru = FastMath.min(paru, lmPar);
+            }
+
+            // compute an improved estimate for lmPar
+            lmPar = FastMath.max(parl, lmPar + correction);
+
+        }
+    }
+
+    /**
+     * Solve a*x = b and d*x = 0 in the least squares sense.
+     * <p>This implementation is a translation in Java of the MINPACK
+     * <a href="http://www.netlib.org/minpack/qrsolv.f">qrsolv</a>
+     * routine.</p>
+     * <p>This method sets the lmDir and lmDiag attributes.</p>
+     * <p>The authors of the original fortran function are:</p>
+     * <ul>
+     *   <li>Argonne National Laboratory. MINPACK project. March 1980</li>
+     *   <li>Burton  S. Garbow</li>
+     *   <li>Kenneth E. Hillstrom</li>
+     *   <li>Jorge   J. More</li>
+     * </ul>
+     * <p>Luc Maisonobe did the Java translation.</p>
+     *
+     * @param qy array containing qTy
+     * @param diag diagonal matrix
+     * @param lmDiag diagonal elements associated with lmDir
+     * @param work work array
+     */
+    private void determineLMDirection(double[] qy, double[] diag,
+                                      double[] lmDiag, double[] work) {
+
+        // copy R and Qty to preserve input and initialize s
+        //  in particular, save the diagonal elements of R in lmDir
+        for (int j = 0; j < solvedCols; ++j) {
+            int pj = permutation[j];
+            for (int i = j + 1; i < solvedCols; ++i) {
+                weightedJacobian[i][pj] = weightedJacobian[j][permutation[i]];
+            }
+            lmDir[j] = diagR[pj];
+            work[j]  = qy[j];
+        }
+
+        // eliminate the diagonal matrix d using a Givens rotation
+        for (int j = 0; j < solvedCols; ++j) {
+
+            // prepare the row of d to be eliminated, locating the
+            // diagonal element using p from the Q.R. factorization
+            int pj = permutation[j];
+            double dpj = diag[pj];
+            if (dpj != 0) {
+                Arrays.fill(lmDiag, j + 1, lmDiag.length, 0);
+            }
+            lmDiag[j] = dpj;
+
+            //  the transformations to eliminate the row of d
+            // modify only a single element of Qty
+            // beyond the first n, which is initially zero.
+            double qtbpj = 0;
+            for (int k = j; k < solvedCols; ++k) {
+                int pk = permutation[k];
+
+                // determine a Givens rotation which eliminates the
+                // appropriate element in the current row of d
+                if (lmDiag[k] != 0) {
+
+                    final double sin;
+                    final double cos;
+                    double rkk = weightedJacobian[k][pk];
+                    if (FastMath.abs(rkk) < FastMath.abs(lmDiag[k])) {
+                        final double cotan = rkk / lmDiag[k];
+                        sin   = 1.0 / FastMath.sqrt(1.0 + cotan * cotan);
+                        cos   = sin * cotan;
+                    } else {
+                        final double tan = lmDiag[k] / rkk;
+                        cos = 1.0 / FastMath.sqrt(1.0 + tan * tan);
+                        sin = cos * tan;
+                    }
+
+                    // compute the modified diagonal element of R and
+                    // the modified element of (Qty,0)
+                    weightedJacobian[k][pk] = cos * rkk + sin * lmDiag[k];
+                    final double temp = cos * work[k] + sin * qtbpj;
+                    qtbpj = -sin * work[k] + cos * qtbpj;
+                    work[k] = temp;
+
+                    // accumulate the tranformation in the row of s
+                    for (int i = k + 1; i < solvedCols; ++i) {
+                        double rik = weightedJacobian[i][pk];
+                        final double temp2 = cos * rik + sin * lmDiag[i];
+                        lmDiag[i] = -sin * rik + cos * lmDiag[i];
+                        weightedJacobian[i][pk] = temp2;
+                    }
+                }
+            }
+
+            // store the diagonal element of s and restore
+            // the corresponding diagonal element of R
+            lmDiag[j] = weightedJacobian[j][permutation[j]];
+            weightedJacobian[j][permutation[j]] = lmDir[j];
+        }
+
+        // solve the triangular system for z, if the system is
+        // singular, then obtain a least squares solution
+        int nSing = solvedCols;
+        for (int j = 0; j < solvedCols; ++j) {
+            if ((lmDiag[j] == 0) && (nSing == solvedCols)) {
+                nSing = j;
+            }
+            if (nSing < solvedCols) {
+                work[j] = 0;
+            }
+        }
+        if (nSing > 0) {
+            for (int j = nSing - 1; j >= 0; --j) {
+                int pj = permutation[j];
+                double sum = 0;
+                for (int i = j + 1; i < nSing; ++i) {
+                    sum += weightedJacobian[i][pj] * work[i];
+                }
+                work[j] = (work[j] - sum) / lmDiag[j];
+            }
+        }
+
+        // permute the components of z back to components of lmDir
+        for (int j = 0; j < lmDir.length; ++j) {
+            lmDir[permutation[j]] = work[j];
+        }
+    }
+
+    /**
+     * Decompose a matrix A as A.P = Q.R using Householder transforms.
+     * <p>As suggested in the P. Lascaux and R. Theodor book
+     * <i>Analyse num&eacute;rique matricielle appliqu&eacute;e &agrave;
+     * l'art de l'ing&eacute;nieur</i> (Masson, 1986), instead of representing
+     * the Householder transforms with u<sub>k</sub> unit vectors such that:
+     * <pre>
+     * H<sub>k</sub> = I - 2u<sub>k</sub>.u<sub>k</sub><sup>t</sup>
+     * </pre>
+     * we use <sub>k</sub> non-unit vectors such that:
+     * <pre>
+     * H<sub>k</sub> = I - beta<sub>k</sub>v<sub>k</sub>.v<sub>k</sub><sup>t</sup>
+     * </pre>
+     * where v<sub>k</sub> = a<sub>k</sub> - alpha<sub>k</sub> e<sub>k</sub>.
+     * The beta<sub>k</sub> coefficients are provided upon exit as recomputing
+     * them from the v<sub>k</sub> vectors would be costly.</p>
+     * <p>This decomposition handles rank deficient cases since the tranformations
+     * are performed in non-increasing columns norms order thanks to columns
+     * pivoting. The diagonal elements of the R matrix are therefore also in
+     * non-increasing absolute values order.</p>
+     *
+     * @param jacobian Weighted Jacobian matrix at the current point.
+     * @exception ConvergenceException if the decomposition cannot be performed
+     */
+    private void qrDecomposition(RealMatrix jacobian) throws ConvergenceException {
+        // Code in this class assumes that the weighted Jacobian is -(W^(1/2) J),
+        // hence the multiplication by -1.
+        weightedJacobian = jacobian.scalarMultiply(-1).getData();
+
+        final int nR = weightedJacobian.length;
+        final int nC = weightedJacobian[0].length;
+
+        // initializations
+        for (int k = 0; k < nC; ++k) {
+            permutation[k] = k;
+            double norm2 = 0;
+            for (int i = 0; i < nR; ++i) {
+                double akk = weightedJacobian[i][k];
+                norm2 += akk * akk;
+            }
+            jacNorm[k] = FastMath.sqrt(norm2);
+        }
+
+        // transform the matrix column after column
+        for (int k = 0; k < nC; ++k) {
+
+            // select the column with the greatest norm on active components
+            int nextColumn = -1;
+            double ak2 = Double.NEGATIVE_INFINITY;
+            for (int i = k; i < nC; ++i) {
+                double norm2 = 0;
+                for (int j = k; j < nR; ++j) {
+                    double aki = weightedJacobian[j][permutation[i]];
+                    norm2 += aki * aki;
+                }
+                if (Double.isInfinite(norm2) || Double.isNaN(norm2)) {
+                    throw new ConvergenceException(LocalizedFormats.UNABLE_TO_PERFORM_QR_DECOMPOSITION_ON_JACOBIAN,
+                                                   nR, nC);
+                }
+                if (norm2 > ak2) {
+                    nextColumn = i;
+                    ak2        = norm2;
+                }
+            }
+            if (ak2 <= qrRankingThreshold) {
+                rank = k;
+                return;
+            }
+            int pk                  = permutation[nextColumn];
+            permutation[nextColumn] = permutation[k];
+            permutation[k]          = pk;
+
+            // choose alpha such that Hk.u = alpha ek
+            double akk   = weightedJacobian[k][pk];
+            double alpha = (akk > 0) ? -FastMath.sqrt(ak2) : FastMath.sqrt(ak2);
+            double betak = 1.0 / (ak2 - akk * alpha);
+            beta[pk]     = betak;
+
+            // transform the current column
+            diagR[pk]        = alpha;
+            weightedJacobian[k][pk] -= alpha;
+
+            // transform the remaining columns
+            for (int dk = nC - 1 - k; dk > 0; --dk) {
+                double gamma = 0;
+                for (int j = k; j < nR; ++j) {
+                    gamma += weightedJacobian[j][pk] * weightedJacobian[j][permutation[k + dk]];
+                }
+                gamma *= betak;
+                for (int j = k; j < nR; ++j) {
+                    weightedJacobian[j][permutation[k + dk]] -= gamma * weightedJacobian[j][pk];
+                }
+            }
+        }
+        rank = solvedCols;
+    }
+
+    /**
+     * Compute the product Qt.y for some Q.R. decomposition.
+     *
+     * @param y vector to multiply (will be overwritten with the result)
+     */
+    private void qTy(double[] y) {
+        final int nR = weightedJacobian.length;
+        final int nC = weightedJacobian[0].length;
+
+        for (int k = 0; k < nC; ++k) {
+            int pk = permutation[k];
+            double gamma = 0;
+            for (int i = k; i < nR; ++i) {
+                gamma += weightedJacobian[i][pk] * y[i];
+            }
+            gamma *= beta[pk];
+            for (int i = k; i < nR; ++i) {
+                y[i] -= gamma * weightedJacobian[i][pk];
+            }
+        }
+    }
+
+    /**
+     * @throws MathUnsupportedOperationException if bounds were passed to the
+     * {@link #optimize(OptimizationData[]) optimize} method.
+     */
+    private void checkParameters() {
+        if (getLowerBound() != null ||
+            getUpperBound() != null) {
+            throw new MathUnsupportedOperationException(LocalizedFormats.CONSTRAINT);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/jacobian/package-info.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/jacobian/package-info.java
new file mode 100644
index 0000000..4c844ba
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/jacobian/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+/**
+ * This package provides optimization algorithms that require derivatives.
+ *
+ * @deprecated All classes and interfaces in this package are deprecated.
+ * The optimizers that were provided here were moved to the
+ * {@link org.apache.commons.math3.fitting.leastsquares} package
+ * (cf. MATH-1008).
+ */
+package org.apache.commons.math3.optim.nonlinear.vector.jacobian;
diff --git a/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/package-info.java b/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/package-info.java
new file mode 100644
index 0000000..439fc3c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/nonlinear/vector/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+/**
+ * Algorithms for optimizing a vector function.
+ *
+ * @deprecated All classes and interfaces in this package are deprecated.
+ * The optimizers that were provided here were moved to the
+ * {@link org.apache.commons.math3.fitting.leastsquares} package
+ * (cf. MATH-1008).
+ */
+package org.apache.commons.math3.optim.nonlinear.vector;
diff --git a/src/main/java/org/apache/commons/math3/optim/package-info.java b/src/main/java/org/apache/commons/math3/optim/package-info.java
new file mode 100644
index 0000000..e2f3c9f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/package-info.java
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+
+/**
+ * Generally, optimizers are algorithms that will either {@link
+ * org.apache.commons.math3.optim.nonlinear.scalar.GoalType#MINIMIZE minimize} or {@link
+ * org.apache.commons.math3.optim.nonlinear.scalar.GoalType#MAXIMIZE maximize} a scalar function,
+ * called the {@link org.apache.commons.math3.optim.nonlinear.scalar.ObjectiveFunction <em>objective
+ * function</em>}. <br>
+ * For some scalar objective functions the gradient can be computed (analytically or numerically).
+ * Algorithms that use this knowledge are defined in the {@link
+ * org.apache.commons.math3.optim.nonlinear.scalar.gradient} package. The algorithms that do not
+ * need this additional information are located in the {@link
+ * org.apache.commons.math3.optim.nonlinear.scalar.noderiv} package.
+ *
+ * <p>Some problems are solved more efficiently by algorithms that, instead of an objective
+ * function, need access to a {@link org.apache.commons.math3.optim.nonlinear.vector.ModelFunction
+ * <em>model function</em>}: such a model predicts a set of values which the algorithm tries to
+ * match with a set of given {@link org.apache.commons.math3.optim.nonlinear.vector.Target target
+ * values}. Those algorithms are located in the {@link
+ * org.apache.commons.math3.optim.nonlinear.vector} package. <br>
+ * Algorithms that also require the {@link
+ * org.apache.commons.math3.optim.nonlinear.vector.ModelFunctionJacobian Jacobian matrix of the
+ * model} are located in the {@link org.apache.commons.math3.optim.nonlinear.vector.jacobian}
+ * package. <br>
+ * The {@link org.apache.commons.math3.optim.nonlinear.vector.jacobian.AbstractLeastSquaresOptimizer
+ * non-linear least-squares optimizers} are a specialization of the the latter, that minimize the
+ * distance (called <em>cost</em> or <em>&chi;<sup>2</sup></em>) between model and observations.
+ * <br>
+ * For cases where the Jacobian cannot be provided, a utility class will {@link
+ * org.apache.commons.math3.optim.nonlinear.scalar.LeastSquaresConverter convert} a (vector) model
+ * into a (scalar) objective function.
+ *
+ * <p>This package provides common functionality for the optimization algorithms. Abstract classes
+ * ({@link org.apache.commons.math3.optim.BaseOptimizer} and {@link
+ * org.apache.commons.math3.optim.BaseMultivariateOptimizer}) contain boiler-plate code for storing
+ * {@link org.apache.commons.math3.optim.MaxEval evaluations} and {@link
+ * org.apache.commons.math3.optim.MaxIter iterations} counters and a user-defined {@link
+ * org.apache.commons.math3.optim.ConvergenceChecker convergence checker}.
+ *
+ * <p>For each of the optimizer types, there is a special implementation that wraps an optimizer
+ * instance and provides a "multi-start" feature: it calls the underlying optimizer several times
+ * with different starting points and returns the best optimum found, or all optima if so desired.
+ * This could be useful to avoid being trapped in a local extremum.
+ */
+package org.apache.commons.math3.optim;
diff --git a/src/main/java/org/apache/commons/math3/optim/univariate/BracketFinder.java b/src/main/java/org/apache/commons/math3/optim/univariate/BracketFinder.java
new file mode 100644
index 0000000..6d42c0d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/univariate/BracketFinder.java
@@ -0,0 +1,290 @@
+/*
+ * 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.math3.optim.univariate;
+
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.IntegerSequence;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.optim.nonlinear.scalar.GoalType;
+
+/**
+ * Provide an interval that brackets a local optimum of a function.
+ * This code is based on a Python implementation (from <em>SciPy</em>,
+ * module {@code optimize.py} v0.5).
+ *
+ * @since 2.2
+ */
+public class BracketFinder {
+    /** Tolerance to avoid division by zero. */
+    private static final double EPS_MIN = 1e-21;
+    /**
+     * Golden section.
+     */
+    private static final double GOLD = 1.618034;
+    /**
+     * Factor for expanding the interval.
+     */
+    private final double growLimit;
+    /**
+     * Counter for function evaluations.
+     */
+    private IntegerSequence.Incrementor evaluations;
+    /**
+     * Lower bound of the bracket.
+     */
+    private double lo;
+    /**
+     * Higher bound of the bracket.
+     */
+    private double hi;
+    /**
+     * Point inside the bracket.
+     */
+    private double mid;
+    /**
+     * Function value at {@link #lo}.
+     */
+    private double fLo;
+    /**
+     * Function value at {@link #hi}.
+     */
+    private double fHi;
+    /**
+     * Function value at {@link #mid}.
+     */
+    private double fMid;
+
+    /**
+     * Constructor with default values {@code 100, 500} (see the
+     * {@link #BracketFinder(double,int) other constructor}).
+     */
+    public BracketFinder() {
+        this(100, 500);
+    }
+
+    /**
+     * Create a bracketing interval finder.
+     *
+     * @param growLimit Expanding factor.
+     * @param maxEvaluations Maximum number of evaluations allowed for finding
+     * a bracketing interval.
+     */
+    public BracketFinder(double growLimit,
+                         int maxEvaluations) {
+        if (growLimit <= 0) {
+            throw new NotStrictlyPositiveException(growLimit);
+        }
+        if (maxEvaluations <= 0) {
+            throw new NotStrictlyPositiveException(maxEvaluations);
+        }
+
+        this.growLimit = growLimit;
+        evaluations = IntegerSequence.Incrementor.create().withMaximalCount(maxEvaluations);
+    }
+
+    /**
+     * Search new points that bracket a local optimum of the function.
+     *
+     * @param func Function whose optimum should be bracketed.
+     * @param goal {@link GoalType Goal type}.
+     * @param xA Initial point.
+     * @param xB Initial point.
+     * @throws TooManyEvaluationsException if the maximum number of evaluations
+     * is exceeded.
+     */
+    public void search(UnivariateFunction func,
+                       GoalType goal,
+                       double xA,
+                       double xB) {
+        evaluations = evaluations.withStart(0);
+        final boolean isMinim = goal == GoalType.MINIMIZE;
+
+        double fA = eval(func, xA);
+        double fB = eval(func, xB);
+        if (isMinim ?
+            fA < fB :
+            fA > fB) {
+
+            double tmp = xA;
+            xA = xB;
+            xB = tmp;
+
+            tmp = fA;
+            fA = fB;
+            fB = tmp;
+        }
+
+        double xC = xB + GOLD * (xB - xA);
+        double fC = eval(func, xC);
+
+        while (isMinim ? fC < fB : fC > fB) {
+            double tmp1 = (xB - xA) * (fB - fC);
+            double tmp2 = (xB - xC) * (fB - fA);
+
+            double val = tmp2 - tmp1;
+            double denom = FastMath.abs(val) < EPS_MIN ? 2 * EPS_MIN : 2 * val;
+
+            double w = xB - ((xB - xC) * tmp2 - (xB - xA) * tmp1) / denom;
+            double wLim = xB + growLimit * (xC - xB);
+
+            double fW;
+            if ((w - xC) * (xB - w) > 0) {
+                fW = eval(func, w);
+                if (isMinim ?
+                    fW < fC :
+                    fW > fC) {
+                    xA = xB;
+                    xB = w;
+                    fA = fB;
+                    fB = fW;
+                    break;
+                } else if (isMinim ?
+                           fW > fB :
+                           fW < fB) {
+                    xC = w;
+                    fC = fW;
+                    break;
+                }
+                w = xC + GOLD * (xC - xB);
+                fW = eval(func, w);
+            } else if ((w - wLim) * (wLim - xC) >= 0) {
+                w = wLim;
+                fW = eval(func, w);
+            } else if ((w - wLim) * (xC - w) > 0) {
+                fW = eval(func, w);
+                if (isMinim ?
+                    fW < fC :
+                    fW > fC) {
+                    xB = xC;
+                    xC = w;
+                    w = xC + GOLD * (xC - xB);
+                    fB = fC;
+                    fC =fW;
+                    fW = eval(func, w);
+                }
+            } else {
+                w = xC + GOLD * (xC - xB);
+                fW = eval(func, w);
+            }
+
+            xA = xB;
+            fA = fB;
+            xB = xC;
+            fB = fC;
+            xC = w;
+            fC = fW;
+        }
+
+        lo = xA;
+        fLo = fA;
+        mid = xB;
+        fMid = fB;
+        hi = xC;
+        fHi = fC;
+
+        if (lo > hi) {
+            double tmp = lo;
+            lo = hi;
+            hi = tmp;
+
+            tmp = fLo;
+            fLo = fHi;
+            fHi = tmp;
+        }
+    }
+
+    /**
+     * @return the number of evalutations.
+     */
+    public int getMaxEvaluations() {
+        return evaluations.getMaximalCount();
+    }
+
+    /**
+     * @return the number of evalutations.
+     */
+    public int getEvaluations() {
+        return evaluations.getCount();
+    }
+
+    /**
+     * @return the lower bound of the bracket.
+     * @see #getFLo()
+     */
+    public double getLo() {
+        return lo;
+    }
+
+    /**
+     * Get function value at {@link #getLo()}.
+     * @return function value at {@link #getLo()}
+     */
+    public double getFLo() {
+        return fLo;
+    }
+
+    /**
+     * @return the higher bound of the bracket.
+     * @see #getFHi()
+     */
+    public double getHi() {
+        return hi;
+    }
+
+    /**
+     * Get function value at {@link #getHi()}.
+     * @return function value at {@link #getHi()}
+     */
+    public double getFHi() {
+        return fHi;
+    }
+
+    /**
+     * @return a point in the middle of the bracket.
+     * @see #getFMid()
+     */
+    public double getMid() {
+        return mid;
+    }
+
+    /**
+     * Get function value at {@link #getMid()}.
+     * @return function value at {@link #getMid()}
+     */
+    public double getFMid() {
+        return fMid;
+    }
+
+    /**
+     * @param f Function.
+     * @param x Argument.
+     * @return {@code f(x)}
+     * @throws TooManyEvaluationsException if the maximal number of evaluations is
+     * exceeded.
+     */
+    private double eval(UnivariateFunction f, double x) {
+        try {
+            evaluations.increment();
+        } catch (MaxCountExceededException e) {
+            throw new TooManyEvaluationsException(e.getMax());
+        }
+        return f.value(x);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/univariate/BrentOptimizer.java b/src/main/java/org/apache/commons/math3/optim/univariate/BrentOptimizer.java
new file mode 100644
index 0000000..d783405
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/univariate/BrentOptimizer.java
@@ -0,0 +1,314 @@
+/*
+ * 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.math3.optim.univariate;
+
+import org.apache.commons.math3.util.Precision;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.optim.ConvergenceChecker;
+import org.apache.commons.math3.optim.nonlinear.scalar.GoalType;
+
+/**
+ * For a function defined on some interval {@code (lo, hi)}, this class
+ * finds an approximation {@code x} to the point at which the function
+ * attains its minimum.
+ * It implements Richard Brent's algorithm (from his book "Algorithms for
+ * Minimization without Derivatives", p. 79) for finding minima of real
+ * univariate functions.
+ * <br/>
+ * This code is an adaptation, partly based on the Python code from SciPy
+ * (module "optimize.py" v0.5); the original algorithm is also modified
+ * <ul>
+ *  <li>to use an initial guess provided by the user,</li>
+ *  <li>to ensure that the best point encountered is the one returned.</li>
+ * </ul>
+ *
+ * @since 2.0
+ */
+public class BrentOptimizer extends UnivariateOptimizer {
+    /**
+     * Golden section.
+     */
+    private static final double GOLDEN_SECTION = 0.5 * (3 - FastMath.sqrt(5));
+    /**
+     * Minimum relative tolerance.
+     */
+    private static final double MIN_RELATIVE_TOLERANCE = 2 * FastMath.ulp(1d);
+    /**
+     * Relative threshold.
+     */
+    private final double relativeThreshold;
+    /**
+     * Absolute threshold.
+     */
+    private final double absoluteThreshold;
+
+    /**
+     * The arguments are used implement the original stopping criterion
+     * of Brent's algorithm.
+     * {@code abs} and {@code rel} define a tolerance
+     * {@code tol = rel |x| + abs}. {@code rel} should be no smaller than
+     * <em>2 macheps</em> and preferably not much less than <em>sqrt(macheps)</em>,
+     * where <em>macheps</em> is the relative machine precision. {@code abs} must
+     * be positive.
+     *
+     * @param rel Relative threshold.
+     * @param abs Absolute threshold.
+     * @param checker Additional, user-defined, convergence checking
+     * procedure.
+     * @throws NotStrictlyPositiveException if {@code abs <= 0}.
+     * @throws NumberIsTooSmallException if {@code rel < 2 * Math.ulp(1d)}.
+     */
+    public BrentOptimizer(double rel,
+                          double abs,
+                          ConvergenceChecker<UnivariatePointValuePair> checker) {
+        super(checker);
+
+        if (rel < MIN_RELATIVE_TOLERANCE) {
+            throw new NumberIsTooSmallException(rel, MIN_RELATIVE_TOLERANCE, true);
+        }
+        if (abs <= 0) {
+            throw new NotStrictlyPositiveException(abs);
+        }
+
+        relativeThreshold = rel;
+        absoluteThreshold = abs;
+    }
+
+    /**
+     * The arguments are used for implementing the original stopping criterion
+     * of Brent's algorithm.
+     * {@code abs} and {@code rel} define a tolerance
+     * {@code tol = rel |x| + abs}. {@code rel} should be no smaller than
+     * <em>2 macheps</em> and preferably not much less than <em>sqrt(macheps)</em>,
+     * where <em>macheps</em> is the relative machine precision. {@code abs} must
+     * be positive.
+     *
+     * @param rel Relative threshold.
+     * @param abs Absolute threshold.
+     * @throws NotStrictlyPositiveException if {@code abs <= 0}.
+     * @throws NumberIsTooSmallException if {@code rel < 2 * Math.ulp(1d)}.
+     */
+    public BrentOptimizer(double rel,
+                          double abs) {
+        this(rel, abs, null);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected UnivariatePointValuePair doOptimize() {
+        final boolean isMinim = getGoalType() == GoalType.MINIMIZE;
+        final double lo = getMin();
+        final double mid = getStartValue();
+        final double hi = getMax();
+
+        // Optional additional convergence criteria.
+        final ConvergenceChecker<UnivariatePointValuePair> checker
+            = getConvergenceChecker();
+
+        double a;
+        double b;
+        if (lo < hi) {
+            a = lo;
+            b = hi;
+        } else {
+            a = hi;
+            b = lo;
+        }
+
+        double x = mid;
+        double v = x;
+        double w = x;
+        double d = 0;
+        double e = 0;
+        double fx = computeObjectiveValue(x);
+        if (!isMinim) {
+            fx = -fx;
+        }
+        double fv = fx;
+        double fw = fx;
+
+        UnivariatePointValuePair previous = null;
+        UnivariatePointValuePair current
+            = new UnivariatePointValuePair(x, isMinim ? fx : -fx);
+        // Best point encountered so far (which is the initial guess).
+        UnivariatePointValuePair best = current;
+
+        while (true) {
+            final double m = 0.5 * (a + b);
+            final double tol1 = relativeThreshold * FastMath.abs(x) + absoluteThreshold;
+            final double tol2 = 2 * tol1;
+
+            // Default stopping criterion.
+            final boolean stop = FastMath.abs(x - m) <= tol2 - 0.5 * (b - a);
+            if (!stop) {
+                double p = 0;
+                double q = 0;
+                double r = 0;
+                double u = 0;
+
+                if (FastMath.abs(e) > tol1) { // Fit parabola.
+                    r = (x - w) * (fx - fv);
+                    q = (x - v) * (fx - fw);
+                    p = (x - v) * q - (x - w) * r;
+                    q = 2 * (q - r);
+
+                    if (q > 0) {
+                        p = -p;
+                    } else {
+                        q = -q;
+                    }
+
+                    r = e;
+                    e = d;
+
+                    if (p > q * (a - x) &&
+                        p < q * (b - x) &&
+                        FastMath.abs(p) < FastMath.abs(0.5 * q * r)) {
+                        // Parabolic interpolation step.
+                        d = p / q;
+                        u = x + d;
+
+                        // f must not be evaluated too close to a or b.
+                        if (u - a < tol2 || b - u < tol2) {
+                            if (x <= m) {
+                                d = tol1;
+                            } else {
+                                d = -tol1;
+                            }
+                        }
+                    } else {
+                        // Golden section step.
+                        if (x < m) {
+                            e = b - x;
+                        } else {
+                            e = a - x;
+                        }
+                        d = GOLDEN_SECTION * e;
+                    }
+                } else {
+                    // Golden section step.
+                    if (x < m) {
+                        e = b - x;
+                    } else {
+                        e = a - x;
+                    }
+                    d = GOLDEN_SECTION * e;
+                }
+
+                // Update by at least "tol1".
+                if (FastMath.abs(d) < tol1) {
+                    if (d >= 0) {
+                        u = x + tol1;
+                    } else {
+                        u = x - tol1;
+                    }
+                } else {
+                    u = x + d;
+                }
+
+                double fu = computeObjectiveValue(u);
+                if (!isMinim) {
+                    fu = -fu;
+                }
+
+                // User-defined convergence checker.
+                previous = current;
+                current = new UnivariatePointValuePair(u, isMinim ? fu : -fu);
+                best = best(best,
+                            best(previous,
+                                 current,
+                                 isMinim),
+                            isMinim);
+
+                if (checker != null && checker.converged(getIterations(), previous, current)) {
+                    return best;
+                }
+
+                // Update a, b, v, w and x.
+                if (fu <= fx) {
+                    if (u < x) {
+                        b = x;
+                    } else {
+                        a = x;
+                    }
+                    v = w;
+                    fv = fw;
+                    w = x;
+                    fw = fx;
+                    x = u;
+                    fx = fu;
+                } else {
+                    if (u < x) {
+                        a = u;
+                    } else {
+                        b = u;
+                    }
+                    if (fu <= fw ||
+                        Precision.equals(w, x)) {
+                        v = w;
+                        fv = fw;
+                        w = u;
+                        fw = fu;
+                    } else if (fu <= fv ||
+                               Precision.equals(v, x) ||
+                               Precision.equals(v, w)) {
+                        v = u;
+                        fv = fu;
+                    }
+                }
+            } else { // Default termination (Brent's criterion).
+                return best(best,
+                            best(previous,
+                                 current,
+                                 isMinim),
+                            isMinim);
+            }
+
+            incrementIterationCount();
+        }
+    }
+
+    /**
+     * Selects the best of two points.
+     *
+     * @param a Point and value.
+     * @param b Point and value.
+     * @param isMinim {@code true} if the selected point must be the one with
+     * the lowest value.
+     * @return the best point, or {@code null} if {@code a} and {@code b} are
+     * both {@code null}. When {@code a} and {@code b} have the same function
+     * value, {@code a} is returned.
+     */
+    private UnivariatePointValuePair best(UnivariatePointValuePair a,
+                                          UnivariatePointValuePair b,
+                                          boolean isMinim) {
+        if (a == null) {
+            return b;
+        }
+        if (b == null) {
+            return a;
+        }
+
+        if (isMinim) {
+            return a.getValue() <= b.getValue() ? a : b;
+        } else {
+            return a.getValue() >= b.getValue() ? a : b;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/univariate/MultiStartUnivariateOptimizer.java b/src/main/java/org/apache/commons/math3/optim/univariate/MultiStartUnivariateOptimizer.java
new file mode 100644
index 0000000..d12ec97
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/univariate/MultiStartUnivariateOptimizer.java
@@ -0,0 +1,228 @@
+/*
+ * 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.math3.optim.univariate;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.optim.MaxEval;
+import org.apache.commons.math3.optim.nonlinear.scalar.GoalType;
+import org.apache.commons.math3.optim.OptimizationData;
+
+/**
+ * Special implementation of the {@link UnivariateOptimizer} interface
+ * adding multi-start features to an existing optimizer.
+ * <br/>
+ * This class wraps an optimizer in order to use it several times in
+ * turn with different starting points (trying to avoid being trapped
+ * in a local extremum when looking for a global one).
+ *
+ * @since 3.0
+ */
+public class MultiStartUnivariateOptimizer
+    extends UnivariateOptimizer {
+    /** Underlying classical optimizer. */
+    private final UnivariateOptimizer optimizer;
+    /** Number of evaluations already performed for all starts. */
+    private int totalEvaluations;
+    /** Number of starts to go. */
+    private int starts;
+    /** Random generator for multi-start. */
+    private RandomGenerator generator;
+    /** Found optima. */
+    private UnivariatePointValuePair[] optima;
+    /** Optimization data. */
+    private OptimizationData[] optimData;
+    /**
+     * Location in {@link #optimData} where the updated maximum
+     * number of evaluations will be stored.
+     */
+    private int maxEvalIndex = -1;
+    /**
+     * Location in {@link #optimData} where the updated start value
+     * will be stored.
+     */
+    private int searchIntervalIndex = -1;
+
+    /**
+     * Create a multi-start optimizer from a single-start optimizer.
+     *
+     * @param optimizer Single-start optimizer to wrap.
+     * @param starts Number of starts to perform. If {@code starts == 1},
+     * the {@code optimize} methods will return the same solution as
+     * {@code optimizer} would.
+     * @param generator Random generator to use for restarts.
+     * @throws NotStrictlyPositiveException if {@code starts < 1}.
+     */
+    public MultiStartUnivariateOptimizer(final UnivariateOptimizer optimizer,
+                                         final int starts,
+                                         final RandomGenerator generator) {
+        super(optimizer.getConvergenceChecker());
+
+        if (starts < 1) {
+            throw new NotStrictlyPositiveException(starts);
+        }
+
+        this.optimizer = optimizer;
+        this.starts = starts;
+        this.generator = generator;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getEvaluations() {
+        return totalEvaluations;
+    }
+
+    /**
+     * Gets all the optima found during the last call to {@code optimize}.
+     * The optimizer stores all the optima found during a set of
+     * restarts. The {@code optimize} method returns the best point only.
+     * This method returns all the points found at the end of each starts,
+     * including the best one already returned by the {@code optimize} method.
+     * <br/>
+     * The returned array as one element for each start as specified
+     * in the constructor. It is ordered with the results from the
+     * runs that did converge first, sorted from best to worst
+     * objective value (i.e in ascending order if minimizing and in
+     * descending order if maximizing), followed by {@code null} elements
+     * corresponding to the runs that did not converge. This means all
+     * elements will be {@code null} if the {@code optimize} method did throw
+     * an exception.
+     * This also means that if the first element is not {@code null}, it is
+     * the best point found across all starts.
+     *
+     * @return an array containing the optima.
+     * @throws MathIllegalStateException if {@link #optimize(OptimizationData[])
+     * optimize} has not been called.
+     */
+    public UnivariatePointValuePair[] getOptima() {
+        if (optima == null) {
+            throw new MathIllegalStateException(LocalizedFormats.NO_OPTIMUM_COMPUTED_YET);
+        }
+        return optima.clone();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws MathIllegalStateException if {@code optData} does not contain an
+     * instance of {@link MaxEval} or {@link SearchInterval}.
+     */
+    @Override
+    public UnivariatePointValuePair optimize(OptimizationData... optData) {
+        // Store arguments in order to pass them to the internal optimizer.
+       optimData = optData;
+        // Set up base class and perform computations.
+        return super.optimize(optData);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected UnivariatePointValuePair doOptimize() {
+        // Remove all instances of "MaxEval" and "SearchInterval" from the
+        // array that will be passed to the internal optimizer.
+        // The former is to enforce smaller numbers of allowed evaluations
+        // (according to how many have been used up already), and the latter
+        // to impose a different start value for each start.
+        for (int i = 0; i < optimData.length; i++) {
+            if (optimData[i] instanceof MaxEval) {
+                optimData[i] = null;
+                maxEvalIndex = i;
+                continue;
+            }
+            if (optimData[i] instanceof SearchInterval) {
+                optimData[i] = null;
+                searchIntervalIndex = i;
+                continue;
+            }
+        }
+        if (maxEvalIndex == -1) {
+            throw new MathIllegalStateException();
+        }
+        if (searchIntervalIndex == -1) {
+            throw new MathIllegalStateException();
+        }
+
+        RuntimeException lastException = null;
+        optima = new UnivariatePointValuePair[starts];
+        totalEvaluations = 0;
+
+        final int maxEval = getMaxEvaluations();
+        final double min = getMin();
+        final double max = getMax();
+        final double startValue = getStartValue();
+
+        // Multi-start loop.
+        for (int i = 0; i < starts; i++) {
+            // CHECKSTYLE: stop IllegalCatch
+            try {
+                // Decrease number of allowed evaluations.
+                optimData[maxEvalIndex] = new MaxEval(maxEval - totalEvaluations);
+                // New start value.
+                final double s = (i == 0) ?
+                    startValue :
+                    min + generator.nextDouble() * (max - min);
+                optimData[searchIntervalIndex] = new SearchInterval(min, max, s);
+                // Optimize.
+                optima[i] = optimizer.optimize(optimData);
+            } catch (RuntimeException mue) {
+                lastException = mue;
+                optima[i] = null;
+            }
+            // CHECKSTYLE: resume IllegalCatch
+
+            totalEvaluations += optimizer.getEvaluations();
+        }
+
+        sortPairs(getGoalType());
+
+        if (optima[0] == null) {
+            throw lastException; // Cannot be null if starts >= 1.
+        }
+
+        // Return the point with the best objective function value.
+        return optima[0];
+    }
+
+    /**
+     * Sort the optima from best to worst, followed by {@code null} elements.
+     *
+     * @param goal Goal type.
+     */
+    private void sortPairs(final GoalType goal) {
+        Arrays.sort(optima, new Comparator<UnivariatePointValuePair>() {
+                /** {@inheritDoc} */
+                public int compare(final UnivariatePointValuePair o1,
+                                   final UnivariatePointValuePair o2) {
+                    if (o1 == null) {
+                        return (o2 == null) ? 0 : 1;
+                    } else if (o2 == null) {
+                        return -1;
+                    }
+                    final double v1 = o1.getValue();
+                    final double v2 = o2.getValue();
+                    return (goal == GoalType.MINIMIZE) ?
+                        Double.compare(v1, v2) : Double.compare(v2, v1);
+                }
+            });
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/univariate/SearchInterval.java b/src/main/java/org/apache/commons/math3/optim/univariate/SearchInterval.java
new file mode 100644
index 0000000..fa80e64
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/univariate/SearchInterval.java
@@ -0,0 +1,95 @@
+/*
+ * 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.math3.optim.univariate;
+
+import org.apache.commons.math3.optim.OptimizationData;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+
+/**
+ * Search interval and (optional) start value.
+ * <br/>
+ * Immutable class.
+ *
+ * @since 3.1
+ */
+public class SearchInterval implements OptimizationData {
+    /** Lower bound. */
+    private final double lower;
+    /** Upper bound. */
+    private final double upper;
+    /** Start value. */
+    private final double start;
+
+    /**
+     * @param lo Lower bound.
+     * @param hi Upper bound.
+     * @param init Start value.
+     * @throws NumberIsTooLargeException if {@code lo >= hi}.
+     * @throws OutOfRangeException if {@code init < lo} or {@code init > hi}.
+     */
+    public SearchInterval(double lo,
+                          double hi,
+                          double init) {
+        if (lo >= hi) {
+            throw new NumberIsTooLargeException(lo, hi, false);
+        }
+        if (init < lo ||
+            init > hi) {
+            throw new OutOfRangeException(init, lo, hi);
+        }
+
+        lower = lo;
+        upper = hi;
+        start = init;
+    }
+
+    /**
+     * @param lo Lower bound.
+     * @param hi Upper bound.
+     * @throws NumberIsTooLargeException if {@code lo >= hi}.
+     */
+    public SearchInterval(double lo,
+                          double hi) {
+        this(lo, hi, 0.5 * (lo + hi));
+    }
+
+    /**
+     * Gets the lower bound.
+     *
+     * @return the lower bound.
+     */
+    public double getMin() {
+        return lower;
+    }
+    /**
+     * Gets the upper bound.
+     *
+     * @return the upper bound.
+     */
+    public double getMax() {
+        return upper;
+    }
+    /**
+     * Gets the start value.
+     *
+     * @return the start value.
+     */
+    public double getStartValue() {
+        return start;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/univariate/SimpleUnivariateValueChecker.java b/src/main/java/org/apache/commons/math3/optim/univariate/SimpleUnivariateValueChecker.java
new file mode 100644
index 0000000..58cc521
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/univariate/SimpleUnivariateValueChecker.java
@@ -0,0 +1,127 @@
+/*
+ * 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.math3.optim.univariate;
+
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.optim.AbstractConvergenceChecker;
+
+/**
+ * Simple implementation of the
+ * {@link org.apache.commons.math3.optimization.ConvergenceChecker} interface
+ * that uses only objective function values.
+ *
+ * Convergence is considered to have been reached if either the relative
+ * difference between the objective function values is smaller than a
+ * threshold or if either the absolute difference between the objective
+ * function values is smaller than another threshold.
+ * <br/>
+ * The {@link #converged(int,UnivariatePointValuePair,UnivariatePointValuePair)
+ * converged} method will also return {@code true} if the number of iterations
+ * has been set (see {@link #SimpleUnivariateValueChecker(double,double,int)
+ * this constructor}).
+ *
+ * @since 3.1
+ */
+public class SimpleUnivariateValueChecker
+    extends AbstractConvergenceChecker<UnivariatePointValuePair> {
+    /**
+     * If {@link #maxIterationCount} is set to this value, the number of
+     * iterations will never cause
+     * {@link #converged(int,UnivariatePointValuePair,UnivariatePointValuePair)}
+     * to return {@code true}.
+     */
+    private static final int ITERATION_CHECK_DISABLED = -1;
+    /**
+     * Number of iterations after which the
+     * {@link #converged(int,UnivariatePointValuePair,UnivariatePointValuePair)}
+     * method will return true (unless the check is disabled).
+     */
+    private final int maxIterationCount;
+
+    /** Build an instance with specified thresholds.
+     *
+     * In order to perform only relative checks, the absolute tolerance
+     * must be set to a negative value. In order to perform only absolute
+     * checks, the relative tolerance must be set to a negative value.
+     *
+     * @param relativeThreshold relative tolerance threshold
+     * @param absoluteThreshold absolute tolerance threshold
+     */
+    public SimpleUnivariateValueChecker(final double relativeThreshold,
+                                        final double absoluteThreshold) {
+        super(relativeThreshold, absoluteThreshold);
+        maxIterationCount = ITERATION_CHECK_DISABLED;
+    }
+
+    /**
+     * Builds an instance with specified thresholds.
+     *
+     * In order to perform only relative checks, the absolute tolerance
+     * must be set to a negative value. In order to perform only absolute
+     * checks, the relative tolerance must be set to a negative value.
+     *
+     * @param relativeThreshold relative tolerance threshold
+     * @param absoluteThreshold absolute tolerance threshold
+     * @param maxIter Maximum iteration count.
+     * @throws NotStrictlyPositiveException if {@code maxIter <= 0}.
+     *
+     * @since 3.1
+     */
+    public SimpleUnivariateValueChecker(final double relativeThreshold,
+                                        final double absoluteThreshold,
+                                        final int maxIter) {
+        super(relativeThreshold, absoluteThreshold);
+
+        if (maxIter <= 0) {
+            throw new NotStrictlyPositiveException(maxIter);
+        }
+        maxIterationCount = maxIter;
+    }
+
+    /**
+     * Check if the optimization algorithm has converged considering the
+     * last two points.
+     * This method may be called several time from the same algorithm
+     * iteration with different points. This can be detected by checking the
+     * iteration number at each call if needed. Each time this method is
+     * called, the previous and current point correspond to points with the
+     * same role at each iteration, so they can be compared. As an example,
+     * simplex-based algorithms call this method for all points of the simplex,
+     * not only for the best or worst ones.
+     *
+     * @param iteration Index of current iteration
+     * @param previous Best point in the previous iteration.
+     * @param current Best point in the current iteration.
+     * @return {@code true} if the algorithm has converged.
+     */
+    @Override
+    public boolean converged(final int iteration,
+                             final UnivariatePointValuePair previous,
+                             final UnivariatePointValuePair current) {
+        if (maxIterationCount != ITERATION_CHECK_DISABLED && iteration >= maxIterationCount) {
+            return true;
+        }
+
+        final double p = previous.getValue();
+        final double c = current.getValue();
+        final double difference = FastMath.abs(p - c);
+        final double size = FastMath.max(FastMath.abs(p), FastMath.abs(c));
+        return difference <= size * getRelativeThreshold() ||
+            difference <= getAbsoluteThreshold();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/univariate/UnivariateObjectiveFunction.java b/src/main/java/org/apache/commons/math3/optim/univariate/UnivariateObjectiveFunction.java
new file mode 100644
index 0000000..ad06d84
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/univariate/UnivariateObjectiveFunction.java
@@ -0,0 +1,46 @@
+/*
+ * 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.math3.optim.univariate;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.optim.OptimizationData;
+
+/**
+ * Scalar function to be optimized.
+ *
+ * @since 3.1
+ */
+public class UnivariateObjectiveFunction implements OptimizationData {
+    /** Function to be optimized. */
+    private final UnivariateFunction function;
+
+    /**
+     * @param f Function to be optimized.
+     */
+    public UnivariateObjectiveFunction(UnivariateFunction f) {
+        function = f;
+    }
+
+    /**
+     * Gets the function to be optimized.
+     *
+     * @return the objective function.
+     */
+    public UnivariateFunction getObjectiveFunction() {
+        return function;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/univariate/UnivariateOptimizer.java b/src/main/java/org/apache/commons/math3/optim/univariate/UnivariateOptimizer.java
new file mode 100644
index 0000000..a7512c1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/univariate/UnivariateOptimizer.java
@@ -0,0 +1,151 @@
+/*
+ * 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.math3.optim.univariate;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.optim.BaseOptimizer;
+import org.apache.commons.math3.optim.OptimizationData;
+import org.apache.commons.math3.optim.nonlinear.scalar.GoalType;
+import org.apache.commons.math3.optim.ConvergenceChecker;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+
+/**
+ * Base class for a univariate scalar function optimizer.
+ *
+ * @since 3.1
+ */
+public abstract class UnivariateOptimizer
+    extends BaseOptimizer<UnivariatePointValuePair> {
+    /** Objective function. */
+    private UnivariateFunction function;
+    /** Type of optimization. */
+    private GoalType goal;
+    /** Initial guess. */
+    private double start;
+    /** Lower bound. */
+    private double min;
+    /** Upper bound. */
+    private double max;
+
+    /**
+     * @param checker Convergence checker.
+     */
+    protected UnivariateOptimizer(ConvergenceChecker<UnivariatePointValuePair> checker) {
+        super(checker);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @param optData Optimization data. In addition to those documented in
+     * {@link BaseOptimizer#parseOptimizationData(OptimizationData[])
+     * BaseOptimizer}, this method will register the following data:
+     * <ul>
+     *  <li>{@link GoalType}</li>
+     *  <li>{@link SearchInterval}</li>
+     *  <li>{@link UnivariateObjectiveFunction}</li>
+     * </ul>
+     * @return {@inheritDoc}
+     * @throws TooManyEvaluationsException if the maximal number of
+     * evaluations is exceeded.
+     */
+    @Override
+    public UnivariatePointValuePair optimize(OptimizationData... optData)
+        throws TooManyEvaluationsException {
+        // Perform computation.
+        return super.optimize(optData);
+    }
+
+    /**
+     * @return the optimization type.
+     */
+    public GoalType getGoalType() {
+        return goal;
+    }
+
+    /**
+     * Scans the list of (required and optional) optimization data that
+     * characterize the problem.
+     *
+     * @param optData Optimization data.
+     * The following data will be looked for:
+     * <ul>
+     *  <li>{@link GoalType}</li>
+     *  <li>{@link SearchInterval}</li>
+     *  <li>{@link UnivariateObjectiveFunction}</li>
+     * </ul>
+     */
+    @Override
+    protected void parseOptimizationData(OptimizationData... optData) {
+        // Allow base class to register its own data.
+        super.parseOptimizationData(optData);
+
+        // The existing values (as set by the previous call) are reused if
+        // not provided in the argument list.
+        for (OptimizationData data : optData) {
+            if (data instanceof SearchInterval) {
+                final SearchInterval interval = (SearchInterval) data;
+                min = interval.getMin();
+                max = interval.getMax();
+                start = interval.getStartValue();
+                continue;
+            }
+            if (data instanceof UnivariateObjectiveFunction) {
+                function = ((UnivariateObjectiveFunction) data).getObjectiveFunction();
+                continue;
+            }
+            if (data instanceof GoalType) {
+                goal = (GoalType) data;
+                continue;
+            }
+        }
+    }
+
+    /**
+     * @return the initial guess.
+     */
+    public double getStartValue() {
+        return start;
+    }
+    /**
+     * @return the lower bounds.
+     */
+    public double getMin() {
+        return min;
+    }
+    /**
+     * @return the upper bounds.
+     */
+    public double getMax() {
+        return max;
+    }
+
+    /**
+     * Computes the objective function value.
+     * This method <em>must</em> be called by subclasses to enforce the
+     * evaluation counter limit.
+     *
+     * @param x Point at which the objective function must be evaluated.
+     * @return the objective function value at the specified point.
+     * @throws TooManyEvaluationsException if the maximal number of
+     * evaluations is exceeded.
+     */
+    protected double computeObjectiveValue(double x) {
+        super.incrementEvaluationCount();
+        return function.value(x);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/univariate/UnivariatePointValuePair.java b/src/main/java/org/apache/commons/math3/optim/univariate/UnivariatePointValuePair.java
new file mode 100644
index 0000000..6b2b51a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/univariate/UnivariatePointValuePair.java
@@ -0,0 +1,66 @@
+/*
+ * 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.math3.optim.univariate;
+
+import java.io.Serializable;
+
+/**
+ * This class holds a point and the value of an objective function at this
+ * point.
+ * This is a simple immutable container.
+ *
+ * @since 3.0
+ */
+public class UnivariatePointValuePair implements Serializable {
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 1003888396256744753L;
+    /** Point. */
+    private final double point;
+    /** Value of the objective function at the point. */
+    private final double value;
+
+    /**
+     * Build a point/objective function value pair.
+     *
+     * @param point Point.
+     * @param value Value of an objective function at the point
+     */
+    public UnivariatePointValuePair(final double point,
+                                    final double value) {
+        this.point = point;
+        this.value = value;
+    }
+
+    /**
+     * Get the point.
+     *
+     * @return the point.
+     */
+    public double getPoint() {
+        return point;
+    }
+
+    /**
+     * Get the value of the objective function.
+     *
+     * @return the stored value of the objective function.
+     */
+    public double getValue() {
+        return value;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optim/univariate/package-info.java b/src/main/java/org/apache/commons/math3/optim/univariate/package-info.java
new file mode 100644
index 0000000..2273bab
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optim/univariate/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/**
+ * One-dimensional optimization algorithms.
+ */
+package org.apache.commons.math3.optim.univariate;
diff --git a/src/main/java/org/apache/commons/math3/optimization/AbstractConvergenceChecker.java b/src/main/java/org/apache/commons/math3/optimization/AbstractConvergenceChecker.java
new file mode 100644
index 0000000..98248ce
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/AbstractConvergenceChecker.java
@@ -0,0 +1,95 @@
+/*
+ * 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.math3.optimization;
+
+import org.apache.commons.math3.util.Precision;
+
+/**
+ * Base class for all convergence checker implementations.
+ *
+ * @param <PAIR> Type of (point, value) pair.
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.0
+ */
+@Deprecated
+public abstract class AbstractConvergenceChecker<PAIR> implements ConvergenceChecker<PAIR> {
+    /**
+     * Default relative threshold.
+     *
+     * @deprecated in 3.1 (to be removed in 4.0) because this value is too small to be useful as a
+     *     default (cf. MATH-798).
+     */
+    @Deprecated private static final double DEFAULT_RELATIVE_THRESHOLD = 100 * Precision.EPSILON;
+
+    /**
+     * Default absolute threshold.
+     *
+     * @deprecated in 3.1 (to be removed in 4.0) because this value is too small to be useful as a
+     *     default (cf. MATH-798).
+     */
+    @Deprecated private static final double DEFAULT_ABSOLUTE_THRESHOLD = 100 * Precision.SAFE_MIN;
+
+    /** Relative tolerance threshold. */
+    private final double relativeThreshold;
+
+    /** Absolute tolerance threshold. */
+    private final double absoluteThreshold;
+
+    /**
+     * Build an instance with default thresholds.
+     *
+     * @deprecated in 3.1 (to be removed in 4.0). Convergence thresholds are problem-dependent. As
+     *     this class is intended for users who want to set their own convergence criterion instead
+     *     of relying on an algorithm's default procedure, they should also set the thresholds
+     *     appropriately (cf. MATH-798).
+     */
+    @Deprecated
+    public AbstractConvergenceChecker() {
+        this.relativeThreshold = DEFAULT_RELATIVE_THRESHOLD;
+        this.absoluteThreshold = DEFAULT_ABSOLUTE_THRESHOLD;
+    }
+
+    /**
+     * Build an instance with a specified thresholds.
+     *
+     * @param relativeThreshold relative tolerance threshold
+     * @param absoluteThreshold absolute tolerance threshold
+     */
+    public AbstractConvergenceChecker(
+            final double relativeThreshold, final double absoluteThreshold) {
+        this.relativeThreshold = relativeThreshold;
+        this.absoluteThreshold = absoluteThreshold;
+    }
+
+    /**
+     * @return the relative threshold.
+     */
+    public double getRelativeThreshold() {
+        return relativeThreshold;
+    }
+
+    /**
+     * @return the absolute threshold.
+     */
+    public double getAbsoluteThreshold() {
+        return absoluteThreshold;
+    }
+
+    /** {@inheritDoc} */
+    public abstract boolean converged(int iteration, PAIR previous, PAIR current);
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/BaseMultivariateMultiStartOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/BaseMultivariateMultiStartOptimizer.java
new file mode 100644
index 0000000..2c4aa17
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/BaseMultivariateMultiStartOptimizer.java
@@ -0,0 +1,192 @@
+/*
+ * 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.math3.optimization;
+
+import org.apache.commons.math3.analysis.MultivariateFunction;
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomVectorGenerator;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * Base class for all implementations of a multi-start optimizer.
+ *
+ * <p>This interface is mainly intended to enforce the internal coherence of Commons-Math. Users of
+ * the API are advised to base their code on {@link MultivariateMultiStartOptimizer} or on {@link
+ * DifferentiableMultivariateMultiStartOptimizer}.
+ *
+ * @param <FUNC> Type of the objective function to be optimized.
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.0
+ */
+@Deprecated
+public class BaseMultivariateMultiStartOptimizer<FUNC extends MultivariateFunction>
+        implements BaseMultivariateOptimizer<FUNC> {
+    /** Underlying classical optimizer. */
+    private final BaseMultivariateOptimizer<FUNC> optimizer;
+
+    /** Maximal number of evaluations allowed. */
+    private int maxEvaluations;
+
+    /** Number of evaluations already performed for all starts. */
+    private int totalEvaluations;
+
+    /** Number of starts to go. */
+    private int starts;
+
+    /** Random generator for multi-start. */
+    private RandomVectorGenerator generator;
+
+    /** Found optima. */
+    private PointValuePair[] optima;
+
+    /**
+     * Create a multi-start optimizer from a single-start optimizer.
+     *
+     * @param optimizer Single-start optimizer to wrap.
+     * @param starts Number of starts to perform. If {@code starts == 1}, the {@link
+     *     #optimize(int,MultivariateFunction,GoalType,double[]) optimize} will return the same
+     *     solution as {@code optimizer} would.
+     * @param generator Random vector generator to use for restarts.
+     * @throws NullArgumentException if {@code optimizer} or {@code generator} is {@code null}.
+     * @throws NotStrictlyPositiveException if {@code starts < 1}.
+     */
+    protected BaseMultivariateMultiStartOptimizer(
+            final BaseMultivariateOptimizer<FUNC> optimizer,
+            final int starts,
+            final RandomVectorGenerator generator) {
+        if (optimizer == null || generator == null) {
+            throw new NullArgumentException();
+        }
+        if (starts < 1) {
+            throw new NotStrictlyPositiveException(starts);
+        }
+
+        this.optimizer = optimizer;
+        this.starts = starts;
+        this.generator = generator;
+    }
+
+    /**
+     * Get all the optima found during the last call to {@link
+     * #optimize(int,MultivariateFunction,GoalType,double[]) optimize}. The optimizer stores all the
+     * optima found during a set of restarts. The {@link
+     * #optimize(int,MultivariateFunction,GoalType,double[]) optimize} method returns the best point
+     * only. This method returns all the points found at the end of each starts, including the best
+     * one already returned by the {@link #optimize(int,MultivariateFunction,GoalType,double[])
+     * optimize} method. <br>
+     * The returned array as one element for each start as specified in the constructor. It is
+     * ordered with the results from the runs that did converge first, sorted from best to worst
+     * objective value (i.e in ascending order if minimizing and in descending order if maximizing),
+     * followed by and null elements corresponding to the runs that did not converge. This means all
+     * elements will be null if the {@link #optimize(int,MultivariateFunction,GoalType,double[])
+     * optimize} method did throw an exception. This also means that if the first element is not
+     * {@code null}, it is the best point found across all starts.
+     *
+     * @return an array containing the optima.
+     * @throws MathIllegalStateException if {@link
+     *     #optimize(int,MultivariateFunction,GoalType,double[]) optimize} has not been called.
+     */
+    public PointValuePair[] getOptima() {
+        if (optima == null) {
+            throw new MathIllegalStateException(LocalizedFormats.NO_OPTIMUM_COMPUTED_YET);
+        }
+        return optima.clone();
+    }
+
+    /** {@inheritDoc} */
+    public int getMaxEvaluations() {
+        return maxEvaluations;
+    }
+
+    /** {@inheritDoc} */
+    public int getEvaluations() {
+        return totalEvaluations;
+    }
+
+    /** {@inheritDoc} */
+    public ConvergenceChecker<PointValuePair> getConvergenceChecker() {
+        return optimizer.getConvergenceChecker();
+    }
+
+    /** {@inheritDoc} */
+    public PointValuePair optimize(
+            int maxEval, final FUNC f, final GoalType goal, double[] startPoint) {
+        maxEvaluations = maxEval;
+        RuntimeException lastException = null;
+        optima = new PointValuePair[starts];
+        totalEvaluations = 0;
+
+        // Multi-start loop.
+        for (int i = 0; i < starts; ++i) {
+            // CHECKSTYLE: stop IllegalCatch
+            try {
+                optima[i] =
+                        optimizer.optimize(
+                                maxEval - totalEvaluations,
+                                f,
+                                goal,
+                                i == 0 ? startPoint : generator.nextVector());
+            } catch (RuntimeException mue) {
+                lastException = mue;
+                optima[i] = null;
+            }
+            // CHECKSTYLE: resume IllegalCatch
+
+            totalEvaluations += optimizer.getEvaluations();
+        }
+
+        sortPairs(goal);
+
+        if (optima[0] == null) {
+            throw lastException; // cannot be null if starts >=1
+        }
+
+        // Return the found point given the best objective function value.
+        return optima[0];
+    }
+
+    /**
+     * Sort the optima from best to worst, followed by {@code null} elements.
+     *
+     * @param goal Goal type.
+     */
+    private void sortPairs(final GoalType goal) {
+        Arrays.sort(
+                optima,
+                new Comparator<PointValuePair>() {
+                    /** {@inheritDoc} */
+                    public int compare(final PointValuePair o1, final PointValuePair o2) {
+                        if (o1 == null) {
+                            return (o2 == null) ? 0 : 1;
+                        } else if (o2 == null) {
+                            return -1;
+                        }
+                        final double v1 = o1.getValue();
+                        final double v2 = o2.getValue();
+                        return (goal == GoalType.MINIMIZE)
+                                ? Double.compare(v1, v2)
+                                : Double.compare(v2, v1);
+                    }
+                });
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/BaseMultivariateOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/BaseMultivariateOptimizer.java
new file mode 100644
index 0000000..2db1401
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/BaseMultivariateOptimizer.java
@@ -0,0 +1,60 @@
+/*
+ * 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.math3.optimization;
+
+import org.apache.commons.math3.analysis.MultivariateFunction;
+
+/**
+ * This interface is mainly intended to enforce the internal coherence of Commons-FastMath. Users of
+ * the API are advised to base their code on the following interfaces:
+ *
+ * <ul>
+ *   <li>{@link org.apache.commons.math3.optimization.MultivariateOptimizer}
+ *   <li>{@link org.apache.commons.math3.optimization.MultivariateDifferentiableOptimizer}
+ * </ul>
+ *
+ * @param <FUNC> Type of the objective function to be optimized.
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.0
+ */
+@Deprecated
+public interface BaseMultivariateOptimizer<FUNC extends MultivariateFunction>
+        extends BaseOptimizer<PointValuePair> {
+    /**
+     * Optimize an objective function.
+     *
+     * @param f Objective function.
+     * @param goalType Type of optimization goal: either {@link GoalType#MAXIMIZE} or {@link
+     *     GoalType#MINIMIZE}.
+     * @param startPoint Start point for optimization.
+     * @param maxEval Maximum number of function evaluations.
+     * @return the point/value pair giving the optimal value for objective function.
+     * @throws org.apache.commons.math3.exception.DimensionMismatchException if the start point
+     *     dimension is wrong.
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException if the maximal number
+     *     of evaluations is exceeded.
+     * @throws org.apache.commons.math3.exception.NullArgumentException if any argument is {@code
+     *     null}.
+     * @deprecated As of 3.1. In 4.0, it will be replaced by the declaration corresponding to this
+     *     {@link
+     *     org.apache.commons.math3.optimization.direct.BaseAbstractMultivariateOptimizer#optimize(int,MultivariateFunction,GoalType,OptimizationData[])
+     *     method}.
+     */
+    @Deprecated
+    PointValuePair optimize(int maxEval, FUNC f, GoalType goalType, double[] startPoint);
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/BaseMultivariateSimpleBoundsOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/BaseMultivariateSimpleBoundsOptimizer.java
new file mode 100644
index 0000000..4a5a901
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/BaseMultivariateSimpleBoundsOptimizer.java
@@ -0,0 +1,67 @@
+/*
+ * 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.math3.optimization;
+
+import org.apache.commons.math3.analysis.MultivariateFunction;
+
+/**
+ * This interface is mainly intended to enforce the internal coherence of Commons-FastMath. Users of
+ * the API are advised to base their code on the following interfaces:
+ *
+ * <ul>
+ *   <li>{@link org.apache.commons.math3.optimization.MultivariateOptimizer}
+ *   <li>{@link org.apache.commons.math3.optimization.MultivariateDifferentiableOptimizer}
+ * </ul>
+ *
+ * @param <FUNC> Type of the objective function to be optimized.
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.0
+ */
+@Deprecated
+public interface BaseMultivariateSimpleBoundsOptimizer<FUNC extends MultivariateFunction>
+        extends BaseMultivariateOptimizer<FUNC> {
+    /**
+     * Optimize an objective function.
+     *
+     * @param f Objective function.
+     * @param goalType Type of optimization goal: either {@link GoalType#MAXIMIZE} or {@link
+     *     GoalType#MINIMIZE}.
+     * @param startPoint Start point for optimization.
+     * @param maxEval Maximum number of function evaluations.
+     * @param lowerBound Lower bound for each of the parameters.
+     * @param upperBound Upper bound for each of the parameters.
+     * @return the point/value pair giving the optimal value for objective function.
+     * @throws org.apache.commons.math3.exception.DimensionMismatchException if the array sizes are
+     *     wrong.
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException if the maximal number
+     *     of evaluations is exceeded.
+     * @throws org.apache.commons.math3.exception.NullArgumentException if {@code f}, {@code
+     *     goalType} or {@code startPoint} is {@code null}.
+     * @throws org.apache.commons.math3.exception.NumberIsTooSmallException if any of the initial
+     *     values is less than its lower bound.
+     * @throws org.apache.commons.math3.exception.NumberIsTooLargeException if any of the initial
+     *     values is greater than its upper bound.
+     */
+    PointValuePair optimize(
+            int maxEval,
+            FUNC f,
+            GoalType goalType,
+            double[] startPoint,
+            double[] lowerBound,
+            double[] upperBound);
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/BaseMultivariateVectorMultiStartOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/BaseMultivariateVectorMultiStartOptimizer.java
new file mode 100644
index 0000000..7b6523a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/BaseMultivariateVectorMultiStartOptimizer.java
@@ -0,0 +1,207 @@
+/*
+ * 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.math3.optimization;
+
+import org.apache.commons.math3.analysis.MultivariateVectorFunction;
+import org.apache.commons.math3.exception.ConvergenceException;
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomVectorGenerator;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * Base class for all implementations of a multi-start optimizer.
+ *
+ * <p>This interface is mainly intended to enforce the internal coherence of Commons-Math. Users of
+ * the API are advised to base their code on {@link
+ * DifferentiableMultivariateVectorMultiStartOptimizer}.
+ *
+ * @param <FUNC> Type of the objective function to be optimized.
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.0
+ */
+@Deprecated
+public class BaseMultivariateVectorMultiStartOptimizer<FUNC extends MultivariateVectorFunction>
+        implements BaseMultivariateVectorOptimizer<FUNC> {
+    /** Underlying classical optimizer. */
+    private final BaseMultivariateVectorOptimizer<FUNC> optimizer;
+
+    /** Maximal number of evaluations allowed. */
+    private int maxEvaluations;
+
+    /** Number of evaluations already performed for all starts. */
+    private int totalEvaluations;
+
+    /** Number of starts to go. */
+    private int starts;
+
+    /** Random generator for multi-start. */
+    private RandomVectorGenerator generator;
+
+    /** Found optima. */
+    private PointVectorValuePair[] optima;
+
+    /**
+     * Create a multi-start optimizer from a single-start optimizer.
+     *
+     * @param optimizer Single-start optimizer to wrap.
+     * @param starts Number of starts to perform. If {@code starts == 1}, the {@link
+     *     #optimize(int,MultivariateVectorFunction,double[],double[],double[]) optimize} will
+     *     return the same solution as {@code optimizer} would.
+     * @param generator Random vector generator to use for restarts.
+     * @throws NullArgumentException if {@code optimizer} or {@code generator} is {@code null}.
+     * @throws NotStrictlyPositiveException if {@code starts < 1}.
+     */
+    protected BaseMultivariateVectorMultiStartOptimizer(
+            final BaseMultivariateVectorOptimizer<FUNC> optimizer,
+            final int starts,
+            final RandomVectorGenerator generator) {
+        if (optimizer == null || generator == null) {
+            throw new NullArgumentException();
+        }
+        if (starts < 1) {
+            throw new NotStrictlyPositiveException(starts);
+        }
+
+        this.optimizer = optimizer;
+        this.starts = starts;
+        this.generator = generator;
+    }
+
+    /**
+     * Get all the optima found during the last call to {@link
+     * #optimize(int,MultivariateVectorFunction,double[],double[],double[]) optimize}. The optimizer
+     * stores all the optima found during a set of restarts. The {@link
+     * #optimize(int,MultivariateVectorFunction,double[],double[],double[]) optimize} method returns
+     * the best point only. This method returns all the points found at the end of each starts,
+     * including the best one already returned by the {@link
+     * #optimize(int,MultivariateVectorFunction,double[],double[],double[]) optimize} method. <br>
+     * The returned array as one element for each start as specified in the constructor. It is
+     * ordered with the results from the runs that did converge first, sorted from best to worst
+     * objective value (i.e. in ascending order if minimizing and in descending order if
+     * maximizing), followed by and null elements corresponding to the runs that did not converge.
+     * This means all elements will be null if the {@link
+     * #optimize(int,MultivariateVectorFunction,double[],double[],double[]) optimize} method did
+     * throw a {@link ConvergenceException}). This also means that if the first element is not
+     * {@code null}, it is the best point found across all starts.
+     *
+     * @return array containing the optima
+     * @throws MathIllegalStateException if {@link
+     *     #optimize(int,MultivariateVectorFunction,double[],double[],double[]) optimize} has not
+     *     been called.
+     */
+    public PointVectorValuePair[] getOptima() {
+        if (optima == null) {
+            throw new MathIllegalStateException(LocalizedFormats.NO_OPTIMUM_COMPUTED_YET);
+        }
+        return optima.clone();
+    }
+
+    /** {@inheritDoc} */
+    public int getMaxEvaluations() {
+        return maxEvaluations;
+    }
+
+    /** {@inheritDoc} */
+    public int getEvaluations() {
+        return totalEvaluations;
+    }
+
+    /** {@inheritDoc} */
+    public ConvergenceChecker<PointVectorValuePair> getConvergenceChecker() {
+        return optimizer.getConvergenceChecker();
+    }
+
+    /** {@inheritDoc} */
+    public PointVectorValuePair optimize(
+            int maxEval, final FUNC f, double[] target, double[] weights, double[] startPoint) {
+        maxEvaluations = maxEval;
+        RuntimeException lastException = null;
+        optima = new PointVectorValuePair[starts];
+        totalEvaluations = 0;
+
+        // Multi-start loop.
+        for (int i = 0; i < starts; ++i) {
+
+            // CHECKSTYLE: stop IllegalCatch
+            try {
+                optima[i] =
+                        optimizer.optimize(
+                                maxEval - totalEvaluations,
+                                f,
+                                target,
+                                weights,
+                                i == 0 ? startPoint : generator.nextVector());
+            } catch (ConvergenceException oe) {
+                optima[i] = null;
+            } catch (RuntimeException mue) {
+                lastException = mue;
+                optima[i] = null;
+            }
+            // CHECKSTYLE: resume IllegalCatch
+
+            totalEvaluations += optimizer.getEvaluations();
+        }
+
+        sortPairs(target, weights);
+
+        if (optima[0] == null) {
+            throw lastException; // cannot be null if starts >=1
+        }
+
+        // Return the found point given the best objective function value.
+        return optima[0];
+    }
+
+    /**
+     * Sort the optima from best to worst, followed by {@code null} elements.
+     *
+     * @param target Target value for the objective functions at optimum.
+     * @param weights Weights for the least-squares cost computation.
+     */
+    private void sortPairs(final double[] target, final double[] weights) {
+        Arrays.sort(
+                optima,
+                new Comparator<PointVectorValuePair>() {
+                    /** {@inheritDoc} */
+                    public int compare(
+                            final PointVectorValuePair o1, final PointVectorValuePair o2) {
+                        if (o1 == null) {
+                            return (o2 == null) ? 0 : 1;
+                        } else if (o2 == null) {
+                            return -1;
+                        }
+                        return Double.compare(weightedResidual(o1), weightedResidual(o2));
+                    }
+
+                    private double weightedResidual(final PointVectorValuePair pv) {
+                        final double[] value = pv.getValueRef();
+                        double sum = 0;
+                        for (int i = 0; i < value.length; ++i) {
+                            final double ri = value[i] - target[i];
+                            sum += weights[i] * ri * ri;
+                        }
+                        return sum;
+                    }
+                });
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/BaseMultivariateVectorOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/BaseMultivariateVectorOptimizer.java
new file mode 100644
index 0000000..b8a386f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/BaseMultivariateVectorOptimizer.java
@@ -0,0 +1,62 @@
+/*
+ * 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.math3.optimization;
+
+import org.apache.commons.math3.analysis.MultivariateVectorFunction;
+
+/**
+ * This interface is mainly intended to enforce the internal coherence of Commons-Math. Users of the
+ * API are advised to base their code on the following interfaces:
+ *
+ * <ul>
+ *   <li>{@link org.apache.commons.math3.optimization.DifferentiableMultivariateVectorOptimizer}
+ * </ul>
+ *
+ * @param <FUNC> Type of the objective function to be optimized.
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.0
+ */
+@Deprecated
+public interface BaseMultivariateVectorOptimizer<FUNC extends MultivariateVectorFunction>
+        extends BaseOptimizer<PointVectorValuePair> {
+    /**
+     * Optimize an objective function. Optimization is considered to be a weighted least-squares
+     * minimization. The cost function to be minimized is <code>
+     * &sum;weight<sub>i</sub>(objective<sub>i</sub> - target<sub>i</sub>)<sup>2</sup></code>
+     *
+     * @param f Objective function.
+     * @param target Target value for the objective functions at optimum.
+     * @param weight Weights for the least squares cost computation.
+     * @param startPoint Start point for optimization.
+     * @return the point/value pair giving the optimal value for objective function.
+     * @param maxEval Maximum number of function evaluations.
+     * @throws org.apache.commons.math3.exception.DimensionMismatchException if the start point
+     *     dimension is wrong.
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException if the maximal number
+     *     of evaluations is exceeded.
+     * @throws org.apache.commons.math3.exception.NullArgumentException if any argument is {@code
+     *     null}.
+     * @deprecated As of 3.1. In 4.0, this will be replaced by the declaration corresponding to this
+     *     {@link
+     *     org.apache.commons.math3.optimization.direct.BaseAbstractMultivariateVectorOptimizer#optimize(int,MultivariateVectorFunction,OptimizationData[])
+     *     method}.
+     */
+    @Deprecated
+    PointVectorValuePair optimize(
+            int maxEval, FUNC f, double[] target, double[] weight, double[] startPoint);
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/BaseOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/BaseOptimizer.java
new file mode 100644
index 0000000..b9db4bd
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/BaseOptimizer.java
@@ -0,0 +1,59 @@
+/*
+ * 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.math3.optimization;
+
+/**
+ * This interface is mainly intended to enforce the internal coherence of Commons-Math. Users of the
+ * API are advised to base their code on the following interfaces:
+ *
+ * <ul>
+ *   <li>{@link org.apache.commons.math3.optimization.MultivariateOptimizer}
+ *   <li>{@link org.apache.commons.math3.optimization.MultivariateDifferentiableOptimizer}
+ *   <li>{@link org.apache.commons.math3.optimization.MultivariateDifferentiableVectorOptimizer}
+ *   <li>{@link org.apache.commons.math3.optimization.univariate.UnivariateOptimizer}
+ * </ul>
+ *
+ * @param <PAIR> Type of the point/objective pair.
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.0
+ */
+@Deprecated
+public interface BaseOptimizer<PAIR> {
+    /**
+     * Get the maximal number of function evaluations.
+     *
+     * @return the maximal number of function evaluations.
+     */
+    int getMaxEvaluations();
+
+    /**
+     * Get the number of evaluations of the objective function. The number of evaluations
+     * corresponds to the last call to the {@code optimize} method. It is 0 if the method has not
+     * been called yet.
+     *
+     * @return the number of evaluations of the objective function.
+     */
+    int getEvaluations();
+
+    /**
+     * Get the convergence checker.
+     *
+     * @return the object used to check for convergence.
+     */
+    ConvergenceChecker<PAIR> getConvergenceChecker();
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/ConvergenceChecker.java b/src/main/java/org/apache/commons/math3/optimization/ConvergenceChecker.java
new file mode 100644
index 0000000..ee43b95
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/ConvergenceChecker.java
@@ -0,0 +1,50 @@
+/*
+ * 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.math3.optimization;
+
+/**
+ * This interface specifies how to check if an optimization algorithm has converged. <br>
+ * Deciding if convergence has been reached is a problem-dependent issue. The user should provide a
+ * class implementing this interface to allow the optimization algorithm to stop its search
+ * according to the problem at hand. <br>
+ * For convenience, three implementations that fit simple needs are already provided: {@link
+ * SimpleValueChecker}, {@link SimpleVectorValueChecker} and {@link SimplePointChecker}. The first
+ * two consider that convergence is reached when the objective function value does not change much
+ * anymore, it does not use the point set at all. The third one considers that convergence is
+ * reached when the input point set does not change much anymore, it does not use objective function
+ * value at all.
+ *
+ * @param <PAIR> Type of the (point, objective value) pair.
+ * @see org.apache.commons.math3.optimization.SimplePointChecker
+ * @see org.apache.commons.math3.optimization.SimpleValueChecker
+ * @see org.apache.commons.math3.optimization.SimpleVectorValueChecker
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.0
+ */
+@Deprecated
+public interface ConvergenceChecker<PAIR> {
+    /**
+     * Check if the optimization algorithm has converged.
+     *
+     * @param iteration Current iteration.
+     * @param previous Best point in the previous iteration.
+     * @param current Best point in the current iteration.
+     * @return {@code true} if the algorithm is considered to have converged.
+     */
+    boolean converged(int iteration, PAIR previous, PAIR current);
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/DifferentiableMultivariateMultiStartOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/DifferentiableMultivariateMultiStartOptimizer.java
new file mode 100644
index 0000000..ae2a48e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/DifferentiableMultivariateMultiStartOptimizer.java
@@ -0,0 +1,51 @@
+/*
+ * 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.math3.optimization;
+
+import org.apache.commons.math3.analysis.DifferentiableMultivariateFunction;
+import org.apache.commons.math3.random.RandomVectorGenerator;
+
+/**
+ * Special implementation of the {@link DifferentiableMultivariateOptimizer} interface adding
+ * multi-start features to an existing optimizer.
+ *
+ * <p>This class wraps a classical optimizer to use it several times in turn with different starting
+ * points in order to avoid being trapped into a local extremum when looking for a global one.
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 2.0
+ */
+@Deprecated
+public class DifferentiableMultivariateMultiStartOptimizer
+        extends BaseMultivariateMultiStartOptimizer<DifferentiableMultivariateFunction>
+        implements DifferentiableMultivariateOptimizer {
+    /**
+     * Create a multi-start optimizer from a single-start optimizer.
+     *
+     * @param optimizer Single-start optimizer to wrap.
+     * @param starts Number of starts to perform (including the first one), multi-start is disabled
+     *     if value is less than or equal to 1.
+     * @param generator Random vector generator to use for restarts.
+     */
+    public DifferentiableMultivariateMultiStartOptimizer(
+            final DifferentiableMultivariateOptimizer optimizer,
+            final int starts,
+            final RandomVectorGenerator generator) {
+        super(optimizer, starts, generator);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/DifferentiableMultivariateOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/DifferentiableMultivariateOptimizer.java
new file mode 100644
index 0000000..51e9f26
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/DifferentiableMultivariateOptimizer.java
@@ -0,0 +1,34 @@
+/*
+ * 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.math3.optimization;
+
+import org.apache.commons.math3.analysis.DifferentiableMultivariateFunction;
+
+/**
+ * This interface represents an optimization algorithm for {@link DifferentiableMultivariateFunction
+ * scalar differentiable objective functions}. Optimization algorithms find the input point set that
+ * either {@link GoalType maximize or minimize} an objective function.
+ *
+ * @see MultivariateOptimizer
+ * @see DifferentiableMultivariateVectorOptimizer
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 2.0
+ */
+@Deprecated
+public interface DifferentiableMultivariateOptimizer
+        extends BaseMultivariateOptimizer<DifferentiableMultivariateFunction> {}
diff --git a/src/main/java/org/apache/commons/math3/optimization/DifferentiableMultivariateVectorMultiStartOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/DifferentiableMultivariateVectorMultiStartOptimizer.java
new file mode 100644
index 0000000..2ad2bbf
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/DifferentiableMultivariateVectorMultiStartOptimizer.java
@@ -0,0 +1,51 @@
+/*
+ * 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.math3.optimization;
+
+import org.apache.commons.math3.analysis.DifferentiableMultivariateVectorFunction;
+import org.apache.commons.math3.random.RandomVectorGenerator;
+
+/**
+ * Special implementation of the {@link DifferentiableMultivariateVectorOptimizer} interface addind
+ * multi-start features to an existing optimizer.
+ *
+ * <p>This class wraps a classical optimizer to use it several times in turn with different starting
+ * points in order to avoid being trapped into a local extremum when looking for a global one.
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 2.0
+ */
+@Deprecated
+public class DifferentiableMultivariateVectorMultiStartOptimizer
+        extends BaseMultivariateVectorMultiStartOptimizer<DifferentiableMultivariateVectorFunction>
+        implements DifferentiableMultivariateVectorOptimizer {
+    /**
+     * Create a multi-start optimizer from a single-start optimizer.
+     *
+     * @param optimizer Single-start optimizer to wrap.
+     * @param starts Number of starts to perform (including the first one), multi-start is disabled
+     *     if value is less than or equal to 1.
+     * @param generator Random vector generator to use for restarts.
+     */
+    public DifferentiableMultivariateVectorMultiStartOptimizer(
+            final DifferentiableMultivariateVectorOptimizer optimizer,
+            final int starts,
+            final RandomVectorGenerator generator) {
+        super(optimizer, starts, generator);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/DifferentiableMultivariateVectorOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/DifferentiableMultivariateVectorOptimizer.java
new file mode 100644
index 0000000..6c697f8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/DifferentiableMultivariateVectorOptimizer.java
@@ -0,0 +1,31 @@
+/*
+ * 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.math3.optimization;
+
+import org.apache.commons.math3.analysis.DifferentiableMultivariateVectorFunction;
+
+/**
+ * This interface represents an optimization algorithm for {@link
+ * DifferentiableMultivariateVectorFunction vectorial differentiable objective functions}.
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.0
+ */
+@Deprecated
+public interface DifferentiableMultivariateVectorOptimizer
+        extends BaseMultivariateVectorOptimizer<DifferentiableMultivariateVectorFunction> {}
diff --git a/src/main/java/org/apache/commons/math3/optimization/GoalType.java b/src/main/java/org/apache/commons/math3/optimization/GoalType.java
new file mode 100644
index 0000000..f43a350
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/GoalType.java
@@ -0,0 +1,36 @@
+/*
+ * 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.math3.optimization;
+
+import java.io.Serializable;
+
+/**
+ * Goal type for an optimization problem.
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 2.0
+ */
+@Deprecated
+public enum GoalType implements Serializable {
+
+    /** Maximization goal. */
+    MAXIMIZE,
+
+    /** Minimization goal. */
+    MINIMIZE
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/InitialGuess.java b/src/main/java/org/apache/commons/math3/optimization/InitialGuess.java
new file mode 100644
index 0000000..d61e129
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/InitialGuess.java
@@ -0,0 +1,47 @@
+/*
+ * 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.math3.optimization;
+
+/**
+ * Starting point (first guess) of the optimization procedure. <br>
+ * Immutable class.
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.1
+ */
+@Deprecated
+public class InitialGuess implements OptimizationData {
+    /** Initial guess. */
+    private final double[] init;
+
+    /**
+     * @param startPoint Initial guess.
+     */
+    public InitialGuess(double[] startPoint) {
+        init = startPoint.clone();
+    }
+
+    /**
+     * Gets the initial guess.
+     *
+     * @return the initial guess.
+     */
+    public double[] getInitialGuess() {
+        return init.clone();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/LeastSquaresConverter.java b/src/main/java/org/apache/commons/math3/optimization/LeastSquaresConverter.java
new file mode 100644
index 0000000..5ee9754
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/LeastSquaresConverter.java
@@ -0,0 +1,181 @@
+/*
+ * 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.math3.optimization;
+
+import org.apache.commons.math3.analysis.MultivariateFunction;
+import org.apache.commons.math3.analysis.MultivariateVectorFunction;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.linear.RealMatrix;
+
+/**
+ * This class converts {@link MultivariateVectorFunction vectorial objective functions} to {@link
+ * MultivariateFunction scalar objective functions} when the goal is to minimize them.
+ *
+ * <p>This class is mostly used when the vectorial objective function represents a theoretical
+ * result computed from a point set applied to a model and the models point must be adjusted to fit
+ * the theoretical result to some reference observations. The observations may be obtained for
+ * example from physical measurements whether the model is built from theoretical considerations.
+ *
+ * <p>This class computes a possibly weighted squared sum of the residuals, which is a scalar value.
+ * The residuals are the difference between the theoretical model (i.e. the output of the vectorial
+ * objective function) and the observations. The class implements the {@link MultivariateFunction}
+ * interface and can therefore be minimized by any optimizer supporting scalar objectives
+ * functions.This is one way to perform a least square estimation. There are other ways to do this
+ * without using this converter, as some optimization algorithms directly support vectorial
+ * objective functions.
+ *
+ * <p>This class support combination of residuals with or without weights and correlations.
+ *
+ * @see MultivariateFunction
+ * @see MultivariateVectorFunction
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 2.0
+ */
+@Deprecated
+public class LeastSquaresConverter implements MultivariateFunction {
+
+    /** Underlying vectorial function. */
+    private final MultivariateVectorFunction function;
+
+    /** Observations to be compared to objective function to compute residuals. */
+    private final double[] observations;
+
+    /** Optional weights for the residuals. */
+    private final double[] weights;
+
+    /** Optional scaling matrix (weight and correlations) for the residuals. */
+    private final RealMatrix scale;
+
+    /**
+     * Build a simple converter for uncorrelated residuals with the same weight.
+     *
+     * @param function vectorial residuals function to wrap
+     * @param observations observations to be compared to objective function to compute residuals
+     */
+    public LeastSquaresConverter(
+            final MultivariateVectorFunction function, final double[] observations) {
+        this.function = function;
+        this.observations = observations.clone();
+        this.weights = null;
+        this.scale = null;
+    }
+
+    /**
+     * Build a simple converter for uncorrelated residuals with the specific weights.
+     *
+     * <p>The scalar objective function value is computed as:
+     *
+     * <pre>
+     * objective = &sum;weight<sub>i</sub>(observation<sub>i</sub>-objective<sub>i</sub>)<sup>2</sup>
+     * </pre>
+     *
+     * <p>Weights can be used for example to combine residuals with different standard deviations.
+     * As an example, consider a residuals array in which even elements are angular measurements in
+     * degrees with a 0.01&deg; standard deviation and odd elements are distance measurements in
+     * meters with a 15m standard deviation. In this case, the weights array should be initialized
+     * with value 1.0/(0.01<sup>2</sup>) in the even elements and 1.0/(15.0<sup>2</sup>) in the odd
+     * elements (i.e. reciprocals of variances).
+     *
+     * <p>The array computed by the objective function, the observations array and the weights array
+     * must have consistent sizes or a {@link DimensionMismatchException} will be triggered while
+     * computing the scalar objective.
+     *
+     * @param function vectorial residuals function to wrap
+     * @param observations observations to be compared to objective function to compute residuals
+     * @param weights weights to apply to the residuals
+     * @exception DimensionMismatchException if the observations vector and the weights vector
+     *     dimensions do not match (objective function dimension is checked only when the {@link
+     *     #value(double[])} method is called)
+     */
+    public LeastSquaresConverter(
+            final MultivariateVectorFunction function,
+            final double[] observations,
+            final double[] weights) {
+        if (observations.length != weights.length) {
+            throw new DimensionMismatchException(observations.length, weights.length);
+        }
+        this.function = function;
+        this.observations = observations.clone();
+        this.weights = weights.clone();
+        this.scale = null;
+    }
+
+    /**
+     * Build a simple converter for correlated residuals with the specific weights.
+     *
+     * <p>The scalar objective function value is computed as:
+     *
+     * <pre>
+     * objective = y<sup>T</sup>y with y = scale&times;(observation-objective)
+     * </pre>
+     *
+     * <p>The array computed by the objective function, the observations array and the the scaling
+     * matrix must have consistent sizes or a {@link DimensionMismatchException} will be triggered
+     * while computing the scalar objective.
+     *
+     * @param function vectorial residuals function to wrap
+     * @param observations observations to be compared to objective function to compute residuals
+     * @param scale scaling matrix
+     * @throws DimensionMismatchException if the observations vector and the scale matrix dimensions
+     *     do not match (objective function dimension is checked only when the {@link
+     *     #value(double[])} method is called)
+     */
+    public LeastSquaresConverter(
+            final MultivariateVectorFunction function,
+            final double[] observations,
+            final RealMatrix scale) {
+        if (observations.length != scale.getColumnDimension()) {
+            throw new DimensionMismatchException(observations.length, scale.getColumnDimension());
+        }
+        this.function = function;
+        this.observations = observations.clone();
+        this.weights = null;
+        this.scale = scale.copy();
+    }
+
+    /** {@inheritDoc} */
+    public double value(final double[] point) {
+        // compute residuals
+        final double[] residuals = function.value(point);
+        if (residuals.length != observations.length) {
+            throw new DimensionMismatchException(residuals.length, observations.length);
+        }
+        for (int i = 0; i < residuals.length; ++i) {
+            residuals[i] -= observations[i];
+        }
+
+        // compute sum of squares
+        double sumSquares = 0;
+        if (weights != null) {
+            for (int i = 0; i < residuals.length; ++i) {
+                final double ri = residuals[i];
+                sumSquares += weights[i] * ri * ri;
+            }
+        } else if (scale != null) {
+            for (final double yi : scale.operate(residuals)) {
+                sumSquares += yi * yi;
+            }
+        } else {
+            for (final double ri : residuals) {
+                sumSquares += ri * ri;
+            }
+        }
+
+        return sumSquares;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/MultivariateDifferentiableMultiStartOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/MultivariateDifferentiableMultiStartOptimizer.java
new file mode 100644
index 0000000..e883805
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/MultivariateDifferentiableMultiStartOptimizer.java
@@ -0,0 +1,51 @@
+/*
+ * 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.math3.optimization;
+
+import org.apache.commons.math3.analysis.differentiation.MultivariateDifferentiableFunction;
+import org.apache.commons.math3.random.RandomVectorGenerator;
+
+/**
+ * Special implementation of the {@link MultivariateDifferentiableOptimizer} interface adding
+ * multi-start features to an existing optimizer.
+ *
+ * <p>This class wraps a classical optimizer to use it several times in turn with different starting
+ * points in order to avoid being trapped into a local extremum when looking for a global one.
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.1
+ */
+@Deprecated
+public class MultivariateDifferentiableMultiStartOptimizer
+        extends BaseMultivariateMultiStartOptimizer<MultivariateDifferentiableFunction>
+        implements MultivariateDifferentiableOptimizer {
+    /**
+     * Create a multi-start optimizer from a single-start optimizer.
+     *
+     * @param optimizer Single-start optimizer to wrap.
+     * @param starts Number of starts to perform (including the first one), multi-start is disabled
+     *     if value is less than or equal to 1.
+     * @param generator Random vector generator to use for restarts.
+     */
+    public MultivariateDifferentiableMultiStartOptimizer(
+            final MultivariateDifferentiableOptimizer optimizer,
+            final int starts,
+            final RandomVectorGenerator generator) {
+        super(optimizer, starts, generator);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/MultivariateDifferentiableOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/MultivariateDifferentiableOptimizer.java
new file mode 100644
index 0000000..a2cf840
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/MultivariateDifferentiableOptimizer.java
@@ -0,0 +1,34 @@
+/*
+ * 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.math3.optimization;
+
+import org.apache.commons.math3.analysis.differentiation.MultivariateDifferentiableFunction;
+
+/**
+ * This interface represents an optimization algorithm for {@link MultivariateDifferentiableFunction
+ * scalar differentiable objective functions}. Optimization algorithms find the input point set that
+ * either {@link GoalType maximize or minimize} an objective function.
+ *
+ * @see MultivariateOptimizer
+ * @see MultivariateDifferentiableVectorOptimizer
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.1
+ */
+@Deprecated
+public interface MultivariateDifferentiableOptimizer
+        extends BaseMultivariateOptimizer<MultivariateDifferentiableFunction> {}
diff --git a/src/main/java/org/apache/commons/math3/optimization/MultivariateDifferentiableVectorMultiStartOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/MultivariateDifferentiableVectorMultiStartOptimizer.java
new file mode 100644
index 0000000..36432cd
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/MultivariateDifferentiableVectorMultiStartOptimizer.java
@@ -0,0 +1,51 @@
+/*
+ * 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.math3.optimization;
+
+import org.apache.commons.math3.analysis.differentiation.MultivariateDifferentiableVectorFunction;
+import org.apache.commons.math3.random.RandomVectorGenerator;
+
+/**
+ * Special implementation of the {@link MultivariateDifferentiableVectorOptimizer} interface adding
+ * multi-start features to an existing optimizer.
+ *
+ * <p>This class wraps a classical optimizer to use it several times in turn with different starting
+ * points in order to avoid being trapped into a local extremum when looking for a global one.
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.1
+ */
+@Deprecated
+public class MultivariateDifferentiableVectorMultiStartOptimizer
+        extends BaseMultivariateVectorMultiStartOptimizer<MultivariateDifferentiableVectorFunction>
+        implements MultivariateDifferentiableVectorOptimizer {
+    /**
+     * Create a multi-start optimizer from a single-start optimizer.
+     *
+     * @param optimizer Single-start optimizer to wrap.
+     * @param starts Number of starts to perform (including the first one), multi-start is disabled
+     *     if value is less than or equal to 1.
+     * @param generator Random vector generator to use for restarts.
+     */
+    public MultivariateDifferentiableVectorMultiStartOptimizer(
+            final MultivariateDifferentiableVectorOptimizer optimizer,
+            final int starts,
+            final RandomVectorGenerator generator) {
+        super(optimizer, starts, generator);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/MultivariateDifferentiableVectorOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/MultivariateDifferentiableVectorOptimizer.java
new file mode 100644
index 0000000..65868db
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/MultivariateDifferentiableVectorOptimizer.java
@@ -0,0 +1,31 @@
+/*
+ * 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.math3.optimization;
+
+import org.apache.commons.math3.analysis.differentiation.MultivariateDifferentiableVectorFunction;
+
+/**
+ * This interface represents an optimization algorithm for {@link
+ * MultivariateDifferentiableVectorFunction differentiable vectorial objective functions}.
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.1
+ */
+@Deprecated
+public interface MultivariateDifferentiableVectorOptimizer
+        extends BaseMultivariateVectorOptimizer<MultivariateDifferentiableVectorFunction> {}
diff --git a/src/main/java/org/apache/commons/math3/optimization/MultivariateMultiStartOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/MultivariateMultiStartOptimizer.java
new file mode 100644
index 0000000..24726c4
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/MultivariateMultiStartOptimizer.java
@@ -0,0 +1,51 @@
+/*
+ * 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.math3.optimization;
+
+import org.apache.commons.math3.analysis.MultivariateFunction;
+import org.apache.commons.math3.random.RandomVectorGenerator;
+
+/**
+ * Special implementation of the {@link MultivariateOptimizer} interface adding multi-start features
+ * to an existing optimizer.
+ *
+ * <p>This class wraps a classical optimizer to use it several times in turn with different starting
+ * points in order to avoid being trapped into a local extremum when looking for a global one.
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 2.0
+ */
+@Deprecated
+public class MultivariateMultiStartOptimizer
+        extends BaseMultivariateMultiStartOptimizer<MultivariateFunction>
+        implements MultivariateOptimizer {
+    /**
+     * Create a multi-start optimizer from a single-start optimizer.
+     *
+     * @param optimizer Single-start optimizer to wrap.
+     * @param starts Number of starts to perform (including the first one), multi-start is disabled
+     *     if value is less than or equal to 1.
+     * @param generator Random vector generator to use for restarts.
+     */
+    public MultivariateMultiStartOptimizer(
+            final MultivariateOptimizer optimizer,
+            final int starts,
+            final RandomVectorGenerator generator) {
+        super(optimizer, starts, generator);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/MultivariateOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/MultivariateOptimizer.java
new file mode 100644
index 0000000..e90443c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/MultivariateOptimizer.java
@@ -0,0 +1,35 @@
+/*
+ * 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.math3.optimization;
+
+import org.apache.commons.math3.analysis.MultivariateFunction;
+
+/**
+ * This interface represents an optimization algorithm for {@link MultivariateFunction scalar
+ * objective functions}.
+ *
+ * <p>Optimization algorithms find the input point set that either {@link GoalType maximize or
+ * minimize} an objective function.
+ *
+ * @see MultivariateDifferentiableOptimizer
+ * @see MultivariateDifferentiableVectorOptimizer
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 2.0
+ */
+@Deprecated
+public interface MultivariateOptimizer extends BaseMultivariateOptimizer<MultivariateFunction> {}
diff --git a/src/main/java/org/apache/commons/math3/optimization/OptimizationData.java b/src/main/java/org/apache/commons/math3/optimization/OptimizationData.java
new file mode 100644
index 0000000..2faaa30
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/OptimizationData.java
@@ -0,0 +1,28 @@
+/*
+ * 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.math3.optimization;
+
+/**
+ * Marker interface. Implementations will provide functionality (optional or required) needed by the
+ * optimizers, and those will need to check the actual type of the arguments and perform the
+ * appropriate cast in order to access the data they need.
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.1
+ */
+@Deprecated
+public interface OptimizationData {}
diff --git a/src/main/java/org/apache/commons/math3/optimization/PointValuePair.java b/src/main/java/org/apache/commons/math3/optimization/PointValuePair.java
new file mode 100644
index 0000000..cb4e0bd
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/PointValuePair.java
@@ -0,0 +1,120 @@
+/*
+ * 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.math3.optimization;
+
+import org.apache.commons.math3.util.Pair;
+
+import java.io.Serializable;
+
+/**
+ * This class holds a point and the value of an objective function at that point.
+ *
+ * @see PointVectorValuePair
+ * @see org.apache.commons.math3.analysis.MultivariateFunction
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.0
+ */
+@Deprecated
+public class PointValuePair extends Pair<double[], Double> implements Serializable {
+
+    /** Serializable UID. */
+    private static final long serialVersionUID = 20120513L;
+
+    /**
+     * Builds a point/objective function value pair.
+     *
+     * @param point Point coordinates. This instance will store a copy of the array, not the array
+     *     passed as argument.
+     * @param value Value of the objective function at the point.
+     */
+    public PointValuePair(final double[] point, final double value) {
+        this(point, value, true);
+    }
+
+    /**
+     * Builds a point/objective function value pair.
+     *
+     * @param point Point coordinates.
+     * @param value Value of the objective function at the point.
+     * @param copyArray if {@code true}, the input array will be copied, otherwise it will be
+     *     referenced.
+     */
+    public PointValuePair(final double[] point, final double value, final boolean copyArray) {
+        super(copyArray ? ((point == null) ? null : point.clone()) : point, value);
+    }
+
+    /**
+     * Gets the point.
+     *
+     * @return a copy of the stored point.
+     */
+    public double[] getPoint() {
+        final double[] p = getKey();
+        return p == null ? null : p.clone();
+    }
+
+    /**
+     * Gets a reference to the point.
+     *
+     * @return a reference to the internal array storing the point.
+     */
+    public double[] getPointRef() {
+        return getKey();
+    }
+
+    /**
+     * Replace the instance with a data transfer object for serialization.
+     *
+     * @return data transfer object that will be serialized
+     */
+    private Object writeReplace() {
+        return new DataTransferObject(getKey(), getValue());
+    }
+
+    /** Internal class used only for serialization. */
+    private static class DataTransferObject implements Serializable {
+        /** Serializable UID. */
+        private static final long serialVersionUID = 20120513L;
+
+        /** Point coordinates. @Serial */
+        private final double[] point;
+
+        /** Value of the objective function at the point. @Serial */
+        private final double value;
+
+        /**
+         * Simple constructor.
+         *
+         * @param point Point coordinates.
+         * @param value Value of the objective function at the point.
+         */
+        DataTransferObject(final double[] point, final double value) {
+            this.point = point.clone();
+            this.value = value;
+        }
+
+        /**
+         * Replace the deserialized data transfer object with a {@link PointValuePair}.
+         *
+         * @return replacement {@link PointValuePair}
+         */
+        private Object readResolve() {
+            return new PointValuePair(point, value, false);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/PointVectorValuePair.java b/src/main/java/org/apache/commons/math3/optimization/PointVectorValuePair.java
new file mode 100644
index 0000000..bf1bf61
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/PointVectorValuePair.java
@@ -0,0 +1,143 @@
+/*
+ * 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.math3.optimization;
+
+import org.apache.commons.math3.util.Pair;
+
+import java.io.Serializable;
+
+/**
+ * This class holds a point and the vectorial value of an objective function at that point.
+ *
+ * @see PointValuePair
+ * @see org.apache.commons.math3.analysis.MultivariateVectorFunction
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.0
+ */
+@Deprecated
+public class PointVectorValuePair extends Pair<double[], double[]> implements Serializable {
+
+    /** Serializable UID. */
+    private static final long serialVersionUID = 20120513L;
+
+    /**
+     * Builds a point/objective function value pair.
+     *
+     * @param point Point coordinates. This instance will store a copy of the array, not the array
+     *     passed as argument.
+     * @param value Value of the objective function at the point.
+     */
+    public PointVectorValuePair(final double[] point, final double[] value) {
+        this(point, value, true);
+    }
+
+    /**
+     * Build a point/objective function value pair.
+     *
+     * @param point Point coordinates.
+     * @param value Value of the objective function at the point.
+     * @param copyArray if {@code true}, the input arrays will be copied, otherwise they will be
+     *     referenced.
+     */
+    public PointVectorValuePair(
+            final double[] point, final double[] value, final boolean copyArray) {
+        super(
+                copyArray ? ((point == null) ? null : point.clone()) : point,
+                copyArray ? ((value == null) ? null : value.clone()) : value);
+    }
+
+    /**
+     * Gets the point.
+     *
+     * @return a copy of the stored point.
+     */
+    public double[] getPoint() {
+        final double[] p = getKey();
+        return p == null ? null : p.clone();
+    }
+
+    /**
+     * Gets a reference to the point.
+     *
+     * @return a reference to the internal array storing the point.
+     */
+    public double[] getPointRef() {
+        return getKey();
+    }
+
+    /**
+     * Gets the value of the objective function.
+     *
+     * @return a copy of the stored value of the objective function.
+     */
+    @Override
+    public double[] getValue() {
+        final double[] v = super.getValue();
+        return v == null ? null : v.clone();
+    }
+
+    /**
+     * Gets a reference to the value of the objective function.
+     *
+     * @return a reference to the internal array storing the value of the objective function.
+     */
+    public double[] getValueRef() {
+        return super.getValue();
+    }
+
+    /**
+     * Replace the instance with a data transfer object for serialization.
+     *
+     * @return data transfer object that will be serialized
+     */
+    private Object writeReplace() {
+        return new DataTransferObject(getKey(), getValue());
+    }
+
+    /** Internal class used only for serialization. */
+    private static class DataTransferObject implements Serializable {
+        /** Serializable UID. */
+        private static final long serialVersionUID = 20120513L;
+
+        /** Point coordinates. @Serial */
+        private final double[] point;
+
+        /** Value of the objective function at the point. @Serial */
+        private final double[] value;
+
+        /**
+         * Simple constructor.
+         *
+         * @param point Point coordinates.
+         * @param value Value of the objective function at the point.
+         */
+        DataTransferObject(final double[] point, final double[] value) {
+            this.point = point.clone();
+            this.value = value.clone();
+        }
+
+        /**
+         * Replace the deserialized data transfer object with a {@link PointValuePair}.
+         *
+         * @return replacement {@link PointValuePair}
+         */
+        private Object readResolve() {
+            return new PointVectorValuePair(point, value, false);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/SimpleBounds.java b/src/main/java/org/apache/commons/math3/optimization/SimpleBounds.java
new file mode 100644
index 0000000..ebda3ca
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/SimpleBounds.java
@@ -0,0 +1,62 @@
+/*
+ * 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.math3.optimization;
+
+/**
+ * Simple optimization constraints: lower and upper bounds. The valid range of the parameters is an
+ * interval that can be infinite (in one or both directions). <br>
+ * Immutable class.
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.1
+ */
+@Deprecated
+public class SimpleBounds implements OptimizationData {
+    /** Lower bounds. */
+    private final double[] lower;
+
+    /** Upper bounds. */
+    private final double[] upper;
+
+    /**
+     * @param lB Lower bounds.
+     * @param uB Upper bounds.
+     */
+    public SimpleBounds(double[] lB, double[] uB) {
+        lower = lB.clone();
+        upper = uB.clone();
+    }
+
+    /**
+     * Gets the lower bounds.
+     *
+     * @return the initial guess.
+     */
+    public double[] getLower() {
+        return lower.clone();
+    }
+
+    /**
+     * Gets the lower bounds.
+     *
+     * @return the initial guess.
+     */
+    public double[] getUpper() {
+        return upper.clone();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/SimplePointChecker.java b/src/main/java/org/apache/commons/math3/optimization/SimplePointChecker.java
new file mode 100644
index 0000000..50ee2c5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/SimplePointChecker.java
@@ -0,0 +1,130 @@
+/*
+ * 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.math3.optimization;
+
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.Pair;
+
+/**
+ * Simple implementation of the {@link ConvergenceChecker} interface using only point coordinates.
+ *
+ * <p>Convergence is considered to have been reached if either the relative difference between each
+ * point coordinate are smaller than a threshold or if either the absolute difference between the
+ * point coordinates are smaller than another threshold. <br>
+ * The {@link #converged(int,Pair,Pair) converged} method will also return {@code true} if the
+ * number of iterations has been set (see {@link #SimplePointChecker(double,double,int) this
+ * constructor}).
+ *
+ * @param <PAIR> Type of the (point, value) pair. The type of the "value" part of the pair (not used
+ *     by this class).
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.0
+ */
+@Deprecated
+public class SimplePointChecker<PAIR extends Pair<double[], ? extends Object>>
+        extends AbstractConvergenceChecker<PAIR> {
+    /**
+     * If {@link #maxIterationCount} is set to this value, the number of iterations will never cause
+     * {@link #converged(int, Pair, Pair)} to return {@code true}.
+     */
+    private static final int ITERATION_CHECK_DISABLED = -1;
+
+    /**
+     * Number of iterations after which the {@link #converged(int, Pair, Pair)} method will return
+     * true (unless the check is disabled).
+     */
+    private final int maxIterationCount;
+
+    /**
+     * Build an instance with default threshold.
+     *
+     * @deprecated See {@link AbstractConvergenceChecker#AbstractConvergenceChecker()}
+     */
+    @Deprecated
+    public SimplePointChecker() {
+        maxIterationCount = ITERATION_CHECK_DISABLED;
+    }
+
+    /**
+     * Build an instance with specified thresholds. In order to perform only relative checks, the
+     * absolute tolerance must be set to a negative value. In order to perform only absolute checks,
+     * the relative tolerance must be set to a negative value.
+     *
+     * @param relativeThreshold relative tolerance threshold
+     * @param absoluteThreshold absolute tolerance threshold
+     */
+    public SimplePointChecker(final double relativeThreshold, final double absoluteThreshold) {
+        super(relativeThreshold, absoluteThreshold);
+        maxIterationCount = ITERATION_CHECK_DISABLED;
+    }
+
+    /**
+     * Builds an instance with specified thresholds. In order to perform only relative checks, the
+     * absolute tolerance must be set to a negative value. In order to perform only absolute checks,
+     * the relative tolerance must be set to a negative value.
+     *
+     * @param relativeThreshold Relative tolerance threshold.
+     * @param absoluteThreshold Absolute tolerance threshold.
+     * @param maxIter Maximum iteration count.
+     * @throws NotStrictlyPositiveException if {@code maxIter <= 0}.
+     * @since 3.1
+     */
+    public SimplePointChecker(
+            final double relativeThreshold, final double absoluteThreshold, final int maxIter) {
+        super(relativeThreshold, absoluteThreshold);
+
+        if (maxIter <= 0) {
+            throw new NotStrictlyPositiveException(maxIter);
+        }
+        maxIterationCount = maxIter;
+    }
+
+    /**
+     * Check if the optimization algorithm has converged considering the last two points. This
+     * method may be called several times from the same algorithm iteration with different points.
+     * This can be detected by checking the iteration number at each call if needed. Each time this
+     * method is called, the previous and current point correspond to points with the same role at
+     * each iteration, so they can be compared. As an example, simplex-based algorithms call this
+     * method for all points of the simplex, not only for the best or worst ones.
+     *
+     * @param iteration Index of current iteration
+     * @param previous Best point in the previous iteration.
+     * @param current Best point in the current iteration.
+     * @return {@code true} if the arguments satify the convergence criterion.
+     */
+    @Override
+    public boolean converged(final int iteration, final PAIR previous, final PAIR current) {
+        if (maxIterationCount != ITERATION_CHECK_DISABLED && iteration >= maxIterationCount) {
+            return true;
+        }
+
+        final double[] p = previous.getKey();
+        final double[] c = current.getKey();
+        for (int i = 0; i < p.length; ++i) {
+            final double pi = p[i];
+            final double ci = c[i];
+            final double difference = FastMath.abs(pi - ci);
+            final double size = FastMath.max(FastMath.abs(pi), FastMath.abs(ci));
+            if (difference > size * getRelativeThreshold() && difference > getAbsoluteThreshold()) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/SimpleValueChecker.java b/src/main/java/org/apache/commons/math3/optimization/SimpleValueChecker.java
new file mode 100644
index 0000000..6f7a2c0
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/SimpleValueChecker.java
@@ -0,0 +1,125 @@
+/*
+ * 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.math3.optimization;
+
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Simple implementation of the {@link ConvergenceChecker} interface using only objective function
+ * values.
+ *
+ * <p>Convergence is considered to have been reached if either the relative difference between the
+ * objective function values is smaller than a threshold or if either the absolute difference
+ * between the objective function values is smaller than another threshold. <br>
+ * The {@link #converged(int,PointValuePair,PointValuePair) converged} method will also return
+ * {@code true} if the number of iterations has been set (see {@link
+ * #SimpleValueChecker(double,double,int) this constructor}).
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.0
+ */
+@Deprecated
+public class SimpleValueChecker extends AbstractConvergenceChecker<PointValuePair> {
+    /**
+     * If {@link #maxIterationCount} is set to this value, the number of iterations will never cause
+     * {@link #converged(int,PointValuePair,PointValuePair)} to return {@code true}.
+     */
+    private static final int ITERATION_CHECK_DISABLED = -1;
+
+    /**
+     * Number of iterations after which the {@link #converged(int,PointValuePair,PointValuePair)}
+     * method will return true (unless the check is disabled).
+     */
+    private final int maxIterationCount;
+
+    /**
+     * Build an instance with default thresholds.
+     *
+     * @deprecated See {@link AbstractConvergenceChecker#AbstractConvergenceChecker()}
+     */
+    @Deprecated
+    public SimpleValueChecker() {
+        maxIterationCount = ITERATION_CHECK_DISABLED;
+    }
+
+    /**
+     * Build an instance with specified thresholds.
+     *
+     * <p>In order to perform only relative checks, the absolute tolerance must be set to a negative
+     * value. In order to perform only absolute checks, the relative tolerance must be set to a
+     * negative value.
+     *
+     * @param relativeThreshold relative tolerance threshold
+     * @param absoluteThreshold absolute tolerance threshold
+     */
+    public SimpleValueChecker(final double relativeThreshold, final double absoluteThreshold) {
+        super(relativeThreshold, absoluteThreshold);
+        maxIterationCount = ITERATION_CHECK_DISABLED;
+    }
+
+    /**
+     * Builds an instance with specified thresholds.
+     *
+     * <p>In order to perform only relative checks, the absolute tolerance must be set to a negative
+     * value. In order to perform only absolute checks, the relative tolerance must be set to a
+     * negative value.
+     *
+     * @param relativeThreshold relative tolerance threshold
+     * @param absoluteThreshold absolute tolerance threshold
+     * @param maxIter Maximum iteration count.
+     * @throws NotStrictlyPositiveException if {@code maxIter <= 0}.
+     * @since 3.1
+     */
+    public SimpleValueChecker(
+            final double relativeThreshold, final double absoluteThreshold, final int maxIter) {
+        super(relativeThreshold, absoluteThreshold);
+
+        if (maxIter <= 0) {
+            throw new NotStrictlyPositiveException(maxIter);
+        }
+        maxIterationCount = maxIter;
+    }
+
+    /**
+     * Check if the optimization algorithm has converged considering the last two points. This
+     * method may be called several time from the same algorithm iteration with different points.
+     * This can be detected by checking the iteration number at each call if needed. Each time this
+     * method is called, the previous and current point correspond to points with the same role at
+     * each iteration, so they can be compared. As an example, simplex-based algorithms call this
+     * method for all points of the simplex, not only for the best or worst ones.
+     *
+     * @param iteration Index of current iteration
+     * @param previous Best point in the previous iteration.
+     * @param current Best point in the current iteration.
+     * @return {@code true} if the algorithm has converged.
+     */
+    @Override
+    public boolean converged(
+            final int iteration, final PointValuePair previous, final PointValuePair current) {
+        if (maxIterationCount != ITERATION_CHECK_DISABLED && iteration >= maxIterationCount) {
+            return true;
+        }
+
+        final double p = previous.getValue();
+        final double c = current.getValue();
+        final double difference = FastMath.abs(p - c);
+        final double size = FastMath.max(FastMath.abs(p), FastMath.abs(c));
+        return difference <= size * getRelativeThreshold() || difference <= getAbsoluteThreshold();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/SimpleVectorValueChecker.java b/src/main/java/org/apache/commons/math3/optimization/SimpleVectorValueChecker.java
new file mode 100644
index 0000000..4ddb93d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/SimpleVectorValueChecker.java
@@ -0,0 +1,137 @@
+/*
+ * 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.math3.optimization;
+
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Simple implementation of the {@link ConvergenceChecker} interface using only objective function
+ * values.
+ *
+ * <p>Convergence is considered to have been reached if either the relative difference between the
+ * objective function values is smaller than a threshold or if either the absolute difference
+ * between the objective function values is smaller than another threshold for all vectors elements.
+ * <br>
+ * The {@link #converged(int,PointVectorValuePair,PointVectorValuePair) converged} method will also
+ * return {@code true} if the number of iterations has been set (see {@link
+ * #SimpleVectorValueChecker(double,double,int) this constructor}).
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.0
+ */
+@Deprecated
+public class SimpleVectorValueChecker extends AbstractConvergenceChecker<PointVectorValuePair> {
+    /**
+     * If {@link #maxIterationCount} is set to this value, the number of iterations will never cause
+     * {@link #converged(int,PointVectorValuePair,PointVectorValuePair)} to return {@code true}.
+     */
+    private static final int ITERATION_CHECK_DISABLED = -1;
+
+    /**
+     * Number of iterations after which the {@link
+     * #converged(int,PointVectorValuePair,PointVectorValuePair)} method will return true (unless
+     * the check is disabled).
+     */
+    private final int maxIterationCount;
+
+    /**
+     * Build an instance with default thresholds.
+     *
+     * @deprecated See {@link AbstractConvergenceChecker#AbstractConvergenceChecker()}
+     */
+    @Deprecated
+    public SimpleVectorValueChecker() {
+        maxIterationCount = ITERATION_CHECK_DISABLED;
+    }
+
+    /**
+     * Build an instance with specified thresholds.
+     *
+     * <p>In order to perform only relative checks, the absolute tolerance must be set to a negative
+     * value. In order to perform only absolute checks, the relative tolerance must be set to a
+     * negative value.
+     *
+     * @param relativeThreshold relative tolerance threshold
+     * @param absoluteThreshold absolute tolerance threshold
+     */
+    public SimpleVectorValueChecker(
+            final double relativeThreshold, final double absoluteThreshold) {
+        super(relativeThreshold, absoluteThreshold);
+        maxIterationCount = ITERATION_CHECK_DISABLED;
+    }
+
+    /**
+     * Builds an instance with specified tolerance thresholds and iteration count.
+     *
+     * <p>In order to perform only relative checks, the absolute tolerance must be set to a negative
+     * value. In order to perform only absolute checks, the relative tolerance must be set to a
+     * negative value.
+     *
+     * @param relativeThreshold Relative tolerance threshold.
+     * @param absoluteThreshold Absolute tolerance threshold.
+     * @param maxIter Maximum iteration count.
+     * @throws NotStrictlyPositiveException if {@code maxIter <= 0}.
+     * @since 3.1
+     */
+    public SimpleVectorValueChecker(
+            final double relativeThreshold, final double absoluteThreshold, final int maxIter) {
+        super(relativeThreshold, absoluteThreshold);
+
+        if (maxIter <= 0) {
+            throw new NotStrictlyPositiveException(maxIter);
+        }
+        maxIterationCount = maxIter;
+    }
+
+    /**
+     * Check if the optimization algorithm has converged considering the last two points. This
+     * method may be called several times from the same algorithm iteration with different points.
+     * This can be detected by checking the iteration number at each call if needed. Each time this
+     * method is called, the previous and current point correspond to points with the same role at
+     * each iteration, so they can be compared. As an example, simplex-based algorithms call this
+     * method for all points of the simplex, not only for the best or worst ones.
+     *
+     * @param iteration Index of current iteration
+     * @param previous Best point in the previous iteration.
+     * @param current Best point in the current iteration.
+     * @return {@code true} if the arguments satify the convergence criterion.
+     */
+    @Override
+    public boolean converged(
+            final int iteration,
+            final PointVectorValuePair previous,
+            final PointVectorValuePair current) {
+        if (maxIterationCount != ITERATION_CHECK_DISABLED && iteration >= maxIterationCount) {
+            return true;
+        }
+
+        final double[] p = previous.getValueRef();
+        final double[] c = current.getValueRef();
+        for (int i = 0; i < p.length; ++i) {
+            final double pi = p[i];
+            final double ci = c[i];
+            final double difference = FastMath.abs(pi - ci);
+            final double size = FastMath.max(FastMath.abs(pi), FastMath.abs(ci));
+            if (difference > size * getRelativeThreshold() && difference > getAbsoluteThreshold()) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/Target.java b/src/main/java/org/apache/commons/math3/optimization/Target.java
new file mode 100644
index 0000000..43940ac
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/Target.java
@@ -0,0 +1,48 @@
+/*
+ * 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.math3.optimization;
+
+/**
+ * Target of the optimization procedure. They are the values which the objective vector function
+ * must reproduce When the parameters of the model have been optimized. <br>
+ * Immutable class.
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.1
+ */
+@Deprecated
+public class Target implements OptimizationData {
+    /** Target values (of the objective vector function). */
+    private final double[] target;
+
+    /**
+     * @param observations Target values.
+     */
+    public Target(double[] observations) {
+        target = observations.clone();
+    }
+
+    /**
+     * Gets the initial guess.
+     *
+     * @return the initial guess.
+     */
+    public double[] getTarget() {
+        return target.clone();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/Weight.java b/src/main/java/org/apache/commons/math3/optimization/Weight.java
new file mode 100644
index 0000000..83226b5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/Weight.java
@@ -0,0 +1,66 @@
+/*
+ * 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.math3.optimization;
+
+import org.apache.commons.math3.linear.DiagonalMatrix;
+import org.apache.commons.math3.linear.NonSquareMatrixException;
+import org.apache.commons.math3.linear.RealMatrix;
+
+/**
+ * Weight matrix of the residuals between model and observations. <br>
+ * Immutable class.
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.1
+ */
+@Deprecated
+public class Weight implements OptimizationData {
+    /** Weight matrix. */
+    private final RealMatrix weightMatrix;
+
+    /**
+     * Creates a diagonal weight matrix.
+     *
+     * @param weight List of the values of the diagonal.
+     */
+    public Weight(double[] weight) {
+        weightMatrix = new DiagonalMatrix(weight);
+    }
+
+    /**
+     * @param weight Weight matrix.
+     * @throws NonSquareMatrixException if the argument is not a square matrix.
+     */
+    public Weight(RealMatrix weight) {
+        if (weight.getColumnDimension() != weight.getRowDimension()) {
+            throw new NonSquareMatrixException(
+                    weight.getColumnDimension(), weight.getRowDimension());
+        }
+
+        weightMatrix = weight.copy();
+    }
+
+    /**
+     * Gets the initial guess.
+     *
+     * @return the initial guess.
+     */
+    public RealMatrix getWeight() {
+        return weightMatrix.copy();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/direct/AbstractSimplex.java b/src/main/java/org/apache/commons/math3/optimization/direct/AbstractSimplex.java
new file mode 100644
index 0000000..b229cd1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/direct/AbstractSimplex.java
@@ -0,0 +1,347 @@
+/*
+ * 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.math3.optimization.direct;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+import org.apache.commons.math3.analysis.MultivariateFunction;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.ZeroException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.optimization.PointValuePair;
+import org.apache.commons.math3.optimization.OptimizationData;
+
+/**
+ * This class implements the simplex concept.
+ * It is intended to be used in conjunction with {@link SimplexOptimizer}.
+ * <br/>
+ * The initial configuration of the simplex is set by the constructors
+ * {@link #AbstractSimplex(double[])} or {@link #AbstractSimplex(double[][])}.
+ * The other {@link #AbstractSimplex(int) constructor} will set all steps
+ * to 1, thus building a default configuration from a unit hypercube.
+ * <br/>
+ * Users <em>must</em> call the {@link #build(double[]) build} method in order
+ * to create the data structure that will be acted on by the other methods of
+ * this class.
+ *
+ * @see SimplexOptimizer
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.0
+ */
+@Deprecated
+public abstract class AbstractSimplex implements OptimizationData {
+    /** Simplex. */
+    private PointValuePair[] simplex;
+    /** Start simplex configuration. */
+    private double[][] startConfiguration;
+    /** Simplex dimension (must be equal to {@code simplex.length - 1}). */
+    private final int dimension;
+
+    /**
+     * Build a unit hypercube simplex.
+     *
+     * @param n Dimension of the simplex.
+     */
+    protected AbstractSimplex(int n) {
+        this(n, 1d);
+    }
+
+    /**
+     * Build a hypercube simplex with the given side length.
+     *
+     * @param n Dimension of the simplex.
+     * @param sideLength Length of the sides of the hypercube.
+     */
+    protected AbstractSimplex(int n,
+                              double sideLength) {
+        this(createHypercubeSteps(n, sideLength));
+    }
+
+    /**
+     * The start configuration for simplex is built from a box parallel to
+     * the canonical axes of the space. The simplex is the subset of vertices
+     * of a box parallel to the canonical axes. It is built as the path followed
+     * while traveling from one vertex of the box to the diagonally opposite
+     * vertex moving only along the box edges. The first vertex of the box will
+     * be located at the start point of the optimization.
+     * As an example, in dimension 3 a simplex has 4 vertices. Setting the
+     * steps to (1, 10, 2) and the start point to (1, 1, 1) would imply the
+     * start simplex would be: { (1, 1, 1), (2, 1, 1), (2, 11, 1), (2, 11, 3) }.
+     * The first vertex would be set to the start point at (1, 1, 1) and the
+     * last vertex would be set to the diagonally opposite vertex at (2, 11, 3).
+     *
+     * @param steps Steps along the canonical axes representing box edges. They
+     * may be negative but not zero.
+     * @throws NullArgumentException if {@code steps} is {@code null}.
+     * @throws ZeroException if one of the steps is zero.
+     */
+    protected AbstractSimplex(final double[] steps) {
+        if (steps == null) {
+            throw new NullArgumentException();
+        }
+        if (steps.length == 0) {
+            throw new ZeroException();
+        }
+        dimension = steps.length;
+
+        // Only the relative position of the n final vertices with respect
+        // to the first one are stored.
+        startConfiguration = new double[dimension][dimension];
+        for (int i = 0; i < dimension; i++) {
+            final double[] vertexI = startConfiguration[i];
+            for (int j = 0; j < i + 1; j++) {
+                if (steps[j] == 0) {
+                    throw new ZeroException(LocalizedFormats.EQUAL_VERTICES_IN_SIMPLEX);
+                }
+                System.arraycopy(steps, 0, vertexI, 0, j + 1);
+            }
+        }
+    }
+
+    /**
+     * The real initial simplex will be set up by moving the reference
+     * simplex such that its first point is located at the start point of the
+     * optimization.
+     *
+     * @param referenceSimplex Reference simplex.
+     * @throws NotStrictlyPositiveException if the reference simplex does not
+     * contain at least one point.
+     * @throws DimensionMismatchException if there is a dimension mismatch
+     * in the reference simplex.
+     * @throws IllegalArgumentException if one of its vertices is duplicated.
+     */
+    protected AbstractSimplex(final double[][] referenceSimplex) {
+        if (referenceSimplex.length <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.SIMPLEX_NEED_ONE_POINT,
+                                                   referenceSimplex.length);
+        }
+        dimension = referenceSimplex.length - 1;
+
+        // Only the relative position of the n final vertices with respect
+        // to the first one are stored.
+        startConfiguration = new double[dimension][dimension];
+        final double[] ref0 = referenceSimplex[0];
+
+        // Loop over vertices.
+        for (int i = 0; i < referenceSimplex.length; i++) {
+            final double[] refI = referenceSimplex[i];
+
+            // Safety checks.
+            if (refI.length != dimension) {
+                throw new DimensionMismatchException(refI.length, dimension);
+            }
+            for (int j = 0; j < i; j++) {
+                final double[] refJ = referenceSimplex[j];
+                boolean allEquals = true;
+                for (int k = 0; k < dimension; k++) {
+                    if (refI[k] != refJ[k]) {
+                        allEquals = false;
+                        break;
+                    }
+                }
+                if (allEquals) {
+                    throw new MathIllegalArgumentException(LocalizedFormats.EQUAL_VERTICES_IN_SIMPLEX,
+                                                           i, j);
+                }
+            }
+
+            // Store vertex i position relative to vertex 0 position.
+            if (i > 0) {
+                final double[] confI = startConfiguration[i - 1];
+                for (int k = 0; k < dimension; k++) {
+                    confI[k] = refI[k] - ref0[k];
+                }
+            }
+        }
+    }
+
+    /**
+     * Get simplex dimension.
+     *
+     * @return the dimension of the simplex.
+     */
+    public int getDimension() {
+        return dimension;
+    }
+
+    /**
+     * Get simplex size.
+     * After calling the {@link #build(double[]) build} method, this method will
+     * will be equivalent to {@code getDimension() + 1}.
+     *
+     * @return the size of the simplex.
+     */
+    public int getSize() {
+        return simplex.length;
+    }
+
+    /**
+     * Compute the next simplex of the algorithm.
+     *
+     * @param evaluationFunction Evaluation function.
+     * @param comparator Comparator to use to sort simplex vertices from best
+     * to worst.
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException
+     * if the algorithm fails to converge.
+     */
+    public abstract void iterate(final MultivariateFunction evaluationFunction,
+                                 final Comparator<PointValuePair> comparator);
+
+    /**
+     * Build an initial simplex.
+     *
+     * @param startPoint First point of the simplex.
+     * @throws DimensionMismatchException if the start point does not match
+     * simplex dimension.
+     */
+    public void build(final double[] startPoint) {
+        if (dimension != startPoint.length) {
+            throw new DimensionMismatchException(dimension, startPoint.length);
+        }
+
+        // Set first vertex.
+        simplex = new PointValuePair[dimension + 1];
+        simplex[0] = new PointValuePair(startPoint, Double.NaN);
+
+        // Set remaining vertices.
+        for (int i = 0; i < dimension; i++) {
+            final double[] confI = startConfiguration[i];
+            final double[] vertexI = new double[dimension];
+            for (int k = 0; k < dimension; k++) {
+                vertexI[k] = startPoint[k] + confI[k];
+            }
+            simplex[i + 1] = new PointValuePair(vertexI, Double.NaN);
+        }
+    }
+
+    /**
+     * Evaluate all the non-evaluated points of the simplex.
+     *
+     * @param evaluationFunction Evaluation function.
+     * @param comparator Comparator to use to sort simplex vertices from best to worst.
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException
+     * if the maximal number of evaluations is exceeded.
+     */
+    public void evaluate(final MultivariateFunction evaluationFunction,
+                         final Comparator<PointValuePair> comparator) {
+        // Evaluate the objective function at all non-evaluated simplex points.
+        for (int i = 0; i < simplex.length; i++) {
+            final PointValuePair vertex = simplex[i];
+            final double[] point = vertex.getPointRef();
+            if (Double.isNaN(vertex.getValue())) {
+                simplex[i] = new PointValuePair(point, evaluationFunction.value(point), false);
+            }
+        }
+
+        // Sort the simplex from best to worst.
+        Arrays.sort(simplex, comparator);
+    }
+
+    /**
+     * Replace the worst point of the simplex by a new point.
+     *
+     * @param pointValuePair Point to insert.
+     * @param comparator Comparator to use for sorting the simplex vertices
+     * from best to worst.
+     */
+    protected void replaceWorstPoint(PointValuePair pointValuePair,
+                                     final Comparator<PointValuePair> comparator) {
+        for (int i = 0; i < dimension; i++) {
+            if (comparator.compare(simplex[i], pointValuePair) > 0) {
+                PointValuePair tmp = simplex[i];
+                simplex[i] = pointValuePair;
+                pointValuePair = tmp;
+            }
+        }
+        simplex[dimension] = pointValuePair;
+    }
+
+    /**
+     * Get the points of the simplex.
+     *
+     * @return all the simplex points.
+     */
+    public PointValuePair[] getPoints() {
+        final PointValuePair[] copy = new PointValuePair[simplex.length];
+        System.arraycopy(simplex, 0, copy, 0, simplex.length);
+        return copy;
+    }
+
+    /**
+     * Get the simplex point stored at the requested {@code index}.
+     *
+     * @param index Location.
+     * @return the point at location {@code index}.
+     */
+    public PointValuePair getPoint(int index) {
+        if (index < 0 ||
+            index >= simplex.length) {
+            throw new OutOfRangeException(index, 0, simplex.length - 1);
+        }
+        return simplex[index];
+    }
+
+    /**
+     * Store a new point at location {@code index}.
+     * Note that no deep-copy of {@code point} is performed.
+     *
+     * @param index Location.
+     * @param point New value.
+     */
+    protected void setPoint(int index, PointValuePair point) {
+        if (index < 0 ||
+            index >= simplex.length) {
+            throw new OutOfRangeException(index, 0, simplex.length - 1);
+        }
+        simplex[index] = point;
+    }
+
+    /**
+     * Replace all points.
+     * Note that no deep-copy of {@code points} is performed.
+     *
+     * @param points New Points.
+     */
+    protected void setPoints(PointValuePair[] points) {
+        if (points.length != simplex.length) {
+            throw new DimensionMismatchException(points.length, simplex.length);
+        }
+        simplex = points;
+    }
+
+    /**
+     * Create steps for a unit hypercube.
+     *
+     * @param n Dimension of the hypercube.
+     * @param sideLength Length of the sides of the hypercube.
+     * @return the steps.
+     */
+    private static double[] createHypercubeSteps(int n,
+                                                 double sideLength) {
+        final double[] steps = new double[n];
+        for (int i = 0; i < n; i++) {
+            steps[i] = sideLength;
+        }
+        return steps;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/direct/BOBYQAOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/direct/BOBYQAOptimizer.java
new file mode 100644
index 0000000..78d2d2c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/direct/BOBYQAOptimizer.java
@@ -0,0 +1,2480 @@
+/*
+ * 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.
+ */
+
+// CHECKSTYLE: stop all
+package org.apache.commons.math3.optimization.direct;
+
+import org.apache.commons.math3.analysis.MultivariateFunction;
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.linear.Array2DRowRealMatrix;
+import org.apache.commons.math3.linear.ArrayRealVector;
+import org.apache.commons.math3.linear.RealVector;
+import org.apache.commons.math3.optimization.GoalType;
+import org.apache.commons.math3.optimization.PointValuePair;
+import org.apache.commons.math3.optimization.MultivariateOptimizer;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Powell's BOBYQA algorithm. This implementation is translated and
+ * adapted from the Fortran version available
+ * <a href="http://plato.asu.edu/ftp/other_software/bobyqa.zip">here</a>.
+ * See <a href="http://www.optimization-online.org/DB_HTML/2010/05/2616.html">
+ * this paper</a> for an introduction.
+ * <br/>
+ * BOBYQA is particularly well suited for high dimensional problems
+ * where derivatives are not available. In most cases it outperforms the
+ * {@link PowellOptimizer} significantly. Stochastic algorithms like
+ * {@link CMAESOptimizer} succeed more often than BOBYQA, but are more
+ * expensive. BOBYQA could also be considered as a replacement of any
+ * derivative-based optimizer when the derivatives are approximated by
+ * finite differences.
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.0
+ */
+@Deprecated
+public class BOBYQAOptimizer
+    extends BaseAbstractMultivariateSimpleBoundsOptimizer<MultivariateFunction>
+    implements MultivariateOptimizer {
+    /** Minimum dimension of the problem: {@value} */
+    public static final int MINIMUM_PROBLEM_DIMENSION = 2;
+    /** Default value for {@link #initialTrustRegionRadius}: {@value} . */
+    public static final double DEFAULT_INITIAL_RADIUS = 10.0;
+    /** Default value for {@link #stoppingTrustRegionRadius}: {@value} . */
+    public static final double DEFAULT_STOPPING_RADIUS = 1E-8;
+    /** Constant 0. */
+    private static final double ZERO = 0d;
+    /** Constant 1. */
+    private static final double ONE = 1d;
+    /** Constant 2. */
+    private static final double TWO = 2d;
+    /** Constant 10. */
+    private static final double TEN = 10d;
+    /** Constant 16. */
+    private static final double SIXTEEN = 16d;
+    /** Constant 250. */
+    private static final double TWO_HUNDRED_FIFTY = 250d;
+    /** Constant -1. */
+    private static final double MINUS_ONE = -ONE;
+    /** Constant 1/2. */
+    private static final double HALF = ONE / 2;
+    /** Constant 1/4. */
+    private static final double ONE_OVER_FOUR = ONE / 4;
+    /** Constant 1/8. */
+    private static final double ONE_OVER_EIGHT = ONE / 8;
+    /** Constant 1/10. */
+    private static final double ONE_OVER_TEN = ONE / 10;
+    /** Constant 1/1000. */
+    private static final double ONE_OVER_A_THOUSAND = ONE / 1000;
+
+    /**
+     * numberOfInterpolationPoints XXX
+     */
+    private final int numberOfInterpolationPoints;
+    /**
+     * initialTrustRegionRadius XXX
+     */
+    private double initialTrustRegionRadius;
+    /**
+     * stoppingTrustRegionRadius XXX
+     */
+    private final double stoppingTrustRegionRadius;
+    /** Goal type (minimize or maximize). */
+    private boolean isMinimize;
+    /**
+     * Current best values for the variables to be optimized.
+     * The vector will be changed in-place to contain the values of the least
+     * calculated objective function values.
+     */
+    private ArrayRealVector currentBest;
+    /** Differences between the upper and lower bounds. */
+    private double[] boundDifference;
+    /**
+     * Index of the interpolation point at the trust region center.
+     */
+    private int trustRegionCenterInterpolationPointIndex;
+    /**
+     * Last <em>n</em> columns of matrix H (where <em>n</em> is the dimension
+     * of the problem).
+     * XXX "bmat" in the original code.
+     */
+    private Array2DRowRealMatrix bMatrix;
+    /**
+     * Factorization of the leading <em>npt</em> square submatrix of H, this
+     * factorization being Z Z<sup>T</sup>, which provides both the correct
+     * rank and positive semi-definiteness.
+     * XXX "zmat" in the original code.
+     */
+    private Array2DRowRealMatrix zMatrix;
+    /**
+     * Coordinates of the interpolation points relative to {@link #originShift}.
+     * XXX "xpt" in the original code.
+     */
+    private Array2DRowRealMatrix interpolationPoints;
+    /**
+     * Shift of origin that should reduce the contributions from rounding
+     * errors to values of the model and Lagrange functions.
+     * XXX "xbase" in the original code.
+     */
+    private ArrayRealVector originShift;
+    /**
+     * Values of the objective function at the interpolation points.
+     * XXX "fval" in the original code.
+     */
+    private ArrayRealVector fAtInterpolationPoints;
+    /**
+     * Displacement from {@link #originShift} of the trust region center.
+     * XXX "xopt" in the original code.
+     */
+    private ArrayRealVector trustRegionCenterOffset;
+    /**
+     * Gradient of the quadratic model at {@link #originShift} +
+     * {@link #trustRegionCenterOffset}.
+     * XXX "gopt" in the original code.
+     */
+    private ArrayRealVector gradientAtTrustRegionCenter;
+    /**
+     * Differences {@link #getLowerBound()} - {@link #originShift}.
+     * All the components of every {@link #trustRegionCenterOffset} are going
+     * to satisfy the bounds<br/>
+     * {@link #getLowerBound() lowerBound}<sub>i</sub> &le;
+     * {@link #trustRegionCenterOffset}<sub>i</sub>,<br/>
+     * with appropriate equalities when {@link #trustRegionCenterOffset} is
+     * on a constraint boundary.
+     * XXX "sl" in the original code.
+     */
+    private ArrayRealVector lowerDifference;
+    /**
+     * Differences {@link #getUpperBound()} - {@link #originShift}
+     * All the components of every {@link #trustRegionCenterOffset} are going
+     * to satisfy the bounds<br/>
+     *  {@link #trustRegionCenterOffset}<sub>i</sub> &le;
+     *  {@link #getUpperBound() upperBound}<sub>i</sub>,<br/>
+     * with appropriate equalities when {@link #trustRegionCenterOffset} is
+     * on a constraint boundary.
+     * XXX "su" in the original code.
+     */
+    private ArrayRealVector upperDifference;
+    /**
+     * Parameters of the implicit second derivatives of the quadratic model.
+     * XXX "pq" in the original code.
+     */
+    private ArrayRealVector modelSecondDerivativesParameters;
+    /**
+     * Point chosen by function {@link #trsbox(double,ArrayRealVector,
+     * ArrayRealVector, ArrayRealVector,ArrayRealVector,ArrayRealVector) trsbox}
+     * or {@link #altmov(int,double) altmov}.
+     * Usually {@link #originShift} + {@link #newPoint} is the vector of
+     * variables for the next evaluation of the objective function.
+     * It also satisfies the constraints indicated in {@link #lowerDifference}
+     * and {@link #upperDifference}.
+     * XXX "xnew" in the original code.
+     */
+    private ArrayRealVector newPoint;
+    /**
+     * Alternative to {@link #newPoint}, chosen by
+     * {@link #altmov(int,double) altmov}.
+     * It may replace {@link #newPoint} in order to increase the denominator
+     * in the {@link #update(double, double, int) updating procedure}.
+     * XXX "xalt" in the original code.
+     */
+    private ArrayRealVector alternativeNewPoint;
+    /**
+     * Trial step from {@link #trustRegionCenterOffset} which is usually
+     * {@link #newPoint} - {@link #trustRegionCenterOffset}.
+     * XXX "d__" in the original code.
+     */
+    private ArrayRealVector trialStepPoint;
+    /**
+     * Values of the Lagrange functions at a new point.
+     * XXX "vlag" in the original code.
+     */
+    private ArrayRealVector lagrangeValuesAtNewPoint;
+    /**
+     * Explicit second derivatives of the quadratic model.
+     * XXX "hq" in the original code.
+     */
+    private ArrayRealVector modelSecondDerivativesValues;
+
+    /**
+     * @param numberOfInterpolationPoints Number of interpolation conditions.
+     * For a problem of dimension {@code n}, its value must be in the interval
+     * {@code [n+2, (n+1)(n+2)/2]}.
+     * Choices that exceed {@code 2n+1} are not recommended.
+     */
+    public BOBYQAOptimizer(int numberOfInterpolationPoints) {
+        this(numberOfInterpolationPoints,
+             DEFAULT_INITIAL_RADIUS,
+             DEFAULT_STOPPING_RADIUS);
+    }
+
+    /**
+     * @param numberOfInterpolationPoints Number of interpolation conditions.
+     * For a problem of dimension {@code n}, its value must be in the interval
+     * {@code [n+2, (n+1)(n+2)/2]}.
+     * Choices that exceed {@code 2n+1} are not recommended.
+     * @param initialTrustRegionRadius Initial trust region radius.
+     * @param stoppingTrustRegionRadius Stopping trust region radius.
+     */
+    public BOBYQAOptimizer(int numberOfInterpolationPoints,
+                           double initialTrustRegionRadius,
+                           double stoppingTrustRegionRadius) {
+        super(null); // No custom convergence criterion.
+        this.numberOfInterpolationPoints = numberOfInterpolationPoints;
+        this.initialTrustRegionRadius = initialTrustRegionRadius;
+        this.stoppingTrustRegionRadius = stoppingTrustRegionRadius;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected PointValuePair doOptimize() {
+        final double[] lowerBound = getLowerBound();
+        final double[] upperBound = getUpperBound();
+
+        // Validity checks.
+        setup(lowerBound, upperBound);
+
+        isMinimize = (getGoalType() == GoalType.MINIMIZE);
+        currentBest = new ArrayRealVector(getStartPoint());
+
+        final double value = bobyqa(lowerBound, upperBound);
+
+        return new PointValuePair(currentBest.getDataRef(),
+                                      isMinimize ? value : -value);
+    }
+
+    /**
+     *     This subroutine seeks the least value of a function of many variables,
+     *     by applying a trust region method that forms quadratic models by
+     *     interpolation. There is usually some freedom in the interpolation
+     *     conditions, which is taken up by minimizing the Frobenius norm of
+     *     the change to the second derivative of the model, beginning with the
+     *     zero matrix. The values of the variables are constrained by upper and
+     *     lower bounds. The arguments of the subroutine are as follows.
+     *
+     *     N must be set to the number of variables and must be at least two.
+     *     NPT is the number of interpolation conditions. Its value must be in
+     *       the interval [N+2,(N+1)(N+2)/2]. Choices that exceed 2*N+1 are not
+     *       recommended.
+     *     Initial values of the variables must be set in X(1),X(2),...,X(N). They
+     *       will be changed to the values that give the least calculated F.
+     *     For I=1,2,...,N, XL(I) and XU(I) must provide the lower and upper
+     *       bounds, respectively, on X(I). The construction of quadratic models
+     *       requires XL(I) to be strictly less than XU(I) for each I. Further,
+     *       the contribution to a model from changes to the I-th variable is
+     *       damaged severely by rounding errors if XU(I)-XL(I) is too small.
+     *     RHOBEG and RHOEND must be set to the initial and final values of a trust
+     *       region radius, so both must be positive with RHOEND no greater than
+     *       RHOBEG. Typically, RHOBEG should be about one tenth of the greatest
+     *       expected change to a variable, while RHOEND should indicate the
+     *       accuracy that is required in the final values of the variables. An
+     *       error return occurs if any of the differences XU(I)-XL(I), I=1,...,N,
+     *       is less than 2*RHOBEG.
+     *     MAXFUN must be set to an upper bound on the number of calls of CALFUN.
+     *     The array W will be used for working space. Its length must be at least
+     *       (NPT+5)*(NPT+N)+3*N*(N+5)/2.
+     *
+     * @param lowerBound Lower bounds.
+     * @param upperBound Upper bounds.
+     * @return the value of the objective at the optimum.
+     */
+    private double bobyqa(double[] lowerBound,
+                          double[] upperBound) {
+        printMethod(); // XXX
+
+        final int n = currentBest.getDimension();
+
+        // Return if there is insufficient space between the bounds. Modify the
+        // initial X if necessary in order to avoid conflicts between the bounds
+        // and the construction of the first quadratic model. The lower and upper
+        // bounds on moves from the updated X are set now, in the ISL and ISU
+        // partitions of W, in order to provide useful and exact information about
+        // components of X that become within distance RHOBEG from their bounds.
+
+        for (int j = 0; j < n; j++) {
+            final double boundDiff = boundDifference[j];
+            lowerDifference.setEntry(j, lowerBound[j] - currentBest.getEntry(j));
+            upperDifference.setEntry(j, upperBound[j] - currentBest.getEntry(j));
+            if (lowerDifference.getEntry(j) >= -initialTrustRegionRadius) {
+                if (lowerDifference.getEntry(j) >= ZERO) {
+                    currentBest.setEntry(j, lowerBound[j]);
+                    lowerDifference.setEntry(j, ZERO);
+                    upperDifference.setEntry(j, boundDiff);
+                } else {
+                    currentBest.setEntry(j, lowerBound[j] + initialTrustRegionRadius);
+                    lowerDifference.setEntry(j, -initialTrustRegionRadius);
+                    // Computing MAX
+                    final double deltaOne = upperBound[j] - currentBest.getEntry(j);
+                    upperDifference.setEntry(j, FastMath.max(deltaOne, initialTrustRegionRadius));
+                }
+            } else if (upperDifference.getEntry(j) <= initialTrustRegionRadius) {
+                if (upperDifference.getEntry(j) <= ZERO) {
+                    currentBest.setEntry(j, upperBound[j]);
+                    lowerDifference.setEntry(j, -boundDiff);
+                    upperDifference.setEntry(j, ZERO);
+                } else {
+                    currentBest.setEntry(j, upperBound[j] - initialTrustRegionRadius);
+                    // Computing MIN
+                    final double deltaOne = lowerBound[j] - currentBest.getEntry(j);
+                    final double deltaTwo = -initialTrustRegionRadius;
+                    lowerDifference.setEntry(j, FastMath.min(deltaOne, deltaTwo));
+                    upperDifference.setEntry(j, initialTrustRegionRadius);
+                }
+            }
+        }
+
+        // Make the call of BOBYQB.
+
+        return bobyqb(lowerBound, upperBound);
+    } // bobyqa
+
+    // ----------------------------------------------------------------------------------------
+
+    /**
+     *     The arguments N, NPT, X, XL, XU, RHOBEG, RHOEND, IPRINT and MAXFUN
+     *       are identical to the corresponding arguments in SUBROUTINE BOBYQA.
+     *     XBASE holds a shift of origin that should reduce the contributions
+     *       from rounding errors to values of the model and Lagrange functions.
+     *     XPT is a two-dimensional array that holds the coordinates of the
+     *       interpolation points relative to XBASE.
+     *     FVAL holds the values of F at the interpolation points.
+     *     XOPT is set to the displacement from XBASE of the trust region centre.
+     *     GOPT holds the gradient of the quadratic model at XBASE+XOPT.
+     *     HQ holds the explicit second derivatives of the quadratic model.
+     *     PQ contains the parameters of the implicit second derivatives of the
+     *       quadratic model.
+     *     BMAT holds the last N columns of H.
+     *     ZMAT holds the factorization of the leading NPT by NPT submatrix of H,
+     *       this factorization being ZMAT times ZMAT^T, which provides both the
+     *       correct rank and positive semi-definiteness.
+     *     NDIM is the first dimension of BMAT and has the value NPT+N.
+     *     SL and SU hold the differences XL-XBASE and XU-XBASE, respectively.
+     *       All the components of every XOPT are going to satisfy the bounds
+     *       SL(I) .LEQ. XOPT(I) .LEQ. SU(I), with appropriate equalities when
+     *       XOPT is on a constraint boundary.
+     *     XNEW is chosen by SUBROUTINE TRSBOX or ALTMOV. Usually XBASE+XNEW is the
+     *       vector of variables for the next call of CALFUN. XNEW also satisfies
+     *       the SL and SU constraints in the way that has just been mentioned.
+     *     XALT is an alternative to XNEW, chosen by ALTMOV, that may replace XNEW
+     *       in order to increase the denominator in the updating of UPDATE.
+     *     D is reserved for a trial step from XOPT, which is usually XNEW-XOPT.
+     *     VLAG contains the values of the Lagrange functions at a new point X.
+     *       They are part of a product that requires VLAG to be of length NDIM.
+     *     W is a one-dimensional array that is used for working space. Its length
+     *       must be at least 3*NDIM = 3*(NPT+N).
+     *
+     * @param lowerBound Lower bounds.
+     * @param upperBound Upper bounds.
+     * @return the value of the objective at the optimum.
+     */
+    private double bobyqb(double[] lowerBound,
+                          double[] upperBound) {
+        printMethod(); // XXX
+
+        final int n = currentBest.getDimension();
+        final int npt = numberOfInterpolationPoints;
+        final int np = n + 1;
+        final int nptm = npt - np;
+        final int nh = n * np / 2;
+
+        final ArrayRealVector work1 = new ArrayRealVector(n);
+        final ArrayRealVector work2 = new ArrayRealVector(npt);
+        final ArrayRealVector work3 = new ArrayRealVector(npt);
+
+        double cauchy = Double.NaN;
+        double alpha = Double.NaN;
+        double dsq = Double.NaN;
+        double crvmin = Double.NaN;
+
+        // Set some constants.
+        // Parameter adjustments
+
+        // Function Body
+
+        // The call of PRELIM sets the elements of XBASE, XPT, FVAL, GOPT, HQ, PQ,
+        // BMAT and ZMAT for the first iteration, with the corresponding values of
+        // of NF and KOPT, which are the number of calls of CALFUN so far and the
+        // index of the interpolation point at the trust region centre. Then the
+        // initial XOPT is set too. The branch to label 720 occurs if MAXFUN is
+        // less than NPT. GOPT will be updated if KOPT is different from KBASE.
+
+        trustRegionCenterInterpolationPointIndex = 0;
+
+        prelim(lowerBound, upperBound);
+        double xoptsq = ZERO;
+        for (int i = 0; i < n; i++) {
+            trustRegionCenterOffset.setEntry(i, interpolationPoints.getEntry(trustRegionCenterInterpolationPointIndex, i));
+            // Computing 2nd power
+            final double deltaOne = trustRegionCenterOffset.getEntry(i);
+            xoptsq += deltaOne * deltaOne;
+        }
+        double fsave = fAtInterpolationPoints.getEntry(0);
+        final int kbase = 0;
+
+        // Complete the settings that are required for the iterative procedure.
+
+        int ntrits = 0;
+        int itest = 0;
+        int knew = 0;
+        int nfsav = getEvaluations();
+        double rho = initialTrustRegionRadius;
+        double delta = rho;
+        double diffa = ZERO;
+        double diffb = ZERO;
+        double diffc = ZERO;
+        double f = ZERO;
+        double beta = ZERO;
+        double adelt = ZERO;
+        double denom = ZERO;
+        double ratio = ZERO;
+        double dnorm = ZERO;
+        double scaden = ZERO;
+        double biglsq = ZERO;
+        double distsq = ZERO;
+
+        // Update GOPT if necessary before the first iteration and after each
+        // call of RESCUE that makes a call of CALFUN.
+
+        int state = 20;
+        for(;;) {
+        switch (state) {
+        case 20: {
+            printState(20); // XXX
+            if (trustRegionCenterInterpolationPointIndex != kbase) {
+                int ih = 0;
+                for (int j = 0; j < n; j++) {
+                    for (int i = 0; i <= j; i++) {
+                        if (i < j) {
+                            gradientAtTrustRegionCenter.setEntry(j,  gradientAtTrustRegionCenter.getEntry(j) + modelSecondDerivativesValues.getEntry(ih) * trustRegionCenterOffset.getEntry(i));
+                        }
+                        gradientAtTrustRegionCenter.setEntry(i,  gradientAtTrustRegionCenter.getEntry(i) + modelSecondDerivativesValues.getEntry(ih) * trustRegionCenterOffset.getEntry(j));
+                        ih++;
+                    }
+                }
+                if (getEvaluations() > npt) {
+                    for (int k = 0; k < npt; k++) {
+                        double temp = ZERO;
+                        for (int j = 0; j < n; j++) {
+                            temp += interpolationPoints.getEntry(k, j) * trustRegionCenterOffset.getEntry(j);
+                        }
+                        temp *= modelSecondDerivativesParameters.getEntry(k);
+                        for (int i = 0; i < n; i++) {
+                            gradientAtTrustRegionCenter.setEntry(i, gradientAtTrustRegionCenter.getEntry(i) + temp * interpolationPoints.getEntry(k, i));
+                        }
+                    }
+                    // throw new PathIsExploredException(); // XXX
+                }
+            }
+
+            // Generate the next point in the trust region that provides a small value
+            // of the quadratic model subject to the constraints on the variables.
+            // The int NTRITS is set to the number "trust region" iterations that
+            // have occurred since the last "alternative" iteration. If the length
+            // of XNEW-XOPT is less than HALF*RHO, however, then there is a branch to
+            // label 650 or 680 with NTRITS=-1, instead of calculating F at XNEW.
+
+        }
+        case 60: {
+            printState(60); // XXX
+            final ArrayRealVector gnew = new ArrayRealVector(n);
+            final ArrayRealVector xbdi = new ArrayRealVector(n);
+            final ArrayRealVector s = new ArrayRealVector(n);
+            final ArrayRealVector hs = new ArrayRealVector(n);
+            final ArrayRealVector hred = new ArrayRealVector(n);
+
+            final double[] dsqCrvmin = trsbox(delta, gnew, xbdi, s,
+                                              hs, hred);
+            dsq = dsqCrvmin[0];
+            crvmin = dsqCrvmin[1];
+
+            // Computing MIN
+            double deltaOne = delta;
+            double deltaTwo = FastMath.sqrt(dsq);
+            dnorm = FastMath.min(deltaOne, deltaTwo);
+            if (dnorm < HALF * rho) {
+                ntrits = -1;
+                // Computing 2nd power
+                deltaOne = TEN * rho;
+                distsq = deltaOne * deltaOne;
+                if (getEvaluations() <= nfsav + 2) {
+                    state = 650; break;
+                }
+
+                // The following choice between labels 650 and 680 depends on whether or
+                // not our work with the current RHO seems to be complete. Either RHO is
+                // decreased or termination occurs if the errors in the quadratic model at
+                // the last three interpolation points compare favourably with predictions
+                // of likely improvements to the model within distance HALF*RHO of XOPT.
+
+                // Computing MAX
+                deltaOne = FastMath.max(diffa, diffb);
+                final double errbig = FastMath.max(deltaOne, diffc);
+                final double frhosq = rho * ONE_OVER_EIGHT * rho;
+                if (crvmin > ZERO &&
+                    errbig > frhosq * crvmin) {
+                    state = 650; break;
+                }
+                final double bdtol = errbig / rho;
+                for (int j = 0; j < n; j++) {
+                    double bdtest = bdtol;
+                    if (newPoint.getEntry(j) == lowerDifference.getEntry(j)) {
+                        bdtest = work1.getEntry(j);
+                    }
+                    if (newPoint.getEntry(j) == upperDifference.getEntry(j)) {
+                        bdtest = -work1.getEntry(j);
+                    }
+                    if (bdtest < bdtol) {
+                        double curv = modelSecondDerivativesValues.getEntry((j + j * j) / 2);
+                        for (int k = 0; k < npt; k++) {
+                            // Computing 2nd power
+                            final double d1 = interpolationPoints.getEntry(k, j);
+                            curv += modelSecondDerivativesParameters.getEntry(k) * (d1 * d1);
+                        }
+                        bdtest += HALF * curv * rho;
+                        if (bdtest < bdtol) {
+                            state = 650; break;
+                        }
+                        // throw new PathIsExploredException(); // XXX
+                    }
+                }
+                state = 680; break;
+            }
+            ++ntrits;
+
+            // Severe cancellation is likely to occur if XOPT is too far from XBASE.
+            // If the following test holds, then XBASE is shifted so that XOPT becomes
+            // zero. The appropriate changes are made to BMAT and to the second
+            // derivatives of the current model, beginning with the changes to BMAT
+            // that do not depend on ZMAT. VLAG is used temporarily for working space.
+
+        }
+        case 90: {
+            printState(90); // XXX
+            if (dsq <= xoptsq * ONE_OVER_A_THOUSAND) {
+                final double fracsq = xoptsq * ONE_OVER_FOUR;
+                double sumpq = ZERO;
+                // final RealVector sumVector
+                //     = new ArrayRealVector(npt, -HALF * xoptsq).add(interpolationPoints.operate(trustRegionCenter));
+                for (int k = 0; k < npt; k++) {
+                    sumpq += modelSecondDerivativesParameters.getEntry(k);
+                    double sum = -HALF * xoptsq;
+                    for (int i = 0; i < n; i++) {
+                        sum += interpolationPoints.getEntry(k, i) * trustRegionCenterOffset.getEntry(i);
+                    }
+                    // sum = sumVector.getEntry(k); // XXX "testAckley" and "testDiffPow" fail.
+                    work2.setEntry(k, sum);
+                    final double temp = fracsq - HALF * sum;
+                    for (int i = 0; i < n; i++) {
+                        work1.setEntry(i, bMatrix.getEntry(k, i));
+                        lagrangeValuesAtNewPoint.setEntry(i, sum * interpolationPoints.getEntry(k, i) + temp * trustRegionCenterOffset.getEntry(i));
+                        final int ip = npt + i;
+                        for (int j = 0; j <= i; j++) {
+                            bMatrix.setEntry(ip, j,
+                                          bMatrix.getEntry(ip, j)
+                                          + work1.getEntry(i) * lagrangeValuesAtNewPoint.getEntry(j)
+                                          + lagrangeValuesAtNewPoint.getEntry(i) * work1.getEntry(j));
+                        }
+                    }
+                }
+
+                // Then the revisions of BMAT that depend on ZMAT are calculated.
+
+                for (int m = 0; m < nptm; m++) {
+                    double sumz = ZERO;
+                    double sumw = ZERO;
+                    for (int k = 0; k < npt; k++) {
+                        sumz += zMatrix.getEntry(k, m);
+                        lagrangeValuesAtNewPoint.setEntry(k, work2.getEntry(k) * zMatrix.getEntry(k, m));
+                        sumw += lagrangeValuesAtNewPoint.getEntry(k);
+                    }
+                    for (int j = 0; j < n; j++) {
+                        double sum = (fracsq * sumz - HALF * sumw) * trustRegionCenterOffset.getEntry(j);
+                        for (int k = 0; k < npt; k++) {
+                            sum += lagrangeValuesAtNewPoint.getEntry(k) * interpolationPoints.getEntry(k, j);
+                        }
+                        work1.setEntry(j, sum);
+                        for (int k = 0; k < npt; k++) {
+                            bMatrix.setEntry(k, j,
+                                          bMatrix.getEntry(k, j)
+                                          + sum * zMatrix.getEntry(k, m));
+                        }
+                    }
+                    for (int i = 0; i < n; i++) {
+                        final int ip = i + npt;
+                        final double temp = work1.getEntry(i);
+                        for (int j = 0; j <= i; j++) {
+                            bMatrix.setEntry(ip, j,
+                                          bMatrix.getEntry(ip, j)
+                                          + temp * work1.getEntry(j));
+                        }
+                    }
+                }
+
+                // The following instructions complete the shift, including the changes
+                // to the second derivative parameters of the quadratic model.
+
+                int ih = 0;
+                for (int j = 0; j < n; j++) {
+                    work1.setEntry(j, -HALF * sumpq * trustRegionCenterOffset.getEntry(j));
+                    for (int k = 0; k < npt; k++) {
+                        work1.setEntry(j, work1.getEntry(j) + modelSecondDerivativesParameters.getEntry(k) * interpolationPoints.getEntry(k, j));
+                        interpolationPoints.setEntry(k, j, interpolationPoints.getEntry(k, j) - trustRegionCenterOffset.getEntry(j));
+                    }
+                    for (int i = 0; i <= j; i++) {
+                         modelSecondDerivativesValues.setEntry(ih,
+                                    modelSecondDerivativesValues.getEntry(ih)
+                                    + work1.getEntry(i) * trustRegionCenterOffset.getEntry(j)
+                                    + trustRegionCenterOffset.getEntry(i) * work1.getEntry(j));
+                        bMatrix.setEntry(npt + i, j, bMatrix.getEntry(npt + j, i));
+                        ih++;
+                    }
+                }
+                for (int i = 0; i < n; i++) {
+                    originShift.setEntry(i, originShift.getEntry(i) + trustRegionCenterOffset.getEntry(i));
+                    newPoint.setEntry(i, newPoint.getEntry(i) - trustRegionCenterOffset.getEntry(i));
+                    lowerDifference.setEntry(i, lowerDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i));
+                    upperDifference.setEntry(i, upperDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i));
+                    trustRegionCenterOffset.setEntry(i, ZERO);
+                }
+                xoptsq = ZERO;
+            }
+            if (ntrits == 0) {
+                state = 210; break;
+            }
+            state = 230; break;
+
+            // XBASE is also moved to XOPT by a call of RESCUE. This calculation is
+            // more expensive than the previous shift, because new matrices BMAT and
+            // ZMAT are generated from scratch, which may include the replacement of
+            // interpolation points whose positions seem to be causing near linear
+            // dependence in the interpolation conditions. Therefore RESCUE is called
+            // only if rounding errors have reduced by at least a factor of two the
+            // denominator of the formula for updating the H matrix. It provides a
+            // useful safeguard, but is not invoked in most applications of BOBYQA.
+
+        }
+        case 210: {
+            printState(210); // XXX
+            // Pick two alternative vectors of variables, relative to XBASE, that
+            // are suitable as new positions of the KNEW-th interpolation point.
+            // Firstly, XNEW is set to the point on a line through XOPT and another
+            // interpolation point that minimizes the predicted value of the next
+            // denominator, subject to ||XNEW - XOPT|| .LEQ. ADELT and to the SL
+            // and SU bounds. Secondly, XALT is set to the best feasible point on
+            // a constrained version of the Cauchy step of the KNEW-th Lagrange
+            // function, the corresponding value of the square of this function
+            // being returned in CAUCHY. The choice between these alternatives is
+            // going to be made when the denominator is calculated.
+
+            final double[] alphaCauchy = altmov(knew, adelt);
+            alpha = alphaCauchy[0];
+            cauchy = alphaCauchy[1];
+
+            for (int i = 0; i < n; i++) {
+                trialStepPoint.setEntry(i, newPoint.getEntry(i) - trustRegionCenterOffset.getEntry(i));
+            }
+
+            // Calculate VLAG and BETA for the current choice of D. The scalar
+            // product of D with XPT(K,.) is going to be held in W(NPT+K) for
+            // use when VQUAD is calculated.
+
+        }
+        case 230: {
+            printState(230); // XXX
+            for (int k = 0; k < npt; k++) {
+                double suma = ZERO;
+                double sumb = ZERO;
+                double sum = ZERO;
+                for (int j = 0; j < n; j++) {
+                    suma += interpolationPoints.getEntry(k, j) * trialStepPoint.getEntry(j);
+                    sumb += interpolationPoints.getEntry(k, j) * trustRegionCenterOffset.getEntry(j);
+                    sum += bMatrix.getEntry(k, j) * trialStepPoint.getEntry(j);
+                }
+                work3.setEntry(k, suma * (HALF * suma + sumb));
+                lagrangeValuesAtNewPoint.setEntry(k, sum);
+                work2.setEntry(k, suma);
+            }
+            beta = ZERO;
+            for (int m = 0; m < nptm; m++) {
+                double sum = ZERO;
+                for (int k = 0; k < npt; k++) {
+                    sum += zMatrix.getEntry(k, m) * work3.getEntry(k);
+                }
+                beta -= sum * sum;
+                for (int k = 0; k < npt; k++) {
+                    lagrangeValuesAtNewPoint.setEntry(k, lagrangeValuesAtNewPoint.getEntry(k) + sum * zMatrix.getEntry(k, m));
+                }
+            }
+            dsq = ZERO;
+            double bsum = ZERO;
+            double dx = ZERO;
+            for (int j = 0; j < n; j++) {
+                // Computing 2nd power
+                final double d1 = trialStepPoint.getEntry(j);
+                dsq += d1 * d1;
+                double sum = ZERO;
+                for (int k = 0; k < npt; k++) {
+                    sum += work3.getEntry(k) * bMatrix.getEntry(k, j);
+                }
+                bsum += sum * trialStepPoint.getEntry(j);
+                final int jp = npt + j;
+                for (int i = 0; i < n; i++) {
+                    sum += bMatrix.getEntry(jp, i) * trialStepPoint.getEntry(i);
+                }
+                lagrangeValuesAtNewPoint.setEntry(jp, sum);
+                bsum += sum * trialStepPoint.getEntry(j);
+                dx += trialStepPoint.getEntry(j) * trustRegionCenterOffset.getEntry(j);
+            }
+
+            beta = dx * dx + dsq * (xoptsq + dx + dx + HALF * dsq) + beta - bsum; // Original
+            // beta += dx * dx + dsq * (xoptsq + dx + dx + HALF * dsq) - bsum; // XXX "testAckley" and "testDiffPow" fail.
+            // beta = dx * dx + dsq * (xoptsq + 2 * dx + HALF * dsq) + beta - bsum; // XXX "testDiffPow" fails.
+
+            lagrangeValuesAtNewPoint.setEntry(trustRegionCenterInterpolationPointIndex,
+                          lagrangeValuesAtNewPoint.getEntry(trustRegionCenterInterpolationPointIndex) + ONE);
+
+            // If NTRITS is zero, the denominator may be increased by replacing
+            // the step D of ALTMOV by a Cauchy step. Then RESCUE may be called if
+            // rounding errors have damaged the chosen denominator.
+
+            if (ntrits == 0) {
+                // Computing 2nd power
+                final double d1 = lagrangeValuesAtNewPoint.getEntry(knew);
+                denom = d1 * d1 + alpha * beta;
+                if (denom < cauchy && cauchy > ZERO) {
+                    for (int i = 0; i < n; i++) {
+                        newPoint.setEntry(i, alternativeNewPoint.getEntry(i));
+                        trialStepPoint.setEntry(i, newPoint.getEntry(i) - trustRegionCenterOffset.getEntry(i));
+                    }
+                    cauchy = ZERO; // XXX Useful statement?
+                    state = 230; break;
+                }
+                // Alternatively, if NTRITS is positive, then set KNEW to the index of
+                // the next interpolation point to be deleted to make room for a trust
+                // region step. Again RESCUE may be called if rounding errors have damaged_
+                // the chosen denominator, which is the reason for attempting to select
+                // KNEW before calculating the next value of the objective function.
+
+            } else {
+                final double delsq = delta * delta;
+                scaden = ZERO;
+                biglsq = ZERO;
+                knew = 0;
+                for (int k = 0; k < npt; k++) {
+                    if (k == trustRegionCenterInterpolationPointIndex) {
+                        continue;
+                    }
+                    double hdiag = ZERO;
+                    for (int m = 0; m < nptm; m++) {
+                        // Computing 2nd power
+                        final double d1 = zMatrix.getEntry(k, m);
+                        hdiag += d1 * d1;
+                    }
+                    // Computing 2nd power
+                    final double d2 = lagrangeValuesAtNewPoint.getEntry(k);
+                    final double den = beta * hdiag + d2 * d2;
+                    distsq = ZERO;
+                    for (int j = 0; j < n; j++) {
+                        // Computing 2nd power
+                        final double d3 = interpolationPoints.getEntry(k, j) - trustRegionCenterOffset.getEntry(j);
+                        distsq += d3 * d3;
+                    }
+                    // Computing MAX
+                    // Computing 2nd power
+                    final double d4 = distsq / delsq;
+                    final double temp = FastMath.max(ONE, d4 * d4);
+                    if (temp * den > scaden) {
+                        scaden = temp * den;
+                        knew = k;
+                        denom = den;
+                    }
+                    // Computing MAX
+                    // Computing 2nd power
+                    final double d5 = lagrangeValuesAtNewPoint.getEntry(k);
+                    biglsq = FastMath.max(biglsq, temp * (d5 * d5));
+                }
+            }
+
+            // Put the variables for the next calculation of the objective function
+            //   in XNEW, with any adjustments for the bounds.
+
+            // Calculate the value of the objective function at XBASE+XNEW, unless
+            //   the limit on the number of calculations of F has been reached.
+
+        }
+        case 360: {
+            printState(360); // XXX
+            for (int i = 0; i < n; i++) {
+                // Computing MIN
+                // Computing MAX
+                final double d3 = lowerBound[i];
+                final double d4 = originShift.getEntry(i) + newPoint.getEntry(i);
+                final double d1 = FastMath.max(d3, d4);
+                final double d2 = upperBound[i];
+                currentBest.setEntry(i, FastMath.min(d1, d2));
+                if (newPoint.getEntry(i) == lowerDifference.getEntry(i)) {
+                    currentBest.setEntry(i, lowerBound[i]);
+                }
+                if (newPoint.getEntry(i) == upperDifference.getEntry(i)) {
+                    currentBest.setEntry(i, upperBound[i]);
+                }
+            }
+
+            f = computeObjectiveValue(currentBest.toArray());
+
+            if (!isMinimize) {
+                f = -f;
+            }
+            if (ntrits == -1) {
+                fsave = f;
+                state = 720; break;
+            }
+
+            // Use the quadratic model to predict the change in F due to the step D,
+            //   and set DIFF to the error of this prediction.
+
+            final double fopt = fAtInterpolationPoints.getEntry(trustRegionCenterInterpolationPointIndex);
+            double vquad = ZERO;
+            int ih = 0;
+            for (int j = 0; j < n; j++) {
+                vquad += trialStepPoint.getEntry(j) * gradientAtTrustRegionCenter.getEntry(j);
+                for (int i = 0; i <= j; i++) {
+                    double temp = trialStepPoint.getEntry(i) * trialStepPoint.getEntry(j);
+                    if (i == j) {
+                        temp *= HALF;
+                    }
+                    vquad += modelSecondDerivativesValues.getEntry(ih) * temp;
+                    ih++;
+               }
+            }
+            for (int k = 0; k < npt; k++) {
+                // Computing 2nd power
+                final double d1 = work2.getEntry(k);
+                final double d2 = d1 * d1; // "d1" must be squared first to prevent test failures.
+                vquad += HALF * modelSecondDerivativesParameters.getEntry(k) * d2;
+            }
+            final double diff = f - fopt - vquad;
+            diffc = diffb;
+            diffb = diffa;
+            diffa = FastMath.abs(diff);
+            if (dnorm > rho) {
+                nfsav = getEvaluations();
+            }
+
+            // Pick the next value of DELTA after a trust region step.
+
+            if (ntrits > 0) {
+                if (vquad >= ZERO) {
+                    throw new MathIllegalStateException(LocalizedFormats.TRUST_REGION_STEP_FAILED, vquad);
+                }
+                ratio = (f - fopt) / vquad;
+                final double hDelta = HALF * delta;
+                if (ratio <= ONE_OVER_TEN) {
+                    // Computing MIN
+                    delta = FastMath.min(hDelta, dnorm);
+                } else if (ratio <= .7) {
+                    // Computing MAX
+                    delta = FastMath.max(hDelta, dnorm);
+                } else {
+                    // Computing MAX
+                    delta = FastMath.max(hDelta, 2 * dnorm);
+                }
+                if (delta <= rho * 1.5) {
+                    delta = rho;
+                }
+
+                // Recalculate KNEW and DENOM if the new F is less than FOPT.
+
+                if (f < fopt) {
+                    final int ksav = knew;
+                    final double densav = denom;
+                    final double delsq = delta * delta;
+                    scaden = ZERO;
+                    biglsq = ZERO;
+                    knew = 0;
+                    for (int k = 0; k < npt; k++) {
+                        double hdiag = ZERO;
+                        for (int m = 0; m < nptm; m++) {
+                            // Computing 2nd power
+                            final double d1 = zMatrix.getEntry(k, m);
+                            hdiag += d1 * d1;
+                        }
+                        // Computing 2nd power
+                        final double d1 = lagrangeValuesAtNewPoint.getEntry(k);
+                        final double den = beta * hdiag + d1 * d1;
+                        distsq = ZERO;
+                        for (int j = 0; j < n; j++) {
+                            // Computing 2nd power
+                            final double d2 = interpolationPoints.getEntry(k, j) - newPoint.getEntry(j);
+                            distsq += d2 * d2;
+                        }
+                        // Computing MAX
+                        // Computing 2nd power
+                        final double d3 = distsq / delsq;
+                        final double temp = FastMath.max(ONE, d3 * d3);
+                        if (temp * den > scaden) {
+                            scaden = temp * den;
+                            knew = k;
+                            denom = den;
+                        }
+                        // Computing MAX
+                        // Computing 2nd power
+                        final double d4 = lagrangeValuesAtNewPoint.getEntry(k);
+                        final double d5 = temp * (d4 * d4);
+                        biglsq = FastMath.max(biglsq, d5);
+                    }
+                    if (scaden <= HALF * biglsq) {
+                        knew = ksav;
+                        denom = densav;
+                    }
+                }
+            }
+
+            // Update BMAT and ZMAT, so that the KNEW-th interpolation point can be
+            // moved. Also update the second derivative terms of the model.
+
+            update(beta, denom, knew);
+
+            ih = 0;
+            final double pqold = modelSecondDerivativesParameters.getEntry(knew);
+            modelSecondDerivativesParameters.setEntry(knew, ZERO);
+            for (int i = 0; i < n; i++) {
+                final double temp = pqold * interpolationPoints.getEntry(knew, i);
+                for (int j = 0; j <= i; j++) {
+                    modelSecondDerivativesValues.setEntry(ih, modelSecondDerivativesValues.getEntry(ih) + temp * interpolationPoints.getEntry(knew, j));
+                    ih++;
+                }
+            }
+            for (int m = 0; m < nptm; m++) {
+                final double temp = diff * zMatrix.getEntry(knew, m);
+                for (int k = 0; k < npt; k++) {
+                    modelSecondDerivativesParameters.setEntry(k, modelSecondDerivativesParameters.getEntry(k) + temp * zMatrix.getEntry(k, m));
+                }
+            }
+
+            // Include the new interpolation point, and make the changes to GOPT at
+            // the old XOPT that are caused by the updating of the quadratic model.
+
+            fAtInterpolationPoints.setEntry(knew,  f);
+            for (int i = 0; i < n; i++) {
+                interpolationPoints.setEntry(knew, i, newPoint.getEntry(i));
+                work1.setEntry(i, bMatrix.getEntry(knew, i));
+            }
+            for (int k = 0; k < npt; k++) {
+                double suma = ZERO;
+                for (int m = 0; m < nptm; m++) {
+                    suma += zMatrix.getEntry(knew, m) * zMatrix.getEntry(k, m);
+                }
+                double sumb = ZERO;
+                for (int j = 0; j < n; j++) {
+                    sumb += interpolationPoints.getEntry(k, j) * trustRegionCenterOffset.getEntry(j);
+                }
+                final double temp = suma * sumb;
+                for (int i = 0; i < n; i++) {
+                    work1.setEntry(i, work1.getEntry(i) + temp * interpolationPoints.getEntry(k, i));
+                }
+            }
+            for (int i = 0; i < n; i++) {
+                gradientAtTrustRegionCenter.setEntry(i, gradientAtTrustRegionCenter.getEntry(i) + diff * work1.getEntry(i));
+            }
+
+            // Update XOPT, GOPT and KOPT if the new calculated F is less than FOPT.
+
+            if (f < fopt) {
+                trustRegionCenterInterpolationPointIndex = knew;
+                xoptsq = ZERO;
+                ih = 0;
+                for (int j = 0; j < n; j++) {
+                    trustRegionCenterOffset.setEntry(j, newPoint.getEntry(j));
+                    // Computing 2nd power
+                    final double d1 = trustRegionCenterOffset.getEntry(j);
+                    xoptsq += d1 * d1;
+                    for (int i = 0; i <= j; i++) {
+                        if (i < j) {
+                            gradientAtTrustRegionCenter.setEntry(j, gradientAtTrustRegionCenter.getEntry(j) + modelSecondDerivativesValues.getEntry(ih) * trialStepPoint.getEntry(i));
+                        }
+                        gradientAtTrustRegionCenter.setEntry(i, gradientAtTrustRegionCenter.getEntry(i) + modelSecondDerivativesValues.getEntry(ih) * trialStepPoint.getEntry(j));
+                        ih++;
+                    }
+                }
+                for (int k = 0; k < npt; k++) {
+                    double temp = ZERO;
+                    for (int j = 0; j < n; j++) {
+                        temp += interpolationPoints.getEntry(k, j) * trialStepPoint.getEntry(j);
+                    }
+                    temp *= modelSecondDerivativesParameters.getEntry(k);
+                    for (int i = 0; i < n; i++) {
+                        gradientAtTrustRegionCenter.setEntry(i, gradientAtTrustRegionCenter.getEntry(i) + temp * interpolationPoints.getEntry(k, i));
+                    }
+                }
+            }
+
+            // Calculate the parameters of the least Frobenius norm interpolant to
+            // the current data, the gradient of this interpolant at XOPT being put
+            // into VLAG(NPT+I), I=1,2,...,N.
+
+            if (ntrits > 0) {
+                for (int k = 0; k < npt; k++) {
+                    lagrangeValuesAtNewPoint.setEntry(k, fAtInterpolationPoints.getEntry(k) - fAtInterpolationPoints.getEntry(trustRegionCenterInterpolationPointIndex));
+                    work3.setEntry(k, ZERO);
+                }
+                for (int j = 0; j < nptm; j++) {
+                    double sum = ZERO;
+                    for (int k = 0; k < npt; k++) {
+                        sum += zMatrix.getEntry(k, j) * lagrangeValuesAtNewPoint.getEntry(k);
+                    }
+                    for (int k = 0; k < npt; k++) {
+                        work3.setEntry(k, work3.getEntry(k) + sum * zMatrix.getEntry(k, j));
+                    }
+                }
+                for (int k = 0; k < npt; k++) {
+                    double sum = ZERO;
+                    for (int j = 0; j < n; j++) {
+                        sum += interpolationPoints.getEntry(k, j) * trustRegionCenterOffset.getEntry(j);
+                    }
+                    work2.setEntry(k, work3.getEntry(k));
+                    work3.setEntry(k, sum * work3.getEntry(k));
+                }
+                double gqsq = ZERO;
+                double gisq = ZERO;
+                for (int i = 0; i < n; i++) {
+                    double sum = ZERO;
+                    for (int k = 0; k < npt; k++) {
+                        sum += bMatrix.getEntry(k, i) *
+                            lagrangeValuesAtNewPoint.getEntry(k) + interpolationPoints.getEntry(k, i) * work3.getEntry(k);
+                    }
+                    if (trustRegionCenterOffset.getEntry(i) == lowerDifference.getEntry(i)) {
+                        // Computing MIN
+                        // Computing 2nd power
+                        final double d1 = FastMath.min(ZERO, gradientAtTrustRegionCenter.getEntry(i));
+                        gqsq += d1 * d1;
+                        // Computing 2nd power
+                        final double d2 = FastMath.min(ZERO, sum);
+                        gisq += d2 * d2;
+                    } else if (trustRegionCenterOffset.getEntry(i) == upperDifference.getEntry(i)) {
+                        // Computing MAX
+                        // Computing 2nd power
+                        final double d1 = FastMath.max(ZERO, gradientAtTrustRegionCenter.getEntry(i));
+                        gqsq += d1 * d1;
+                        // Computing 2nd power
+                        final double d2 = FastMath.max(ZERO, sum);
+                        gisq += d2 * d2;
+                    } else {
+                        // Computing 2nd power
+                        final double d1 = gradientAtTrustRegionCenter.getEntry(i);
+                        gqsq += d1 * d1;
+                        gisq += sum * sum;
+                    }
+                    lagrangeValuesAtNewPoint.setEntry(npt + i, sum);
+                }
+
+                // Test whether to replace the new quadratic model by the least Frobenius
+                // norm interpolant, making the replacement if the test is satisfied.
+
+                ++itest;
+                if (gqsq < TEN * gisq) {
+                    itest = 0;
+                }
+                if (itest >= 3) {
+                    for (int i = 0, max = FastMath.max(npt, nh); i < max; i++) {
+                        if (i < n) {
+                            gradientAtTrustRegionCenter.setEntry(i, lagrangeValuesAtNewPoint.getEntry(npt + i));
+                        }
+                        if (i < npt) {
+                            modelSecondDerivativesParameters.setEntry(i, work2.getEntry(i));
+                        }
+                        if (i < nh) {
+                            modelSecondDerivativesValues.setEntry(i, ZERO);
+                        }
+                        itest = 0;
+                    }
+                }
+            }
+
+            // If a trust region step has provided a sufficient decrease in F, then
+            // branch for another trust region calculation. The case NTRITS=0 occurs
+            // when the new interpolation point was reached by an alternative step.
+
+            if (ntrits == 0) {
+                state = 60; break;
+            }
+            if (f <= fopt + ONE_OVER_TEN * vquad) {
+                state = 60; break;
+            }
+
+            // Alternatively, find out if the interpolation points are close enough
+            //   to the best point so far.
+
+            // Computing MAX
+            // Computing 2nd power
+            final double d1 = TWO * delta;
+            // Computing 2nd power
+            final double d2 = TEN * rho;
+            distsq = FastMath.max(d1 * d1, d2 * d2);
+        }
+        case 650: {
+            printState(650); // XXX
+            knew = -1;
+            for (int k = 0; k < npt; k++) {
+                double sum = ZERO;
+                for (int j = 0; j < n; j++) {
+                    // Computing 2nd power
+                    final double d1 = interpolationPoints.getEntry(k, j) - trustRegionCenterOffset.getEntry(j);
+                    sum += d1 * d1;
+                }
+                if (sum > distsq) {
+                    knew = k;
+                    distsq = sum;
+                }
+            }
+
+            // If KNEW is positive, then ALTMOV finds alternative new positions for
+            // the KNEW-th interpolation point within distance ADELT of XOPT. It is
+            // reached via label 90. Otherwise, there is a branch to label 60 for
+            // another trust region iteration, unless the calculations with the
+            // current RHO are complete.
+
+            if (knew >= 0) {
+                final double dist = FastMath.sqrt(distsq);
+                if (ntrits == -1) {
+                    // Computing MIN
+                    delta = FastMath.min(ONE_OVER_TEN * delta, HALF * dist);
+                    if (delta <= rho * 1.5) {
+                        delta = rho;
+                    }
+                }
+                ntrits = 0;
+                // Computing MAX
+                // Computing MIN
+                final double d1 = FastMath.min(ONE_OVER_TEN * dist, delta);
+                adelt = FastMath.max(d1, rho);
+                dsq = adelt * adelt;
+                state = 90; break;
+            }
+            if (ntrits == -1) {
+                state = 680; break;
+            }
+            if (ratio > ZERO) {
+                state = 60; break;
+            }
+            if (FastMath.max(delta, dnorm) > rho) {
+                state = 60; break;
+            }
+
+            // The calculations with the current value of RHO are complete. Pick the
+            //   next values of RHO and DELTA.
+        }
+        case 680: {
+            printState(680); // XXX
+            if (rho > stoppingTrustRegionRadius) {
+                delta = HALF * rho;
+                ratio = rho / stoppingTrustRegionRadius;
+                if (ratio <= SIXTEEN) {
+                    rho = stoppingTrustRegionRadius;
+                } else if (ratio <= TWO_HUNDRED_FIFTY) {
+                    rho = FastMath.sqrt(ratio) * stoppingTrustRegionRadius;
+                } else {
+                    rho *= ONE_OVER_TEN;
+                }
+                delta = FastMath.max(delta, rho);
+                ntrits = 0;
+                nfsav = getEvaluations();
+                state = 60; break;
+            }
+
+            // Return from the calculation, after another Newton-Raphson step, if
+            //   it is too short to have been tried before.
+
+            if (ntrits == -1) {
+                state = 360; break;
+            }
+        }
+        case 720: {
+            printState(720); // XXX
+            if (fAtInterpolationPoints.getEntry(trustRegionCenterInterpolationPointIndex) <= fsave) {
+                for (int i = 0; i < n; i++) {
+                    // Computing MIN
+                    // Computing MAX
+                    final double d3 = lowerBound[i];
+                    final double d4 = originShift.getEntry(i) + trustRegionCenterOffset.getEntry(i);
+                    final double d1 = FastMath.max(d3, d4);
+                    final double d2 = upperBound[i];
+                    currentBest.setEntry(i, FastMath.min(d1, d2));
+                    if (trustRegionCenterOffset.getEntry(i) == lowerDifference.getEntry(i)) {
+                        currentBest.setEntry(i, lowerBound[i]);
+                    }
+                    if (trustRegionCenterOffset.getEntry(i) == upperDifference.getEntry(i)) {
+                        currentBest.setEntry(i, upperBound[i]);
+                    }
+                }
+                f = fAtInterpolationPoints.getEntry(trustRegionCenterInterpolationPointIndex);
+            }
+            return f;
+        }
+        default: {
+            throw new MathIllegalStateException(LocalizedFormats.SIMPLE_MESSAGE, "bobyqb");
+        }}}
+    } // bobyqb
+
+    // ----------------------------------------------------------------------------------------
+
+    /**
+     *     The arguments N, NPT, XPT, XOPT, BMAT, ZMAT, NDIM, SL and SU all have
+     *       the same meanings as the corresponding arguments of BOBYQB.
+     *     KOPT is the index of the optimal interpolation point.
+     *     KNEW is the index of the interpolation point that is going to be moved.
+     *     ADELT is the current trust region bound.
+     *     XNEW will be set to a suitable new position for the interpolation point
+     *       XPT(KNEW,.). Specifically, it satisfies the SL, SU and trust region
+     *       bounds and it should provide a large denominator in the next call of
+     *       UPDATE. The step XNEW-XOPT from XOPT is restricted to moves along the
+     *       straight lines through XOPT and another interpolation point.
+     *     XALT also provides a large value of the modulus of the KNEW-th Lagrange
+     *       function subject to the constraints that have been mentioned, its main
+     *       difference from XNEW being that XALT-XOPT is a constrained version of
+     *       the Cauchy step within the trust region. An exception is that XALT is
+     *       not calculated if all components of GLAG (see below) are zero.
+     *     ALPHA will be set to the KNEW-th diagonal element of the H matrix.
+     *     CAUCHY will be set to the square of the KNEW-th Lagrange function at
+     *       the step XALT-XOPT from XOPT for the vector XALT that is returned,
+     *       except that CAUCHY is set to zero if XALT is not calculated.
+     *     GLAG is a working space vector of length N for the gradient of the
+     *       KNEW-th Lagrange function at XOPT.
+     *     HCOL is a working space vector of length NPT for the second derivative
+     *       coefficients of the KNEW-th Lagrange function.
+     *     W is a working space vector of length 2N that is going to hold the
+     *       constrained Cauchy step from XOPT of the Lagrange function, followed
+     *       by the downhill version of XALT when the uphill step is calculated.
+     *
+     *     Set the first NPT components of W to the leading elements of the
+     *     KNEW-th column of the H matrix.
+     * @param knew
+     * @param adelt
+     */
+    private double[] altmov(
+            int knew,
+            double adelt
+    ) {
+        printMethod(); // XXX
+
+        final int n = currentBest.getDimension();
+        final int npt = numberOfInterpolationPoints;
+
+        final ArrayRealVector glag = new ArrayRealVector(n);
+        final ArrayRealVector hcol = new ArrayRealVector(npt);
+
+        final ArrayRealVector work1 = new ArrayRealVector(n);
+        final ArrayRealVector work2 = new ArrayRealVector(n);
+
+        for (int k = 0; k < npt; k++) {
+            hcol.setEntry(k, ZERO);
+        }
+        for (int j = 0, max = npt - n - 1; j < max; j++) {
+            final double tmp = zMatrix.getEntry(knew, j);
+            for (int k = 0; k < npt; k++) {
+                hcol.setEntry(k, hcol.getEntry(k) + tmp * zMatrix.getEntry(k, j));
+            }
+        }
+        final double alpha = hcol.getEntry(knew);
+        final double ha = HALF * alpha;
+
+        // Calculate the gradient of the KNEW-th Lagrange function at XOPT.
+
+        for (int i = 0; i < n; i++) {
+            glag.setEntry(i, bMatrix.getEntry(knew, i));
+        }
+        for (int k = 0; k < npt; k++) {
+            double tmp = ZERO;
+            for (int j = 0; j < n; j++) {
+                tmp += interpolationPoints.getEntry(k, j) * trustRegionCenterOffset.getEntry(j);
+            }
+            tmp *= hcol.getEntry(k);
+            for (int i = 0; i < n; i++) {
+                glag.setEntry(i, glag.getEntry(i) + tmp * interpolationPoints.getEntry(k, i));
+            }
+        }
+
+        // Search for a large denominator along the straight lines through XOPT
+        // and another interpolation point. SLBD and SUBD will be lower and upper
+        // bounds on the step along each of these lines in turn. PREDSQ will be
+        // set to the square of the predicted denominator for each line. PRESAV
+        // will be set to the largest admissible value of PREDSQ that occurs.
+
+        double presav = ZERO;
+        double step = Double.NaN;
+        int ksav = 0;
+        int ibdsav = 0;
+        double stpsav = 0;
+        for (int k = 0; k < npt; k++) {
+            if (k == trustRegionCenterInterpolationPointIndex) {
+                continue;
+            }
+            double dderiv = ZERO;
+            double distsq = ZERO;
+            for (int i = 0; i < n; i++) {
+                final double tmp = interpolationPoints.getEntry(k, i) - trustRegionCenterOffset.getEntry(i);
+                dderiv += glag.getEntry(i) * tmp;
+                distsq += tmp * tmp;
+            }
+            double subd = adelt / FastMath.sqrt(distsq);
+            double slbd = -subd;
+            int ilbd = 0;
+            int iubd = 0;
+            final double sumin = FastMath.min(ONE, subd);
+
+            // Revise SLBD and SUBD if necessary because of the bounds in SL and SU.
+
+            for (int i = 0; i < n; i++) {
+                final double tmp = interpolationPoints.getEntry(k, i) - trustRegionCenterOffset.getEntry(i);
+                if (tmp > ZERO) {
+                    if (slbd * tmp < lowerDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)) {
+                        slbd = (lowerDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)) / tmp;
+                        ilbd = -i - 1;
+                    }
+                    if (subd * tmp > upperDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)) {
+                        // Computing MAX
+                        subd = FastMath.max(sumin,
+                                            (upperDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)) / tmp);
+                        iubd = i + 1;
+                    }
+                } else if (tmp < ZERO) {
+                    if (slbd * tmp > upperDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)) {
+                        slbd = (upperDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)) / tmp;
+                        ilbd = i + 1;
+                    }
+                    if (subd * tmp < lowerDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)) {
+                        // Computing MAX
+                        subd = FastMath.max(sumin,
+                                            (lowerDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i)) / tmp);
+                        iubd = -i - 1;
+                    }
+                }
+            }
+
+            // Seek a large modulus of the KNEW-th Lagrange function when the index
+            // of the other interpolation point on the line through XOPT is KNEW.
+
+            step = slbd;
+            int isbd = ilbd;
+            double vlag = Double.NaN;
+            if (k == knew) {
+                final double diff = dderiv - ONE;
+                vlag = slbd * (dderiv - slbd * diff);
+                final double d1 = subd * (dderiv - subd * diff);
+                if (FastMath.abs(d1) > FastMath.abs(vlag)) {
+                    step = subd;
+                    vlag = d1;
+                    isbd = iubd;
+                }
+                final double d2 = HALF * dderiv;
+                final double d3 = d2 - diff * slbd;
+                final double d4 = d2 - diff * subd;
+                if (d3 * d4 < ZERO) {
+                    final double d5 = d2 * d2 / diff;
+                    if (FastMath.abs(d5) > FastMath.abs(vlag)) {
+                        step = d2 / diff;
+                        vlag = d5;
+                        isbd = 0;
+                    }
+                }
+
+                // Search along each of the other lines through XOPT and another point.
+
+            } else {
+                vlag = slbd * (ONE - slbd);
+                final double tmp = subd * (ONE - subd);
+                if (FastMath.abs(tmp) > FastMath.abs(vlag)) {
+                    step = subd;
+                    vlag = tmp;
+                    isbd = iubd;
+                }
+                if (subd > HALF && FastMath.abs(vlag) < ONE_OVER_FOUR) {
+                    step = HALF;
+                    vlag = ONE_OVER_FOUR;
+                    isbd = 0;
+                }
+                vlag *= dderiv;
+            }
+
+            // Calculate PREDSQ for the current line search and maintain PRESAV.
+
+            final double tmp = step * (ONE - step) * distsq;
+            final double predsq = vlag * vlag * (vlag * vlag + ha * tmp * tmp);
+            if (predsq > presav) {
+                presav = predsq;
+                ksav = k;
+                stpsav = step;
+                ibdsav = isbd;
+            }
+        }
+
+        // Construct XNEW in a way that satisfies the bound constraints exactly.
+
+        for (int i = 0; i < n; i++) {
+            final double tmp = trustRegionCenterOffset.getEntry(i) + stpsav * (interpolationPoints.getEntry(ksav, i) - trustRegionCenterOffset.getEntry(i));
+            newPoint.setEntry(i, FastMath.max(lowerDifference.getEntry(i),
+                                              FastMath.min(upperDifference.getEntry(i), tmp)));
+        }
+        if (ibdsav < 0) {
+            newPoint.setEntry(-ibdsav - 1, lowerDifference.getEntry(-ibdsav - 1));
+        }
+        if (ibdsav > 0) {
+            newPoint.setEntry(ibdsav - 1, upperDifference.getEntry(ibdsav - 1));
+        }
+
+        // Prepare for the iterative method that assembles the constrained Cauchy
+        // step in W. The sum of squares of the fixed components of W is formed in
+        // WFIXSQ, and the free components of W are set to BIGSTP.
+
+        final double bigstp = adelt + adelt;
+        int iflag = 0;
+        double cauchy = Double.NaN;
+        double csave = ZERO;
+        while (true) {
+            double wfixsq = ZERO;
+            double ggfree = ZERO;
+            for (int i = 0; i < n; i++) {
+                final double glagValue = glag.getEntry(i);
+                work1.setEntry(i, ZERO);
+                if (FastMath.min(trustRegionCenterOffset.getEntry(i) - lowerDifference.getEntry(i), glagValue) > ZERO ||
+                    FastMath.max(trustRegionCenterOffset.getEntry(i) - upperDifference.getEntry(i), glagValue) < ZERO) {
+                    work1.setEntry(i, bigstp);
+                    // Computing 2nd power
+                    ggfree += glagValue * glagValue;
+                }
+            }
+            if (ggfree == ZERO) {
+                return new double[] { alpha, ZERO };
+            }
+
+            // Investigate whether more components of W can be fixed.
+            final double tmp1 = adelt * adelt - wfixsq;
+            if (tmp1 > ZERO) {
+                step = FastMath.sqrt(tmp1 / ggfree);
+                ggfree = ZERO;
+                for (int i = 0; i < n; i++) {
+                    if (work1.getEntry(i) == bigstp) {
+                        final double tmp2 = trustRegionCenterOffset.getEntry(i) - step * glag.getEntry(i);
+                        if (tmp2 <= lowerDifference.getEntry(i)) {
+                            work1.setEntry(i, lowerDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i));
+                            // Computing 2nd power
+                            final double d1 = work1.getEntry(i);
+                            wfixsq += d1 * d1;
+                        } else if (tmp2 >= upperDifference.getEntry(i)) {
+                            work1.setEntry(i, upperDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i));
+                            // Computing 2nd power
+                            final double d1 = work1.getEntry(i);
+                            wfixsq += d1 * d1;
+                        } else {
+                            // Computing 2nd power
+                            final double d1 = glag.getEntry(i);
+                            ggfree += d1 * d1;
+                        }
+                    }
+                }
+            }
+
+            // Set the remaining free components of W and all components of XALT,
+            // except that W may be scaled later.
+
+            double gw = ZERO;
+            for (int i = 0; i < n; i++) {
+                final double glagValue = glag.getEntry(i);
+                if (work1.getEntry(i) == bigstp) {
+                    work1.setEntry(i, -step * glagValue);
+                    final double min = FastMath.min(upperDifference.getEntry(i),
+                                                    trustRegionCenterOffset.getEntry(i) + work1.getEntry(i));
+                    alternativeNewPoint.setEntry(i, FastMath.max(lowerDifference.getEntry(i), min));
+                } else if (work1.getEntry(i) == ZERO) {
+                    alternativeNewPoint.setEntry(i, trustRegionCenterOffset.getEntry(i));
+                } else if (glagValue > ZERO) {
+                    alternativeNewPoint.setEntry(i, lowerDifference.getEntry(i));
+                } else {
+                    alternativeNewPoint.setEntry(i, upperDifference.getEntry(i));
+                }
+                gw += glagValue * work1.getEntry(i);
+            }
+
+            // Set CURV to the curvature of the KNEW-th Lagrange function along W.
+            // Scale W by a factor less than one if that can reduce the modulus of
+            // the Lagrange function at XOPT+W. Set CAUCHY to the final value of
+            // the square of this function.
+
+            double curv = ZERO;
+            for (int k = 0; k < npt; k++) {
+                double tmp = ZERO;
+                for (int j = 0; j < n; j++) {
+                    tmp += interpolationPoints.getEntry(k, j) * work1.getEntry(j);
+                }
+                curv += hcol.getEntry(k) * tmp * tmp;
+            }
+            if (iflag == 1) {
+                curv = -curv;
+            }
+            if (curv > -gw &&
+                curv < -gw * (ONE + FastMath.sqrt(TWO))) {
+                final double scale = -gw / curv;
+                for (int i = 0; i < n; i++) {
+                    final double tmp = trustRegionCenterOffset.getEntry(i) + scale * work1.getEntry(i);
+                    alternativeNewPoint.setEntry(i, FastMath.max(lowerDifference.getEntry(i),
+                                                    FastMath.min(upperDifference.getEntry(i), tmp)));
+                }
+                // Computing 2nd power
+                final double d1 = HALF * gw * scale;
+                cauchy = d1 * d1;
+            } else {
+                // Computing 2nd power
+                final double d1 = gw + HALF * curv;
+                cauchy = d1 * d1;
+            }
+
+            // If IFLAG is zero, then XALT is calculated as before after reversing
+            // the sign of GLAG. Thus two XALT vectors become available. The one that
+            // is chosen is the one that gives the larger value of CAUCHY.
+
+            if (iflag == 0) {
+                for (int i = 0; i < n; i++) {
+                    glag.setEntry(i, -glag.getEntry(i));
+                    work2.setEntry(i, alternativeNewPoint.getEntry(i));
+                }
+                csave = cauchy;
+                iflag = 1;
+            } else {
+                break;
+            }
+        }
+        if (csave > cauchy) {
+            for (int i = 0; i < n; i++) {
+                alternativeNewPoint.setEntry(i, work2.getEntry(i));
+            }
+            cauchy = csave;
+        }
+
+        return new double[] { alpha, cauchy };
+    } // altmov
+
+    // ----------------------------------------------------------------------------------------
+
+    /**
+     *     SUBROUTINE PRELIM sets the elements of XBASE, XPT, FVAL, GOPT, HQ, PQ,
+     *     BMAT and ZMAT for the first iteration, and it maintains the values of
+     *     NF and KOPT. The vector X is also changed by PRELIM.
+     *
+     *     The arguments N, NPT, X, XL, XU, RHOBEG, IPRINT and MAXFUN are the
+     *       same as the corresponding arguments in SUBROUTINE BOBYQA.
+     *     The arguments XBASE, XPT, FVAL, HQ, PQ, BMAT, ZMAT, NDIM, SL and SU
+     *       are the same as the corresponding arguments in BOBYQB, the elements
+     *       of SL and SU being set in BOBYQA.
+     *     GOPT is usually the gradient of the quadratic model at XOPT+XBASE, but
+     *       it is set by PRELIM to the gradient of the quadratic model at XBASE.
+     *       If XOPT is nonzero, BOBYQB will change it to its usual value later.
+     *     NF is maintaned as the number of calls of CALFUN so far.
+     *     KOPT will be such that the least calculated value of F so far is at
+     *       the point XPT(KOPT,.)+XBASE in the space of the variables.
+     *
+     * @param lowerBound Lower bounds.
+     * @param upperBound Upper bounds.
+     */
+    private void prelim(double[] lowerBound,
+                        double[] upperBound) {
+        printMethod(); // XXX
+
+        final int n = currentBest.getDimension();
+        final int npt = numberOfInterpolationPoints;
+        final int ndim = bMatrix.getRowDimension();
+
+        final double rhosq = initialTrustRegionRadius * initialTrustRegionRadius;
+        final double recip = 1d / rhosq;
+        final int np = n + 1;
+
+        // Set XBASE to the initial vector of variables, and set the initial
+        // elements of XPT, BMAT, HQ, PQ and ZMAT to zero.
+
+        for (int j = 0; j < n; j++) {
+            originShift.setEntry(j, currentBest.getEntry(j));
+            for (int k = 0; k < npt; k++) {
+                interpolationPoints.setEntry(k, j, ZERO);
+            }
+            for (int i = 0; i < ndim; i++) {
+                bMatrix.setEntry(i, j, ZERO);
+            }
+        }
+        for (int i = 0, max = n * np / 2; i < max; i++) {
+            modelSecondDerivativesValues.setEntry(i, ZERO);
+        }
+        for (int k = 0; k < npt; k++) {
+            modelSecondDerivativesParameters.setEntry(k, ZERO);
+            for (int j = 0, max = npt - np; j < max; j++) {
+                zMatrix.setEntry(k, j, ZERO);
+            }
+        }
+
+        // Begin the initialization procedure. NF becomes one more than the number
+        // of function values so far. The coordinates of the displacement of the
+        // next initial interpolation point from XBASE are set in XPT(NF+1,.).
+
+        int ipt = 0;
+        int jpt = 0;
+        double fbeg = Double.NaN;
+        do {
+            final int nfm = getEvaluations();
+            final int nfx = nfm - n;
+            final int nfmm = nfm - 1;
+            final int nfxm = nfx - 1;
+            double stepa = 0;
+            double stepb = 0;
+            if (nfm <= 2 * n) {
+                if (nfm >= 1 &&
+                    nfm <= n) {
+                    stepa = initialTrustRegionRadius;
+                    if (upperDifference.getEntry(nfmm) == ZERO) {
+                        stepa = -stepa;
+                        // throw new PathIsExploredException(); // XXX
+                    }
+                    interpolationPoints.setEntry(nfm, nfmm, stepa);
+                } else if (nfm > n) {
+                    stepa = interpolationPoints.getEntry(nfx, nfxm);
+                    stepb = -initialTrustRegionRadius;
+                    if (lowerDifference.getEntry(nfxm) == ZERO) {
+                        stepb = FastMath.min(TWO * initialTrustRegionRadius, upperDifference.getEntry(nfxm));
+                        // throw new PathIsExploredException(); // XXX
+                    }
+                    if (upperDifference.getEntry(nfxm) == ZERO) {
+                        stepb = FastMath.max(-TWO * initialTrustRegionRadius, lowerDifference.getEntry(nfxm));
+                        // throw new PathIsExploredException(); // XXX
+                    }
+                    interpolationPoints.setEntry(nfm, nfxm, stepb);
+                }
+            } else {
+                final int tmp1 = (nfm - np) / n;
+                jpt = nfm - tmp1 * n - n;
+                ipt = jpt + tmp1;
+                if (ipt > n) {
+                    final int tmp2 = jpt;
+                    jpt = ipt - n;
+                    ipt = tmp2;
+//                     throw new PathIsExploredException(); // XXX
+                }
+                final int iptMinus1 = ipt - 1;
+                final int jptMinus1 = jpt - 1;
+                interpolationPoints.setEntry(nfm, iptMinus1, interpolationPoints.getEntry(ipt, iptMinus1));
+                interpolationPoints.setEntry(nfm, jptMinus1, interpolationPoints.getEntry(jpt, jptMinus1));
+            }
+
+            // Calculate the next value of F. The least function value so far and
+            // its index are required.
+
+            for (int j = 0; j < n; j++) {
+                currentBest.setEntry(j, FastMath.min(FastMath.max(lowerBound[j],
+                                                                  originShift.getEntry(j) + interpolationPoints.getEntry(nfm, j)),
+                                                     upperBound[j]));
+                if (interpolationPoints.getEntry(nfm, j) == lowerDifference.getEntry(j)) {
+                    currentBest.setEntry(j, lowerBound[j]);
+                }
+                if (interpolationPoints.getEntry(nfm, j) == upperDifference.getEntry(j)) {
+                    currentBest.setEntry(j, upperBound[j]);
+                }
+            }
+
+            final double objectiveValue = computeObjectiveValue(currentBest.toArray());
+            final double f = isMinimize ? objectiveValue : -objectiveValue;
+            final int numEval = getEvaluations(); // nfm + 1
+            fAtInterpolationPoints.setEntry(nfm, f);
+
+            if (numEval == 1) {
+                fbeg = f;
+                trustRegionCenterInterpolationPointIndex = 0;
+            } else if (f < fAtInterpolationPoints.getEntry(trustRegionCenterInterpolationPointIndex)) {
+                trustRegionCenterInterpolationPointIndex = nfm;
+            }
+
+            // Set the nonzero initial elements of BMAT and the quadratic model in the
+            // cases when NF is at most 2*N+1. If NF exceeds N+1, then the positions
+            // of the NF-th and (NF-N)-th interpolation points may be switched, in
+            // order that the function value at the first of them contributes to the
+            // off-diagonal second derivative terms of the initial quadratic model.
+
+            if (numEval <= 2 * n + 1) {
+                if (numEval >= 2 &&
+                    numEval <= n + 1) {
+                    gradientAtTrustRegionCenter.setEntry(nfmm, (f - fbeg) / stepa);
+                    if (npt < numEval + n) {
+                        final double oneOverStepA = ONE / stepa;
+                        bMatrix.setEntry(0, nfmm, -oneOverStepA);
+                        bMatrix.setEntry(nfm, nfmm, oneOverStepA);
+                        bMatrix.setEntry(npt + nfmm, nfmm, -HALF * rhosq);
+                        // throw new PathIsExploredException(); // XXX
+                    }
+                } else if (numEval >= n + 2) {
+                    final int ih = nfx * (nfx + 1) / 2 - 1;
+                    final double tmp = (f - fbeg) / stepb;
+                    final double diff = stepb - stepa;
+                    modelSecondDerivativesValues.setEntry(ih, TWO * (tmp - gradientAtTrustRegionCenter.getEntry(nfxm)) / diff);
+                    gradientAtTrustRegionCenter.setEntry(nfxm, (gradientAtTrustRegionCenter.getEntry(nfxm) * stepb - tmp * stepa) / diff);
+                    if (stepa * stepb < ZERO && f < fAtInterpolationPoints.getEntry(nfm - n)) {
+                        fAtInterpolationPoints.setEntry(nfm, fAtInterpolationPoints.getEntry(nfm - n));
+                        fAtInterpolationPoints.setEntry(nfm - n, f);
+                        if (trustRegionCenterInterpolationPointIndex == nfm) {
+                            trustRegionCenterInterpolationPointIndex = nfm - n;
+                        }
+                        interpolationPoints.setEntry(nfm - n, nfxm, stepb);
+                        interpolationPoints.setEntry(nfm, nfxm, stepa);
+                    }
+                    bMatrix.setEntry(0, nfxm, -(stepa + stepb) / (stepa * stepb));
+                    bMatrix.setEntry(nfm, nfxm, -HALF / interpolationPoints.getEntry(nfm - n, nfxm));
+                    bMatrix.setEntry(nfm - n, nfxm,
+                                  -bMatrix.getEntry(0, nfxm) - bMatrix.getEntry(nfm, nfxm));
+                    zMatrix.setEntry(0, nfxm, FastMath.sqrt(TWO) / (stepa * stepb));
+                    zMatrix.setEntry(nfm, nfxm, FastMath.sqrt(HALF) / rhosq);
+                    // zMatrix.setEntry(nfm, nfxm, FastMath.sqrt(HALF) * recip); // XXX "testAckley" and "testDiffPow" fail.
+                    zMatrix.setEntry(nfm - n, nfxm,
+                                  -zMatrix.getEntry(0, nfxm) - zMatrix.getEntry(nfm, nfxm));
+                }
+
+                // Set the off-diagonal second derivatives of the Lagrange functions and
+                // the initial quadratic model.
+
+            } else {
+                zMatrix.setEntry(0, nfxm, recip);
+                zMatrix.setEntry(nfm, nfxm, recip);
+                zMatrix.setEntry(ipt, nfxm, -recip);
+                zMatrix.setEntry(jpt, nfxm, -recip);
+
+                final int ih = ipt * (ipt - 1) / 2 + jpt - 1;
+                final double tmp = interpolationPoints.getEntry(nfm, ipt - 1) * interpolationPoints.getEntry(nfm, jpt - 1);
+                modelSecondDerivativesValues.setEntry(ih, (fbeg - fAtInterpolationPoints.getEntry(ipt) - fAtInterpolationPoints.getEntry(jpt) + f) / tmp);
+//                 throw new PathIsExploredException(); // XXX
+            }
+        } while (getEvaluations() < npt);
+    } // prelim
+
+
+    // ----------------------------------------------------------------------------------------
+
+    /**
+     *     A version of the truncated conjugate gradient is applied. If a line
+     *     search is restricted by a constraint, then the procedure is restarted,
+     *     the values of the variables that are at their bounds being fixed. If
+     *     the trust region boundary is reached, then further changes may be made
+     *     to D, each one being in the two dimensional space that is spanned
+     *     by the current D and the gradient of Q at XOPT+D, staying on the trust
+     *     region boundary. Termination occurs when the reduction in Q seems to
+     *     be close to the greatest reduction that can be achieved.
+     *     The arguments N, NPT, XPT, XOPT, GOPT, HQ, PQ, SL and SU have the same
+     *       meanings as the corresponding arguments of BOBYQB.
+     *     DELTA is the trust region radius for the present calculation, which
+     *       seeks a small value of the quadratic model within distance DELTA of
+     *       XOPT subject to the bounds on the variables.
+     *     XNEW will be set to a new vector of variables that is approximately
+     *       the one that minimizes the quadratic model within the trust region
+     *       subject to the SL and SU constraints on the variables. It satisfies
+     *       as equations the bounds that become active during the calculation.
+     *     D is the calculated trial step from XOPT, generated iteratively from an
+     *       initial value of zero. Thus XNEW is XOPT+D after the final iteration.
+     *     GNEW holds the gradient of the quadratic model at XOPT+D. It is updated
+     *       when D is updated.
+     *     xbdi.get( is a working space vector. For I=1,2,...,N, the element xbdi.get((I) is
+     *       set to -1.0, 0.0, or 1.0, the value being nonzero if and only if the
+     *       I-th variable has become fixed at a bound, the bound being SL(I) or
+     *       SU(I) in the case xbdi.get((I)=-1.0 or xbdi.get((I)=1.0, respectively. This
+     *       information is accumulated during the construction of XNEW.
+     *     The arrays S, HS and HRED are also used for working space. They hold the
+     *       current search direction, and the changes in the gradient of Q along S
+     *       and the reduced D, respectively, where the reduced D is the same as D,
+     *       except that the components of the fixed variables are zero.
+     *     DSQ will be set to the square of the length of XNEW-XOPT.
+     *     CRVMIN is set to zero if D reaches the trust region boundary. Otherwise
+     *       it is set to the least curvature of H that occurs in the conjugate
+     *       gradient searches that are not restricted by any constraints. The
+     *       value CRVMIN=-1.0D0 is set, however, if all of these searches are
+     *       constrained.
+     * @param delta
+     * @param gnew
+     * @param xbdi
+     * @param s
+     * @param hs
+     * @param hred
+     */
+    private double[] trsbox(
+            double delta,
+            ArrayRealVector gnew,
+            ArrayRealVector xbdi,
+            ArrayRealVector s,
+            ArrayRealVector hs,
+            ArrayRealVector hred
+    ) {
+        printMethod(); // XXX
+
+        final int n = currentBest.getDimension();
+        final int npt = numberOfInterpolationPoints;
+
+        double dsq = Double.NaN;
+        double crvmin = Double.NaN;
+
+        // Local variables
+        double ds;
+        int iu;
+        double dhd, dhs, cth, shs, sth, ssq, beta=0, sdec, blen;
+        int iact = -1;
+        int nact = 0;
+        double angt = 0, qred;
+        int isav;
+        double temp = 0, xsav = 0, xsum = 0, angbd = 0, dredg = 0, sredg = 0;
+        int iterc;
+        double resid = 0, delsq = 0, ggsav = 0, tempa = 0, tempb = 0,
+        redmax = 0, dredsq = 0, redsav = 0, gredsq = 0, rednew = 0;
+        int itcsav = 0;
+        double rdprev = 0, rdnext = 0, stplen = 0, stepsq = 0;
+        int itermax = 0;
+
+        // Set some constants.
+
+        // Function Body
+
+        // The sign of GOPT(I) gives the sign of the change to the I-th variable
+        // that will reduce Q from its value at XOPT. Thus xbdi.get((I) shows whether
+        // or not to fix the I-th variable at one of its bounds initially, with
+        // NACT being set to the number of fixed variables. D and GNEW are also
+        // set for the first iteration. DELSQ is the upper bound on the sum of
+        // squares of the free variables. QRED is the reduction in Q so far.
+
+        iterc = 0;
+        nact = 0;
+        for (int i = 0; i < n; i++) {
+            xbdi.setEntry(i, ZERO);
+            if (trustRegionCenterOffset.getEntry(i) <= lowerDifference.getEntry(i)) {
+                if (gradientAtTrustRegionCenter.getEntry(i) >= ZERO) {
+                    xbdi.setEntry(i, MINUS_ONE);
+                }
+            } else if (trustRegionCenterOffset.getEntry(i) >= upperDifference.getEntry(i) &&
+                       gradientAtTrustRegionCenter.getEntry(i) <= ZERO) {
+                xbdi.setEntry(i, ONE);
+            }
+            if (xbdi.getEntry(i) != ZERO) {
+                ++nact;
+            }
+            trialStepPoint.setEntry(i, ZERO);
+            gnew.setEntry(i, gradientAtTrustRegionCenter.getEntry(i));
+        }
+        delsq = delta * delta;
+        qred = ZERO;
+        crvmin = MINUS_ONE;
+
+        // Set the next search direction of the conjugate gradient method. It is
+        // the steepest descent direction initially and when the iterations are
+        // restarted because a variable has just been fixed by a bound, and of
+        // course the components of the fixed variables are zero. ITERMAX is an
+        // upper bound on the indices of the conjugate gradient iterations.
+
+        int state = 20;
+        for(;;) {
+            switch (state) {
+        case 20: {
+            printState(20); // XXX
+            beta = ZERO;
+        }
+        case 30: {
+            printState(30); // XXX
+            stepsq = ZERO;
+            for (int i = 0; i < n; i++) {
+                if (xbdi.getEntry(i) != ZERO) {
+                    s.setEntry(i, ZERO);
+                } else if (beta == ZERO) {
+                    s.setEntry(i, -gnew.getEntry(i));
+                } else {
+                    s.setEntry(i, beta * s.getEntry(i) - gnew.getEntry(i));
+                }
+                // Computing 2nd power
+                final double d1 = s.getEntry(i);
+                stepsq += d1 * d1;
+            }
+            if (stepsq == ZERO) {
+                state = 190; break;
+            }
+            if (beta == ZERO) {
+                gredsq = stepsq;
+                itermax = iterc + n - nact;
+            }
+            if (gredsq * delsq <= qred * 1e-4 * qred) {
+                state = 190; break;
+            }
+
+            // Multiply the search direction by the second derivative matrix of Q and
+            // calculate some scalars for the choice of steplength. Then set BLEN to
+            // the length of the the step to the trust region boundary and STPLEN to
+            // the steplength, ignoring the simple bounds.
+
+            state = 210; break;
+        }
+        case 50: {
+            printState(50); // XXX
+            resid = delsq;
+            ds = ZERO;
+            shs = ZERO;
+            for (int i = 0; i < n; i++) {
+                if (xbdi.getEntry(i) == ZERO) {
+                    // Computing 2nd power
+                    final double d1 = trialStepPoint.getEntry(i);
+                    resid -= d1 * d1;
+                    ds += s.getEntry(i) * trialStepPoint.getEntry(i);
+                    shs += s.getEntry(i) * hs.getEntry(i);
+                }
+            }
+            if (resid <= ZERO) {
+                state = 90; break;
+            }
+            temp = FastMath.sqrt(stepsq * resid + ds * ds);
+            if (ds < ZERO) {
+                blen = (temp - ds) / stepsq;
+            } else {
+                blen = resid / (temp + ds);
+            }
+            stplen = blen;
+            if (shs > ZERO) {
+                // Computing MIN
+                stplen = FastMath.min(blen, gredsq / shs);
+            }
+
+            // Reduce STPLEN if necessary in order to preserve the simple bounds,
+            // letting IACT be the index of the new constrained variable.
+
+            iact = -1;
+            for (int i = 0; i < n; i++) {
+                if (s.getEntry(i) != ZERO) {
+                    xsum = trustRegionCenterOffset.getEntry(i) + trialStepPoint.getEntry(i);
+                    if (s.getEntry(i) > ZERO) {
+                        temp = (upperDifference.getEntry(i) - xsum) / s.getEntry(i);
+                    } else {
+                        temp = (lowerDifference.getEntry(i) - xsum) / s.getEntry(i);
+                    }
+                    if (temp < stplen) {
+                        stplen = temp;
+                        iact = i;
+                    }
+                }
+            }
+
+            // Update CRVMIN, GNEW and D. Set SDEC to the decrease that occurs in Q.
+
+            sdec = ZERO;
+            if (stplen > ZERO) {
+                ++iterc;
+                temp = shs / stepsq;
+                if (iact == -1 && temp > ZERO) {
+                    crvmin = FastMath.min(crvmin,temp);
+                    if (crvmin == MINUS_ONE) {
+                        crvmin = temp;
+                    }
+                }
+                ggsav = gredsq;
+                gredsq = ZERO;
+                for (int i = 0; i < n; i++) {
+                    gnew.setEntry(i, gnew.getEntry(i) + stplen * hs.getEntry(i));
+                    if (xbdi.getEntry(i) == ZERO) {
+                        // Computing 2nd power
+                        final double d1 = gnew.getEntry(i);
+                        gredsq += d1 * d1;
+                    }
+                    trialStepPoint.setEntry(i, trialStepPoint.getEntry(i) + stplen * s.getEntry(i));
+                }
+                // Computing MAX
+                final double d1 = stplen * (ggsav - HALF * stplen * shs);
+                sdec = FastMath.max(d1, ZERO);
+                qred += sdec;
+            }
+
+            // Restart the conjugate gradient method if it has hit a new bound.
+
+            if (iact >= 0) {
+                ++nact;
+                xbdi.setEntry(iact, ONE);
+                if (s.getEntry(iact) < ZERO) {
+                    xbdi.setEntry(iact, MINUS_ONE);
+                }
+                // Computing 2nd power
+                final double d1 = trialStepPoint.getEntry(iact);
+                delsq -= d1 * d1;
+                if (delsq <= ZERO) {
+                    state = 190; break;
+                }
+                state = 20; break;
+            }
+
+            // If STPLEN is less than BLEN, then either apply another conjugate
+            // gradient iteration or RETURN.
+
+            if (stplen < blen) {
+                if (iterc == itermax) {
+                    state = 190; break;
+                }
+                if (sdec <= qred * .01) {
+                    state = 190; break;
+                }
+                beta = gredsq / ggsav;
+                state = 30; break;
+            }
+        }
+        case 90: {
+            printState(90); // XXX
+            crvmin = ZERO;
+
+            // Prepare for the alternative iteration by calculating some scalars
+            // and by multiplying the reduced D by the second derivative matrix of
+            // Q, where S holds the reduced D in the call of GGMULT.
+
+        }
+        case 100: {
+            printState(100); // XXX
+            if (nact >= n - 1) {
+                state = 190; break;
+            }
+            dredsq = ZERO;
+            dredg = ZERO;
+            gredsq = ZERO;
+            for (int i = 0; i < n; i++) {
+                if (xbdi.getEntry(i) == ZERO) {
+                    // Computing 2nd power
+                    double d1 = trialStepPoint.getEntry(i);
+                    dredsq += d1 * d1;
+                    dredg += trialStepPoint.getEntry(i) * gnew.getEntry(i);
+                    // Computing 2nd power
+                    d1 = gnew.getEntry(i);
+                    gredsq += d1 * d1;
+                    s.setEntry(i, trialStepPoint.getEntry(i));
+                } else {
+                    s.setEntry(i, ZERO);
+                }
+            }
+            itcsav = iterc;
+            state = 210; break;
+            // Let the search direction S be a linear combination of the reduced D
+            // and the reduced G that is orthogonal to the reduced D.
+        }
+        case 120: {
+            printState(120); // XXX
+            ++iterc;
+            temp = gredsq * dredsq - dredg * dredg;
+            if (temp <= qred * 1e-4 * qred) {
+                state = 190; break;
+            }
+            temp = FastMath.sqrt(temp);
+            for (int i = 0; i < n; i++) {
+                if (xbdi.getEntry(i) == ZERO) {
+                    s.setEntry(i, (dredg * trialStepPoint.getEntry(i) - dredsq * gnew.getEntry(i)) / temp);
+                } else {
+                    s.setEntry(i, ZERO);
+                }
+            }
+            sredg = -temp;
+
+            // By considering the simple bounds on the variables, calculate an upper
+            // bound on the tangent of half the angle of the alternative iteration,
+            // namely ANGBD, except that, if already a free variable has reached a
+            // bound, there is a branch back to label 100 after fixing that variable.
+
+            angbd = ONE;
+            iact = -1;
+            for (int i = 0; i < n; i++) {
+                if (xbdi.getEntry(i) == ZERO) {
+                    tempa = trustRegionCenterOffset.getEntry(i) + trialStepPoint.getEntry(i) - lowerDifference.getEntry(i);
+                    tempb = upperDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i) - trialStepPoint.getEntry(i);
+                    if (tempa <= ZERO) {
+                        ++nact;
+                        xbdi.setEntry(i, MINUS_ONE);
+                        state = 100; break;
+                    } else if (tempb <= ZERO) {
+                        ++nact;
+                        xbdi.setEntry(i, ONE);
+                        state = 100; break;
+                    }
+                    // Computing 2nd power
+                    double d1 = trialStepPoint.getEntry(i);
+                    // Computing 2nd power
+                    double d2 = s.getEntry(i);
+                    ssq = d1 * d1 + d2 * d2;
+                    // Computing 2nd power
+                    d1 = trustRegionCenterOffset.getEntry(i) - lowerDifference.getEntry(i);
+                    temp = ssq - d1 * d1;
+                    if (temp > ZERO) {
+                        temp = FastMath.sqrt(temp) - s.getEntry(i);
+                        if (angbd * temp > tempa) {
+                            angbd = tempa / temp;
+                            iact = i;
+                            xsav = MINUS_ONE;
+                        }
+                    }
+                    // Computing 2nd power
+                    d1 = upperDifference.getEntry(i) - trustRegionCenterOffset.getEntry(i);
+                    temp = ssq - d1 * d1;
+                    if (temp > ZERO) {
+                        temp = FastMath.sqrt(temp) + s.getEntry(i);
+                        if (angbd * temp > tempb) {
+                            angbd = tempb / temp;
+                            iact = i;
+                            xsav = ONE;
+                        }
+                    }
+                }
+            }
+
+            // Calculate HHD and some curvatures for the alternative iteration.
+
+            state = 210; break;
+        }
+        case 150: {
+            printState(150); // XXX
+            shs = ZERO;
+            dhs = ZERO;
+            dhd = ZERO;
+            for (int i = 0; i < n; i++) {
+                if (xbdi.getEntry(i) == ZERO) {
+                    shs += s.getEntry(i) * hs.getEntry(i);
+                    dhs += trialStepPoint.getEntry(i) * hs.getEntry(i);
+                    dhd += trialStepPoint.getEntry(i) * hred.getEntry(i);
+                }
+            }
+
+            // Seek the greatest reduction in Q for a range of equally spaced values
+            // of ANGT in [0,ANGBD], where ANGT is the tangent of half the angle of
+            // the alternative iteration.
+
+            redmax = ZERO;
+            isav = -1;
+            redsav = ZERO;
+            iu = (int) (angbd * 17. + 3.1);
+            for (int i = 0; i < iu; i++) {
+                angt = angbd * i / iu;
+                sth = (angt + angt) / (ONE + angt * angt);
+                temp = shs + angt * (angt * dhd - dhs - dhs);
+                rednew = sth * (angt * dredg - sredg - HALF * sth * temp);
+                if (rednew > redmax) {
+                    redmax = rednew;
+                    isav = i;
+                    rdprev = redsav;
+                } else if (i == isav + 1) {
+                    rdnext = rednew;
+                }
+                redsav = rednew;
+            }
+
+            // Return if the reduction is zero. Otherwise, set the sine and cosine
+            // of the angle of the alternative iteration, and calculate SDEC.
+
+            if (isav < 0) {
+                state = 190; break;
+            }
+            if (isav < iu) {
+                temp = (rdnext - rdprev) / (redmax + redmax - rdprev - rdnext);
+                angt = angbd * (isav + HALF * temp) / iu;
+            }
+            cth = (ONE - angt * angt) / (ONE + angt * angt);
+            sth = (angt + angt) / (ONE + angt * angt);
+            temp = shs + angt * (angt * dhd - dhs - dhs);
+            sdec = sth * (angt * dredg - sredg - HALF * sth * temp);
+            if (sdec <= ZERO) {
+                state = 190; break;
+            }
+
+            // Update GNEW, D and HRED. If the angle of the alternative iteration
+            // is restricted by a bound on a free variable, that variable is fixed
+            // at the bound.
+
+            dredg = ZERO;
+            gredsq = ZERO;
+            for (int i = 0; i < n; i++) {
+                gnew.setEntry(i, gnew.getEntry(i) + (cth - ONE) * hred.getEntry(i) + sth * hs.getEntry(i));
+                if (xbdi.getEntry(i) == ZERO) {
+                    trialStepPoint.setEntry(i, cth * trialStepPoint.getEntry(i) + sth * s.getEntry(i));
+                    dredg += trialStepPoint.getEntry(i) * gnew.getEntry(i);
+                    // Computing 2nd power
+                    final double d1 = gnew.getEntry(i);
+                    gredsq += d1 * d1;
+                }
+                hred.setEntry(i, cth * hred.getEntry(i) + sth * hs.getEntry(i));
+            }
+            qred += sdec;
+            if (iact >= 0 && isav == iu) {
+                ++nact;
+                xbdi.setEntry(iact, xsav);
+                state = 100; break;
+            }
+
+            // If SDEC is sufficiently small, then RETURN after setting XNEW to
+            // XOPT+D, giving careful attention to the bounds.
+
+            if (sdec > qred * .01) {
+                state = 120; break;
+            }
+        }
+        case 190: {
+            printState(190); // XXX
+            dsq = ZERO;
+            for (int i = 0; i < n; i++) {
+                // Computing MAX
+                // Computing MIN
+                final double min = FastMath.min(trustRegionCenterOffset.getEntry(i) + trialStepPoint.getEntry(i),
+                                                upperDifference.getEntry(i));
+                newPoint.setEntry(i, FastMath.max(min, lowerDifference.getEntry(i)));
+                if (xbdi.getEntry(i) == MINUS_ONE) {
+                    newPoint.setEntry(i, lowerDifference.getEntry(i));
+                }
+                if (xbdi.getEntry(i) == ONE) {
+                    newPoint.setEntry(i, upperDifference.getEntry(i));
+                }
+                trialStepPoint.setEntry(i, newPoint.getEntry(i) - trustRegionCenterOffset.getEntry(i));
+                // Computing 2nd power
+                final double d1 = trialStepPoint.getEntry(i);
+                dsq += d1 * d1;
+            }
+            return new double[] { dsq, crvmin };
+            // The following instructions multiply the current S-vector by the second
+            // derivative matrix of the quadratic model, putting the product in HS.
+            // They are reached from three different parts of the software above and
+            // they can be regarded as an external subroutine.
+        }
+        case 210: {
+            printState(210); // XXX
+            int ih = 0;
+            for (int j = 0; j < n; j++) {
+                hs.setEntry(j, ZERO);
+                for (int i = 0; i <= j; i++) {
+                    if (i < j) {
+                        hs.setEntry(j, hs.getEntry(j) + modelSecondDerivativesValues.getEntry(ih) * s.getEntry(i));
+                    }
+                    hs.setEntry(i, hs.getEntry(i) + modelSecondDerivativesValues.getEntry(ih) * s.getEntry(j));
+                    ih++;
+                }
+            }
+            final RealVector tmp = interpolationPoints.operate(s).ebeMultiply(modelSecondDerivativesParameters);
+            for (int k = 0; k < npt; k++) {
+                if (modelSecondDerivativesParameters.getEntry(k) != ZERO) {
+                    for (int i = 0; i < n; i++) {
+                        hs.setEntry(i, hs.getEntry(i) + tmp.getEntry(k) * interpolationPoints.getEntry(k, i));
+                    }
+                }
+            }
+            if (crvmin != ZERO) {
+                state = 50; break;
+            }
+            if (iterc > itcsav) {
+                state = 150; break;
+            }
+            for (int i = 0; i < n; i++) {
+                hred.setEntry(i, hs.getEntry(i));
+            }
+            state = 120; break;
+        }
+        default: {
+            throw new MathIllegalStateException(LocalizedFormats.SIMPLE_MESSAGE, "trsbox");
+        }}
+        }
+    } // trsbox
+
+    // ----------------------------------------------------------------------------------------
+
+    /**
+     *     The arrays BMAT and ZMAT are updated, as required by the new position
+     *     of the interpolation point that has the index KNEW. The vector VLAG has
+     *     N+NPT components, set on entry to the first NPT and last N components
+     *     of the product Hw in equation (4.11) of the Powell (2006) paper on
+     *     NEWUOA. Further, BETA is set on entry to the value of the parameter
+     *     with that name, and DENOM is set to the denominator of the updating
+     *     formula. Elements of ZMAT may be treated as zero if their moduli are
+     *     at most ZTEST. The first NDIM elements of W are used for working space.
+     * @param beta
+     * @param denom
+     * @param knew
+     */
+    private void update(
+            double beta,
+            double denom,
+            int knew
+    ) {
+        printMethod(); // XXX
+
+        final int n = currentBest.getDimension();
+        final int npt = numberOfInterpolationPoints;
+        final int nptm = npt - n - 1;
+
+        // XXX Should probably be split into two arrays.
+        final ArrayRealVector work = new ArrayRealVector(npt + n);
+
+        double ztest = ZERO;
+        for (int k = 0; k < npt; k++) {
+            for (int j = 0; j < nptm; j++) {
+                // Computing MAX
+                ztest = FastMath.max(ztest, FastMath.abs(zMatrix.getEntry(k, j)));
+            }
+        }
+        ztest *= 1e-20;
+
+        // Apply the rotations that put zeros in the KNEW-th row of ZMAT.
+
+        for (int j = 1; j < nptm; j++) {
+            final double d1 = zMatrix.getEntry(knew, j);
+            if (FastMath.abs(d1) > ztest) {
+                // Computing 2nd power
+                final double d2 = zMatrix.getEntry(knew, 0);
+                // Computing 2nd power
+                final double d3 = zMatrix.getEntry(knew, j);
+                final double d4 = FastMath.sqrt(d2 * d2 + d3 * d3);
+                final double d5 = zMatrix.getEntry(knew, 0) / d4;
+                final double d6 = zMatrix.getEntry(knew, j) / d4;
+                for (int i = 0; i < npt; i++) {
+                    final double d7 = d5 * zMatrix.getEntry(i, 0) + d6 * zMatrix.getEntry(i, j);
+                    zMatrix.setEntry(i, j, d5 * zMatrix.getEntry(i, j) - d6 * zMatrix.getEntry(i, 0));
+                    zMatrix.setEntry(i, 0, d7);
+                }
+            }
+            zMatrix.setEntry(knew, j, ZERO);
+        }
+
+        // Put the first NPT components of the KNEW-th column of HLAG into W,
+        // and calculate the parameters of the updating formula.
+
+        for (int i = 0; i < npt; i++) {
+            work.setEntry(i, zMatrix.getEntry(knew, 0) * zMatrix.getEntry(i, 0));
+        }
+        final double alpha = work.getEntry(knew);
+        final double tau = lagrangeValuesAtNewPoint.getEntry(knew);
+        lagrangeValuesAtNewPoint.setEntry(knew, lagrangeValuesAtNewPoint.getEntry(knew) - ONE);
+
+        // Complete the updating of ZMAT.
+
+        final double sqrtDenom = FastMath.sqrt(denom);
+        final double d1 = tau / sqrtDenom;
+        final double d2 = zMatrix.getEntry(knew, 0) / sqrtDenom;
+        for (int i = 0; i < npt; i++) {
+            zMatrix.setEntry(i, 0,
+                          d1 * zMatrix.getEntry(i, 0) - d2 * lagrangeValuesAtNewPoint.getEntry(i));
+        }
+
+        // Finally, update the matrix BMAT.
+
+        for (int j = 0; j < n; j++) {
+            final int jp = npt + j;
+            work.setEntry(jp, bMatrix.getEntry(knew, j));
+            final double d3 = (alpha * lagrangeValuesAtNewPoint.getEntry(jp) - tau * work.getEntry(jp)) / denom;
+            final double d4 = (-beta * work.getEntry(jp) - tau * lagrangeValuesAtNewPoint.getEntry(jp)) / denom;
+            for (int i = 0; i <= jp; i++) {
+                bMatrix.setEntry(i, j,
+                              bMatrix.getEntry(i, j) + d3 * lagrangeValuesAtNewPoint.getEntry(i) + d4 * work.getEntry(i));
+                if (i >= npt) {
+                    bMatrix.setEntry(jp, (i - npt), bMatrix.getEntry(i, j));
+                }
+            }
+        }
+    } // update
+
+    /**
+     * Performs validity checks.
+     *
+     * @param lowerBound Lower bounds (constraints) of the objective variables.
+     * @param upperBound Upperer bounds (constraints) of the objective variables.
+     */
+    private void setup(double[] lowerBound,
+                       double[] upperBound) {
+        printMethod(); // XXX
+
+        double[] init = getStartPoint();
+        final int dimension = init.length;
+
+        // Check problem dimension.
+        if (dimension < MINIMUM_PROBLEM_DIMENSION) {
+            throw new NumberIsTooSmallException(dimension, MINIMUM_PROBLEM_DIMENSION, true);
+        }
+        // Check number of interpolation points.
+        final int[] nPointsInterval = { dimension + 2, (dimension + 2) * (dimension + 1) / 2 };
+        if (numberOfInterpolationPoints < nPointsInterval[0] ||
+            numberOfInterpolationPoints > nPointsInterval[1]) {
+            throw new OutOfRangeException(LocalizedFormats.NUMBER_OF_INTERPOLATION_POINTS,
+                                          numberOfInterpolationPoints,
+                                          nPointsInterval[0],
+                                          nPointsInterval[1]);
+        }
+
+        // Initialize bound differences.
+        boundDifference = new double[dimension];
+
+        double requiredMinDiff = 2 * initialTrustRegionRadius;
+        double minDiff = Double.POSITIVE_INFINITY;
+        for (int i = 0; i < dimension; i++) {
+            boundDifference[i] = upperBound[i] - lowerBound[i];
+            minDiff = FastMath.min(minDiff, boundDifference[i]);
+        }
+        if (minDiff < requiredMinDiff) {
+            initialTrustRegionRadius = minDiff / 3.0;
+        }
+
+        // Initialize the data structures used by the "bobyqa" method.
+        bMatrix = new Array2DRowRealMatrix(dimension + numberOfInterpolationPoints,
+                                           dimension);
+        zMatrix = new Array2DRowRealMatrix(numberOfInterpolationPoints,
+                                           numberOfInterpolationPoints - dimension - 1);
+        interpolationPoints = new Array2DRowRealMatrix(numberOfInterpolationPoints,
+                                                       dimension);
+        originShift = new ArrayRealVector(dimension);
+        fAtInterpolationPoints = new ArrayRealVector(numberOfInterpolationPoints);
+        trustRegionCenterOffset = new ArrayRealVector(dimension);
+        gradientAtTrustRegionCenter = new ArrayRealVector(dimension);
+        lowerDifference = new ArrayRealVector(dimension);
+        upperDifference = new ArrayRealVector(dimension);
+        modelSecondDerivativesParameters = new ArrayRealVector(numberOfInterpolationPoints);
+        newPoint = new ArrayRealVector(dimension);
+        alternativeNewPoint = new ArrayRealVector(dimension);
+        trialStepPoint = new ArrayRealVector(dimension);
+        lagrangeValuesAtNewPoint = new ArrayRealVector(dimension + numberOfInterpolationPoints);
+        modelSecondDerivativesValues = new ArrayRealVector(dimension * (dimension + 1) / 2);
+    }
+
+    // XXX utility for figuring out call sequence.
+    private static String caller(int n) {
+        final Throwable t = new Throwable();
+        final StackTraceElement[] elements = t.getStackTrace();
+        final StackTraceElement e = elements[n];
+        return e.getMethodName() + " (at line " + e.getLineNumber() + ")";
+    }
+    // XXX utility for figuring out call sequence.
+    private static void printState(int s) {
+        //        System.out.println(caller(2) + ": state " + s);
+    }
+    // XXX utility for figuring out call sequence.
+    private static void printMethod() {
+        //        System.out.println(caller(2));
+    }
+
+    /**
+     * Marker for code paths that are not explored with the current unit tests.
+     * If the path becomes explored, it should just be removed from the code.
+     */
+    private static class PathIsExploredException extends RuntimeException {
+        /** Serializable UID. */
+        private static final long serialVersionUID = 745350979634801853L;
+
+        /** Message string. */
+        private static final String PATH_IS_EXPLORED
+            = "If this exception is thrown, just remove it from the code";
+
+        PathIsExploredException() {
+            super(PATH_IS_EXPLORED + " " + BOBYQAOptimizer.caller(3));
+        }
+    }
+}
+//CHECKSTYLE: resume all
diff --git a/src/main/java/org/apache/commons/math3/optimization/direct/BaseAbstractMultivariateOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/direct/BaseAbstractMultivariateOptimizer.java
new file mode 100644
index 0000000..d148d8c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/direct/BaseAbstractMultivariateOptimizer.java
@@ -0,0 +1,318 @@
+/*
+ * 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.math3.optimization.direct;
+
+import org.apache.commons.math3.util.Incrementor;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+import org.apache.commons.math3.analysis.MultivariateFunction;
+import org.apache.commons.math3.optimization.BaseMultivariateOptimizer;
+import org.apache.commons.math3.optimization.OptimizationData;
+import org.apache.commons.math3.optimization.GoalType;
+import org.apache.commons.math3.optimization.InitialGuess;
+import org.apache.commons.math3.optimization.SimpleBounds;
+import org.apache.commons.math3.optimization.ConvergenceChecker;
+import org.apache.commons.math3.optimization.PointValuePair;
+import org.apache.commons.math3.optimization.SimpleValueChecker;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+
+/**
+ * Base class for implementing optimizers for multivariate scalar functions.
+ * This base class handles the boiler-plate methods associated to thresholds,
+ * evaluations counting, initial guess and simple bounds settings.
+ *
+ * @param <FUNC> Type of the objective function to be optimized.
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 2.2
+ */
+@Deprecated
+public abstract class BaseAbstractMultivariateOptimizer<FUNC extends MultivariateFunction>
+    implements BaseMultivariateOptimizer<FUNC> {
+    /** Evaluations counter. */
+    protected final Incrementor evaluations = new Incrementor();
+    /** Convergence checker. */
+    private ConvergenceChecker<PointValuePair> checker;
+    /** Type of optimization. */
+    private GoalType goal;
+    /** Initial guess. */
+    private double[] start;
+    /** Lower bounds. */
+    private double[] lowerBound;
+    /** Upper bounds. */
+    private double[] upperBound;
+    /** Objective function. */
+    private MultivariateFunction function;
+
+    /**
+     * Simple constructor with default settings.
+     * The convergence check is set to a {@link SimpleValueChecker}.
+     * @deprecated See {@link SimpleValueChecker#SimpleValueChecker()}
+     */
+    @Deprecated
+    protected BaseAbstractMultivariateOptimizer() {
+        this(new SimpleValueChecker());
+    }
+    /**
+     * @param checker Convergence checker.
+     */
+    protected BaseAbstractMultivariateOptimizer(ConvergenceChecker<PointValuePair> checker) {
+        this.checker = checker;
+    }
+
+    /** {@inheritDoc} */
+    public int getMaxEvaluations() {
+        return evaluations.getMaximalCount();
+    }
+
+    /** {@inheritDoc} */
+    public int getEvaluations() {
+        return evaluations.getCount();
+    }
+
+    /** {@inheritDoc} */
+    public ConvergenceChecker<PointValuePair> getConvergenceChecker() {
+        return checker;
+    }
+
+    /**
+     * Compute the objective function value.
+     *
+     * @param point Point at which the objective function must be evaluated.
+     * @return the objective function value at the specified point.
+     * @throws TooManyEvaluationsException if the maximal number of
+     * evaluations is exceeded.
+     */
+    protected double computeObjectiveValue(double[] point) {
+        try {
+            evaluations.incrementCount();
+        } catch (MaxCountExceededException e) {
+            throw new TooManyEvaluationsException(e.getMax());
+        }
+        return function.value(point);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @deprecated As of 3.1. Please use
+     * {@link #optimize(int,MultivariateFunction,GoalType,OptimizationData[])}
+     * instead.
+     */
+    @Deprecated
+    public PointValuePair optimize(int maxEval, FUNC f, GoalType goalType,
+                                   double[] startPoint) {
+        return optimizeInternal(maxEval, f, goalType, new InitialGuess(startPoint));
+    }
+
+    /**
+     * Optimize an objective function.
+     *
+     * @param maxEval Allowed number of evaluations of the objective function.
+     * @param f Objective function.
+     * @param goalType Optimization type.
+     * @param optData Optimization data. The following data will be looked for:
+     * <ul>
+     *  <li>{@link InitialGuess}</li>
+     *  <li>{@link SimpleBounds}</li>
+     * </ul>
+     * @return the point/value pair giving the optimal value of the objective
+     * function.
+     * @since 3.1
+     */
+    public PointValuePair optimize(int maxEval,
+                                   FUNC f,
+                                   GoalType goalType,
+                                   OptimizationData... optData) {
+        return optimizeInternal(maxEval, f, goalType, optData);
+    }
+
+    /**
+     * Optimize an objective function.
+     *
+     * @param f Objective function.
+     * @param goalType Type of optimization goal: either
+     * {@link GoalType#MAXIMIZE} or {@link GoalType#MINIMIZE}.
+     * @param startPoint Start point for optimization.
+     * @param maxEval Maximum number of function evaluations.
+     * @return the point/value pair giving the optimal value for objective
+     * function.
+     * @throws org.apache.commons.math3.exception.DimensionMismatchException
+     * if the start point dimension is wrong.
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException
+     * if the maximal number of evaluations is exceeded.
+     * @throws org.apache.commons.math3.exception.NullArgumentException if
+     * any argument is {@code null}.
+     * @deprecated As of 3.1. Please use
+     * {@link #optimize(int,MultivariateFunction,GoalType,OptimizationData[])}
+     * instead.
+     */
+    @Deprecated
+    protected PointValuePair optimizeInternal(int maxEval, FUNC f, GoalType goalType,
+                                              double[] startPoint) {
+        return optimizeInternal(maxEval, f, goalType, new InitialGuess(startPoint));
+    }
+
+    /**
+     * Optimize an objective function.
+     *
+     * @param maxEval Allowed number of evaluations of the objective function.
+     * @param f Objective function.
+     * @param goalType Optimization type.
+     * @param optData Optimization data. The following data will be looked for:
+     * <ul>
+     *  <li>{@link InitialGuess}</li>
+     *  <li>{@link SimpleBounds}</li>
+     * </ul>
+     * @return the point/value pair giving the optimal value of the objective
+     * function.
+     * @throws TooManyEvaluationsException if the maximal number of
+     * evaluations is exceeded.
+     * @since 3.1
+     */
+    protected PointValuePair optimizeInternal(int maxEval,
+                                              FUNC f,
+                                              GoalType goalType,
+                                              OptimizationData... optData)
+        throws TooManyEvaluationsException {
+        // Set internal state.
+        evaluations.setMaximalCount(maxEval);
+        evaluations.resetCount();
+        function = f;
+        goal = goalType;
+        // Retrieve other settings.
+        parseOptimizationData(optData);
+        // Check input consistency.
+        checkParameters();
+        // Perform computation.
+        return doOptimize();
+    }
+
+    /**
+     * Scans the list of (required and optional) optimization data that
+     * characterize the problem.
+     *
+     * @param optData Optimization data. The following data will be looked for:
+     * <ul>
+     *  <li>{@link InitialGuess}</li>
+     *  <li>{@link SimpleBounds}</li>
+     * </ul>
+     */
+    private void parseOptimizationData(OptimizationData... optData) {
+        // The existing values (as set by the previous call) are reused if
+        // not provided in the argument list.
+        for (OptimizationData data : optData) {
+            if (data instanceof InitialGuess) {
+                start = ((InitialGuess) data).getInitialGuess();
+                continue;
+            }
+            if (data instanceof SimpleBounds) {
+                final SimpleBounds bounds = (SimpleBounds) data;
+                lowerBound = bounds.getLower();
+                upperBound = bounds.getUpper();
+                continue;
+            }
+        }
+    }
+
+    /**
+     * @return the optimization type.
+     */
+    public GoalType getGoalType() {
+        return goal;
+    }
+
+    /**
+     * @return the initial guess.
+     */
+    public double[] getStartPoint() {
+        return start == null ? null : start.clone();
+    }
+    /**
+     * @return the lower bounds.
+     * @since 3.1
+     */
+    public double[] getLowerBound() {
+        return lowerBound == null ? null : lowerBound.clone();
+    }
+    /**
+     * @return the upper bounds.
+     * @since 3.1
+     */
+    public double[] getUpperBound() {
+        return upperBound == null ? null : upperBound.clone();
+    }
+
+    /**
+     * Perform the bulk of the optimization algorithm.
+     *
+     * @return the point/value pair giving the optimal value of the
+     * objective function.
+     */
+    protected abstract PointValuePair doOptimize();
+
+    /**
+     * Check parameters consistency.
+     */
+    private void checkParameters() {
+        if (start != null) {
+            final int dim = start.length;
+            if (lowerBound != null) {
+                if (lowerBound.length != dim) {
+                    throw new DimensionMismatchException(lowerBound.length, dim);
+                }
+                for (int i = 0; i < dim; i++) {
+                    final double v = start[i];
+                    final double lo = lowerBound[i];
+                    if (v < lo) {
+                        throw new NumberIsTooSmallException(v, lo, true);
+                    }
+                }
+            }
+            if (upperBound != null) {
+                if (upperBound.length != dim) {
+                    throw new DimensionMismatchException(upperBound.length, dim);
+                }
+                for (int i = 0; i < dim; i++) {
+                    final double v = start[i];
+                    final double hi = upperBound[i];
+                    if (v > hi) {
+                        throw new NumberIsTooLargeException(v, hi, true);
+                    }
+                }
+            }
+
+            // If the bounds were not specified, the allowed interval is
+            // assumed to be [-inf, +inf].
+            if (lowerBound == null) {
+                lowerBound = new double[dim];
+                for (int i = 0; i < dim; i++) {
+                    lowerBound[i] = Double.NEGATIVE_INFINITY;
+                }
+            }
+            if (upperBound == null) {
+                upperBound = new double[dim];
+                for (int i = 0; i < dim; i++) {
+                    upperBound[i] = Double.POSITIVE_INFINITY;
+                }
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/direct/BaseAbstractMultivariateSimpleBoundsOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/direct/BaseAbstractMultivariateSimpleBoundsOptimizer.java
new file mode 100644
index 0000000..67a4296
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/direct/BaseAbstractMultivariateSimpleBoundsOptimizer.java
@@ -0,0 +1,82 @@
+/*
+ * 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.math3.optimization.direct;
+
+import org.apache.commons.math3.analysis.MultivariateFunction;
+import org.apache.commons.math3.optimization.BaseMultivariateOptimizer;
+import org.apache.commons.math3.optimization.BaseMultivariateSimpleBoundsOptimizer;
+import org.apache.commons.math3.optimization.GoalType;
+import org.apache.commons.math3.optimization.InitialGuess;
+import org.apache.commons.math3.optimization.SimpleBounds;
+import org.apache.commons.math3.optimization.PointValuePair;
+import org.apache.commons.math3.optimization.ConvergenceChecker;
+
+/**
+ * Base class for implementing optimizers for multivariate scalar functions,
+ * subject to simple bounds: The valid range of the parameters is an interval.
+ * The interval can possibly be infinite (in one or both directions).
+ * This base class handles the boiler-plate methods associated to thresholds
+ * settings, iterations and evaluations counting.
+ *
+ * @param <FUNC> Type of the objective function to be optimized.
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.0
+ * @deprecated As of 3.1 since the {@link BaseAbstractMultivariateOptimizer
+ * base class} contains similar functionality.
+ */
+@Deprecated
+public abstract class BaseAbstractMultivariateSimpleBoundsOptimizer<FUNC extends MultivariateFunction>
+    extends BaseAbstractMultivariateOptimizer<FUNC>
+    implements BaseMultivariateOptimizer<FUNC>,
+               BaseMultivariateSimpleBoundsOptimizer<FUNC> {
+    /**
+     * Simple constructor with default settings.
+     * The convergence checker is set to a
+     * {@link org.apache.commons.math3.optimization.SimpleValueChecker}.
+     *
+     * @see BaseAbstractMultivariateOptimizer#BaseAbstractMultivariateOptimizer()
+     * @deprecated See {@link org.apache.commons.math3.optimization.SimpleValueChecker#SimpleValueChecker()}
+     */
+    @Deprecated
+    protected BaseAbstractMultivariateSimpleBoundsOptimizer() {}
+
+    /**
+     * @param checker Convergence checker.
+     */
+    protected BaseAbstractMultivariateSimpleBoundsOptimizer(ConvergenceChecker<PointValuePair> checker) {
+        super(checker);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public PointValuePair optimize(int maxEval, FUNC f, GoalType goalType,
+                                   double[] startPoint) {
+        return super.optimizeInternal(maxEval, f, goalType,
+                                      new InitialGuess(startPoint));
+    }
+
+    /** {@inheritDoc} */
+    public PointValuePair optimize(int maxEval, FUNC f, GoalType goalType,
+                                   double[] startPoint,
+                                   double[] lower, double[] upper) {
+        return super.optimizeInternal(maxEval, f, goalType,
+                                      new InitialGuess(startPoint),
+                                      new SimpleBounds(lower, upper));
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/direct/BaseAbstractMultivariateVectorOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/direct/BaseAbstractMultivariateVectorOptimizer.java
new file mode 100644
index 0000000..e070632
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/direct/BaseAbstractMultivariateVectorOptimizer.java
@@ -0,0 +1,370 @@
+/*
+ * 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.math3.optimization.direct;
+
+import org.apache.commons.math3.util.Incrementor;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.analysis.MultivariateVectorFunction;
+import org.apache.commons.math3.optimization.OptimizationData;
+import org.apache.commons.math3.optimization.InitialGuess;
+import org.apache.commons.math3.optimization.Target;
+import org.apache.commons.math3.optimization.Weight;
+import org.apache.commons.math3.optimization.BaseMultivariateVectorOptimizer;
+import org.apache.commons.math3.optimization.ConvergenceChecker;
+import org.apache.commons.math3.optimization.PointVectorValuePair;
+import org.apache.commons.math3.optimization.SimpleVectorValueChecker;
+import org.apache.commons.math3.linear.RealMatrix;
+
+/**
+ * Base class for implementing optimizers for multivariate scalar functions.
+ * This base class handles the boiler-plate methods associated to thresholds
+ * settings, iterations and evaluations counting.
+ *
+ * @param <FUNC> the type of the objective function to be optimized
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.0
+ */
+@Deprecated
+public abstract class BaseAbstractMultivariateVectorOptimizer<FUNC extends MultivariateVectorFunction>
+    implements BaseMultivariateVectorOptimizer<FUNC> {
+    /** Evaluations counter. */
+    protected final Incrementor evaluations = new Incrementor();
+    /** Convergence checker. */
+    private ConvergenceChecker<PointVectorValuePair> checker;
+    /** Target value for the objective functions at optimum. */
+    private double[] target;
+    /** Weight matrix. */
+    private RealMatrix weightMatrix;
+    /** Weight for the least squares cost computation.
+     * @deprecated
+     */
+    @Deprecated
+    private double[] weight;
+    /** Initial guess. */
+    private double[] start;
+    /** Objective function. */
+    private FUNC function;
+
+    /**
+     * Simple constructor with default settings.
+     * The convergence check is set to a {@link SimpleVectorValueChecker}.
+     * @deprecated See {@link SimpleVectorValueChecker#SimpleVectorValueChecker()}
+     */
+    @Deprecated
+    protected BaseAbstractMultivariateVectorOptimizer() {
+        this(new SimpleVectorValueChecker());
+    }
+    /**
+     * @param checker Convergence checker.
+     */
+    protected BaseAbstractMultivariateVectorOptimizer(ConvergenceChecker<PointVectorValuePair> checker) {
+        this.checker = checker;
+    }
+
+    /** {@inheritDoc} */
+    public int getMaxEvaluations() {
+        return evaluations.getMaximalCount();
+    }
+
+    /** {@inheritDoc} */
+    public int getEvaluations() {
+        return evaluations.getCount();
+    }
+
+    /** {@inheritDoc} */
+    public ConvergenceChecker<PointVectorValuePair> getConvergenceChecker() {
+        return checker;
+    }
+
+    /**
+     * Compute the objective function value.
+     *
+     * @param point Point at which the objective function must be evaluated.
+     * @return the objective function value at the specified point.
+     * @throws TooManyEvaluationsException if the maximal number of evaluations is
+     * exceeded.
+     */
+    protected double[] computeObjectiveValue(double[] point) {
+        try {
+            evaluations.incrementCount();
+        } catch (MaxCountExceededException e) {
+            throw new TooManyEvaluationsException(e.getMax());
+        }
+        return function.value(point);
+    }
+
+    /** {@inheritDoc}
+     *
+     * @deprecated As of 3.1. Please use
+     * {@link #optimize(int,MultivariateVectorFunction,OptimizationData[])}
+     * instead.
+     */
+    @Deprecated
+    public PointVectorValuePair optimize(int maxEval, FUNC f, double[] t, double[] w,
+                                         double[] startPoint) {
+        return optimizeInternal(maxEval, f, t, w, startPoint);
+    }
+
+    /**
+     * Optimize an objective function.
+     *
+     * @param maxEval Allowed number of evaluations of the objective function.
+     * @param f Objective function.
+     * @param optData Optimization data. The following data will be looked for:
+     * <ul>
+     *  <li>{@link Target}</li>
+     *  <li>{@link Weight}</li>
+     *  <li>{@link InitialGuess}</li>
+     * </ul>
+     * @return the point/value pair giving the optimal value of the objective
+     * function.
+     * @throws TooManyEvaluationsException if the maximal number of
+     * evaluations is exceeded.
+     * @throws DimensionMismatchException if the initial guess, target, and weight
+     * arguments have inconsistent dimensions.
+     *
+     * @since 3.1
+     */
+    protected PointVectorValuePair optimize(int maxEval,
+                                            FUNC f,
+                                            OptimizationData... optData)
+        throws TooManyEvaluationsException,
+               DimensionMismatchException {
+        return optimizeInternal(maxEval, f, optData);
+    }
+
+    /**
+     * Optimize an objective function.
+     * Optimization is considered to be a weighted least-squares minimization.
+     * The cost function to be minimized is
+     * <code>&sum;weight<sub>i</sub>(objective<sub>i</sub> - target<sub>i</sub>)<sup>2</sup></code>
+     *
+     * @param f Objective function.
+     * @param t Target value for the objective functions at optimum.
+     * @param w Weights for the least squares cost computation.
+     * @param startPoint Start point for optimization.
+     * @return the point/value pair giving the optimal value for objective
+     * function.
+     * @param maxEval Maximum number of function evaluations.
+     * @throws org.apache.commons.math3.exception.DimensionMismatchException
+     * if the start point dimension is wrong.
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException
+     * if the maximal number of evaluations is exceeded.
+     * @throws org.apache.commons.math3.exception.NullArgumentException if
+     * any argument is {@code null}.
+     * @deprecated As of 3.1. Please use
+     * {@link #optimizeInternal(int,MultivariateVectorFunction,OptimizationData[])}
+     * instead.
+     */
+    @Deprecated
+    protected PointVectorValuePair optimizeInternal(final int maxEval, final FUNC f,
+                                                    final double[] t, final double[] w,
+                                                    final double[] startPoint) {
+        // Checks.
+        if (f == null) {
+            throw new NullArgumentException();
+        }
+        if (t == null) {
+            throw new NullArgumentException();
+        }
+        if (w == null) {
+            throw new NullArgumentException();
+        }
+        if (startPoint == null) {
+            throw new NullArgumentException();
+        }
+        if (t.length != w.length) {
+            throw new DimensionMismatchException(t.length, w.length);
+        }
+
+        return optimizeInternal(maxEval, f,
+                                new Target(t),
+                                new Weight(w),
+                                new InitialGuess(startPoint));
+    }
+
+    /**
+     * Optimize an objective function.
+     *
+     * @param maxEval Allowed number of evaluations of the objective function.
+     * @param f Objective function.
+     * @param optData Optimization data. The following data will be looked for:
+     * <ul>
+     *  <li>{@link Target}</li>
+     *  <li>{@link Weight}</li>
+     *  <li>{@link InitialGuess}</li>
+     * </ul>
+     * @return the point/value pair giving the optimal value of the objective
+     * function.
+     * @throws TooManyEvaluationsException if the maximal number of
+     * evaluations is exceeded.
+     * @throws DimensionMismatchException if the initial guess, target, and weight
+     * arguments have inconsistent dimensions.
+     *
+     * @since 3.1
+     */
+    protected PointVectorValuePair optimizeInternal(int maxEval,
+                                                    FUNC f,
+                                                    OptimizationData... optData)
+        throws TooManyEvaluationsException,
+               DimensionMismatchException {
+        // Set internal state.
+        evaluations.setMaximalCount(maxEval);
+        evaluations.resetCount();
+        function = f;
+        // Retrieve other settings.
+        parseOptimizationData(optData);
+        // Check input consistency.
+        checkParameters();
+        // Allow subclasses to reset their own internal state.
+        setUp();
+        // Perform computation.
+        return doOptimize();
+    }
+
+    /**
+     * Gets the initial values of the optimized parameters.
+     *
+     * @return the initial guess.
+     */
+    public double[] getStartPoint() {
+        return start.clone();
+    }
+
+    /**
+     * Gets the weight matrix of the observations.
+     *
+     * @return the weight matrix.
+     * @since 3.1
+     */
+    public RealMatrix getWeight() {
+        return weightMatrix.copy();
+    }
+    /**
+     * Gets the observed values to be matched by the objective vector
+     * function.
+     *
+     * @return the target values.
+     * @since 3.1
+     */
+    public double[] getTarget() {
+        return target.clone();
+    }
+
+    /**
+     * Gets the objective vector function.
+     * Note that this access bypasses the evaluation counter.
+     *
+     * @return the objective vector function.
+     * @since 3.1
+     */
+    protected FUNC getObjectiveFunction() {
+        return function;
+    }
+
+    /**
+     * Perform the bulk of the optimization algorithm.
+     *
+     * @return the point/value pair giving the optimal value for the
+     * objective function.
+     */
+    protected abstract PointVectorValuePair doOptimize();
+
+    /**
+     * @return a reference to the {@link #target array}.
+     * @deprecated As of 3.1.
+     */
+    @Deprecated
+    protected double[] getTargetRef() {
+        return target;
+    }
+    /**
+     * @return a reference to the {@link #weight array}.
+     * @deprecated As of 3.1.
+     */
+    @Deprecated
+    protected double[] getWeightRef() {
+        return weight;
+    }
+
+    /**
+     * Method which a subclass <em>must</em> override whenever its internal
+     * state depend on the {@link OptimizationData input} parsed by this base
+     * class.
+     * It will be called after the parsing step performed in the
+     * {@link #optimize(int,MultivariateVectorFunction,OptimizationData[])
+     * optimize} method and just before {@link #doOptimize()}.
+     *
+     * @since 3.1
+     */
+    protected void setUp() {
+        // XXX Temporary code until the new internal data is used everywhere.
+        final int dim = target.length;
+        weight = new double[dim];
+        for (int i = 0; i < dim; i++) {
+            weight[i] = weightMatrix.getEntry(i, i);
+        }
+    }
+
+    /**
+     * Scans the list of (required and optional) optimization data that
+     * characterize the problem.
+     *
+     * @param optData Optimization data. The following data will be looked for:
+     * <ul>
+     *  <li>{@link Target}</li>
+     *  <li>{@link Weight}</li>
+     *  <li>{@link InitialGuess}</li>
+     * </ul>
+     */
+    private void parseOptimizationData(OptimizationData... optData) {
+        // The existing values (as set by the previous call) are reused if
+        // not provided in the argument list.
+        for (OptimizationData data : optData) {
+            if (data instanceof Target) {
+                target = ((Target) data).getTarget();
+                continue;
+            }
+            if (data instanceof Weight) {
+                weightMatrix = ((Weight) data).getWeight();
+                continue;
+            }
+            if (data instanceof InitialGuess) {
+                start = ((InitialGuess) data).getInitialGuess();
+                continue;
+            }
+        }
+    }
+
+    /**
+     * Check parameters consistency.
+     *
+     * @throws DimensionMismatchException if {@link #target} and
+     * {@link #weightMatrix} have inconsistent dimensions.
+     */
+    private void checkParameters() {
+        if (target.length != weightMatrix.getColumnDimension()) {
+            throw new DimensionMismatchException(target.length,
+                                                 weightMatrix.getColumnDimension());
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/direct/CMAESOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/direct/CMAESOptimizer.java
new file mode 100644
index 0000000..388a6f7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/direct/CMAESOptimizer.java
@@ -0,0 +1,1441 @@
+/*
+ * 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.math3.optimization.direct;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.math3.analysis.MultivariateFunction;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+import org.apache.commons.math3.linear.Array2DRowRealMatrix;
+import org.apache.commons.math3.linear.EigenDecomposition;
+import org.apache.commons.math3.linear.MatrixUtils;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.optimization.ConvergenceChecker;
+import org.apache.commons.math3.optimization.OptimizationData;
+import org.apache.commons.math3.optimization.GoalType;
+import org.apache.commons.math3.optimization.MultivariateOptimizer;
+import org.apache.commons.math3.optimization.PointValuePair;
+import org.apache.commons.math3.optimization.SimpleValueChecker;
+import org.apache.commons.math3.random.MersenneTwister;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * <p>An implementation of the active Covariance Matrix Adaptation Evolution Strategy (CMA-ES)
+ * for non-linear, non-convex, non-smooth, global function minimization.
+ * The CMA-Evolution Strategy (CMA-ES) is a reliable stochastic optimization method
+ * which should be applied if derivative-based methods, e.g. quasi-Newton BFGS or
+ * conjugate gradient, fail due to a rugged search landscape (e.g. noise, local
+ * optima, outlier, etc.) of the objective function. Like a
+ * quasi-Newton method, the CMA-ES learns and applies a variable metric
+ * on the underlying search space. Unlike a quasi-Newton method, the
+ * CMA-ES neither estimates nor uses gradients, making it considerably more
+ * reliable in terms of finding a good, or even close to optimal, solution.</p>
+ *
+ * <p>In general, on smooth objective functions the CMA-ES is roughly ten times
+ * slower than BFGS (counting objective function evaluations, no gradients provided).
+ * For up to <math>N=10</math> variables also the derivative-free simplex
+ * direct search method (Nelder and Mead) can be faster, but it is
+ * far less reliable than CMA-ES.</p>
+ *
+ * <p>The CMA-ES is particularly well suited for non-separable
+ * and/or badly conditioned problems. To observe the advantage of CMA compared
+ * to a conventional evolution strategy, it will usually take about
+ * <math>30 N</math> function evaluations. On difficult problems the complete
+ * optimization (a single run) is expected to take <em>roughly</em> between
+ * <math>30 N</math> and <math>300 N<sup>2</sup></math>
+ * function evaluations.</p>
+ *
+ * <p>This implementation is translated and adapted from the Matlab version
+ * of the CMA-ES algorithm as implemented in module {@code cmaes.m} version 3.51.</p>
+ *
+ * For more information, please refer to the following links:
+ * <ul>
+ *  <li><a href="http://www.lri.fr/~hansen/cmaes.m">Matlab code</a></li>
+ *  <li><a href="http://www.lri.fr/~hansen/cmaesintro.html">Introduction to CMA-ES</a></li>
+ *  <li><a href="http://en.wikipedia.org/wiki/CMA-ES">Wikipedia</a></li>
+ * </ul>
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.0
+ */
+@Deprecated
+public class CMAESOptimizer
+    extends BaseAbstractMultivariateSimpleBoundsOptimizer<MultivariateFunction>
+    implements MultivariateOptimizer {
+    /** Default value for {@link #checkFeasableCount}: {@value}. */
+    public static final int DEFAULT_CHECKFEASABLECOUNT = 0;
+    /** Default value for {@link #stopFitness}: {@value}. */
+    public static final double DEFAULT_STOPFITNESS = 0;
+    /** Default value for {@link #isActiveCMA}: {@value}. */
+    public static final boolean DEFAULT_ISACTIVECMA = true;
+    /** Default value for {@link #maxIterations}: {@value}. */
+    public static final int DEFAULT_MAXITERATIONS = 30000;
+    /** Default value for {@link #diagonalOnly}: {@value}. */
+    public static final int DEFAULT_DIAGONALONLY = 0;
+    /** Default value for {@link #random}. */
+    public static final RandomGenerator DEFAULT_RANDOMGENERATOR = new MersenneTwister();
+
+    // global search parameters
+    /**
+     * Population size, offspring number. The primary strategy parameter to play
+     * with, which can be increased from its default value. Increasing the
+     * population size improves global search properties in exchange to speed.
+     * Speed decreases, as a rule, at most linearly with increasing population
+     * size. It is advisable to begin with the default small population size.
+     */
+    private int lambda; // population size
+    /**
+     * Covariance update mechanism, default is active CMA. isActiveCMA = true
+     * turns on "active CMA" with a negative update of the covariance matrix and
+     * checks for positive definiteness. OPTS.CMA.active = 2 does not check for
+     * pos. def. and is numerically faster. Active CMA usually speeds up the
+     * adaptation.
+     */
+    private boolean isActiveCMA;
+    /**
+     * Determines how often a new random offspring is generated in case it is
+     * not feasible / beyond the defined limits, default is 0.
+     */
+    private int checkFeasableCount;
+    /**
+     * @see Sigma
+     */
+    private double[] inputSigma;
+    /** Number of objective variables/problem dimension */
+    private int dimension;
+    /**
+     * Defines the number of initial iterations, where the covariance matrix
+     * remains diagonal and the algorithm has internally linear time complexity.
+     * diagonalOnly = 1 means keeping the covariance matrix always diagonal and
+     * this setting also exhibits linear space complexity. This can be
+     * particularly useful for dimension > 100.
+     * @see <a href="http://hal.archives-ouvertes.fr/inria-00287367/en">A Simple Modification in CMA-ES</a>
+     */
+    private int diagonalOnly = 0;
+    /** Number of objective variables/problem dimension */
+    private boolean isMinimize = true;
+    /** Indicates whether statistic data is collected. */
+    private boolean generateStatistics = false;
+
+    // termination criteria
+    /** Maximal number of iterations allowed. */
+    private int maxIterations;
+    /** Limit for fitness value. */
+    private double stopFitness;
+    /** Stop if x-changes larger stopTolUpX. */
+    private double stopTolUpX;
+    /** Stop if x-change smaller stopTolX. */
+    private double stopTolX;
+    /** Stop if fun-changes smaller stopTolFun. */
+    private double stopTolFun;
+    /** Stop if back fun-changes smaller stopTolHistFun. */
+    private double stopTolHistFun;
+
+    // selection strategy parameters
+    /** Number of parents/points for recombination. */
+    private int mu; //
+    /** log(mu + 0.5), stored for efficiency. */
+    private double logMu2;
+    /** Array for weighted recombination. */
+    private RealMatrix weights;
+    /** Variance-effectiveness of sum w_i x_i. */
+    private double mueff; //
+
+    // dynamic strategy parameters and constants
+    /** Overall standard deviation - search volume. */
+    private double sigma;
+    /** Cumulation constant. */
+    private double cc;
+    /** Cumulation constant for step-size. */
+    private double cs;
+    /** Damping for step-size. */
+    private double damps;
+    /** Learning rate for rank-one update. */
+    private double ccov1;
+    /** Learning rate for rank-mu update' */
+    private double ccovmu;
+    /** Expectation of ||N(0,I)|| == norm(randn(N,1)). */
+    private double chiN;
+    /** Learning rate for rank-one update - diagonalOnly */
+    private double ccov1Sep;
+    /** Learning rate for rank-mu update - diagonalOnly */
+    private double ccovmuSep;
+
+    // CMA internal values - updated each generation
+    /** Objective variables. */
+    private RealMatrix xmean;
+    /** Evolution path. */
+    private RealMatrix pc;
+    /** Evolution path for sigma. */
+    private RealMatrix ps;
+    /** Norm of ps, stored for efficiency. */
+    private double normps;
+    /** Coordinate system. */
+    private RealMatrix B;
+    /** Scaling. */
+    private RealMatrix D;
+    /** B*D, stored for efficiency. */
+    private RealMatrix BD;
+    /** Diagonal of sqrt(D), stored for efficiency. */
+    private RealMatrix diagD;
+    /** Covariance matrix. */
+    private RealMatrix C;
+    /** Diagonal of C, used for diagonalOnly. */
+    private RealMatrix diagC;
+    /** Number of iterations already performed. */
+    private int iterations;
+
+    /** History queue of best values. */
+    private double[] fitnessHistory;
+    /** Size of history queue of best values. */
+    private int historySize;
+
+    /** Random generator. */
+    private RandomGenerator random;
+
+    /** History of sigma values. */
+    private List<Double> statisticsSigmaHistory = new ArrayList<Double>();
+    /** History of mean matrix. */
+    private List<RealMatrix> statisticsMeanHistory = new ArrayList<RealMatrix>();
+    /** History of fitness values. */
+    private List<Double> statisticsFitnessHistory = new ArrayList<Double>();
+    /** History of D matrix. */
+    private List<RealMatrix> statisticsDHistory = new ArrayList<RealMatrix>();
+
+    /**
+     * Default constructor, uses default parameters
+     *
+     * @deprecated As of version 3.1: Parameter {@code lambda} must be
+     * passed with the call to {@link #optimize(int,MultivariateFunction,GoalType,OptimizationData[])
+     * optimize} (whereas in the current code it is set to an undocumented value).
+     */
+    @Deprecated
+    public CMAESOptimizer() {
+        this(0);
+    }
+
+    /**
+     * @param lambda Population size.
+     * @deprecated As of version 3.1: Parameter {@code lambda} must be
+     * passed with the call to {@link #optimize(int,MultivariateFunction,GoalType,OptimizationData[])
+     * optimize} (whereas in the current code it is set to an undocumented value)..
+     */
+    @Deprecated
+    public CMAESOptimizer(int lambda) {
+        this(lambda, null, DEFAULT_MAXITERATIONS, DEFAULT_STOPFITNESS,
+             DEFAULT_ISACTIVECMA, DEFAULT_DIAGONALONLY,
+             DEFAULT_CHECKFEASABLECOUNT, DEFAULT_RANDOMGENERATOR,
+             false, null);
+    }
+
+    /**
+     * @param lambda Population size.
+     * @param inputSigma Initial standard deviations to sample new points
+     * around the initial guess.
+     * @deprecated As of version 3.1: Parameters {@code lambda} and {@code inputSigma} must be
+     * passed with the call to {@link #optimize(int,MultivariateFunction,GoalType,OptimizationData[])
+     * optimize}.
+     */
+    @Deprecated
+    public CMAESOptimizer(int lambda, double[] inputSigma) {
+        this(lambda, inputSigma, DEFAULT_MAXITERATIONS, DEFAULT_STOPFITNESS,
+             DEFAULT_ISACTIVECMA, DEFAULT_DIAGONALONLY,
+             DEFAULT_CHECKFEASABLECOUNT, DEFAULT_RANDOMGENERATOR, false);
+    }
+
+    /**
+     * @param lambda Population size.
+     * @param inputSigma Initial standard deviations to sample new points
+     * around the initial guess.
+     * @param maxIterations Maximal number of iterations.
+     * @param stopFitness Whether to stop if objective function value is smaller than
+     * {@code stopFitness}.
+     * @param isActiveCMA Chooses the covariance matrix update method.
+     * @param diagonalOnly Number of initial iterations, where the covariance matrix
+     * remains diagonal.
+     * @param checkFeasableCount Determines how often new random objective variables are
+     * generated in case they are out of bounds.
+     * @param random Random generator.
+     * @param generateStatistics Whether statistic data is collected.
+     * @deprecated See {@link SimpleValueChecker#SimpleValueChecker()}
+     */
+    @Deprecated
+    public CMAESOptimizer(int lambda, double[] inputSigma,
+                          int maxIterations, double stopFitness,
+                          boolean isActiveCMA, int diagonalOnly, int checkFeasableCount,
+                          RandomGenerator random, boolean generateStatistics) {
+        this(lambda, inputSigma, maxIterations, stopFitness, isActiveCMA,
+             diagonalOnly, checkFeasableCount, random, generateStatistics,
+             new SimpleValueChecker());
+    }
+
+    /**
+     * @param lambda Population size.
+     * @param inputSigma Initial standard deviations to sample new points
+     * around the initial guess.
+     * @param maxIterations Maximal number of iterations.
+     * @param stopFitness Whether to stop if objective function value is smaller than
+     * {@code stopFitness}.
+     * @param isActiveCMA Chooses the covariance matrix update method.
+     * @param diagonalOnly Number of initial iterations, where the covariance matrix
+     * remains diagonal.
+     * @param checkFeasableCount Determines how often new random objective variables are
+     * generated in case they are out of bounds.
+     * @param random Random generator.
+     * @param generateStatistics Whether statistic data is collected.
+     * @param checker Convergence checker.
+     * @deprecated As of version 3.1: Parameters {@code lambda} and {@code inputSigma} must be
+     * passed with the call to {@link #optimize(int,MultivariateFunction,GoalType,OptimizationData[])
+     * optimize}.
+     */
+    @Deprecated
+    public CMAESOptimizer(int lambda, double[] inputSigma,
+                          int maxIterations, double stopFitness,
+                          boolean isActiveCMA, int diagonalOnly, int checkFeasableCount,
+                          RandomGenerator random, boolean generateStatistics,
+                          ConvergenceChecker<PointValuePair> checker) {
+        super(checker);
+        this.lambda = lambda;
+        this.inputSigma = inputSigma == null ? null : (double[]) inputSigma.clone();
+        this.maxIterations = maxIterations;
+        this.stopFitness = stopFitness;
+        this.isActiveCMA = isActiveCMA;
+        this.diagonalOnly = diagonalOnly;
+        this.checkFeasableCount = checkFeasableCount;
+        this.random = random;
+        this.generateStatistics = generateStatistics;
+    }
+
+    /**
+     * @param maxIterations Maximal number of iterations.
+     * @param stopFitness Whether to stop if objective function value is smaller than
+     * {@code stopFitness}.
+     * @param isActiveCMA Chooses the covariance matrix update method.
+     * @param diagonalOnly Number of initial iterations, where the covariance matrix
+     * remains diagonal.
+     * @param checkFeasableCount Determines how often new random objective variables are
+     * generated in case they are out of bounds.
+     * @param random Random generator.
+     * @param generateStatistics Whether statistic data is collected.
+     * @param checker Convergence checker.
+     *
+     * @since 3.1
+     */
+    public CMAESOptimizer(int maxIterations,
+                          double stopFitness,
+                          boolean isActiveCMA,
+                          int diagonalOnly,
+                          int checkFeasableCount,
+                          RandomGenerator random,
+                          boolean generateStatistics,
+                          ConvergenceChecker<PointValuePair> checker) {
+        super(checker);
+        this.maxIterations = maxIterations;
+        this.stopFitness = stopFitness;
+        this.isActiveCMA = isActiveCMA;
+        this.diagonalOnly = diagonalOnly;
+        this.checkFeasableCount = checkFeasableCount;
+        this.random = random;
+        this.generateStatistics = generateStatistics;
+    }
+
+    /**
+     * @return History of sigma values.
+     */
+    public List<Double> getStatisticsSigmaHistory() {
+        return statisticsSigmaHistory;
+    }
+
+    /**
+     * @return History of mean matrix.
+     */
+    public List<RealMatrix> getStatisticsMeanHistory() {
+        return statisticsMeanHistory;
+    }
+
+    /**
+     * @return History of fitness values.
+     */
+    public List<Double> getStatisticsFitnessHistory() {
+        return statisticsFitnessHistory;
+    }
+
+    /**
+     * @return History of D matrix.
+     */
+    public List<RealMatrix> getStatisticsDHistory() {
+        return statisticsDHistory;
+    }
+
+    /**
+     * Input sigma values.
+     * They define the initial coordinate-wise standard deviations for
+     * sampling new search points around the initial guess.
+     * It is suggested to set them to the estimated distance from the
+     * initial to the desired optimum.
+     * Small values induce the search to be more local (and very small
+     * values are more likely to find a local optimum close to the initial
+     * guess).
+     * Too small values might however lead to early termination.
+     * @since 3.1
+     */
+    public static class Sigma implements OptimizationData {
+        /** Sigma values. */
+        private final double[] sigma;
+
+        /**
+         * @param s Sigma values.
+         * @throws NotPositiveException if any of the array entries is smaller
+         * than zero.
+         */
+        public Sigma(double[] s)
+            throws NotPositiveException {
+            for (int i = 0; i < s.length; i++) {
+                if (s[i] < 0) {
+                    throw new NotPositiveException(s[i]);
+                }
+            }
+
+            sigma = s.clone();
+        }
+
+        /**
+         * @return the sigma values.
+         */
+        public double[] getSigma() {
+            return sigma.clone();
+        }
+    }
+
+    /**
+     * Population size.
+     * The number of offspring is the primary strategy parameter.
+     * In the absence of better clues, a good default could be an
+     * integer close to {@code 4 + 3 ln(n)}, where {@code n} is the
+     * number of optimized parameters.
+     * Increasing the population size improves global search properties
+     * at the expense of speed (which in general decreases at most
+     * linearly with increasing population size).
+     * @since 3.1
+     */
+    public static class PopulationSize implements OptimizationData {
+        /** Population size. */
+        private final int lambda;
+
+        /**
+         * @param size Population size.
+         * @throws NotStrictlyPositiveException if {@code size <= 0}.
+         */
+        public PopulationSize(int size)
+            throws NotStrictlyPositiveException {
+            if (size <= 0) {
+                throw new NotStrictlyPositiveException(size);
+            }
+            lambda = size;
+        }
+
+        /**
+         * @return the population size.
+         */
+        public int getPopulationSize() {
+            return lambda;
+        }
+    }
+
+    /**
+     * Optimize an objective function.
+     *
+     * @param maxEval Allowed number of evaluations of the objective function.
+     * @param f Objective function.
+     * @param goalType Optimization type.
+     * @param optData Optimization data. The following data will be looked for:
+     * <ul>
+     *  <li>{@link org.apache.commons.math3.optimization.InitialGuess InitialGuess}</li>
+     *  <li>{@link Sigma}</li>
+     *  <li>{@link PopulationSize}</li>
+     * </ul>
+     * @return the point/value pair giving the optimal value for objective
+     * function.
+     */
+    @Override
+    protected PointValuePair optimizeInternal(int maxEval, MultivariateFunction f,
+                                              GoalType goalType,
+                                              OptimizationData... optData) {
+        // Scan "optData" for the input specific to this optimizer.
+        parseOptimizationData(optData);
+
+        // The parent's method will retrieve the common parameters from
+        // "optData" and call "doOptimize".
+        return super.optimizeInternal(maxEval, f, goalType, optData);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected PointValuePair doOptimize() {
+        checkParameters();
+         // -------------------- Initialization --------------------------------
+        isMinimize = getGoalType().equals(GoalType.MINIMIZE);
+        final FitnessFunction fitfun = new FitnessFunction();
+        final double[] guess = getStartPoint();
+        // number of objective variables/problem dimension
+        dimension = guess.length;
+        initializeCMA(guess);
+        iterations = 0;
+        double bestValue = fitfun.value(guess);
+        push(fitnessHistory, bestValue);
+        PointValuePair optimum = new PointValuePair(getStartPoint(),
+                isMinimize ? bestValue : -bestValue);
+        PointValuePair lastResult = null;
+
+        // -------------------- Generation Loop --------------------------------
+
+        generationLoop:
+        for (iterations = 1; iterations <= maxIterations; iterations++) {
+            // Generate and evaluate lambda offspring
+            final RealMatrix arz = randn1(dimension, lambda);
+            final RealMatrix arx = zeros(dimension, lambda);
+            final double[] fitness = new double[lambda];
+            // generate random offspring
+            for (int k = 0; k < lambda; k++) {
+                RealMatrix arxk = null;
+                for (int i = 0; i < checkFeasableCount + 1; i++) {
+                    if (diagonalOnly <= 0) {
+                        arxk = xmean.add(BD.multiply(arz.getColumnMatrix(k))
+                                         .scalarMultiply(sigma)); // m + sig * Normal(0,C)
+                    } else {
+                        arxk = xmean.add(times(diagD,arz.getColumnMatrix(k))
+                                         .scalarMultiply(sigma));
+                    }
+                    if (i >= checkFeasableCount ||
+                        fitfun.isFeasible(arxk.getColumn(0))) {
+                        break;
+                    }
+                    // regenerate random arguments for row
+                    arz.setColumn(k, randn(dimension));
+                }
+                copyColumn(arxk, 0, arx, k);
+                try {
+                    fitness[k] = fitfun.value(arx.getColumn(k)); // compute fitness
+                } catch (TooManyEvaluationsException e) {
+                    break generationLoop;
+                }
+            }
+            // Sort by fitness and compute weighted mean into xmean
+            final int[] arindex = sortedIndices(fitness);
+            // Calculate new xmean, this is selection and recombination
+            final RealMatrix xold = xmean; // for speed up of Eq. (2) and (3)
+            final RealMatrix bestArx = selectColumns(arx, MathArrays.copyOf(arindex, mu));
+            xmean = bestArx.multiply(weights);
+            final RealMatrix bestArz = selectColumns(arz, MathArrays.copyOf(arindex, mu));
+            final RealMatrix zmean = bestArz.multiply(weights);
+            final boolean hsig = updateEvolutionPaths(zmean, xold);
+            if (diagonalOnly <= 0) {
+                updateCovariance(hsig, bestArx, arz, arindex, xold);
+            } else {
+                updateCovarianceDiagonalOnly(hsig, bestArz);
+            }
+            // Adapt step size sigma - Eq. (5)
+            sigma *= FastMath.exp(FastMath.min(1, (normps/chiN - 1) * cs / damps));
+            final double bestFitness = fitness[arindex[0]];
+            final double worstFitness = fitness[arindex[arindex.length - 1]];
+            if (bestValue > bestFitness) {
+                bestValue = bestFitness;
+                lastResult = optimum;
+                optimum = new PointValuePair(fitfun.repair(bestArx.getColumn(0)),
+                                             isMinimize ? bestFitness : -bestFitness);
+                if (getConvergenceChecker() != null && lastResult != null &&
+                    getConvergenceChecker().converged(iterations, optimum, lastResult)) {
+                    break generationLoop;
+                }
+            }
+            // handle termination criteria
+            // Break, if fitness is good enough
+            if (stopFitness != 0 && bestFitness < (isMinimize ? stopFitness : -stopFitness)) {
+                break generationLoop;
+            }
+            final double[] sqrtDiagC = sqrt(diagC).getColumn(0);
+            final double[] pcCol = pc.getColumn(0);
+            for (int i = 0; i < dimension; i++) {
+                if (sigma * FastMath.max(FastMath.abs(pcCol[i]), sqrtDiagC[i]) > stopTolX) {
+                    break;
+                }
+                if (i >= dimension - 1) {
+                    break generationLoop;
+                }
+            }
+            for (int i = 0; i < dimension; i++) {
+                if (sigma * sqrtDiagC[i] > stopTolUpX) {
+                    break generationLoop;
+                }
+            }
+            final double historyBest = min(fitnessHistory);
+            final double historyWorst = max(fitnessHistory);
+            if (iterations > 2 &&
+                FastMath.max(historyWorst, worstFitness) -
+                FastMath.min(historyBest, bestFitness) < stopTolFun) {
+                break generationLoop;
+            }
+            if (iterations > fitnessHistory.length &&
+                historyWorst-historyBest < stopTolHistFun) {
+                break generationLoop;
+            }
+            // condition number of the covariance matrix exceeds 1e14
+            if (max(diagD)/min(diagD) > 1e7) {
+                break generationLoop;
+            }
+            // user defined termination
+            if (getConvergenceChecker() != null) {
+                final PointValuePair current
+                    = new PointValuePair(bestArx.getColumn(0),
+                                         isMinimize ? bestFitness : -bestFitness);
+                if (lastResult != null &&
+                    getConvergenceChecker().converged(iterations, current, lastResult)) {
+                    break generationLoop;
+                    }
+                lastResult = current;
+            }
+            // Adjust step size in case of equal function values (flat fitness)
+            if (bestValue == fitness[arindex[(int)(0.1+lambda/4.)]]) {
+                sigma *= FastMath.exp(0.2 + cs / damps);
+            }
+            if (iterations > 2 && FastMath.max(historyWorst, bestFitness) -
+                FastMath.min(historyBest, bestFitness) == 0) {
+                sigma *= FastMath.exp(0.2 + cs / damps);
+            }
+            // store best in history
+            push(fitnessHistory,bestFitness);
+            fitfun.setValueRange(worstFitness-bestFitness);
+            if (generateStatistics) {
+                statisticsSigmaHistory.add(sigma);
+                statisticsFitnessHistory.add(bestFitness);
+                statisticsMeanHistory.add(xmean.transpose());
+                statisticsDHistory.add(diagD.transpose().scalarMultiply(1E5));
+            }
+        }
+        return optimum;
+    }
+
+    /**
+     * Scans the list of (required and optional) optimization data that
+     * characterize the problem.
+     *
+     * @param optData Optimization data. The following data will be looked for:
+     * <ul>
+     *  <li>{@link Sigma}</li>
+     *  <li>{@link PopulationSize}</li>
+     * </ul>
+     */
+    private void parseOptimizationData(OptimizationData... optData) {
+        // The existing values (as set by the previous call) are reused if
+        // not provided in the argument list.
+        for (OptimizationData data : optData) {
+            if (data instanceof Sigma) {
+                inputSigma = ((Sigma) data).getSigma();
+                continue;
+            }
+            if (data instanceof PopulationSize) {
+                lambda = ((PopulationSize) data).getPopulationSize();
+                continue;
+            }
+        }
+    }
+
+    /**
+     * Checks dimensions and values of boundaries and inputSigma if defined.
+     */
+    private void checkParameters() {
+        final double[] init = getStartPoint();
+        final double[] lB = getLowerBound();
+        final double[] uB = getUpperBound();
+
+        if (inputSigma != null) {
+            if (inputSigma.length != init.length) {
+                throw new DimensionMismatchException(inputSigma.length, init.length);
+            }
+            for (int i = 0; i < init.length; i++) {
+                if (inputSigma[i] < 0) {
+                    // XXX Remove this block in 4.0 (check performed in "Sigma" class).
+                    throw new NotPositiveException(inputSigma[i]);
+                }
+                if (inputSigma[i] > uB[i] - lB[i]) {
+                    throw new OutOfRangeException(inputSigma[i], 0, uB[i] - lB[i]);
+                }
+            }
+        }
+    }
+
+    /**
+     * Initialization of the dynamic search parameters
+     *
+     * @param guess Initial guess for the arguments of the fitness function.
+     */
+    private void initializeCMA(double[] guess) {
+        if (lambda <= 0) {
+            // XXX Line below to replace the current one in 4.0 (MATH-879).
+            // throw new NotStrictlyPositiveException(lambda);
+            lambda = 4 + (int) (3 * FastMath.log(dimension));
+        }
+        // initialize sigma
+        final double[][] sigmaArray = new double[guess.length][1];
+        for (int i = 0; i < guess.length; i++) {
+            // XXX Line below to replace the current one in 4.0 (MATH-868).
+            // sigmaArray[i][0] = inputSigma[i];
+            sigmaArray[i][0] = inputSigma == null ? 0.3 : inputSigma[i];
+        }
+        final RealMatrix insigma = new Array2DRowRealMatrix(sigmaArray, false);
+        sigma = max(insigma); // overall standard deviation
+
+        // initialize termination criteria
+        stopTolUpX = 1e3 * max(insigma);
+        stopTolX = 1e-11 * max(insigma);
+        stopTolFun = 1e-12;
+        stopTolHistFun = 1e-13;
+
+        // initialize selection strategy parameters
+        mu = lambda / 2; // number of parents/points for recombination
+        logMu2 = FastMath.log(mu + 0.5);
+        weights = log(sequence(1, mu, 1)).scalarMultiply(-1).scalarAdd(logMu2);
+        double sumw = 0;
+        double sumwq = 0;
+        for (int i = 0; i < mu; i++) {
+            double w = weights.getEntry(i, 0);
+            sumw += w;
+            sumwq += w * w;
+        }
+        weights = weights.scalarMultiply(1 / sumw);
+        mueff = sumw * sumw / sumwq; // variance-effectiveness of sum w_i x_i
+
+        // initialize dynamic strategy parameters and constants
+        cc = (4 + mueff / dimension) /
+                (dimension + 4 + 2 * mueff / dimension);
+        cs = (mueff + 2) / (dimension + mueff + 3.);
+        damps = (1 + 2 * FastMath.max(0, FastMath.sqrt((mueff - 1) /
+                                                       (dimension + 1)) - 1)) *
+            FastMath.max(0.3,
+                         1 - dimension / (1e-6 + maxIterations)) + cs; // minor increment
+        ccov1 = 2 / ((dimension + 1.3) * (dimension + 1.3) + mueff);
+        ccovmu = FastMath.min(1 - ccov1, 2 * (mueff - 2 + 1 / mueff) /
+                              ((dimension + 2) * (dimension + 2) + mueff));
+        ccov1Sep = FastMath.min(1, ccov1 * (dimension + 1.5) / 3);
+        ccovmuSep = FastMath.min(1 - ccov1, ccovmu * (dimension + 1.5) / 3);
+        chiN = FastMath.sqrt(dimension) *
+            (1 - 1 / ((double) 4 * dimension) + 1 / ((double) 21 * dimension * dimension));
+        // intialize CMA internal values - updated each generation
+        xmean = MatrixUtils.createColumnRealMatrix(guess); // objective variables
+        diagD = insigma.scalarMultiply(1 / sigma);
+        diagC = square(diagD);
+        pc = zeros(dimension, 1); // evolution paths for C and sigma
+        ps = zeros(dimension, 1); // B defines the coordinate system
+        normps = ps.getFrobeniusNorm();
+
+        B = eye(dimension, dimension);
+        D = ones(dimension, 1); // diagonal D defines the scaling
+        BD = times(B, repmat(diagD.transpose(), dimension, 1));
+        C = B.multiply(diag(square(D)).multiply(B.transpose())); // covariance
+        historySize = 10 + (int) (3 * 10 * dimension / (double) lambda);
+        fitnessHistory = new double[historySize]; // history of fitness values
+        for (int i = 0; i < historySize; i++) {
+            fitnessHistory[i] = Double.MAX_VALUE;
+        }
+    }
+
+    /**
+     * Update of the evolution paths ps and pc.
+     *
+     * @param zmean Weighted row matrix of the gaussian random numbers generating
+     * the current offspring.
+     * @param xold xmean matrix of the previous generation.
+     * @return hsig flag indicating a small correction.
+     */
+    private boolean updateEvolutionPaths(RealMatrix zmean, RealMatrix xold) {
+        ps = ps.scalarMultiply(1 - cs).add(
+                B.multiply(zmean).scalarMultiply(FastMath.sqrt(cs * (2 - cs) * mueff)));
+        normps = ps.getFrobeniusNorm();
+        final boolean hsig = normps /
+            FastMath.sqrt(1 - FastMath.pow(1 - cs, 2 * iterations)) /
+            chiN < 1.4 + 2 / ((double) dimension + 1);
+        pc = pc.scalarMultiply(1 - cc);
+        if (hsig) {
+            pc = pc.add(xmean.subtract(xold).scalarMultiply(FastMath.sqrt(cc * (2 - cc) * mueff) / sigma));
+        }
+        return hsig;
+    }
+
+    /**
+     * Update of the covariance matrix C for diagonalOnly > 0
+     *
+     * @param hsig Flag indicating a small correction.
+     * @param bestArz Fitness-sorted matrix of the gaussian random values of the
+     * current offspring.
+     */
+    private void updateCovarianceDiagonalOnly(boolean hsig,
+                                              final RealMatrix bestArz) {
+        // minor correction if hsig==false
+        double oldFac = hsig ? 0 : ccov1Sep * cc * (2 - cc);
+        oldFac += 1 - ccov1Sep - ccovmuSep;
+        diagC = diagC.scalarMultiply(oldFac) // regard old matrix
+            .add(square(pc).scalarMultiply(ccov1Sep)) // plus rank one update
+            .add((times(diagC, square(bestArz).multiply(weights))) // plus rank mu update
+                 .scalarMultiply(ccovmuSep));
+        diagD = sqrt(diagC); // replaces eig(C)
+        if (diagonalOnly > 1 &&
+            iterations > diagonalOnly) {
+            // full covariance matrix from now on
+            diagonalOnly = 0;
+            B = eye(dimension, dimension);
+            BD = diag(diagD);
+            C = diag(diagC);
+        }
+    }
+
+    /**
+     * Update of the covariance matrix C.
+     *
+     * @param hsig Flag indicating a small correction.
+     * @param bestArx Fitness-sorted matrix of the argument vectors producing the
+     * current offspring.
+     * @param arz Unsorted matrix containing the gaussian random values of the
+     * current offspring.
+     * @param arindex Indices indicating the fitness-order of the current offspring.
+     * @param xold xmean matrix of the previous generation.
+     */
+    private void updateCovariance(boolean hsig, final RealMatrix bestArx,
+                                  final RealMatrix arz, final int[] arindex,
+                                  final RealMatrix xold) {
+        double negccov = 0;
+        if (ccov1 + ccovmu > 0) {
+            final RealMatrix arpos = bestArx.subtract(repmat(xold, 1, mu))
+                .scalarMultiply(1 / sigma); // mu difference vectors
+            final RealMatrix roneu = pc.multiply(pc.transpose())
+                .scalarMultiply(ccov1); // rank one update
+            // minor correction if hsig==false
+            double oldFac = hsig ? 0 : ccov1 * cc * (2 - cc);
+            oldFac += 1 - ccov1 - ccovmu;
+            if (isActiveCMA) {
+                // Adapt covariance matrix C active CMA
+                negccov = (1 - ccovmu) * 0.25 * mueff / (FastMath.pow(dimension + 2, 1.5) + 2 * mueff);
+                // keep at least 0.66 in all directions, small popsize are most
+                // critical
+                final double negminresidualvariance = 0.66;
+                // where to make up for the variance loss
+                final double negalphaold = 0.5;
+                // prepare vectors, compute negative updating matrix Cneg
+                final int[] arReverseIndex = reverse(arindex);
+                RealMatrix arzneg = selectColumns(arz, MathArrays.copyOf(arReverseIndex, mu));
+                RealMatrix arnorms = sqrt(sumRows(square(arzneg)));
+                final int[] idxnorms = sortedIndices(arnorms.getRow(0));
+                final RealMatrix arnormsSorted = selectColumns(arnorms, idxnorms);
+                final int[] idxReverse = reverse(idxnorms);
+                final RealMatrix arnormsReverse = selectColumns(arnorms, idxReverse);
+                arnorms = divide(arnormsReverse, arnormsSorted);
+                final int[] idxInv = inverse(idxnorms);
+                final RealMatrix arnormsInv = selectColumns(arnorms, idxInv);
+                // check and set learning rate negccov
+                final double negcovMax = (1 - negminresidualvariance) /
+                    square(arnormsInv).multiply(weights).getEntry(0, 0);
+                if (negccov > negcovMax) {
+                    negccov = negcovMax;
+                }
+                arzneg = times(arzneg, repmat(arnormsInv, dimension, 1));
+                final RealMatrix artmp = BD.multiply(arzneg);
+                final RealMatrix Cneg = artmp.multiply(diag(weights)).multiply(artmp.transpose());
+                oldFac += negalphaold * negccov;
+                C = C.scalarMultiply(oldFac)
+                    .add(roneu) // regard old matrix
+                    .add(arpos.scalarMultiply( // plus rank one update
+                                              ccovmu + (1 - negalphaold) * negccov) // plus rank mu update
+                         .multiply(times(repmat(weights, 1, dimension),
+                                         arpos.transpose())))
+                    .subtract(Cneg.scalarMultiply(negccov));
+            } else {
+                // Adapt covariance matrix C - nonactive
+                C = C.scalarMultiply(oldFac) // regard old matrix
+                    .add(roneu) // plus rank one update
+                    .add(arpos.scalarMultiply(ccovmu) // plus rank mu update
+                         .multiply(times(repmat(weights, 1, dimension),
+                                         arpos.transpose())));
+            }
+        }
+        updateBD(negccov);
+    }
+
+    /**
+     * Update B and D from C.
+     *
+     * @param negccov Negative covariance factor.
+     */
+    private void updateBD(double negccov) {
+        if (ccov1 + ccovmu + negccov > 0 &&
+            (iterations % 1. / (ccov1 + ccovmu + negccov) / dimension / 10.) < 1) {
+            // to achieve O(N^2)
+            C = triu(C, 0).add(triu(C, 1).transpose());
+            // enforce symmetry to prevent complex numbers
+            final EigenDecomposition eig = new EigenDecomposition(C);
+            B = eig.getV(); // eigen decomposition, B==normalized eigenvectors
+            D = eig.getD();
+            diagD = diag(D);
+            if (min(diagD) <= 0) {
+                for (int i = 0; i < dimension; i++) {
+                    if (diagD.getEntry(i, 0) < 0) {
+                        diagD.setEntry(i, 0, 0);
+                    }
+                }
+                final double tfac = max(diagD) / 1e14;
+                C = C.add(eye(dimension, dimension).scalarMultiply(tfac));
+                diagD = diagD.add(ones(dimension, 1).scalarMultiply(tfac));
+            }
+            if (max(diagD) > 1e14 * min(diagD)) {
+                final double tfac = max(diagD) / 1e14 - min(diagD);
+                C = C.add(eye(dimension, dimension).scalarMultiply(tfac));
+                diagD = diagD.add(ones(dimension, 1).scalarMultiply(tfac));
+            }
+            diagC = diag(C);
+            diagD = sqrt(diagD); // D contains standard deviations now
+            BD = times(B, repmat(diagD.transpose(), dimension, 1)); // O(n^2)
+        }
+    }
+
+    /**
+     * Pushes the current best fitness value in a history queue.
+     *
+     * @param vals History queue.
+     * @param val Current best fitness value.
+     */
+    private static void push(double[] vals, double val) {
+        for (int i = vals.length-1; i > 0; i--) {
+            vals[i] = vals[i-1];
+        }
+        vals[0] = val;
+    }
+
+    /**
+     * Sorts fitness values.
+     *
+     * @param doubles Array of values to be sorted.
+     * @return a sorted array of indices pointing into doubles.
+     */
+    private int[] sortedIndices(final double[] doubles) {
+        final DoubleIndex[] dis = new DoubleIndex[doubles.length];
+        for (int i = 0; i < doubles.length; i++) {
+            dis[i] = new DoubleIndex(doubles[i], i);
+        }
+        Arrays.sort(dis);
+        final int[] indices = new int[doubles.length];
+        for (int i = 0; i < doubles.length; i++) {
+            indices[i] = dis[i].index;
+        }
+        return indices;
+    }
+
+    /**
+     * Used to sort fitness values. Sorting is always in lower value first
+     * order.
+     */
+    private static class DoubleIndex implements Comparable<DoubleIndex> {
+        /** Value to compare. */
+        private final double value;
+        /** Index into sorted array. */
+        private final int index;
+
+        /**
+         * @param value Value to compare.
+         * @param index Index into sorted array.
+         */
+        DoubleIndex(double value, int index) {
+            this.value = value;
+            this.index = index;
+        }
+
+        /** {@inheritDoc} */
+        public int compareTo(DoubleIndex o) {
+            return Double.compare(value, o.value);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public boolean equals(Object other) {
+
+            if (this == other) {
+                return true;
+            }
+
+            if (other instanceof DoubleIndex) {
+                return Double.compare(value, ((DoubleIndex) other).value) == 0;
+            }
+
+            return false;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public int hashCode() {
+            long bits = Double.doubleToLongBits(value);
+            return (int) ((1438542 ^ (bits >>> 32) ^ bits) & 0xffffffff);
+        }
+    }
+
+    /**
+     * Normalizes fitness values to the range [0,1]. Adds a penalty to the
+     * fitness value if out of range. The penalty is adjusted by calling
+     * setValueRange().
+     */
+    private class FitnessFunction {
+        /** Determines the penalty for boundary violations */
+        private double valueRange;
+        /**
+         * Flag indicating whether the objective variables are forced into their
+         * bounds if defined
+         */
+        private final boolean isRepairMode;
+
+        /** Simple constructor.
+         */
+        FitnessFunction() {
+            valueRange = 1;
+            isRepairMode = true;
+        }
+
+        /**
+         * @param point Normalized objective variables.
+         * @return the objective value + penalty for violated bounds.
+         */
+        public double value(final double[] point) {
+            double value;
+            if (isRepairMode) {
+                double[] repaired = repair(point);
+                value = CMAESOptimizer.this.computeObjectiveValue(repaired) +
+                    penalty(point, repaired);
+            } else {
+                value = CMAESOptimizer.this.computeObjectiveValue(point);
+            }
+            return isMinimize ? value : -value;
+        }
+
+        /**
+         * @param x Normalized objective variables.
+         * @return {@code true} if in bounds.
+         */
+        public boolean isFeasible(final double[] x) {
+            final double[] lB = CMAESOptimizer.this.getLowerBound();
+            final double[] uB = CMAESOptimizer.this.getUpperBound();
+
+            for (int i = 0; i < x.length; i++) {
+                if (x[i] < lB[i]) {
+                    return false;
+                }
+                if (x[i] > uB[i]) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        /**
+         * @param valueRange Adjusts the penalty computation.
+         */
+        public void setValueRange(double valueRange) {
+            this.valueRange = valueRange;
+        }
+
+        /**
+         * @param x Normalized objective variables.
+         * @return the repaired (i.e. all in bounds) objective variables.
+         */
+        private double[] repair(final double[] x) {
+            final double[] lB = CMAESOptimizer.this.getLowerBound();
+            final double[] uB = CMAESOptimizer.this.getUpperBound();
+
+            final double[] repaired = new double[x.length];
+            for (int i = 0; i < x.length; i++) {
+                if (x[i] < lB[i]) {
+                    repaired[i] = lB[i];
+                } else if (x[i] > uB[i]) {
+                    repaired[i] = uB[i];
+                } else {
+                    repaired[i] = x[i];
+                }
+            }
+            return repaired;
+        }
+
+        /**
+         * @param x Normalized objective variables.
+         * @param repaired Repaired objective variables.
+         * @return Penalty value according to the violation of the bounds.
+         */
+        private double penalty(final double[] x, final double[] repaired) {
+            double penalty = 0;
+            for (int i = 0; i < x.length; i++) {
+                double diff = FastMath.abs(x[i] - repaired[i]);
+                penalty += diff * valueRange;
+            }
+            return isMinimize ? penalty : -penalty;
+        }
+    }
+
+    // -----Matrix utility functions similar to the Matlab build in functions------
+
+    /**
+     * @param m Input matrix
+     * @return Matrix representing the element-wise logarithm of m.
+     */
+    private static RealMatrix log(final RealMatrix m) {
+        final double[][] d = new double[m.getRowDimension()][m.getColumnDimension()];
+        for (int r = 0; r < m.getRowDimension(); r++) {
+            for (int c = 0; c < m.getColumnDimension(); c++) {
+                d[r][c] = FastMath.log(m.getEntry(r, c));
+            }
+        }
+        return new Array2DRowRealMatrix(d, false);
+    }
+
+    /**
+     * @param m Input matrix.
+     * @return Matrix representing the element-wise square root of m.
+     */
+    private static RealMatrix sqrt(final RealMatrix m) {
+        final double[][] d = new double[m.getRowDimension()][m.getColumnDimension()];
+        for (int r = 0; r < m.getRowDimension(); r++) {
+            for (int c = 0; c < m.getColumnDimension(); c++) {
+                d[r][c] = FastMath.sqrt(m.getEntry(r, c));
+            }
+        }
+        return new Array2DRowRealMatrix(d, false);
+    }
+
+    /**
+     * @param m Input matrix.
+     * @return Matrix representing the element-wise square of m.
+     */
+    private static RealMatrix square(final RealMatrix m) {
+        final double[][] d = new double[m.getRowDimension()][m.getColumnDimension()];
+        for (int r = 0; r < m.getRowDimension(); r++) {
+            for (int c = 0; c < m.getColumnDimension(); c++) {
+                double e = m.getEntry(r, c);
+                d[r][c] = e * e;
+            }
+        }
+        return new Array2DRowRealMatrix(d, false);
+    }
+
+    /**
+     * @param m Input matrix 1.
+     * @param n Input matrix 2.
+     * @return the matrix where the elements of m and n are element-wise multiplied.
+     */
+    private static RealMatrix times(final RealMatrix m, final RealMatrix n) {
+        final double[][] d = new double[m.getRowDimension()][m.getColumnDimension()];
+        for (int r = 0; r < m.getRowDimension(); r++) {
+            for (int c = 0; c < m.getColumnDimension(); c++) {
+                d[r][c] = m.getEntry(r, c) * n.getEntry(r, c);
+            }
+        }
+        return new Array2DRowRealMatrix(d, false);
+    }
+
+    /**
+     * @param m Input matrix 1.
+     * @param n Input matrix 2.
+     * @return Matrix where the elements of m and n are element-wise divided.
+     */
+    private static RealMatrix divide(final RealMatrix m, final RealMatrix n) {
+        final double[][] d = new double[m.getRowDimension()][m.getColumnDimension()];
+        for (int r = 0; r < m.getRowDimension(); r++) {
+            for (int c = 0; c < m.getColumnDimension(); c++) {
+                d[r][c] = m.getEntry(r, c) / n.getEntry(r, c);
+            }
+        }
+        return new Array2DRowRealMatrix(d, false);
+    }
+
+    /**
+     * @param m Input matrix.
+     * @param cols Columns to select.
+     * @return Matrix representing the selected columns.
+     */
+    private static RealMatrix selectColumns(final RealMatrix m, final int[] cols) {
+        final double[][] d = new double[m.getRowDimension()][cols.length];
+        for (int r = 0; r < m.getRowDimension(); r++) {
+            for (int c = 0; c < cols.length; c++) {
+                d[r][c] = m.getEntry(r, cols[c]);
+            }
+        }
+        return new Array2DRowRealMatrix(d, false);
+    }
+
+    /**
+     * @param m Input matrix.
+     * @param k Diagonal position.
+     * @return Upper triangular part of matrix.
+     */
+    private static RealMatrix triu(final RealMatrix m, int k) {
+        final double[][] d = new double[m.getRowDimension()][m.getColumnDimension()];
+        for (int r = 0; r < m.getRowDimension(); r++) {
+            for (int c = 0; c < m.getColumnDimension(); c++) {
+                d[r][c] = r <= c - k ? m.getEntry(r, c) : 0;
+            }
+        }
+        return new Array2DRowRealMatrix(d, false);
+    }
+
+    /**
+     * @param m Input matrix.
+     * @return Row matrix representing the sums of the rows.
+     */
+    private static RealMatrix sumRows(final RealMatrix m) {
+        final double[][] d = new double[1][m.getColumnDimension()];
+        for (int c = 0; c < m.getColumnDimension(); c++) {
+            double sum = 0;
+            for (int r = 0; r < m.getRowDimension(); r++) {
+                sum += m.getEntry(r, c);
+            }
+            d[0][c] = sum;
+        }
+        return new Array2DRowRealMatrix(d, false);
+    }
+
+    /**
+     * @param m Input matrix.
+     * @return the diagonal n-by-n matrix if m is a column matrix or the column
+     * matrix representing the diagonal if m is a n-by-n matrix.
+     */
+    private static RealMatrix diag(final RealMatrix m) {
+        if (m.getColumnDimension() == 1) {
+            final double[][] d = new double[m.getRowDimension()][m.getRowDimension()];
+            for (int i = 0; i < m.getRowDimension(); i++) {
+                d[i][i] = m.getEntry(i, 0);
+            }
+            return new Array2DRowRealMatrix(d, false);
+        } else {
+            final double[][] d = new double[m.getRowDimension()][1];
+            for (int i = 0; i < m.getColumnDimension(); i++) {
+                d[i][0] = m.getEntry(i, i);
+            }
+            return new Array2DRowRealMatrix(d, false);
+        }
+    }
+
+    /**
+     * Copies a column from m1 to m2.
+     *
+     * @param m1 Source matrix.
+     * @param col1 Source column.
+     * @param m2 Target matrix.
+     * @param col2 Target column.
+     */
+    private static void copyColumn(final RealMatrix m1, int col1,
+                                   RealMatrix m2, int col2) {
+        for (int i = 0; i < m1.getRowDimension(); i++) {
+            m2.setEntry(i, col2, m1.getEntry(i, col1));
+        }
+    }
+
+    /**
+     * @param n Number of rows.
+     * @param m Number of columns.
+     * @return n-by-m matrix filled with 1.
+     */
+    private static RealMatrix ones(int n, int m) {
+        final double[][] d = new double[n][m];
+        for (int r = 0; r < n; r++) {
+            Arrays.fill(d[r], 1);
+        }
+        return new Array2DRowRealMatrix(d, false);
+    }
+
+    /**
+     * @param n Number of rows.
+     * @param m Number of columns.
+     * @return n-by-m matrix of 0 values out of diagonal, and 1 values on
+     * the diagonal.
+     */
+    private static RealMatrix eye(int n, int m) {
+        final double[][] d = new double[n][m];
+        for (int r = 0; r < n; r++) {
+            if (r < m) {
+                d[r][r] = 1;
+            }
+        }
+        return new Array2DRowRealMatrix(d, false);
+    }
+
+    /**
+     * @param n Number of rows.
+     * @param m Number of columns.
+     * @return n-by-m matrix of zero values.
+     */
+    private static RealMatrix zeros(int n, int m) {
+        return new Array2DRowRealMatrix(n, m);
+    }
+
+    /**
+     * @param mat Input matrix.
+     * @param n Number of row replicates.
+     * @param m Number of column replicates.
+     * @return a matrix which replicates the input matrix in both directions.
+     */
+    private static RealMatrix repmat(final RealMatrix mat, int n, int m) {
+        final int rd = mat.getRowDimension();
+        final int cd = mat.getColumnDimension();
+        final double[][] d = new double[n * rd][m * cd];
+        for (int r = 0; r < n * rd; r++) {
+            for (int c = 0; c < m * cd; c++) {
+                d[r][c] = mat.getEntry(r % rd, c % cd);
+            }
+        }
+        return new Array2DRowRealMatrix(d, false);
+    }
+
+    /**
+     * @param start Start value.
+     * @param end End value.
+     * @param step Step size.
+     * @return a sequence as column matrix.
+     */
+    private static RealMatrix sequence(double start, double end, double step) {
+        final int size = (int) ((end - start) / step + 1);
+        final double[][] d = new double[size][1];
+        double value = start;
+        for (int r = 0; r < size; r++) {
+            d[r][0] = value;
+            value += step;
+        }
+        return new Array2DRowRealMatrix(d, false);
+    }
+
+    /**
+     * @param m Input matrix.
+     * @return the maximum of the matrix element values.
+     */
+    private static double max(final RealMatrix m) {
+        double max = -Double.MAX_VALUE;
+        for (int r = 0; r < m.getRowDimension(); r++) {
+            for (int c = 0; c < m.getColumnDimension(); c++) {
+                double e = m.getEntry(r, c);
+                if (max < e) {
+                    max = e;
+                }
+            }
+        }
+        return max;
+    }
+
+    /**
+     * @param m Input matrix.
+     * @return the minimum of the matrix element values.
+     */
+    private static double min(final RealMatrix m) {
+        double min = Double.MAX_VALUE;
+        for (int r = 0; r < m.getRowDimension(); r++) {
+            for (int c = 0; c < m.getColumnDimension(); c++) {
+                double e = m.getEntry(r, c);
+                if (min > e) {
+                    min = e;
+                }
+            }
+        }
+        return min;
+    }
+
+    /**
+     * @param m Input array.
+     * @return the maximum of the array values.
+     */
+    private static double max(final double[] m) {
+        double max = -Double.MAX_VALUE;
+        for (int r = 0; r < m.length; r++) {
+            if (max < m[r]) {
+                max = m[r];
+            }
+        }
+        return max;
+    }
+
+    /**
+     * @param m Input array.
+     * @return the minimum of the array values.
+     */
+    private static double min(final double[] m) {
+        double min = Double.MAX_VALUE;
+        for (int r = 0; r < m.length; r++) {
+            if (min > m[r]) {
+                min = m[r];
+            }
+        }
+        return min;
+    }
+
+    /**
+     * @param indices Input index array.
+     * @return the inverse of the mapping defined by indices.
+     */
+    private static int[] inverse(final int[] indices) {
+        final int[] inverse = new int[indices.length];
+        for (int i = 0; i < indices.length; i++) {
+            inverse[indices[i]] = i;
+        }
+        return inverse;
+    }
+
+    /**
+     * @param indices Input index array.
+     * @return the indices in inverse order (last is first).
+     */
+    private static int[] reverse(final int[] indices) {
+        final int[] reverse = new int[indices.length];
+        for (int i = 0; i < indices.length; i++) {
+            reverse[i] = indices[indices.length - i - 1];
+        }
+        return reverse;
+    }
+
+    /**
+     * @param size Length of random array.
+     * @return an array of Gaussian random numbers.
+     */
+    private double[] randn(int size) {
+        final double[] randn = new double[size];
+        for (int i = 0; i < size; i++) {
+            randn[i] = random.nextGaussian();
+        }
+        return randn;
+    }
+
+    /**
+     * @param size Number of rows.
+     * @param popSize Population size.
+     * @return a 2-dimensional matrix of Gaussian random numbers.
+     */
+    private RealMatrix randn1(int size, int popSize) {
+        final double[][] d = new double[size][popSize];
+        for (int r = 0; r < size; r++) {
+            for (int c = 0; c < popSize; c++) {
+                d[r][c] = random.nextGaussian();
+            }
+        }
+        return new Array2DRowRealMatrix(d, false);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/direct/MultiDirectionalSimplex.java b/src/main/java/org/apache/commons/math3/optimization/direct/MultiDirectionalSimplex.java
new file mode 100644
index 0000000..c06bf96
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/direct/MultiDirectionalSimplex.java
@@ -0,0 +1,218 @@
+/*
+ * 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.math3.optimization.direct;
+
+import java.util.Comparator;
+
+import org.apache.commons.math3.analysis.MultivariateFunction;
+import org.apache.commons.math3.optimization.PointValuePair;
+
+/**
+ * This class implements the multi-directional direct search method.
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.0
+ */
+@Deprecated
+public class MultiDirectionalSimplex extends AbstractSimplex {
+    /** Default value for {@link #khi}: {@value}. */
+    private static final double DEFAULT_KHI = 2;
+    /** Default value for {@link #gamma}: {@value}. */
+    private static final double DEFAULT_GAMMA = 0.5;
+    /** Expansion coefficient. */
+    private final double khi;
+    /** Contraction coefficient. */
+    private final double gamma;
+
+    /**
+     * Build a multi-directional simplex with default coefficients.
+     * The default values are 2.0 for khi and 0.5 for gamma.
+     *
+     * @param n Dimension of the simplex.
+     */
+    public MultiDirectionalSimplex(final int n) {
+        this(n, 1d);
+    }
+
+    /**
+     * Build a multi-directional simplex with default coefficients.
+     * The default values are 2.0 for khi and 0.5 for gamma.
+     *
+     * @param n Dimension of the simplex.
+     * @param sideLength Length of the sides of the default (hypercube)
+     * simplex. See {@link AbstractSimplex#AbstractSimplex(int,double)}.
+     */
+    public MultiDirectionalSimplex(final int n, double sideLength) {
+        this(n, sideLength, DEFAULT_KHI, DEFAULT_GAMMA);
+    }
+
+    /**
+     * Build a multi-directional simplex with specified coefficients.
+     *
+     * @param n Dimension of the simplex. See
+     * {@link AbstractSimplex#AbstractSimplex(int,double)}.
+     * @param khi Expansion coefficient.
+     * @param gamma Contraction coefficient.
+     */
+    public MultiDirectionalSimplex(final int n,
+                                   final double khi, final double gamma) {
+        this(n, 1d, khi, gamma);
+    }
+
+    /**
+     * Build a multi-directional simplex with specified coefficients.
+     *
+     * @param n Dimension of the simplex. See
+     * {@link AbstractSimplex#AbstractSimplex(int,double)}.
+     * @param sideLength Length of the sides of the default (hypercube)
+     * simplex. See {@link AbstractSimplex#AbstractSimplex(int,double)}.
+     * @param khi Expansion coefficient.
+     * @param gamma Contraction coefficient.
+     */
+    public MultiDirectionalSimplex(final int n, double sideLength,
+                                   final double khi, final double gamma) {
+        super(n, sideLength);
+
+        this.khi   = khi;
+        this.gamma = gamma;
+    }
+
+    /**
+     * Build a multi-directional simplex with default coefficients.
+     * The default values are 2.0 for khi and 0.5 for gamma.
+     *
+     * @param steps Steps along the canonical axes representing box edges.
+     * They may be negative but not zero. See
+     */
+    public MultiDirectionalSimplex(final double[] steps) {
+        this(steps, DEFAULT_KHI, DEFAULT_GAMMA);
+    }
+
+    /**
+     * Build a multi-directional simplex with specified coefficients.
+     *
+     * @param steps Steps along the canonical axes representing box edges.
+     * They may be negative but not zero. See
+     * {@link AbstractSimplex#AbstractSimplex(double[])}.
+     * @param khi Expansion coefficient.
+     * @param gamma Contraction coefficient.
+     */
+    public MultiDirectionalSimplex(final double[] steps,
+                                   final double khi, final double gamma) {
+        super(steps);
+
+        this.khi   = khi;
+        this.gamma = gamma;
+    }
+
+    /**
+     * Build a multi-directional simplex with default coefficients.
+     * The default values are 2.0 for khi and 0.5 for gamma.
+     *
+     * @param referenceSimplex Reference simplex. See
+     * {@link AbstractSimplex#AbstractSimplex(double[][])}.
+     */
+    public MultiDirectionalSimplex(final double[][] referenceSimplex) {
+        this(referenceSimplex, DEFAULT_KHI, DEFAULT_GAMMA);
+    }
+
+    /**
+     * Build a multi-directional simplex with specified coefficients.
+     *
+     * @param referenceSimplex Reference simplex. See
+     * {@link AbstractSimplex#AbstractSimplex(double[][])}.
+     * @param khi Expansion coefficient.
+     * @param gamma Contraction coefficient.
+     * @throws org.apache.commons.math3.exception.NotStrictlyPositiveException
+     * if the reference simplex does not contain at least one point.
+     * @throws org.apache.commons.math3.exception.DimensionMismatchException
+     * if there is a dimension mismatch in the reference simplex.
+     */
+    public MultiDirectionalSimplex(final double[][] referenceSimplex,
+                                   final double khi, final double gamma) {
+        super(referenceSimplex);
+
+        this.khi   = khi;
+        this.gamma = gamma;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void iterate(final MultivariateFunction evaluationFunction,
+                        final Comparator<PointValuePair> comparator) {
+        // Save the original simplex.
+        final PointValuePair[] original = getPoints();
+        final PointValuePair best = original[0];
+
+        // Perform a reflection step.
+        final PointValuePair reflected = evaluateNewSimplex(evaluationFunction,
+                                                                original, 1, comparator);
+        if (comparator.compare(reflected, best) < 0) {
+            // Compute the expanded simplex.
+            final PointValuePair[] reflectedSimplex = getPoints();
+            final PointValuePair expanded = evaluateNewSimplex(evaluationFunction,
+                                                                   original, khi, comparator);
+            if (comparator.compare(reflected, expanded) <= 0) {
+                // Keep the reflected simplex.
+                setPoints(reflectedSimplex);
+            }
+            // Keep the expanded simplex.
+            return;
+        }
+
+        // Compute the contracted simplex.
+        evaluateNewSimplex(evaluationFunction, original, gamma, comparator);
+
+    }
+
+    /**
+     * Compute and evaluate a new simplex.
+     *
+     * @param evaluationFunction Evaluation function.
+     * @param original Original simplex (to be preserved).
+     * @param coeff Linear coefficient.
+     * @param comparator Comparator to use to sort simplex vertices from best
+     * to poorest.
+     * @return the best point in the transformed simplex.
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException
+     * if the maximal number of evaluations is exceeded.
+     */
+    private PointValuePair evaluateNewSimplex(final MultivariateFunction evaluationFunction,
+                                                  final PointValuePair[] original,
+                                                  final double coeff,
+                                                  final Comparator<PointValuePair> comparator) {
+        final double[] xSmallest = original[0].getPointRef();
+        // Perform a linear transformation on all the simplex points,
+        // except the first one.
+        setPoint(0, original[0]);
+        final int dim = getDimension();
+        for (int i = 1; i < getSize(); i++) {
+            final double[] xOriginal = original[i].getPointRef();
+            final double[] xTransformed = new double[dim];
+            for (int j = 0; j < dim; j++) {
+                xTransformed[j] = xSmallest[j] + coeff * (xSmallest[j] - xOriginal[j]);
+            }
+            setPoint(i, new PointValuePair(xTransformed, Double.NaN, false));
+        }
+
+        // Evaluate the simplex.
+        evaluate(evaluationFunction, comparator);
+
+        return getPoint(0);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/direct/MultivariateFunctionMappingAdapter.java b/src/main/java/org/apache/commons/math3/optimization/direct/MultivariateFunctionMappingAdapter.java
new file mode 100644
index 0000000..32f2a2c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/direct/MultivariateFunctionMappingAdapter.java
@@ -0,0 +1,301 @@
+/*
+ * 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.math3.optimization.direct;
+
+import org.apache.commons.math3.analysis.MultivariateFunction;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.function.Logit;
+import org.apache.commons.math3.analysis.function.Sigmoid;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * <p>Adapter for mapping bounded {@link MultivariateFunction} to unbounded ones.</p>
+ *
+ * <p>
+ * This adapter can be used to wrap functions subject to simple bounds on
+ * parameters so they can be used by optimizers that do <em>not</em> directly
+ * support simple bounds.
+ * </p>
+ * <p>
+ * The principle is that the user function that will be wrapped will see its
+ * parameters bounded as required, i.e when its {@code value} method is called
+ * with argument array {@code point}, the elements array will fulfill requirement
+ * {@code lower[i] <= point[i] <= upper[i]} for all i. Some of the components
+ * may be unbounded or bounded only on one side if the corresponding bound is
+ * set to an infinite value. The optimizer will not manage the user function by
+ * itself, but it will handle this adapter and it is this adapter that will take
+ * care the bounds are fulfilled. The adapter {@link #value(double[])} method will
+ * be called by the optimizer with unbound parameters, and the adapter will map
+ * the unbounded value to the bounded range using appropriate functions like
+ * {@link Sigmoid} for double bounded elements for example.
+ * </p>
+ * <p>
+ * As the optimizer sees only unbounded parameters, it should be noted that the
+ * start point or simplex expected by the optimizer should be unbounded, so the
+ * user is responsible for converting his bounded point to unbounded by calling
+ * {@link #boundedToUnbounded(double[])} before providing them to the optimizer.
+ * For the same reason, the point returned by the {@link
+ * org.apache.commons.math3.optimization.BaseMultivariateOptimizer#optimize(int,
+ * MultivariateFunction, org.apache.commons.math3.optimization.GoalType, double[])}
+ * method is unbounded. So to convert this point to bounded, users must call
+ * {@link #unboundedToBounded(double[])} by themselves!</p>
+ * <p>
+ * This adapter is only a poor man solution to simple bounds optimization constraints
+ * that can be used with simple optimizers like {@link SimplexOptimizer} with {@link
+ * NelderMeadSimplex} or {@link MultiDirectionalSimplex}. A better solution is to use
+ * an optimizer that directly supports simple bounds like {@link CMAESOptimizer} or
+ * {@link BOBYQAOptimizer}. One caveat of this poor man solution is that behavior near
+ * the bounds may be numerically unstable as bounds are mapped from infinite values.
+ * Another caveat is that convergence values are evaluated by the optimizer with respect
+ * to unbounded variables, so there will be scales differences when converted to bounded
+ * variables.
+ * </p>
+ *
+ * @see MultivariateFunctionPenaltyAdapter
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.0
+ */
+
+@Deprecated
+public class MultivariateFunctionMappingAdapter implements MultivariateFunction {
+
+    /** Underlying bounded function. */
+    private final MultivariateFunction bounded;
+
+    /** Mapping functions. */
+    private final Mapper[] mappers;
+
+    /** Simple constructor.
+     * @param bounded bounded function
+     * @param lower lower bounds for each element of the input parameters array
+     * (some elements may be set to {@code Double.NEGATIVE_INFINITY} for
+     * unbounded values)
+     * @param upper upper bounds for each element of the input parameters array
+     * (some elements may be set to {@code Double.POSITIVE_INFINITY} for
+     * unbounded values)
+     * @exception DimensionMismatchException if lower and upper bounds are not
+     * consistent, either according to dimension or to values
+     */
+    public MultivariateFunctionMappingAdapter(final MultivariateFunction bounded,
+                                                  final double[] lower, final double[] upper) {
+
+        // safety checks
+        MathUtils.checkNotNull(lower);
+        MathUtils.checkNotNull(upper);
+        if (lower.length != upper.length) {
+            throw new DimensionMismatchException(lower.length, upper.length);
+        }
+        for (int i = 0; i < lower.length; ++i) {
+            // note the following test is written in such a way it also fails for NaN
+            if (!(upper[i] >= lower[i])) {
+                throw new NumberIsTooSmallException(upper[i], lower[i], true);
+            }
+        }
+
+        this.bounded = bounded;
+        this.mappers = new Mapper[lower.length];
+        for (int i = 0; i < mappers.length; ++i) {
+            if (Double.isInfinite(lower[i])) {
+                if (Double.isInfinite(upper[i])) {
+                    // element is unbounded, no transformation is needed
+                    mappers[i] = new NoBoundsMapper();
+                } else {
+                    // element is simple-bounded on the upper side
+                    mappers[i] = new UpperBoundMapper(upper[i]);
+                }
+            } else {
+                if (Double.isInfinite(upper[i])) {
+                    // element is simple-bounded on the lower side
+                    mappers[i] = new LowerBoundMapper(lower[i]);
+                } else {
+                    // element is double-bounded
+                    mappers[i] = new LowerUpperBoundMapper(lower[i], upper[i]);
+                }
+            }
+        }
+
+    }
+
+    /** Map an array from unbounded to bounded.
+     * @param point unbounded value
+     * @return bounded value
+     */
+    public double[] unboundedToBounded(double[] point) {
+
+        // map unbounded input point to bounded point
+        final double[] mapped = new double[mappers.length];
+        for (int i = 0; i < mappers.length; ++i) {
+            mapped[i] = mappers[i].unboundedToBounded(point[i]);
+        }
+
+        return mapped;
+
+    }
+
+    /** Map an array from bounded to unbounded.
+     * @param point bounded value
+     * @return unbounded value
+     */
+    public double[] boundedToUnbounded(double[] point) {
+
+        // map bounded input point to unbounded point
+        final double[] mapped = new double[mappers.length];
+        for (int i = 0; i < mappers.length; ++i) {
+            mapped[i] = mappers[i].boundedToUnbounded(point[i]);
+        }
+
+        return mapped;
+
+    }
+
+    /** Compute the underlying function value from an unbounded point.
+     * <p>
+     * This method simply bounds the unbounded point using the mappings
+     * set up at construction and calls the underlying function using
+     * the bounded point.
+     * </p>
+     * @param point unbounded value
+     * @return underlying function value
+     * @see #unboundedToBounded(double[])
+     */
+    public double value(double[] point) {
+        return bounded.value(unboundedToBounded(point));
+    }
+
+    /** Mapping interface. */
+    private interface Mapper {
+
+        /** Map a value from unbounded to bounded.
+         * @param y unbounded value
+         * @return bounded value
+         */
+        double unboundedToBounded(double y);
+
+        /** Map a value from bounded to unbounded.
+         * @param x bounded value
+         * @return unbounded value
+         */
+        double boundedToUnbounded(double x);
+
+    }
+
+    /** Local class for no bounds mapping. */
+    private static class NoBoundsMapper implements Mapper {
+
+        /** Simple constructor.
+         */
+        NoBoundsMapper() {
+        }
+
+        /** {@inheritDoc} */
+        public double unboundedToBounded(final double y) {
+            return y;
+        }
+
+        /** {@inheritDoc} */
+        public double boundedToUnbounded(final double x) {
+            return x;
+        }
+
+    }
+
+    /** Local class for lower bounds mapping. */
+    private static class LowerBoundMapper implements Mapper {
+
+        /** Low bound. */
+        private final double lower;
+
+        /** Simple constructor.
+         * @param lower lower bound
+         */
+        LowerBoundMapper(final double lower) {
+            this.lower = lower;
+        }
+
+        /** {@inheritDoc} */
+        public double unboundedToBounded(final double y) {
+            return lower + FastMath.exp(y);
+        }
+
+        /** {@inheritDoc} */
+        public double boundedToUnbounded(final double x) {
+            return FastMath.log(x - lower);
+        }
+
+    }
+
+    /** Local class for upper bounds mapping. */
+    private static class UpperBoundMapper implements Mapper {
+
+        /** Upper bound. */
+        private final double upper;
+
+        /** Simple constructor.
+         * @param upper upper bound
+         */
+        UpperBoundMapper(final double upper) {
+            this.upper = upper;
+        }
+
+        /** {@inheritDoc} */
+        public double unboundedToBounded(final double y) {
+            return upper - FastMath.exp(-y);
+        }
+
+        /** {@inheritDoc} */
+        public double boundedToUnbounded(final double x) {
+            return -FastMath.log(upper - x);
+        }
+
+    }
+
+    /** Local class for lower and bounds mapping. */
+    private static class LowerUpperBoundMapper implements Mapper {
+
+        /** Function from unbounded to bounded. */
+        private final UnivariateFunction boundingFunction;
+
+        /** Function from bounded to unbounded. */
+        private final UnivariateFunction unboundingFunction;
+
+        /** Simple constructor.
+         * @param lower lower bound
+         * @param upper upper bound
+         */
+        LowerUpperBoundMapper(final double lower, final double upper) {
+            boundingFunction   = new Sigmoid(lower, upper);
+            unboundingFunction = new Logit(lower, upper);
+        }
+
+        /** {@inheritDoc} */
+        public double unboundedToBounded(final double y) {
+            return boundingFunction.value(y);
+        }
+
+        /** {@inheritDoc} */
+        public double boundedToUnbounded(final double x) {
+            return unboundingFunction.value(x);
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/direct/MultivariateFunctionPenaltyAdapter.java b/src/main/java/org/apache/commons/math3/optimization/direct/MultivariateFunctionPenaltyAdapter.java
new file mode 100644
index 0000000..4946487
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/direct/MultivariateFunctionPenaltyAdapter.java
@@ -0,0 +1,190 @@
+/*
+ * 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.math3.optimization.direct;
+
+import org.apache.commons.math3.analysis.MultivariateFunction;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * <p>Adapter extending bounded {@link MultivariateFunction} to an unbouded
+ * domain using a penalty function.</p>
+ *
+ * <p>
+ * This adapter can be used to wrap functions subject to simple bounds on
+ * parameters so they can be used by optimizers that do <em>not</em> directly
+ * support simple bounds.
+ * </p>
+ * <p>
+ * The principle is that the user function that will be wrapped will see its
+ * parameters bounded as required, i.e when its {@code value} method is called
+ * with argument array {@code point}, the elements array will fulfill requirement
+ * {@code lower[i] <= point[i] <= upper[i]} for all i. Some of the components
+ * may be unbounded or bounded only on one side if the corresponding bound is
+ * set to an infinite value. The optimizer will not manage the user function by
+ * itself, but it will handle this adapter and it is this adapter that will take
+ * care the bounds are fulfilled. The adapter {@link #value(double[])} method will
+ * be called by the optimizer with unbound parameters, and the adapter will check
+ * if the parameters is within range or not. If it is in range, then the underlying
+ * user function will be called, and if it is not the value of a penalty function
+ * will be returned instead.
+ * </p>
+ * <p>
+ * This adapter is only a poor man solution to simple bounds optimization constraints
+ * that can be used with simple optimizers like {@link SimplexOptimizer} with {@link
+ * NelderMeadSimplex} or {@link MultiDirectionalSimplex}. A better solution is to use
+ * an optimizer that directly supports simple bounds like {@link CMAESOptimizer} or
+ * {@link BOBYQAOptimizer}. One caveat of this poor man solution is that if start point
+ * or start simplex is completely outside of the allowed range, only the penalty function
+ * is used, and the optimizer may converge without ever entering the range.
+ * </p>
+ *
+ * @see MultivariateFunctionMappingAdapter
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.0
+ */
+
+@Deprecated
+public class MultivariateFunctionPenaltyAdapter implements MultivariateFunction {
+
+    /** Underlying bounded function. */
+    private final MultivariateFunction bounded;
+
+    /** Lower bounds. */
+    private final double[] lower;
+
+    /** Upper bounds. */
+    private final double[] upper;
+
+    /** Penalty offset. */
+    private final double offset;
+
+    /** Penalty scales. */
+    private final double[] scale;
+
+    /** Simple constructor.
+     * <p>
+     * When the optimizer provided points are out of range, the value of the
+     * penalty function will be used instead of the value of the underlying
+     * function. In order for this penalty to be effective in rejecting this
+     * point during the optimization process, the penalty function value should
+     * be defined with care. This value is computed as:
+     * <pre>
+     *   penalty(point) = offset + &sum;<sub>i</sub>[scale[i] * &radic;|point[i]-boundary[i]|]
+     * </pre>
+     * where indices i correspond to all the components that violates their boundaries.
+     * </p>
+     * <p>
+     * So when attempting a function minimization, offset should be larger than
+     * the maximum expected value of the underlying function and scale components
+     * should all be positive. When attempting a function maximization, offset
+     * should be lesser than the minimum expected value of the underlying function
+     * and scale components should all be negative.
+     * minimization, and lesser than the minimum expected value of the underlying
+     * function when attempting maximization.
+     * </p>
+     * <p>
+     * These choices for the penalty function have two properties. First, all out
+     * of range points will return a function value that is worse than the value
+     * returned by any in range point. Second, the penalty is worse for large
+     * boundaries violation than for small violations, so the optimizer has an hint
+     * about the direction in which it should search for acceptable points.
+     * </p>
+     * @param bounded bounded function
+     * @param lower lower bounds for each element of the input parameters array
+     * (some elements may be set to {@code Double.NEGATIVE_INFINITY} for
+     * unbounded values)
+     * @param upper upper bounds for each element of the input parameters array
+     * (some elements may be set to {@code Double.POSITIVE_INFINITY} for
+     * unbounded values)
+     * @param offset base offset of the penalty function
+     * @param scale scale of the penalty function
+     * @exception DimensionMismatchException if lower bounds, upper bounds and
+     * scales are not consistent, either according to dimension or to bounadary
+     * values
+     */
+    public MultivariateFunctionPenaltyAdapter(final MultivariateFunction bounded,
+                                                  final double[] lower, final double[] upper,
+                                                  final double offset, final double[] scale) {
+
+        // safety checks
+        MathUtils.checkNotNull(lower);
+        MathUtils.checkNotNull(upper);
+        MathUtils.checkNotNull(scale);
+        if (lower.length != upper.length) {
+            throw new DimensionMismatchException(lower.length, upper.length);
+        }
+        if (lower.length != scale.length) {
+            throw new DimensionMismatchException(lower.length, scale.length);
+        }
+        for (int i = 0; i < lower.length; ++i) {
+            // note the following test is written in such a way it also fails for NaN
+            if (!(upper[i] >= lower[i])) {
+                throw new NumberIsTooSmallException(upper[i], lower[i], true);
+            }
+        }
+
+        this.bounded = bounded;
+        this.lower   = lower.clone();
+        this.upper   = upper.clone();
+        this.offset  = offset;
+        this.scale   = scale.clone();
+
+    }
+
+    /** Compute the underlying function value from an unbounded point.
+     * <p>
+     * This method simply returns the value of the underlying function
+     * if the unbounded point already fulfills the bounds, and compute
+     * a replacement value using the offset and scale if bounds are
+     * violated, without calling the function at all.
+     * </p>
+     * @param point unbounded point
+     * @return either underlying function value or penalty function value
+     */
+    public double value(double[] point) {
+
+        for (int i = 0; i < scale.length; ++i) {
+            if ((point[i] < lower[i]) || (point[i] > upper[i])) {
+                // bound violation starting at this component
+                double sum = 0;
+                for (int j = i; j < scale.length; ++j) {
+                    final double overshoot;
+                    if (point[j] < lower[j]) {
+                        overshoot = scale[j] * (lower[j] - point[j]);
+                    } else if (point[j] > upper[j]) {
+                        overshoot = scale[j] * (point[j] - upper[j]);
+                    } else {
+                        overshoot = 0;
+                    }
+                    sum += FastMath.sqrt(overshoot);
+                }
+                return offset + sum;
+            }
+        }
+
+        // all boundaries are fulfilled, we are in the expected
+        // domain of the underlying function
+        return bounded.value(point);
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/direct/NelderMeadSimplex.java b/src/main/java/org/apache/commons/math3/optimization/direct/NelderMeadSimplex.java
new file mode 100644
index 0000000..a17586b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/direct/NelderMeadSimplex.java
@@ -0,0 +1,283 @@
+/*
+ * 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.math3.optimization.direct;
+
+import java.util.Comparator;
+
+import org.apache.commons.math3.optimization.PointValuePair;
+import org.apache.commons.math3.analysis.MultivariateFunction;
+
+/**
+ * This class implements the Nelder-Mead simplex algorithm.
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.0
+ */
+@Deprecated
+public class NelderMeadSimplex extends AbstractSimplex {
+    /** Default value for {@link #rho}: {@value}. */
+    private static final double DEFAULT_RHO = 1;
+    /** Default value for {@link #khi}: {@value}. */
+    private static final double DEFAULT_KHI = 2;
+    /** Default value for {@link #gamma}: {@value}. */
+    private static final double DEFAULT_GAMMA = 0.5;
+    /** Default value for {@link #sigma}: {@value}. */
+    private static final double DEFAULT_SIGMA = 0.5;
+    /** Reflection coefficient. */
+    private final double rho;
+    /** Expansion coefficient. */
+    private final double khi;
+    /** Contraction coefficient. */
+    private final double gamma;
+    /** Shrinkage coefficient. */
+    private final double sigma;
+
+    /**
+     * Build a Nelder-Mead simplex with default coefficients.
+     * The default coefficients are 1.0 for rho, 2.0 for khi and 0.5
+     * for both gamma and sigma.
+     *
+     * @param n Dimension of the simplex.
+     */
+    public NelderMeadSimplex(final int n) {
+        this(n, 1d);
+    }
+
+    /**
+     * Build a Nelder-Mead simplex with default coefficients.
+     * The default coefficients are 1.0 for rho, 2.0 for khi and 0.5
+     * for both gamma and sigma.
+     *
+     * @param n Dimension of the simplex.
+     * @param sideLength Length of the sides of the default (hypercube)
+     * simplex. See {@link AbstractSimplex#AbstractSimplex(int,double)}.
+     */
+    public NelderMeadSimplex(final int n, double sideLength) {
+        this(n, sideLength,
+             DEFAULT_RHO, DEFAULT_KHI, DEFAULT_GAMMA, DEFAULT_SIGMA);
+    }
+
+    /**
+     * Build a Nelder-Mead simplex with specified coefficients.
+     *
+     * @param n Dimension of the simplex. See
+     * {@link AbstractSimplex#AbstractSimplex(int,double)}.
+     * @param sideLength Length of the sides of the default (hypercube)
+     * simplex. See {@link AbstractSimplex#AbstractSimplex(int,double)}.
+     * @param rho Reflection coefficient.
+     * @param khi Expansion coefficient.
+     * @param gamma Contraction coefficient.
+     * @param sigma Shrinkage coefficient.
+     */
+    public NelderMeadSimplex(final int n, double sideLength,
+                             final double rho, final double khi,
+                             final double gamma, final double sigma) {
+        super(n, sideLength);
+
+        this.rho = rho;
+        this.khi = khi;
+        this.gamma = gamma;
+        this.sigma = sigma;
+    }
+
+    /**
+     * Build a Nelder-Mead simplex with specified coefficients.
+     *
+     * @param n Dimension of the simplex. See
+     * {@link AbstractSimplex#AbstractSimplex(int)}.
+     * @param rho Reflection coefficient.
+     * @param khi Expansion coefficient.
+     * @param gamma Contraction coefficient.
+     * @param sigma Shrinkage coefficient.
+     */
+    public NelderMeadSimplex(final int n,
+                             final double rho, final double khi,
+                             final double gamma, final double sigma) {
+        this(n, 1d, rho, khi, gamma, sigma);
+    }
+
+    /**
+     * Build a Nelder-Mead simplex with default coefficients.
+     * The default coefficients are 1.0 for rho, 2.0 for khi and 0.5
+     * for both gamma and sigma.
+     *
+     * @param steps Steps along the canonical axes representing box edges.
+     * They may be negative but not zero. See
+     */
+    public NelderMeadSimplex(final double[] steps) {
+        this(steps, DEFAULT_RHO, DEFAULT_KHI, DEFAULT_GAMMA, DEFAULT_SIGMA);
+    }
+
+    /**
+     * Build a Nelder-Mead simplex with specified coefficients.
+     *
+     * @param steps Steps along the canonical axes representing box edges.
+     * They may be negative but not zero. See
+     * {@link AbstractSimplex#AbstractSimplex(double[])}.
+     * @param rho Reflection coefficient.
+     * @param khi Expansion coefficient.
+     * @param gamma Contraction coefficient.
+     * @param sigma Shrinkage coefficient.
+     * @throws IllegalArgumentException if one of the steps is zero.
+     */
+    public NelderMeadSimplex(final double[] steps,
+                             final double rho, final double khi,
+                             final double gamma, final double sigma) {
+        super(steps);
+
+        this.rho = rho;
+        this.khi = khi;
+        this.gamma = gamma;
+        this.sigma = sigma;
+    }
+
+    /**
+     * Build a Nelder-Mead simplex with default coefficients.
+     * The default coefficients are 1.0 for rho, 2.0 for khi and 0.5
+     * for both gamma and sigma.
+     *
+     * @param referenceSimplex Reference simplex. See
+     * {@link AbstractSimplex#AbstractSimplex(double[][])}.
+     */
+    public NelderMeadSimplex(final double[][] referenceSimplex) {
+        this(referenceSimplex, DEFAULT_RHO, DEFAULT_KHI, DEFAULT_GAMMA, DEFAULT_SIGMA);
+    }
+
+    /**
+     * Build a Nelder-Mead simplex with specified coefficients.
+     *
+     * @param referenceSimplex Reference simplex. See
+     * {@link AbstractSimplex#AbstractSimplex(double[][])}.
+     * @param rho Reflection coefficient.
+     * @param khi Expansion coefficient.
+     * @param gamma Contraction coefficient.
+     * @param sigma Shrinkage coefficient.
+     * @throws org.apache.commons.math3.exception.NotStrictlyPositiveException
+     * if the reference simplex does not contain at least one point.
+     * @throws org.apache.commons.math3.exception.DimensionMismatchException
+     * if there is a dimension mismatch in the reference simplex.
+     */
+    public NelderMeadSimplex(final double[][] referenceSimplex,
+                             final double rho, final double khi,
+                             final double gamma, final double sigma) {
+        super(referenceSimplex);
+
+        this.rho = rho;
+        this.khi = khi;
+        this.gamma = gamma;
+        this.sigma = sigma;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void iterate(final MultivariateFunction evaluationFunction,
+                        final Comparator<PointValuePair> comparator) {
+        // The simplex has n + 1 points if dimension is n.
+        final int n = getDimension();
+
+        // Interesting values.
+        final PointValuePair best = getPoint(0);
+        final PointValuePair secondBest = getPoint(n - 1);
+        final PointValuePair worst = getPoint(n);
+        final double[] xWorst = worst.getPointRef();
+
+        // Compute the centroid of the best vertices (dismissing the worst
+        // point at index n).
+        final double[] centroid = new double[n];
+        for (int i = 0; i < n; i++) {
+            final double[] x = getPoint(i).getPointRef();
+            for (int j = 0; j < n; j++) {
+                centroid[j] += x[j];
+            }
+        }
+        final double scaling = 1.0 / n;
+        for (int j = 0; j < n; j++) {
+            centroid[j] *= scaling;
+        }
+
+        // compute the reflection point
+        final double[] xR = new double[n];
+        for (int j = 0; j < n; j++) {
+            xR[j] = centroid[j] + rho * (centroid[j] - xWorst[j]);
+        }
+        final PointValuePair reflected
+            = new PointValuePair(xR, evaluationFunction.value(xR), false);
+
+        if (comparator.compare(best, reflected) <= 0 &&
+            comparator.compare(reflected, secondBest) < 0) {
+            // Accept the reflected point.
+            replaceWorstPoint(reflected, comparator);
+        } else if (comparator.compare(reflected, best) < 0) {
+            // Compute the expansion point.
+            final double[] xE = new double[n];
+            for (int j = 0; j < n; j++) {
+                xE[j] = centroid[j] + khi * (xR[j] - centroid[j]);
+            }
+            final PointValuePair expanded
+                = new PointValuePair(xE, evaluationFunction.value(xE), false);
+
+            if (comparator.compare(expanded, reflected) < 0) {
+                // Accept the expansion point.
+                replaceWorstPoint(expanded, comparator);
+            } else {
+                // Accept the reflected point.
+                replaceWorstPoint(reflected, comparator);
+            }
+        } else {
+            if (comparator.compare(reflected, worst) < 0) {
+                // Perform an outside contraction.
+                final double[] xC = new double[n];
+                for (int j = 0; j < n; j++) {
+                    xC[j] = centroid[j] + gamma * (xR[j] - centroid[j]);
+                }
+                final PointValuePair outContracted
+                    = new PointValuePair(xC, evaluationFunction.value(xC), false);
+                if (comparator.compare(outContracted, reflected) <= 0) {
+                    // Accept the contraction point.
+                    replaceWorstPoint(outContracted, comparator);
+                    return;
+                }
+            } else {
+                // Perform an inside contraction.
+                final double[] xC = new double[n];
+                for (int j = 0; j < n; j++) {
+                    xC[j] = centroid[j] - gamma * (centroid[j] - xWorst[j]);
+                }
+                final PointValuePair inContracted
+                    = new PointValuePair(xC, evaluationFunction.value(xC), false);
+
+                if (comparator.compare(inContracted, worst) < 0) {
+                    // Accept the contraction point.
+                    replaceWorstPoint(inContracted, comparator);
+                    return;
+                }
+            }
+
+            // Perform a shrink.
+            final double[] xSmallest = getPoint(0).getPointRef();
+            for (int i = 1; i <= n; i++) {
+                final double[] x = getPoint(i).getPoint();
+                for (int j = 0; j < n; j++) {
+                    x[j] = xSmallest[j] + sigma * (x[j] - xSmallest[j]);
+                }
+                setPoint(i, new PointValuePair(x, Double.NaN, false));
+            }
+            evaluate(evaluationFunction, comparator);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/direct/PowellOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/direct/PowellOptimizer.java
new file mode 100644
index 0000000..8f5dd2b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/direct/PowellOptimizer.java
@@ -0,0 +1,353 @@
+/*
+ * 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.math3.optimization.direct;
+
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.MultivariateFunction;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.optimization.GoalType;
+import org.apache.commons.math3.optimization.PointValuePair;
+import org.apache.commons.math3.optimization.ConvergenceChecker;
+import org.apache.commons.math3.optimization.MultivariateOptimizer;
+import org.apache.commons.math3.optimization.univariate.BracketFinder;
+import org.apache.commons.math3.optimization.univariate.BrentOptimizer;
+import org.apache.commons.math3.optimization.univariate.UnivariatePointValuePair;
+import org.apache.commons.math3.optimization.univariate.SimpleUnivariateValueChecker;
+
+/**
+ * Powell algorithm.
+ * This code is translated and adapted from the Python version of this
+ * algorithm (as implemented in module {@code optimize.py} v0.5 of
+ * <em>SciPy</em>).
+ * <br/>
+ * The default stopping criterion is based on the differences of the
+ * function value between two successive iterations. It is however possible
+ * to define a custom convergence checker that might terminate the algorithm
+ * earlier.
+ * <br/>
+ * The internal line search optimizer is a {@link BrentOptimizer} with a
+ * convergence checker set to {@link SimpleUnivariateValueChecker}.
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 2.2
+ */
+@Deprecated
+public class PowellOptimizer
+    extends BaseAbstractMultivariateOptimizer<MultivariateFunction>
+    implements MultivariateOptimizer {
+    /**
+     * Minimum relative tolerance.
+     */
+    private static final double MIN_RELATIVE_TOLERANCE = 2 * FastMath.ulp(1d);
+    /**
+     * Relative threshold.
+     */
+    private final double relativeThreshold;
+    /**
+     * Absolute threshold.
+     */
+    private final double absoluteThreshold;
+    /**
+     * Line search.
+     */
+    private final LineSearch line;
+
+    /**
+     * This constructor allows to specify a user-defined convergence checker,
+     * in addition to the parameters that control the default convergence
+     * checking procedure.
+     * <br/>
+     * The internal line search tolerances are set to the square-root of their
+     * corresponding value in the multivariate optimizer.
+     *
+     * @param rel Relative threshold.
+     * @param abs Absolute threshold.
+     * @param checker Convergence checker.
+     * @throws NotStrictlyPositiveException if {@code abs <= 0}.
+     * @throws NumberIsTooSmallException if {@code rel < 2 * Math.ulp(1d)}.
+     */
+    public PowellOptimizer(double rel,
+                           double abs,
+                           ConvergenceChecker<PointValuePair> checker) {
+        this(rel, abs, FastMath.sqrt(rel), FastMath.sqrt(abs), checker);
+    }
+
+    /**
+     * This constructor allows to specify a user-defined convergence checker,
+     * in addition to the parameters that control the default convergence
+     * checking procedure and the line search tolerances.
+     *
+     * @param rel Relative threshold for this optimizer.
+     * @param abs Absolute threshold for this optimizer.
+     * @param lineRel Relative threshold for the internal line search optimizer.
+     * @param lineAbs Absolute threshold for the internal line search optimizer.
+     * @param checker Convergence checker.
+     * @throws NotStrictlyPositiveException if {@code abs <= 0}.
+     * @throws NumberIsTooSmallException if {@code rel < 2 * Math.ulp(1d)}.
+     */
+    public PowellOptimizer(double rel,
+                           double abs,
+                           double lineRel,
+                           double lineAbs,
+                           ConvergenceChecker<PointValuePair> checker) {
+        super(checker);
+
+        if (rel < MIN_RELATIVE_TOLERANCE) {
+            throw new NumberIsTooSmallException(rel, MIN_RELATIVE_TOLERANCE, true);
+        }
+        if (abs <= 0) {
+            throw new NotStrictlyPositiveException(abs);
+        }
+        relativeThreshold = rel;
+        absoluteThreshold = abs;
+
+        // Create the line search optimizer.
+        line = new LineSearch(lineRel,
+                              lineAbs);
+    }
+
+    /**
+     * The parameters control the default convergence checking procedure.
+     * <br/>
+     * The internal line search tolerances are set to the square-root of their
+     * corresponding value in the multivariate optimizer.
+     *
+     * @param rel Relative threshold.
+     * @param abs Absolute threshold.
+     * @throws NotStrictlyPositiveException if {@code abs <= 0}.
+     * @throws NumberIsTooSmallException if {@code rel < 2 * Math.ulp(1d)}.
+     */
+    public PowellOptimizer(double rel,
+                           double abs) {
+        this(rel, abs, null);
+    }
+
+    /**
+     * Builds an instance with the default convergence checking procedure.
+     *
+     * @param rel Relative threshold.
+     * @param abs Absolute threshold.
+     * @param lineRel Relative threshold for the internal line search optimizer.
+     * @param lineAbs Absolute threshold for the internal line search optimizer.
+     * @throws NotStrictlyPositiveException if {@code abs <= 0}.
+     * @throws NumberIsTooSmallException if {@code rel < 2 * Math.ulp(1d)}.
+     * @since 3.1
+     */
+    public PowellOptimizer(double rel,
+                           double abs,
+                           double lineRel,
+                           double lineAbs) {
+        this(rel, abs, lineRel, lineAbs, null);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected PointValuePair doOptimize() {
+        final GoalType goal = getGoalType();
+        final double[] guess = getStartPoint();
+        final int n = guess.length;
+
+        final double[][] direc = new double[n][n];
+        for (int i = 0; i < n; i++) {
+            direc[i][i] = 1;
+        }
+
+        final ConvergenceChecker<PointValuePair> checker
+            = getConvergenceChecker();
+
+        double[] x = guess;
+        double fVal = computeObjectiveValue(x);
+        double[] x1 = x.clone();
+        int iter = 0;
+        while (true) {
+            ++iter;
+
+            double fX = fVal;
+            double fX2 = 0;
+            double delta = 0;
+            int bigInd = 0;
+            double alphaMin = 0;
+
+            for (int i = 0; i < n; i++) {
+                final double[] d = MathArrays.copyOf(direc[i]);
+
+                fX2 = fVal;
+
+                final UnivariatePointValuePair optimum = line.search(x, d);
+                fVal = optimum.getValue();
+                alphaMin = optimum.getPoint();
+                final double[][] result = newPointAndDirection(x, d, alphaMin);
+                x = result[0];
+
+                if ((fX2 - fVal) > delta) {
+                    delta = fX2 - fVal;
+                    bigInd = i;
+                }
+            }
+
+            // Default convergence check.
+            boolean stop = 2 * (fX - fVal) <=
+                (relativeThreshold * (FastMath.abs(fX) + FastMath.abs(fVal)) +
+                 absoluteThreshold);
+
+            final PointValuePair previous = new PointValuePair(x1, fX);
+            final PointValuePair current = new PointValuePair(x, fVal);
+            if (!stop && checker != null) {
+                stop = checker.converged(iter, previous, current);
+            }
+            if (stop) {
+                if (goal == GoalType.MINIMIZE) {
+                    return (fVal < fX) ? current : previous;
+                } else {
+                    return (fVal > fX) ? current : previous;
+                }
+            }
+
+            final double[] d = new double[n];
+            final double[] x2 = new double[n];
+            for (int i = 0; i < n; i++) {
+                d[i] = x[i] - x1[i];
+                x2[i] = 2 * x[i] - x1[i];
+            }
+
+            x1 = x.clone();
+            fX2 = computeObjectiveValue(x2);
+
+            if (fX > fX2) {
+                double t = 2 * (fX + fX2 - 2 * fVal);
+                double temp = fX - fVal - delta;
+                t *= temp * temp;
+                temp = fX - fX2;
+                t -= delta * temp * temp;
+
+                if (t < 0.0) {
+                    final UnivariatePointValuePair optimum = line.search(x, d);
+                    fVal = optimum.getValue();
+                    alphaMin = optimum.getPoint();
+                    final double[][] result = newPointAndDirection(x, d, alphaMin);
+                    x = result[0];
+
+                    final int lastInd = n - 1;
+                    direc[bigInd] = direc[lastInd];
+                    direc[lastInd] = result[1];
+                }
+            }
+        }
+    }
+
+    /**
+     * Compute a new point (in the original space) and a new direction
+     * vector, resulting from the line search.
+     *
+     * @param p Point used in the line search.
+     * @param d Direction used in the line search.
+     * @param optimum Optimum found by the line search.
+     * @return a 2-element array containing the new point (at index 0) and
+     * the new direction (at index 1).
+     */
+    private double[][] newPointAndDirection(double[] p,
+                                            double[] d,
+                                            double optimum) {
+        final int n = p.length;
+        final double[] nP = new double[n];
+        final double[] nD = new double[n];
+        for (int i = 0; i < n; i++) {
+            nD[i] = d[i] * optimum;
+            nP[i] = p[i] + nD[i];
+        }
+
+        final double[][] result = new double[2][];
+        result[0] = nP;
+        result[1] = nD;
+
+        return result;
+    }
+
+    /**
+     * Class for finding the minimum of the objective function along a given
+     * direction.
+     */
+    private class LineSearch extends BrentOptimizer {
+        /**
+         * Value that will pass the precondition check for {@link BrentOptimizer}
+         * but will not pass the convergence check, so that the custom checker
+         * will always decide when to stop the line search.
+         */
+        private static final double REL_TOL_UNUSED = 1e-15;
+        /**
+         * Value that will pass the precondition check for {@link BrentOptimizer}
+         * but will not pass the convergence check, so that the custom checker
+         * will always decide when to stop the line search.
+         */
+        private static final double ABS_TOL_UNUSED = Double.MIN_VALUE;
+        /**
+         * Automatic bracketing.
+         */
+        private final BracketFinder bracket = new BracketFinder();
+
+        /**
+         * The "BrentOptimizer" default stopping criterion uses the tolerances
+         * to check the domain (point) values, not the function values.
+         * We thus create a custom checker to use function values.
+         *
+         * @param rel Relative threshold.
+         * @param abs Absolute threshold.
+         */
+        LineSearch(double rel,
+                   double abs) {
+            super(REL_TOL_UNUSED,
+                  ABS_TOL_UNUSED,
+                  new SimpleUnivariateValueChecker(rel, abs));
+        }
+
+        /**
+         * Find the minimum of the function {@code f(p + alpha * d)}.
+         *
+         * @param p Starting point.
+         * @param d Search direction.
+         * @return the optimum.
+         * @throws org.apache.commons.math3.exception.TooManyEvaluationsException
+         * if the number of evaluations is exceeded.
+         */
+        public UnivariatePointValuePair search(final double[] p, final double[] d) {
+            final int n = p.length;
+            final UnivariateFunction f = new UnivariateFunction() {
+                    /** {@inheritDoc} */
+                    public double value(double alpha) {
+                        final double[] x = new double[n];
+                        for (int i = 0; i < n; i++) {
+                            x[i] = p[i] + alpha * d[i];
+                        }
+                        final double obj = PowellOptimizer.this.computeObjectiveValue(x);
+                        return obj;
+                    }
+                };
+
+            final GoalType goal = PowellOptimizer.this.getGoalType();
+            bracket.search(f, goal, 0, 1);
+            // Passing "MAX_VALUE" as a dummy value because it is the enclosing
+            // class that counts the number of evaluations (and will eventually
+            // generate the exception).
+            return optimize(Integer.MAX_VALUE, f, goal,
+                            bracket.getLo(), bracket.getHi(), bracket.getMid());
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/direct/SimplexOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/direct/SimplexOptimizer.java
new file mode 100644
index 0000000..8136704
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/direct/SimplexOptimizer.java
@@ -0,0 +1,235 @@
+/*
+ * 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.math3.optimization.direct;
+
+import java.util.Comparator;
+
+import org.apache.commons.math3.analysis.MultivariateFunction;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.optimization.GoalType;
+import org.apache.commons.math3.optimization.ConvergenceChecker;
+import org.apache.commons.math3.optimization.PointValuePair;
+import org.apache.commons.math3.optimization.SimpleValueChecker;
+import org.apache.commons.math3.optimization.MultivariateOptimizer;
+import org.apache.commons.math3.optimization.OptimizationData;
+
+/**
+ * This class implements simplex-based direct search optimization.
+ *
+ * <p>
+ *  Direct search methods only use objective function values, they do
+ *  not need derivatives and don't either try to compute approximation
+ *  of the derivatives. According to a 1996 paper by Margaret H. Wright
+ *  (<a href="http://cm.bell-labs.com/cm/cs/doc/96/4-02.ps.gz">Direct
+ *  Search Methods: Once Scorned, Now Respectable</a>), they are used
+ *  when either the computation of the derivative is impossible (noisy
+ *  functions, unpredictable discontinuities) or difficult (complexity,
+ *  computation cost). In the first cases, rather than an optimum, a
+ *  <em>not too bad</em> point is desired. In the latter cases, an
+ *  optimum is desired but cannot be reasonably found. In all cases
+ *  direct search methods can be useful.
+ * </p>
+ * <p>
+ *  Simplex-based direct search methods are based on comparison of
+ *  the objective function values at the vertices of a simplex (which is a
+ *  set of n+1 points in dimension n) that is updated by the algorithms
+ *  steps.
+ * <p>
+ * <p>
+ *  The {@link #setSimplex(AbstractSimplex) setSimplex} method <em>must</em>
+ *  be called prior to calling the {@code optimize} method.
+ * </p>
+ * <p>
+ *  Each call to {@link #optimize(int,MultivariateFunction,GoalType,double[])
+ *  optimize} will re-use the start configuration of the current simplex and
+ *  move it such that its first vertex is at the provided start point of the
+ *  optimization. If the {@code optimize} method is called to solve a different
+ *  problem and the number of parameters change, the simplex must be
+ *  re-initialized to one with the appropriate dimensions.
+ * </p>
+ * <p>
+ *  Convergence is checked by providing the <em>worst</em> points of
+ *  previous and current simplex to the convergence checker, not the best
+ *  ones.
+ * </p>
+ * <p>
+ * This simplex optimizer implementation does not directly support constrained
+ * optimization with simple bounds, so for such optimizations, either a more
+ * dedicated method must be used like {@link CMAESOptimizer} or {@link
+ * BOBYQAOptimizer}, or the optimized method must be wrapped in an adapter like
+ * {@link MultivariateFunctionMappingAdapter} or {@link
+ * MultivariateFunctionPenaltyAdapter}.
+ * </p>
+ *
+ * @see AbstractSimplex
+ * @see MultivariateFunctionMappingAdapter
+ * @see MultivariateFunctionPenaltyAdapter
+ * @see CMAESOptimizer
+ * @see BOBYQAOptimizer
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.0
+ */
+@SuppressWarnings("boxing") // deprecated anyway
+@Deprecated
+public class SimplexOptimizer
+    extends BaseAbstractMultivariateOptimizer<MultivariateFunction>
+    implements MultivariateOptimizer {
+    /** Simplex. */
+    private AbstractSimplex simplex;
+
+    /**
+     * Constructor using a default {@link SimpleValueChecker convergence
+     * checker}.
+     * @deprecated See {@link SimpleValueChecker#SimpleValueChecker()}
+     */
+    @Deprecated
+    public SimplexOptimizer() {
+        this(new SimpleValueChecker());
+    }
+
+    /**
+     * @param checker Convergence checker.
+     */
+    public SimplexOptimizer(ConvergenceChecker<PointValuePair> checker) {
+        super(checker);
+    }
+
+    /**
+     * @param rel Relative threshold.
+     * @param abs Absolute threshold.
+     */
+    public SimplexOptimizer(double rel, double abs) {
+        this(new SimpleValueChecker(rel, abs));
+    }
+
+    /**
+     * Set the simplex algorithm.
+     *
+     * @param simplex Simplex.
+     * @deprecated As of 3.1. The initial simplex can now be passed as an
+     * argument of the {@link #optimize(int,MultivariateFunction,GoalType,OptimizationData[])}
+     * method.
+     */
+    @Deprecated
+    public void setSimplex(AbstractSimplex simplex) {
+        parseOptimizationData(simplex);
+    }
+
+    /**
+     * Optimize an objective function.
+     *
+     * @param maxEval Allowed number of evaluations of the objective function.
+     * @param f Objective function.
+     * @param goalType Optimization type.
+     * @param optData Optimization data. The following data will be looked for:
+     * <ul>
+     *  <li>{@link org.apache.commons.math3.optimization.InitialGuess InitialGuess}</li>
+     *  <li>{@link AbstractSimplex}</li>
+     * </ul>
+     * @return the point/value pair giving the optimal value for objective
+     * function.
+     */
+    @Override
+    protected PointValuePair optimizeInternal(int maxEval, MultivariateFunction f,
+                                              GoalType goalType,
+                                              OptimizationData... optData) {
+        // Scan "optData" for the input specific to this optimizer.
+        parseOptimizationData(optData);
+
+        // The parent's method will retrieve the common parameters from
+        // "optData" and call "doOptimize".
+        return super.optimizeInternal(maxEval, f, goalType, optData);
+    }
+
+    /**
+     * Scans the list of (required and optional) optimization data that
+     * characterize the problem.
+     *
+     * @param optData Optimization data. The following data will be looked for:
+     * <ul>
+     *  <li>{@link AbstractSimplex}</li>
+     * </ul>
+     */
+    private void parseOptimizationData(OptimizationData... optData) {
+        // The existing values (as set by the previous call) are reused if
+        // not provided in the argument list.
+        for (OptimizationData data : optData) {
+            if (data instanceof AbstractSimplex) {
+                simplex = (AbstractSimplex) data;
+                continue;
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected PointValuePair doOptimize() {
+        if (simplex == null) {
+            throw new NullArgumentException();
+        }
+
+        // Indirect call to "computeObjectiveValue" in order to update the
+        // evaluations counter.
+        final MultivariateFunction evalFunc
+            = new MultivariateFunction() {
+                /** {@inheritDoc} */
+                public double value(double[] point) {
+                    return computeObjectiveValue(point);
+                }
+            };
+
+        final boolean isMinim = getGoalType() == GoalType.MINIMIZE;
+        final Comparator<PointValuePair> comparator
+            = new Comparator<PointValuePair>() {
+            /** {@inheritDoc} */
+            public int compare(final PointValuePair o1,
+                               final PointValuePair o2) {
+                final double v1 = o1.getValue();
+                final double v2 = o2.getValue();
+                return isMinim ? Double.compare(v1, v2) : Double.compare(v2, v1);
+            }
+        };
+
+        // Initialize search.
+        simplex.build(getStartPoint());
+        simplex.evaluate(evalFunc, comparator);
+
+        PointValuePair[] previous = null;
+        int iteration = 0;
+        final ConvergenceChecker<PointValuePair> checker = getConvergenceChecker();
+        while (true) {
+            if (iteration > 0) {
+                boolean converged = true;
+                for (int i = 0; i < simplex.getSize(); i++) {
+                    PointValuePair prev = previous[i];
+                    converged = converged &&
+                        checker.converged(iteration, prev, simplex.getPoint(i));
+                }
+                if (converged) {
+                    // We have found an optimum.
+                    return simplex.getPoint(0);
+                }
+            }
+
+            // We still need to search.
+            previous = simplex.getPoints();
+            simplex.iterate(evalFunc, comparator);
+            ++iteration;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/direct/package-info.java b/src/main/java/org/apache/commons/math3/optimization/direct/package-info.java
new file mode 100644
index 0000000..a587bcf
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/direct/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * <p>
+ * This package provides optimization algorithms that don't require derivatives.
+ * </p>
+ *
+ */
+package org.apache.commons.math3.optimization.direct;
diff --git a/src/main/java/org/apache/commons/math3/optimization/fitting/CurveFitter.java b/src/main/java/org/apache/commons/math3/optimization/fitting/CurveFitter.java
new file mode 100644
index 0000000..26e39f5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/fitting/CurveFitter.java
@@ -0,0 +1,299 @@
+/*
+ * 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.math3.optimization.fitting;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.math3.analysis.DifferentiableMultivariateVectorFunction;
+import org.apache.commons.math3.analysis.MultivariateMatrixFunction;
+import org.apache.commons.math3.analysis.ParametricUnivariateFunction;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.MultivariateDifferentiableVectorFunction;
+import org.apache.commons.math3.optimization.DifferentiableMultivariateVectorOptimizer;
+import org.apache.commons.math3.optimization.MultivariateDifferentiableVectorOptimizer;
+import org.apache.commons.math3.optimization.PointVectorValuePair;
+
+/** Fitter for parametric univariate real functions y = f(x).
+ * <br/>
+ * When a univariate real function y = f(x) does depend on some
+ * unknown parameters p<sub>0</sub>, p<sub>1</sub> ... p<sub>n-1</sub>,
+ * this class can be used to find these parameters. It does this
+ * by <em>fitting</em> the curve so it remains very close to a set of
+ * observed points (x<sub>0</sub>, y<sub>0</sub>), (x<sub>1</sub>,
+ * y<sub>1</sub>) ... (x<sub>k-1</sub>, y<sub>k-1</sub>). This fitting
+ * is done by finding the parameters values that minimizes the objective
+ * function &sum;(y<sub>i</sub>-f(x<sub>i</sub>))<sup>2</sup>. This is
+ * really a least squares problem.
+ *
+ * @param <T> Function to use for the fit.
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 2.0
+ */
+@Deprecated
+public class CurveFitter<T extends ParametricUnivariateFunction> {
+
+    /** Optimizer to use for the fitting.
+     * @deprecated as of 3.1 replaced by {@link #optimizer}
+     */
+    @Deprecated
+    private final DifferentiableMultivariateVectorOptimizer oldOptimizer;
+
+    /** Optimizer to use for the fitting. */
+    private final MultivariateDifferentiableVectorOptimizer optimizer;
+
+    /** Observed points. */
+    private final List<WeightedObservedPoint> observations;
+
+    /** Simple constructor.
+     * @param optimizer optimizer to use for the fitting
+     * @deprecated as of 3.1 replaced by {@link #CurveFitter(MultivariateDifferentiableVectorOptimizer)}
+     */
+    @Deprecated
+    public CurveFitter(final DifferentiableMultivariateVectorOptimizer optimizer) {
+        this.oldOptimizer = optimizer;
+        this.optimizer    = null;
+        observations      = new ArrayList<WeightedObservedPoint>();
+    }
+
+    /** Simple constructor.
+     * @param optimizer optimizer to use for the fitting
+     * @since 3.1
+     */
+    public CurveFitter(final MultivariateDifferentiableVectorOptimizer optimizer) {
+        this.oldOptimizer = null;
+        this.optimizer    = optimizer;
+        observations      = new ArrayList<WeightedObservedPoint>();
+    }
+
+    /** Add an observed (x,y) point to the sample with unit weight.
+     * <p>Calling this method is equivalent to call
+     * {@code addObservedPoint(1.0, x, y)}.</p>
+     * @param x abscissa of the point
+     * @param y observed value of the point at x, after fitting we should
+     * have f(x) as close as possible to this value
+     * @see #addObservedPoint(double, double, double)
+     * @see #addObservedPoint(WeightedObservedPoint)
+     * @see #getObservations()
+     */
+    public void addObservedPoint(double x, double y) {
+        addObservedPoint(1.0, x, y);
+    }
+
+    /** Add an observed weighted (x,y) point to the sample.
+     * @param weight weight of the observed point in the fit
+     * @param x abscissa of the point
+     * @param y observed value of the point at x, after fitting we should
+     * have f(x) as close as possible to this value
+     * @see #addObservedPoint(double, double)
+     * @see #addObservedPoint(WeightedObservedPoint)
+     * @see #getObservations()
+     */
+    public void addObservedPoint(double weight, double x, double y) {
+        observations.add(new WeightedObservedPoint(weight, x, y));
+    }
+
+    /** Add an observed weighted (x,y) point to the sample.
+     * @param observed observed point to add
+     * @see #addObservedPoint(double, double)
+     * @see #addObservedPoint(double, double, double)
+     * @see #getObservations()
+     */
+    public void addObservedPoint(WeightedObservedPoint observed) {
+        observations.add(observed);
+    }
+
+    /** Get the observed points.
+     * @return observed points
+     * @see #addObservedPoint(double, double)
+     * @see #addObservedPoint(double, double, double)
+     * @see #addObservedPoint(WeightedObservedPoint)
+     */
+    public WeightedObservedPoint[] getObservations() {
+        return observations.toArray(new WeightedObservedPoint[observations.size()]);
+    }
+
+    /**
+     * Remove all observations.
+     */
+    public void clearObservations() {
+        observations.clear();
+    }
+
+    /**
+     * Fit a curve.
+     * This method compute the coefficients of the curve that best
+     * fit the sample of observed points previously given through calls
+     * to the {@link #addObservedPoint(WeightedObservedPoint)
+     * addObservedPoint} method.
+     *
+     * @param f parametric function to fit.
+     * @param initialGuess first guess of the function parameters.
+     * @return the fitted parameters.
+     * @throws org.apache.commons.math3.exception.DimensionMismatchException
+     * if the start point dimension is wrong.
+     */
+    public double[] fit(T f, final double[] initialGuess) {
+        return fit(Integer.MAX_VALUE, f, initialGuess);
+    }
+
+    /**
+     * Fit a curve.
+     * This method compute the coefficients of the curve that best
+     * fit the sample of observed points previously given through calls
+     * to the {@link #addObservedPoint(WeightedObservedPoint)
+     * addObservedPoint} method.
+     *
+     * @param f parametric function to fit.
+     * @param initialGuess first guess of the function parameters.
+     * @param maxEval Maximum number of function evaluations.
+     * @return the fitted parameters.
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException
+     * if the number of allowed evaluations is exceeded.
+     * @throws org.apache.commons.math3.exception.DimensionMismatchException
+     * if the start point dimension is wrong.
+     * @since 3.0
+     */
+    public double[] fit(int maxEval, T f,
+                        final double[] initialGuess) {
+        // prepare least squares problem
+        double[] target  = new double[observations.size()];
+        double[] weights = new double[observations.size()];
+        int i = 0;
+        for (WeightedObservedPoint point : observations) {
+            target[i]  = point.getY();
+            weights[i] = point.getWeight();
+            ++i;
+        }
+
+        // perform the fit
+        final PointVectorValuePair optimum;
+        if (optimizer == null) {
+            // to be removed in 4.0
+            optimum = oldOptimizer.optimize(maxEval, new OldTheoreticalValuesFunction(f),
+                                            target, weights, initialGuess);
+        } else {
+            optimum = optimizer.optimize(maxEval, new TheoreticalValuesFunction(f),
+                                         target, weights, initialGuess);
+        }
+
+        // extract the coefficients
+        return optimum.getPointRef();
+    }
+
+    /** Vectorial function computing function theoretical values. */
+    @Deprecated
+    private class OldTheoreticalValuesFunction
+        implements DifferentiableMultivariateVectorFunction {
+        /** Function to fit. */
+        private final ParametricUnivariateFunction f;
+
+        /** Simple constructor.
+         * @param f function to fit.
+         */
+        OldTheoreticalValuesFunction(final ParametricUnivariateFunction f) {
+            this.f = f;
+        }
+
+        /** {@inheritDoc} */
+        public MultivariateMatrixFunction jacobian() {
+            return new MultivariateMatrixFunction() {
+                /** {@inheritDoc} */
+                public double[][] value(double[] point) {
+                    final double[][] jacobian = new double[observations.size()][];
+
+                    int i = 0;
+                    for (WeightedObservedPoint observed : observations) {
+                        jacobian[i++] = f.gradient(observed.getX(), point);
+                    }
+
+                    return jacobian;
+                }
+            };
+        }
+
+        /** {@inheritDoc} */
+        public double[] value(double[] point) {
+            // compute the residuals
+            final double[] values = new double[observations.size()];
+            int i = 0;
+            for (WeightedObservedPoint observed : observations) {
+                values[i++] = f.value(observed.getX(), point);
+            }
+
+            return values;
+        }
+    }
+
+    /** Vectorial function computing function theoretical values. */
+    private class TheoreticalValuesFunction implements MultivariateDifferentiableVectorFunction {
+
+        /** Function to fit. */
+        private final ParametricUnivariateFunction f;
+
+        /** Simple constructor.
+         * @param f function to fit.
+         */
+        TheoreticalValuesFunction(final ParametricUnivariateFunction f) {
+            this.f = f;
+        }
+
+        /** {@inheritDoc} */
+        public double[] value(double[] point) {
+            // compute the residuals
+            final double[] values = new double[observations.size()];
+            int i = 0;
+            for (WeightedObservedPoint observed : observations) {
+                values[i++] = f.value(observed.getX(), point);
+            }
+
+            return values;
+        }
+
+        /** {@inheritDoc} */
+        public DerivativeStructure[] value(DerivativeStructure[] point) {
+
+            // extract parameters
+            final double[] parameters = new double[point.length];
+            for (int k = 0; k < point.length; ++k) {
+                parameters[k] = point[k].getValue();
+            }
+
+            // compute the residuals
+            final DerivativeStructure[] values = new DerivativeStructure[observations.size()];
+            int i = 0;
+            for (WeightedObservedPoint observed : observations) {
+
+                // build the DerivativeStructure by adding first the value as a constant
+                // and then adding derivatives
+                DerivativeStructure vi = new DerivativeStructure(point.length, 1, f.value(observed.getX(), parameters));
+                for (int k = 0; k < point.length; ++k) {
+                    vi = vi.add(new DerivativeStructure(point.length, 1, k, 0.0));
+                }
+
+                values[i++] = vi;
+
+            }
+
+            return values;
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/fitting/GaussianFitter.java b/src/main/java/org/apache/commons/math3/optimization/fitting/GaussianFitter.java
new file mode 100644
index 0000000..375f12e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/fitting/GaussianFitter.java
@@ -0,0 +1,371 @@
+/*
+ * 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.math3.optimization.fitting;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+import org.apache.commons.math3.analysis.function.Gaussian;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.ZeroException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.optimization.DifferentiableMultivariateVectorOptimizer;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Fits points to a {@link
+ * org.apache.commons.math3.analysis.function.Gaussian.Parametric Gaussian} function.
+ * <p>
+ * Usage example:
+ * <pre>
+ *   GaussianFitter fitter = new GaussianFitter(
+ *     new LevenbergMarquardtOptimizer());
+ *   fitter.addObservedPoint(4.0254623,  531026.0);
+ *   fitter.addObservedPoint(4.03128248, 984167.0);
+ *   fitter.addObservedPoint(4.03839603, 1887233.0);
+ *   fitter.addObservedPoint(4.04421621, 2687152.0);
+ *   fitter.addObservedPoint(4.05132976, 3461228.0);
+ *   fitter.addObservedPoint(4.05326982, 3580526.0);
+ *   fitter.addObservedPoint(4.05779662, 3439750.0);
+ *   fitter.addObservedPoint(4.0636168,  2877648.0);
+ *   fitter.addObservedPoint(4.06943698, 2175960.0);
+ *   fitter.addObservedPoint(4.07525716, 1447024.0);
+ *   fitter.addObservedPoint(4.08237071, 717104.0);
+ *   fitter.addObservedPoint(4.08366408, 620014.0);
+ *   double[] parameters = fitter.fit();
+ * </pre>
+ *
+ * @since 2.2
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ */
+@Deprecated
+public class GaussianFitter extends CurveFitter<Gaussian.Parametric> {
+    /**
+     * Constructs an instance using the specified optimizer.
+     *
+     * @param optimizer Optimizer to use for the fitting.
+     */
+    public GaussianFitter(DifferentiableMultivariateVectorOptimizer optimizer) {
+        super(optimizer);
+    }
+
+    /**
+     * Fits a Gaussian function to the observed points.
+     *
+     * @param initialGuess First guess values in the following order:
+     * <ul>
+     *  <li>Norm</li>
+     *  <li>Mean</li>
+     *  <li>Sigma</li>
+     * </ul>
+     * @return the parameters of the Gaussian function that best fits the
+     * observed points (in the same order as above).
+     * @since 3.0
+     */
+    public double[] fit(double[] initialGuess) {
+        final Gaussian.Parametric f = new Gaussian.Parametric() {
+                /** {@inheritDoc} */
+                @Override
+                public double value(double x, double ... p) {
+                    double v = Double.POSITIVE_INFINITY;
+                    try {
+                        v = super.value(x, p);
+                    } catch (NotStrictlyPositiveException e) { // NOPMD
+                        // Do nothing.
+                    }
+                    return v;
+                }
+
+                /** {@inheritDoc} */
+                @Override
+                public double[] gradient(double x, double ... p) {
+                    double[] v = { Double.POSITIVE_INFINITY,
+                                   Double.POSITIVE_INFINITY,
+                                   Double.POSITIVE_INFINITY };
+                    try {
+                        v = super.gradient(x, p);
+                    } catch (NotStrictlyPositiveException e) { // NOPMD
+                        // Do nothing.
+                    }
+                    return v;
+                }
+            };
+
+        return fit(f, initialGuess);
+    }
+
+    /**
+     * Fits a Gaussian function to the observed points.
+     *
+     * @return the parameters of the Gaussian function that best fits the
+     * observed points (in the same order as above).
+     */
+    public double[] fit() {
+        final double[] guess = (new ParameterGuesser(getObservations())).guess();
+        return fit(guess);
+    }
+
+    /**
+     * Guesses the parameters {@code norm}, {@code mean}, and {@code sigma}
+     * of a {@link org.apache.commons.math3.analysis.function.Gaussian.Parametric}
+     * based on the specified observed points.
+     */
+    public static class ParameterGuesser {
+        /** Normalization factor. */
+        private final double norm;
+        /** Mean. */
+        private final double mean;
+        /** Standard deviation. */
+        private final double sigma;
+
+        /**
+         * Constructs instance with the specified observed points.
+         *
+         * @param observations Observed points from which to guess the
+         * parameters of the Gaussian.
+         * @throws NullArgumentException if {@code observations} is
+         * {@code null}.
+         * @throws NumberIsTooSmallException if there are less than 3
+         * observations.
+         */
+        public ParameterGuesser(WeightedObservedPoint[] observations) {
+            if (observations == null) {
+                throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY);
+            }
+            if (observations.length < 3) {
+                throw new NumberIsTooSmallException(observations.length, 3, true);
+            }
+
+            final WeightedObservedPoint[] sorted = sortObservations(observations);
+            final double[] params = basicGuess(sorted);
+
+            norm = params[0];
+            mean = params[1];
+            sigma = params[2];
+        }
+
+        /**
+         * Gets an estimation of the parameters.
+         *
+         * @return the guessed parameters, in the following order:
+         * <ul>
+         *  <li>Normalization factor</li>
+         *  <li>Mean</li>
+         *  <li>Standard deviation</li>
+         * </ul>
+         */
+        public double[] guess() {
+            return new double[] { norm, mean, sigma };
+        }
+
+        /**
+         * Sort the observations.
+         *
+         * @param unsorted Input observations.
+         * @return the input observations, sorted.
+         */
+        private WeightedObservedPoint[] sortObservations(WeightedObservedPoint[] unsorted) {
+            final WeightedObservedPoint[] observations = unsorted.clone();
+            final Comparator<WeightedObservedPoint> cmp
+                = new Comparator<WeightedObservedPoint>() {
+                /** {@inheritDoc} */
+                public int compare(WeightedObservedPoint p1,
+                                   WeightedObservedPoint p2) {
+                    if (p1 == null && p2 == null) {
+                        return 0;
+                    }
+                    if (p1 == null) {
+                        return -1;
+                    }
+                    if (p2 == null) {
+                        return 1;
+                    }
+                    final int cmpX = Double.compare(p1.getX(), p2.getX());
+                    if (cmpX < 0) {
+                        return -1;
+                    }
+                    if (cmpX > 0) {
+                        return 1;
+                    }
+                    final int cmpY = Double.compare(p1.getY(), p2.getY());
+                    if (cmpY < 0) {
+                        return -1;
+                    }
+                    if (cmpY > 0) {
+                        return 1;
+                    }
+                    final int cmpW = Double.compare(p1.getWeight(), p2.getWeight());
+                    if (cmpW < 0) {
+                        return -1;
+                    }
+                    if (cmpW > 0) {
+                        return 1;
+                    }
+                    return 0;
+                }
+            };
+
+            Arrays.sort(observations, cmp);
+            return observations;
+        }
+
+        /**
+         * Guesses the parameters based on the specified observed points.
+         *
+         * @param points Observed points, sorted.
+         * @return the guessed parameters (normalization factor, mean and
+         * sigma).
+         */
+        private double[] basicGuess(WeightedObservedPoint[] points) {
+            final int maxYIdx = findMaxY(points);
+            final double n = points[maxYIdx].getY();
+            final double m = points[maxYIdx].getX();
+
+            double fwhmApprox;
+            try {
+                final double halfY = n + ((m - n) / 2);
+                final double fwhmX1 = interpolateXAtY(points, maxYIdx, -1, halfY);
+                final double fwhmX2 = interpolateXAtY(points, maxYIdx, 1, halfY);
+                fwhmApprox = fwhmX2 - fwhmX1;
+            } catch (OutOfRangeException e) {
+                // TODO: Exceptions should not be used for flow control.
+                fwhmApprox = points[points.length - 1].getX() - points[0].getX();
+            }
+            final double s = fwhmApprox / (2 * FastMath.sqrt(2 * FastMath.log(2)));
+
+            return new double[] { n, m, s };
+        }
+
+        /**
+         * Finds index of point in specified points with the largest Y.
+         *
+         * @param points Points to search.
+         * @return the index in specified points array.
+         */
+        private int findMaxY(WeightedObservedPoint[] points) {
+            int maxYIdx = 0;
+            for (int i = 1; i < points.length; i++) {
+                if (points[i].getY() > points[maxYIdx].getY()) {
+                    maxYIdx = i;
+                }
+            }
+            return maxYIdx;
+        }
+
+        /**
+         * Interpolates using the specified points to determine X at the
+         * specified Y.
+         *
+         * @param points Points to use for interpolation.
+         * @param startIdx Index within points from which to start the search for
+         * interpolation bounds points.
+         * @param idxStep Index step for searching interpolation bounds points.
+         * @param y Y value for which X should be determined.
+         * @return the value of X for the specified Y.
+         * @throws ZeroException if {@code idxStep} is 0.
+         * @throws OutOfRangeException if specified {@code y} is not within the
+         * range of the specified {@code points}.
+         */
+        private double interpolateXAtY(WeightedObservedPoint[] points,
+                                       int startIdx,
+                                       int idxStep,
+                                       double y)
+            throws OutOfRangeException {
+            if (idxStep == 0) {
+                throw new ZeroException();
+            }
+            final WeightedObservedPoint[] twoPoints
+                = getInterpolationPointsForY(points, startIdx, idxStep, y);
+            final WeightedObservedPoint p1 = twoPoints[0];
+            final WeightedObservedPoint p2 = twoPoints[1];
+            if (p1.getY() == y) {
+                return p1.getX();
+            }
+            if (p2.getY() == y) {
+                return p2.getX();
+            }
+            return p1.getX() + (((y - p1.getY()) * (p2.getX() - p1.getX())) /
+                                (p2.getY() - p1.getY()));
+        }
+
+        /**
+         * Gets the two bounding interpolation points from the specified points
+         * suitable for determining X at the specified Y.
+         *
+         * @param points Points to use for interpolation.
+         * @param startIdx Index within points from which to start search for
+         * interpolation bounds points.
+         * @param idxStep Index step for search for interpolation bounds points.
+         * @param y Y value for which X should be determined.
+         * @return the array containing two points suitable for determining X at
+         * the specified Y.
+         * @throws ZeroException if {@code idxStep} is 0.
+         * @throws OutOfRangeException if specified {@code y} is not within the
+         * range of the specified {@code points}.
+         */
+        private WeightedObservedPoint[] getInterpolationPointsForY(WeightedObservedPoint[] points,
+                                                                   int startIdx,
+                                                                   int idxStep,
+                                                                   double y)
+            throws OutOfRangeException {
+            if (idxStep == 0) {
+                throw new ZeroException();
+            }
+            for (int i = startIdx;
+                 idxStep < 0 ? i + idxStep >= 0 : i + idxStep < points.length;
+                 i += idxStep) {
+                final WeightedObservedPoint p1 = points[i];
+                final WeightedObservedPoint p2 = points[i + idxStep];
+                if (isBetween(y, p1.getY(), p2.getY())) {
+                    if (idxStep < 0) {
+                        return new WeightedObservedPoint[] { p2, p1 };
+                    } else {
+                        return new WeightedObservedPoint[] { p1, p2 };
+                    }
+                }
+            }
+
+            // Boundaries are replaced by dummy values because the raised
+            // exception is caught and the message never displayed.
+            // TODO: Exceptions should not be used for flow control.
+            throw new OutOfRangeException(y,
+                                          Double.NEGATIVE_INFINITY,
+                                          Double.POSITIVE_INFINITY);
+        }
+
+        /**
+         * Determines whether a value is between two other values.
+         *
+         * @param value Value to test whether it is between {@code boundary1}
+         * and {@code boundary2}.
+         * @param boundary1 One end of the range.
+         * @param boundary2 Other end of the range.
+         * @return {@code true} if {@code value} is between {@code boundary1} and
+         * {@code boundary2} (inclusive), {@code false} otherwise.
+         */
+        private boolean isBetween(double value,
+                                  double boundary1,
+                                  double boundary2) {
+            return (value >= boundary1 && value <= boundary2) ||
+                (value >= boundary2 && value <= boundary1);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/fitting/HarmonicFitter.java b/src/main/java/org/apache/commons/math3/optimization/fitting/HarmonicFitter.java
new file mode 100644
index 0000000..85c6d18
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/fitting/HarmonicFitter.java
@@ -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.math3.optimization.fitting;
+
+import org.apache.commons.math3.optimization.DifferentiableMultivariateVectorOptimizer;
+import org.apache.commons.math3.analysis.function.HarmonicOscillator;
+import org.apache.commons.math3.exception.ZeroException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Class that implements a curve fitting specialized for sinusoids.
+ *
+ * Harmonic fitting is a very simple case of curve fitting. The
+ * estimated coefficients are the amplitude a, the pulsation &omega; and
+ * the phase &phi;: <code>f (t) = a cos (&omega; t + &phi;)</code>. They are
+ * searched by a least square estimator initialized with a rough guess
+ * based on integrals.
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 2.0
+ */
+@Deprecated
+public class HarmonicFitter extends CurveFitter<HarmonicOscillator.Parametric> {
+    /**
+     * Simple constructor.
+     * @param optimizer Optimizer to use for the fitting.
+     */
+    public HarmonicFitter(final DifferentiableMultivariateVectorOptimizer optimizer) {
+        super(optimizer);
+    }
+
+    /**
+     * Fit an harmonic function to the observed points.
+     *
+     * @param initialGuess First guess values in the following order:
+     * <ul>
+     *  <li>Amplitude</li>
+     *  <li>Angular frequency</li>
+     *  <li>Phase</li>
+     * </ul>
+     * @return the parameters of the harmonic function that best fits the
+     * observed points (in the same order as above).
+     */
+    public double[] fit(double[] initialGuess) {
+        return fit(new HarmonicOscillator.Parametric(), initialGuess);
+    }
+
+    /**
+     * Fit an harmonic function to the observed points.
+     * An initial guess will be automatically computed.
+     *
+     * @return the parameters of the harmonic function that best fits the
+     * observed points (see the other {@link #fit(double[]) fit} method.
+     * @throws NumberIsTooSmallException if the sample is too short for the
+     * the first guess to be computed.
+     * @throws ZeroException if the first guess cannot be computed because
+     * the abscissa range is zero.
+     */
+    public double[] fit() {
+        return fit((new ParameterGuesser(getObservations())).guess());
+    }
+
+    /**
+     * This class guesses harmonic coefficients from a sample.
+     * <p>The algorithm used to guess the coefficients is as follows:</p>
+     *
+     * <p>We know f (t) at some sampling points t<sub>i</sub> and want to find a,
+     * &omega; and &phi; such that f (t) = a cos (&omega; t + &phi;).
+     * </p>
+     *
+     * <p>From the analytical expression, we can compute two primitives :
+     * <pre>
+     *     If2  (t) = &int; f<sup>2</sup>  = a<sup>2</sup> &times; [t + S (t)] / 2
+     *     If'2 (t) = &int; f'<sup>2</sup> = a<sup>2</sup> &omega;<sup>2</sup> &times; [t - S (t)] / 2
+     *     where S (t) = sin (2 (&omega; t + &phi;)) / (2 &omega;)
+     * </pre>
+     * </p>
+     *
+     * <p>We can remove S between these expressions :
+     * <pre>
+     *     If'2 (t) = a<sup>2</sup> &omega;<sup>2</sup> t - &omega;<sup>2</sup> If2 (t)
+     * </pre>
+     * </p>
+     *
+     * <p>The preceding expression shows that If'2 (t) is a linear
+     * combination of both t and If2 (t): If'2 (t) = A &times; t + B &times; If2 (t)
+     * </p>
+     *
+     * <p>From the primitive, we can deduce the same form for definite
+     * integrals between t<sub>1</sub> and t<sub>i</sub> for each t<sub>i</sub> :
+     * <pre>
+     *   If2 (t<sub>i</sub>) - If2 (t<sub>1</sub>) = A &times; (t<sub>i</sub> - t<sub>1</sub>) + B &times; (If2 (t<sub>i</sub>) - If2 (t<sub>1</sub>))
+     * </pre>
+     * </p>
+     *
+     * <p>We can find the coefficients A and B that best fit the sample
+     * to this linear expression by computing the definite integrals for
+     * each sample points.
+     * </p>
+     *
+     * <p>For a bilinear expression z (x<sub>i</sub>, y<sub>i</sub>) = A &times; x<sub>i</sub> + B &times; y<sub>i</sub>, the
+     * coefficients A and B that minimize a least square criterion
+     * &sum; (z<sub>i</sub> - z (x<sub>i</sub>, y<sub>i</sub>))<sup>2</sup> are given by these expressions:</p>
+     * <pre>
+     *
+     *         &sum;y<sub>i</sub>y<sub>i</sub> &sum;x<sub>i</sub>z<sub>i</sub> - &sum;x<sub>i</sub>y<sub>i</sub> &sum;y<sub>i</sub>z<sub>i</sub>
+     *     A = ------------------------
+     *         &sum;x<sub>i</sub>x<sub>i</sub> &sum;y<sub>i</sub>y<sub>i</sub> - &sum;x<sub>i</sub>y<sub>i</sub> &sum;x<sub>i</sub>y<sub>i</sub>
+     *
+     *         &sum;x<sub>i</sub>x<sub>i</sub> &sum;y<sub>i</sub>z<sub>i</sub> - &sum;x<sub>i</sub>y<sub>i</sub> &sum;x<sub>i</sub>z<sub>i</sub>
+     *     B = ------------------------
+     *         &sum;x<sub>i</sub>x<sub>i</sub> &sum;y<sub>i</sub>y<sub>i</sub> - &sum;x<sub>i</sub>y<sub>i</sub> &sum;x<sub>i</sub>y<sub>i</sub>
+     * </pre>
+     * </p>
+     *
+     *
+     * <p>In fact, we can assume both a and &omega; are positive and
+     * compute them directly, knowing that A = a<sup>2</sup> &omega;<sup>2</sup> and that
+     * B = - &omega;<sup>2</sup>. The complete algorithm is therefore:</p>
+     * <pre>
+     *
+     * for each t<sub>i</sub> from t<sub>1</sub> to t<sub>n-1</sub>, compute:
+     *   f  (t<sub>i</sub>)
+     *   f' (t<sub>i</sub>) = (f (t<sub>i+1</sub>) - f(t<sub>i-1</sub>)) / (t<sub>i+1</sub> - t<sub>i-1</sub>)
+     *   x<sub>i</sub> = t<sub>i</sub> - t<sub>1</sub>
+     *   y<sub>i</sub> = &int; f<sup>2</sup> from t<sub>1</sub> to t<sub>i</sub>
+     *   z<sub>i</sub> = &int; f'<sup>2</sup> from t<sub>1</sub> to t<sub>i</sub>
+     *   update the sums &sum;x<sub>i</sub>x<sub>i</sub>, &sum;y<sub>i</sub>y<sub>i</sub>, &sum;x<sub>i</sub>y<sub>i</sub>, &sum;x<sub>i</sub>z<sub>i</sub> and &sum;y<sub>i</sub>z<sub>i</sub>
+     * end for
+     *
+     *            |--------------------------
+     *         \  | &sum;y<sub>i</sub>y<sub>i</sub> &sum;x<sub>i</sub>z<sub>i</sub> - &sum;x<sub>i</sub>y<sub>i</sub> &sum;y<sub>i</sub>z<sub>i</sub>
+     * a     =  \ | ------------------------
+     *           \| &sum;x<sub>i</sub>y<sub>i</sub> &sum;x<sub>i</sub>z<sub>i</sub> - &sum;x<sub>i</sub>x<sub>i</sub> &sum;y<sub>i</sub>z<sub>i</sub>
+     *
+     *
+     *            |--------------------------
+     *         \  | &sum;x<sub>i</sub>y<sub>i</sub> &sum;x<sub>i</sub>z<sub>i</sub> - &sum;x<sub>i</sub>x<sub>i</sub> &sum;y<sub>i</sub>z<sub>i</sub>
+     * &omega;     =  \ | ------------------------
+     *           \| &sum;x<sub>i</sub>x<sub>i</sub> &sum;y<sub>i</sub>y<sub>i</sub> - &sum;x<sub>i</sub>y<sub>i</sub> &sum;x<sub>i</sub>y<sub>i</sub>
+     *
+     * </pre>
+     * </p>
+     *
+     * <p>Once we know &omega;, we can compute:
+     * <pre>
+     *    fc = &omega; f (t) cos (&omega; t) - f' (t) sin (&omega; t)
+     *    fs = &omega; f (t) sin (&omega; t) + f' (t) cos (&omega; t)
+     * </pre>
+     * </p>
+     *
+     * <p>It appears that <code>fc = a &omega; cos (&phi;)</code> and
+     * <code>fs = -a &omega; sin (&phi;)</code>, so we can use these
+     * expressions to compute &phi;. The best estimate over the sample is
+     * given by averaging these expressions.
+     * </p>
+     *
+     * <p>Since integrals and means are involved in the preceding
+     * estimations, these operations run in O(n) time, where n is the
+     * number of measurements.</p>
+     */
+    public static class ParameterGuesser {
+        /** Amplitude. */
+        private final double a;
+        /** Angular frequency. */
+        private final double omega;
+        /** Phase. */
+        private final double phi;
+
+        /**
+         * Simple constructor.
+         *
+         * @param observations Sampled observations.
+         * @throws NumberIsTooSmallException if the sample is too short.
+         * @throws ZeroException if the abscissa range is zero.
+         * @throws MathIllegalStateException when the guessing procedure cannot
+         * produce sensible results.
+         */
+        public ParameterGuesser(WeightedObservedPoint[] observations) {
+            if (observations.length < 4) {
+                throw new NumberIsTooSmallException(LocalizedFormats.INSUFFICIENT_OBSERVED_POINTS_IN_SAMPLE,
+                                                    observations.length, 4, true);
+            }
+
+            final WeightedObservedPoint[] sorted = sortObservations(observations);
+
+            final double aOmega[] = guessAOmega(sorted);
+            a = aOmega[0];
+            omega = aOmega[1];
+
+            phi = guessPhi(sorted);
+        }
+
+        /**
+         * Gets an estimation of the parameters.
+         *
+         * @return the guessed parameters, in the following order:
+         * <ul>
+         *  <li>Amplitude</li>
+         *  <li>Angular frequency</li>
+         *  <li>Phase</li>
+         * </ul>
+         */
+        public double[] guess() {
+            return new double[] { a, omega, phi };
+        }
+
+        /**
+         * Sort the observations with respect to the abscissa.
+         *
+         * @param unsorted Input observations.
+         * @return the input observations, sorted.
+         */
+        private WeightedObservedPoint[] sortObservations(WeightedObservedPoint[] unsorted) {
+            final WeightedObservedPoint[] observations = unsorted.clone();
+
+            // Since the samples are almost always already sorted, this
+            // method is implemented as an insertion sort that reorders the
+            // elements in place. Insertion sort is very efficient in this case.
+            WeightedObservedPoint curr = observations[0];
+            for (int j = 1; j < observations.length; ++j) {
+                WeightedObservedPoint prec = curr;
+                curr = observations[j];
+                if (curr.getX() < prec.getX()) {
+                    // the current element should be inserted closer to the beginning
+                    int i = j - 1;
+                    WeightedObservedPoint mI = observations[i];
+                    while ((i >= 0) && (curr.getX() < mI.getX())) {
+                        observations[i + 1] = mI;
+                        if (i-- != 0) {
+                            mI = observations[i];
+                        }
+                    }
+                    observations[i + 1] = curr;
+                    curr = observations[j];
+                }
+            }
+
+            return observations;
+        }
+
+        /**
+         * Estimate a first guess of the amplitude and angular frequency.
+         * This method assumes that the {@link #sortObservations(WeightedObservedPoint[])} method
+         * has been called previously.
+         *
+         * @param observations Observations, sorted w.r.t. abscissa.
+         * @throws ZeroException if the abscissa range is zero.
+         * @throws MathIllegalStateException when the guessing procedure cannot
+         * produce sensible results.
+         * @return the guessed amplitude (at index 0) and circular frequency
+         * (at index 1).
+         */
+        private double[] guessAOmega(WeightedObservedPoint[] observations) {
+            final double[] aOmega = new double[2];
+
+            // initialize the sums for the linear model between the two integrals
+            double sx2 = 0;
+            double sy2 = 0;
+            double sxy = 0;
+            double sxz = 0;
+            double syz = 0;
+
+            double currentX = observations[0].getX();
+            double currentY = observations[0].getY();
+            double f2Integral = 0;
+            double fPrime2Integral = 0;
+            final double startX = currentX;
+            for (int i = 1; i < observations.length; ++i) {
+                // one step forward
+                final double previousX = currentX;
+                final double previousY = currentY;
+                currentX = observations[i].getX();
+                currentY = observations[i].getY();
+
+                // update the integrals of f<sup>2</sup> and f'<sup>2</sup>
+                // considering a linear model for f (and therefore constant f')
+                final double dx = currentX - previousX;
+                final double dy = currentY - previousY;
+                final double f2StepIntegral =
+                    dx * (previousY * previousY + previousY * currentY + currentY * currentY) / 3;
+                final double fPrime2StepIntegral = dy * dy / dx;
+
+                final double x = currentX - startX;
+                f2Integral += f2StepIntegral;
+                fPrime2Integral += fPrime2StepIntegral;
+
+                sx2 += x * x;
+                sy2 += f2Integral * f2Integral;
+                sxy += x * f2Integral;
+                sxz += x * fPrime2Integral;
+                syz += f2Integral * fPrime2Integral;
+            }
+
+            // compute the amplitude and pulsation coefficients
+            double c1 = sy2 * sxz - sxy * syz;
+            double c2 = sxy * sxz - sx2 * syz;
+            double c3 = sx2 * sy2 - sxy * sxy;
+            if ((c1 / c2 < 0) || (c2 / c3 < 0)) {
+                final int last = observations.length - 1;
+                // Range of the observations, assuming that the
+                // observations are sorted.
+                final double xRange = observations[last].getX() - observations[0].getX();
+                if (xRange == 0) {
+                    throw new ZeroException();
+                }
+                aOmega[1] = 2 * Math.PI / xRange;
+
+                double yMin = Double.POSITIVE_INFINITY;
+                double yMax = Double.NEGATIVE_INFINITY;
+                for (int i = 1; i < observations.length; ++i) {
+                    final double y = observations[i].getY();
+                    if (y < yMin) {
+                        yMin = y;
+                    }
+                    if (y > yMax) {
+                        yMax = y;
+                    }
+                }
+                aOmega[0] = 0.5 * (yMax - yMin);
+            } else {
+                if (c2 == 0) {
+                    // In some ill-conditioned cases (cf. MATH-844), the guesser
+                    // procedure cannot produce sensible results.
+                    throw new MathIllegalStateException(LocalizedFormats.ZERO_DENOMINATOR);
+                }
+
+                aOmega[0] = FastMath.sqrt(c1 / c2);
+                aOmega[1] = FastMath.sqrt(c2 / c3);
+            }
+
+            return aOmega;
+        }
+
+        /**
+         * Estimate a first guess of the phase.
+         *
+         * @param observations Observations, sorted w.r.t. abscissa.
+         * @return the guessed phase.
+         */
+        private double guessPhi(WeightedObservedPoint[] observations) {
+            // initialize the means
+            double fcMean = 0;
+            double fsMean = 0;
+
+            double currentX = observations[0].getX();
+            double currentY = observations[0].getY();
+            for (int i = 1; i < observations.length; ++i) {
+                // one step forward
+                final double previousX = currentX;
+                final double previousY = currentY;
+                currentX = observations[i].getX();
+                currentY = observations[i].getY();
+                final double currentYPrime = (currentY - previousY) / (currentX - previousX);
+
+                double omegaX = omega * currentX;
+                double cosine = FastMath.cos(omegaX);
+                double sine = FastMath.sin(omegaX);
+                fcMean += omega * currentY * cosine - currentYPrime * sine;
+                fsMean += omega * currentY * sine + currentYPrime * cosine;
+            }
+
+            return FastMath.atan2(-fsMean, fcMean);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/fitting/PolynomialFitter.java b/src/main/java/org/apache/commons/math3/optimization/fitting/PolynomialFitter.java
new file mode 100644
index 0000000..dbefcc2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/fitting/PolynomialFitter.java
@@ -0,0 +1,111 @@
+/*
+ * 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.math3.optimization.fitting;
+
+import org.apache.commons.math3.analysis.polynomials.PolynomialFunction;
+import org.apache.commons.math3.optimization.DifferentiableMultivariateVectorOptimizer;
+
+/**
+ * Polynomial fitting is a very simple case of {@link CurveFitter curve fitting}.
+ * The estimated coefficients are the polynomial coefficients (see the
+ * {@link #fit(double[]) fit} method).
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 2.0
+ */
+@Deprecated
+public class PolynomialFitter extends CurveFitter<PolynomialFunction.Parametric> {
+    /** Polynomial degree.
+     * @deprecated
+     */
+    @Deprecated
+    private final int degree;
+
+    /**
+     * Simple constructor.
+     * <p>The polynomial fitter built this way are complete polynomials,
+     * ie. a n-degree polynomial has n+1 coefficients.</p>
+     *
+     * @param degree Maximal degree of the polynomial.
+     * @param optimizer Optimizer to use for the fitting.
+     * @deprecated Since 3.1 (to be removed in 4.0). Please use
+     * {@link #PolynomialFitter(DifferentiableMultivariateVectorOptimizer)} instead.
+     */
+    @Deprecated
+    public PolynomialFitter(int degree, final DifferentiableMultivariateVectorOptimizer optimizer) {
+        super(optimizer);
+        this.degree = degree;
+    }
+
+    /**
+     * Simple constructor.
+     *
+     * @param optimizer Optimizer to use for the fitting.
+     * @since 3.1
+     */
+    public PolynomialFitter(DifferentiableMultivariateVectorOptimizer optimizer) {
+        super(optimizer);
+        degree = -1; // To avoid compilation error until the instance variable is removed.
+    }
+
+    /**
+     * Get the polynomial fitting the weighted (x, y) points.
+     *
+     * @return the coefficients of the polynomial that best fits the observed points.
+     * @throws org.apache.commons.math3.exception.ConvergenceException
+     * if the algorithm failed to converge.
+     * @deprecated Since 3.1 (to be removed in 4.0). Please use {@link #fit(double[])} instead.
+     */
+    @Deprecated
+    public double[] fit() {
+        return fit(new PolynomialFunction.Parametric(), new double[degree + 1]);
+    }
+
+    /**
+     * Get the coefficients of the polynomial fitting the weighted data points.
+     * The degree of the fitting polynomial is {@code guess.length - 1}.
+     *
+     * @param guess First guess for the coefficients. They must be sorted in
+     * increasing order of the polynomial's degree.
+     * @param maxEval Maximum number of evaluations of the polynomial.
+     * @return the coefficients of the polynomial that best fits the observed points.
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException if
+     * the number of evaluations exceeds {@code maxEval}.
+     * @throws org.apache.commons.math3.exception.ConvergenceException
+     * if the algorithm failed to converge.
+     * @since 3.1
+     */
+    public double[] fit(int maxEval, double[] guess) {
+        return fit(maxEval, new PolynomialFunction.Parametric(), guess);
+    }
+
+    /**
+     * Get the coefficients of the polynomial fitting the weighted data points.
+     * The degree of the fitting polynomial is {@code guess.length - 1}.
+     *
+     * @param guess First guess for the coefficients. They must be sorted in
+     * increasing order of the polynomial's degree.
+     * @return the coefficients of the polynomial that best fits the observed points.
+     * @throws org.apache.commons.math3.exception.ConvergenceException
+     * if the algorithm failed to converge.
+     * @since 3.1
+     */
+    public double[] fit(double[] guess) {
+        return fit(new PolynomialFunction.Parametric(), guess);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/fitting/WeightedObservedPoint.java b/src/main/java/org/apache/commons/math3/optimization/fitting/WeightedObservedPoint.java
new file mode 100644
index 0000000..899a502
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/fitting/WeightedObservedPoint.java
@@ -0,0 +1,76 @@
+/*
+ * 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.math3.optimization.fitting;
+
+import java.io.Serializable;
+
+/** This class is a simple container for weighted observed point in
+ * {@link CurveFitter curve fitting}.
+ * <p>Instances of this class are guaranteed to be immutable.</p>
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 2.0
+ */
+@Deprecated
+public class WeightedObservedPoint implements Serializable {
+
+    /** Serializable version id. */
+    private static final long serialVersionUID = 5306874947404636157L;
+
+    /** Weight of the measurement in the fitting process. */
+    private final double weight;
+
+    /** Abscissa of the point. */
+    private final double x;
+
+    /** Observed value of the function at x. */
+    private final double y;
+
+    /** Simple constructor.
+     * @param weight weight of the measurement in the fitting process
+     * @param x abscissa of the measurement
+     * @param y ordinate of the measurement
+     */
+    public WeightedObservedPoint(final double weight, final double x, final double y) {
+        this.weight = weight;
+        this.x      = x;
+        this.y      = y;
+    }
+
+    /** Get the weight of the measurement in the fitting process.
+     * @return weight of the measurement in the fitting process
+     */
+    public double getWeight() {
+        return weight;
+    }
+
+    /** Get the abscissa of the point.
+     * @return abscissa of the point
+     */
+    public double getX() {
+        return x;
+    }
+
+    /** Get the observed value of the function at x.
+     * @return observed value of the function at x
+     */
+    public double getY() {
+        return y;
+    }
+
+}
+
diff --git a/src/main/java/org/apache/commons/math3/optimization/fitting/package-info.java b/src/main/java/org/apache/commons/math3/optimization/fitting/package-info.java
new file mode 100644
index 0000000..b25e5fd
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/fitting/package-info.java
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * This package provides classes to perform curve fitting.
+ *
+ * <p>Curve fitting is a special case of a least squares problem
+ * were the parameters are the coefficients of a function <code>f</code>
+ * whose graph <code>y=f(x)</code> should pass through sample points, and
+ * were the objective function is the squared sum of residuals
+ * <code>f(x<sub>i</sub>)-y<sub>i</sub></code> for observed points
+ * (x<sub>i</sub>, y<sub>i</sub>).</p>
+ *
+ *
+ */
+package org.apache.commons.math3.optimization.fitting;
diff --git a/src/main/java/org/apache/commons/math3/optimization/general/AbstractDifferentiableOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/general/AbstractDifferentiableOptimizer.java
new file mode 100644
index 0000000..d175863
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/general/AbstractDifferentiableOptimizer.java
@@ -0,0 +1,90 @@
+/*
+ * 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.math3.optimization.general;
+
+import org.apache.commons.math3.analysis.MultivariateVectorFunction;
+import org.apache.commons.math3.analysis.differentiation.GradientFunction;
+import org.apache.commons.math3.analysis.differentiation.MultivariateDifferentiableFunction;
+import org.apache.commons.math3.optimization.ConvergenceChecker;
+import org.apache.commons.math3.optimization.GoalType;
+import org.apache.commons.math3.optimization.OptimizationData;
+import org.apache.commons.math3.optimization.InitialGuess;
+import org.apache.commons.math3.optimization.PointValuePair;
+import org.apache.commons.math3.optimization.direct.BaseAbstractMultivariateOptimizer;
+
+/**
+ * Base class for implementing optimizers for multivariate scalar
+ * differentiable functions.
+ * It contains boiler-plate code for dealing with gradient evaluation.
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.1
+ */
+@Deprecated
+public abstract class AbstractDifferentiableOptimizer
+    extends BaseAbstractMultivariateOptimizer<MultivariateDifferentiableFunction> {
+    /**
+     * Objective function gradient.
+     */
+    private MultivariateVectorFunction gradient;
+
+    /**
+     * @param checker Convergence checker.
+     */
+    protected AbstractDifferentiableOptimizer(ConvergenceChecker<PointValuePair> checker) {
+        super(checker);
+    }
+
+    /**
+     * Compute the gradient vector.
+     *
+     * @param evaluationPoint Point at which the gradient must be evaluated.
+     * @return the gradient at the specified point.
+     */
+    protected double[] computeObjectiveGradient(final double[] evaluationPoint) {
+        return gradient.value(evaluationPoint);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @deprecated In 3.1. Please use
+     * {@link #optimizeInternal(int,MultivariateDifferentiableFunction,GoalType,OptimizationData[])}
+     * instead.
+     */
+    @Override@Deprecated
+    protected PointValuePair optimizeInternal(final int maxEval,
+                                              final MultivariateDifferentiableFunction f,
+                                              final GoalType goalType,
+                                              final double[] startPoint) {
+        return optimizeInternal(maxEval, f, goalType, new InitialGuess(startPoint));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected PointValuePair optimizeInternal(final int maxEval,
+                                              final MultivariateDifferentiableFunction f,
+                                              final GoalType goalType,
+                                              final OptimizationData... optData) {
+        // Store optimization problem characteristics.
+        gradient = new GradientFunction(f);
+
+        // Perform optimization.
+        return super.optimizeInternal(maxEval, f, goalType, optData);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/general/AbstractLeastSquaresOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/general/AbstractLeastSquaresOptimizer.java
new file mode 100644
index 0000000..96f7fb2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/general/AbstractLeastSquaresOptimizer.java
@@ -0,0 +1,577 @@
+/*
+ * 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.math3.optimization.general;
+
+import org.apache.commons.math3.analysis.DifferentiableMultivariateVectorFunction;
+import org.apache.commons.math3.analysis.FunctionUtils;
+import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
+import org.apache.commons.math3.analysis.differentiation.MultivariateDifferentiableVectorFunction;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.linear.ArrayRealVector;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.linear.DiagonalMatrix;
+import org.apache.commons.math3.linear.DecompositionSolver;
+import org.apache.commons.math3.linear.MatrixUtils;
+import org.apache.commons.math3.linear.QRDecomposition;
+import org.apache.commons.math3.linear.EigenDecomposition;
+import org.apache.commons.math3.optimization.OptimizationData;
+import org.apache.commons.math3.optimization.InitialGuess;
+import org.apache.commons.math3.optimization.Target;
+import org.apache.commons.math3.optimization.Weight;
+import org.apache.commons.math3.optimization.ConvergenceChecker;
+import org.apache.commons.math3.optimization.DifferentiableMultivariateVectorOptimizer;
+import org.apache.commons.math3.optimization.PointVectorValuePair;
+import org.apache.commons.math3.optimization.direct.BaseAbstractMultivariateVectorOptimizer;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Base class for implementing least squares optimizers.
+ * It handles the boilerplate methods associated to thresholds settings,
+ * Jacobian and error estimation.
+ * <br/>
+ * This class constructs the Jacobian matrix of the function argument in method
+ * {@link BaseAbstractMultivariateVectorOptimizer#optimize(int,
+ * org.apache.commons.math3.analysis.MultivariateVectorFunction,OptimizationData[])
+ * optimize} and assumes that the rows of that matrix iterate on the model
+ * functions while the columns iterate on the parameters; thus, the numbers
+ * of rows is equal to the dimension of the
+ * {@link org.apache.commons.math3.optimization.Target Target} while
+ * the number of columns is equal to the dimension of the
+ * {@link org.apache.commons.math3.optimization.InitialGuess InitialGuess}.
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 1.2
+ */
+@Deprecated
+public abstract class AbstractLeastSquaresOptimizer
+    extends BaseAbstractMultivariateVectorOptimizer<DifferentiableMultivariateVectorFunction>
+    implements DifferentiableMultivariateVectorOptimizer {
+    /**
+     * Singularity threshold (cf. {@link #getCovariances(double)}).
+     * @deprecated As of 3.1.
+     */
+    @Deprecated
+    private static final double DEFAULT_SINGULARITY_THRESHOLD = 1e-14;
+    /**
+     * Jacobian matrix of the weighted residuals.
+     * This matrix is in canonical form just after the calls to
+     * {@link #updateJacobian()}, but may be modified by the solver
+     * in the derived class (the {@link LevenbergMarquardtOptimizer
+     * Levenberg-Marquardt optimizer} does this).
+     * @deprecated As of 3.1. To be removed in 4.0. Please use
+     * {@link #computeWeightedJacobian(double[])} instead.
+     */
+    @Deprecated
+    protected double[][] weightedResidualJacobian;
+    /** Number of columns of the jacobian matrix.
+     * @deprecated As of 3.1.
+     */
+    @Deprecated
+    protected int cols;
+    /** Number of rows of the jacobian matrix.
+     * @deprecated As of 3.1.
+     */
+    @Deprecated
+    protected int rows;
+    /** Current point.
+     * @deprecated As of 3.1.
+     */
+    @Deprecated
+    protected double[] point;
+    /** Current objective function value.
+     * @deprecated As of 3.1.
+     */
+    @Deprecated
+    protected double[] objective;
+    /** Weighted residuals
+     * @deprecated As of 3.1.
+     */
+    @Deprecated
+    protected double[] weightedResiduals;
+    /** Cost value (square root of the sum of the residuals).
+     * @deprecated As of 3.1. Field to become "private" in 4.0.
+     * Please use {@link #setCost(double)}.
+     */
+    @Deprecated
+    protected double cost;
+    /** Objective function derivatives. */
+    private MultivariateDifferentiableVectorFunction jF;
+    /** Number of evaluations of the Jacobian. */
+    private int jacobianEvaluations;
+    /** Square-root of the weight matrix. */
+    private RealMatrix weightMatrixSqrt;
+
+    /**
+     * Simple constructor with default settings.
+     * The convergence check is set to a {@link
+     * org.apache.commons.math3.optimization.SimpleVectorValueChecker}.
+     * @deprecated See {@link org.apache.commons.math3.optimization.SimpleValueChecker#SimpleValueChecker()}
+     */
+    @Deprecated
+    protected AbstractLeastSquaresOptimizer() {}
+
+    /**
+     * @param checker Convergence checker.
+     */
+    protected AbstractLeastSquaresOptimizer(ConvergenceChecker<PointVectorValuePair> checker) {
+        super(checker);
+    }
+
+    /**
+     * @return the number of evaluations of the Jacobian function.
+     */
+    public int getJacobianEvaluations() {
+        return jacobianEvaluations;
+    }
+
+    /**
+     * Update the jacobian matrix.
+     *
+     * @throws DimensionMismatchException if the Jacobian dimension does not
+     * match problem dimension.
+     * @deprecated As of 3.1. Please use {@link #computeWeightedJacobian(double[])}
+     * instead.
+     */
+    @Deprecated
+    protected void updateJacobian() {
+        final RealMatrix weightedJacobian = computeWeightedJacobian(point);
+        weightedResidualJacobian = weightedJacobian.scalarMultiply(-1).getData();
+    }
+
+    /**
+     * Computes the Jacobian matrix.
+     *
+     * @param params Model parameters at which to compute the Jacobian.
+     * @return the weighted Jacobian: W<sup>1/2</sup> J.
+     * @throws DimensionMismatchException if the Jacobian dimension does not
+     * match problem dimension.
+     * @since 3.1
+     */
+    protected RealMatrix computeWeightedJacobian(double[] params) {
+        ++jacobianEvaluations;
+
+        final DerivativeStructure[] dsPoint = new DerivativeStructure[params.length];
+        final int nC = params.length;
+        for (int i = 0; i < nC; ++i) {
+            dsPoint[i] = new DerivativeStructure(nC, 1, i, params[i]);
+        }
+        final DerivativeStructure[] dsValue = jF.value(dsPoint);
+        final int nR = getTarget().length;
+        if (dsValue.length != nR) {
+            throw new DimensionMismatchException(dsValue.length, nR);
+        }
+        final double[][] jacobianData = new double[nR][nC];
+        for (int i = 0; i < nR; ++i) {
+            int[] orders = new int[nC];
+            for (int j = 0; j < nC; ++j) {
+                orders[j] = 1;
+                jacobianData[i][j] = dsValue[i].getPartialDerivative(orders);
+                orders[j] = 0;
+            }
+        }
+
+        return weightMatrixSqrt.multiply(MatrixUtils.createRealMatrix(jacobianData));
+    }
+
+    /**
+     * Update the residuals array and cost function value.
+     * @throws DimensionMismatchException if the dimension does not match the
+     * problem dimension.
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException
+     * if the maximal number of evaluations is exceeded.
+     * @deprecated As of 3.1. Please use {@link #computeResiduals(double[])},
+     * {@link #computeObjectiveValue(double[])}, {@link #computeCost(double[])}
+     * and {@link #setCost(double)} instead.
+     */
+    @Deprecated
+    protected void updateResidualsAndCost() {
+        objective = computeObjectiveValue(point);
+        final double[] res = computeResiduals(objective);
+
+        // Compute cost.
+        cost = computeCost(res);
+
+        // Compute weighted residuals.
+        final ArrayRealVector residuals = new ArrayRealVector(res);
+        weightedResiduals = weightMatrixSqrt.operate(residuals).toArray();
+    }
+
+    /**
+     * Computes the cost.
+     *
+     * @param residuals Residuals.
+     * @return the cost.
+     * @see #computeResiduals(double[])
+     * @since 3.1
+     */
+    protected double computeCost(double[] residuals) {
+        final ArrayRealVector r = new ArrayRealVector(residuals);
+        return FastMath.sqrt(r.dotProduct(getWeight().operate(r)));
+    }
+
+    /**
+     * Get the Root Mean Square value.
+     * Get the Root Mean Square value, i.e. the root of the arithmetic
+     * mean of the square of all weighted residuals. This is related to the
+     * criterion that is minimized by the optimizer as follows: if
+     * <em>c</em> if the criterion, and <em>n</em> is the number of
+     * measurements, then the RMS is <em>sqrt (c/n)</em>.
+     *
+     * @return RMS value
+     */
+    public double getRMS() {
+        return FastMath.sqrt(getChiSquare() / rows);
+    }
+
+    /**
+     * Get a Chi-Square-like value assuming the N residuals follow N
+     * distinct normal distributions centered on 0 and whose variances are
+     * the reciprocal of the weights.
+     * @return chi-square value
+     */
+    public double getChiSquare() {
+        return cost * cost;
+    }
+
+    /**
+     * Gets the square-root of the weight matrix.
+     *
+     * @return the square-root of the weight matrix.
+     * @since 3.1
+     */
+    public RealMatrix getWeightSquareRoot() {
+        return weightMatrixSqrt.copy();
+    }
+
+    /**
+     * Sets the cost.
+     *
+     * @param cost Cost value.
+     * @since 3.1
+     */
+    protected void setCost(double cost) {
+        this.cost = cost;
+    }
+
+    /**
+     * Get the covariance matrix of the optimized parameters.
+     *
+     * @return the covariance matrix.
+     * @throws org.apache.commons.math3.linear.SingularMatrixException
+     * if the covariance matrix cannot be computed (singular problem).
+     * @see #getCovariances(double)
+     * @deprecated As of 3.1. Please use {@link #computeCovariances(double[],double)}
+     * instead.
+     */
+    @Deprecated
+    public double[][] getCovariances() {
+        return getCovariances(DEFAULT_SINGULARITY_THRESHOLD);
+    }
+
+    /**
+     * Get the covariance matrix of the optimized parameters.
+     * <br/>
+     * Note that this operation involves the inversion of the
+     * <code>J<sup>T</sup>J</code> matrix, where {@code J} is the
+     * Jacobian matrix.
+     * The {@code threshold} parameter is a way for the caller to specify
+     * that the result of this computation should be considered meaningless,
+     * and thus trigger an exception.
+     *
+     * @param threshold Singularity threshold.
+     * @return the covariance matrix.
+     * @throws org.apache.commons.math3.linear.SingularMatrixException
+     * if the covariance matrix cannot be computed (singular problem).
+     * @deprecated As of 3.1. Please use {@link #computeCovariances(double[],double)}
+     * instead.
+     */
+    @Deprecated
+    public double[][] getCovariances(double threshold) {
+        return computeCovariances(point, threshold);
+    }
+
+    /**
+     * Get the covariance matrix of the optimized parameters.
+     * <br/>
+     * Note that this operation involves the inversion of the
+     * <code>J<sup>T</sup>J</code> matrix, where {@code J} is the
+     * Jacobian matrix.
+     * The {@code threshold} parameter is a way for the caller to specify
+     * that the result of this computation should be considered meaningless,
+     * and thus trigger an exception.
+     *
+     * @param params Model parameters.
+     * @param threshold Singularity threshold.
+     * @return the covariance matrix.
+     * @throws org.apache.commons.math3.linear.SingularMatrixException
+     * if the covariance matrix cannot be computed (singular problem).
+     * @since 3.1
+     */
+    public double[][] computeCovariances(double[] params,
+                                         double threshold) {
+        // Set up the Jacobian.
+        final RealMatrix j = computeWeightedJacobian(params);
+
+        // Compute transpose(J)J.
+        final RealMatrix jTj = j.transpose().multiply(j);
+
+        // Compute the covariances matrix.
+        final DecompositionSolver solver
+            = new QRDecomposition(jTj, threshold).getSolver();
+        return solver.getInverse().getData();
+    }
+
+    /**
+     * <p>
+     * Returns an estimate of the standard deviation of each parameter. The
+     * returned values are the so-called (asymptotic) standard errors on the
+     * parameters, defined as {@code sd(a[i]) = sqrt(S / (n - m) * C[i][i])},
+     * where {@code a[i]} is the optimized value of the {@code i}-th parameter,
+     * {@code S} is the minimized value of the sum of squares objective function
+     * (as returned by {@link #getChiSquare()}), {@code n} is the number of
+     * observations, {@code m} is the number of parameters and {@code C} is the
+     * covariance matrix.
+     * </p>
+     * <p>
+     * See also
+     * <a href="http://en.wikipedia.org/wiki/Least_squares">Wikipedia</a>,
+     * or
+     * <a href="http://mathworld.wolfram.com/LeastSquaresFitting.html">MathWorld</a>,
+     * equations (34) and (35) for a particular case.
+     * </p>
+     *
+     * @return an estimate of the standard deviation of the optimized parameters
+     * @throws org.apache.commons.math3.linear.SingularMatrixException
+     * if the covariance matrix cannot be computed.
+     * @throws NumberIsTooSmallException if the number of degrees of freedom is not
+     * positive, i.e. the number of measurements is less or equal to the number of
+     * parameters.
+     * @deprecated as of version 3.1, {@link #computeSigma(double[],double)} should be used
+     * instead. It should be emphasized that {@code guessParametersErrors} and
+     * {@code computeSigma} are <em>not</em> strictly equivalent.
+     */
+    @Deprecated
+    public double[] guessParametersErrors() {
+        if (rows <= cols) {
+            throw new NumberIsTooSmallException(LocalizedFormats.NO_DEGREES_OF_FREEDOM,
+                                                rows, cols, false);
+        }
+        double[] errors = new double[cols];
+        final double c = FastMath.sqrt(getChiSquare() / (rows - cols));
+        double[][] covar = computeCovariances(point, 1e-14);
+        for (int i = 0; i < errors.length; ++i) {
+            errors[i] = FastMath.sqrt(covar[i][i]) * c;
+        }
+        return errors;
+    }
+
+    /**
+     * Computes an estimate of the standard deviation of the parameters. The
+     * returned values are the square root of the diagonal coefficients of the
+     * covariance matrix, {@code sd(a[i]) ~= sqrt(C[i][i])}, where {@code a[i]}
+     * is the optimized value of the {@code i}-th parameter, and {@code C} is
+     * the covariance matrix.
+     *
+     * @param params Model parameters.
+     * @param covarianceSingularityThreshold Singularity threshold (see
+     * {@link #computeCovariances(double[],double) computeCovariances}).
+     * @return an estimate of the standard deviation of the optimized parameters
+     * @throws org.apache.commons.math3.linear.SingularMatrixException
+     * if the covariance matrix cannot be computed.
+     * @since 3.1
+     */
+    public double[] computeSigma(double[] params,
+                                 double covarianceSingularityThreshold) {
+        final int nC = params.length;
+        final double[] sig = new double[nC];
+        final double[][] cov = computeCovariances(params, covarianceSingularityThreshold);
+        for (int i = 0; i < nC; ++i) {
+            sig[i] = FastMath.sqrt(cov[i][i]);
+        }
+        return sig;
+    }
+
+    /** {@inheritDoc}
+     * @deprecated As of 3.1. Please use
+     * {@link BaseAbstractMultivariateVectorOptimizer#optimize(int,
+     * org.apache.commons.math3.analysis.MultivariateVectorFunction,OptimizationData[])
+     * optimize(int,MultivariateDifferentiableVectorFunction,OptimizationData...)}
+     * instead.
+     */
+    @Override
+    @Deprecated
+    public PointVectorValuePair optimize(int maxEval,
+                                         final DifferentiableMultivariateVectorFunction f,
+                                         final double[] target, final double[] weights,
+                                         final double[] startPoint) {
+        return optimizeInternal(maxEval,
+                                FunctionUtils.toMultivariateDifferentiableVectorFunction(f),
+                                new Target(target),
+                                new Weight(weights),
+                                new InitialGuess(startPoint));
+    }
+
+    /**
+     * Optimize an objective function.
+     * Optimization is considered to be a weighted least-squares minimization.
+     * The cost function to be minimized is
+     * <code>&sum;weight<sub>i</sub>(objective<sub>i</sub> - target<sub>i</sub>)<sup>2</sup></code>
+     *
+     * @param f Objective function.
+     * @param target Target value for the objective functions at optimum.
+     * @param weights Weights for the least squares cost computation.
+     * @param startPoint Start point for optimization.
+     * @return the point/value pair giving the optimal value for objective
+     * function.
+     * @param maxEval Maximum number of function evaluations.
+     * @throws org.apache.commons.math3.exception.DimensionMismatchException
+     * if the start point dimension is wrong.
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException
+     * if the maximal number of evaluations is exceeded.
+     * @throws org.apache.commons.math3.exception.NullArgumentException if
+     * any argument is {@code null}.
+     * @deprecated As of 3.1. Please use
+     * {@link BaseAbstractMultivariateVectorOptimizer#optimize(int,
+     * org.apache.commons.math3.analysis.MultivariateVectorFunction,OptimizationData[])
+     * optimize(int,MultivariateDifferentiableVectorFunction,OptimizationData...)}
+     * instead.
+     */
+    @Deprecated
+    public PointVectorValuePair optimize(final int maxEval,
+                                         final MultivariateDifferentiableVectorFunction f,
+                                         final double[] target, final double[] weights,
+                                         final double[] startPoint) {
+        return optimizeInternal(maxEval, f,
+                                new Target(target),
+                                new Weight(weights),
+                                new InitialGuess(startPoint));
+    }
+
+    /**
+     * Optimize an objective function.
+     * Optimization is considered to be a weighted least-squares minimization.
+     * The cost function to be minimized is
+     * <code>&sum;weight<sub>i</sub>(objective<sub>i</sub> - target<sub>i</sub>)<sup>2</sup></code>
+     *
+     * @param maxEval Allowed number of evaluations of the objective function.
+     * @param f Objective function.
+     * @param optData Optimization data. The following data will be looked for:
+     * <ul>
+     *  <li>{@link Target}</li>
+     *  <li>{@link Weight}</li>
+     *  <li>{@link InitialGuess}</li>
+     * </ul>
+     * @return the point/value pair giving the optimal value of the objective
+     * function.
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException if
+     * the maximal number of evaluations is exceeded.
+     * @throws DimensionMismatchException if the target, and weight arguments
+     * have inconsistent dimensions.
+     * @see BaseAbstractMultivariateVectorOptimizer#optimizeInternal(int,
+     * org.apache.commons.math3.analysis.MultivariateVectorFunction,OptimizationData[])
+     * @since 3.1
+     * @deprecated As of 3.1. Override is necessary only until this class's generic
+     * argument is changed to {@code MultivariateDifferentiableVectorFunction}.
+     */
+    @Deprecated
+    protected PointVectorValuePair optimizeInternal(final int maxEval,
+                                                    final MultivariateDifferentiableVectorFunction f,
+                                                    OptimizationData... optData) {
+        // XXX Conversion will be removed when the generic argument of the
+        // base class becomes "MultivariateDifferentiableVectorFunction".
+        return super.optimizeInternal(maxEval, FunctionUtils.toDifferentiableMultivariateVectorFunction(f), optData);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void setUp() {
+        super.setUp();
+
+        // Reset counter.
+        jacobianEvaluations = 0;
+
+        // Square-root of the weight matrix.
+        weightMatrixSqrt = squareRoot(getWeight());
+
+        // Store least squares problem characteristics.
+        // XXX The conversion won't be necessary when the generic argument of
+        // the base class becomes "MultivariateDifferentiableVectorFunction".
+        // XXX "jF" is not strictly necessary anymore but is currently more
+        // efficient than converting the value returned from "getObjectiveFunction()"
+        // every time it is used.
+        jF = FunctionUtils.toMultivariateDifferentiableVectorFunction((DifferentiableMultivariateVectorFunction) getObjectiveFunction());
+
+        // Arrays shared with "private" and "protected" methods.
+        point = getStartPoint();
+        rows = getTarget().length;
+        cols = point.length;
+    }
+
+    /**
+     * Computes the residuals.
+     * The residual is the difference between the observed (target)
+     * values and the model (objective function) value.
+     * There is one residual for each element of the vector-valued
+     * function.
+     *
+     * @param objectiveValue Value of the the objective function. This is
+     * the value returned from a call to
+     * {@link #computeObjectiveValue(double[]) computeObjectiveValue}
+     * (whose array argument contains the model parameters).
+     * @return the residuals.
+     * @throws DimensionMismatchException if {@code params} has a wrong
+     * length.
+     * @since 3.1
+     */
+    protected double[] computeResiduals(double[] objectiveValue) {
+        final double[] target = getTarget();
+        if (objectiveValue.length != target.length) {
+            throw new DimensionMismatchException(target.length,
+                                                 objectiveValue.length);
+        }
+
+        final double[] residuals = new double[target.length];
+        for (int i = 0; i < target.length; i++) {
+            residuals[i] = target[i] - objectiveValue[i];
+        }
+
+        return residuals;
+    }
+
+    /**
+     * Computes the square-root of the weight matrix.
+     *
+     * @param m Symmetric, positive-definite (weight) matrix.
+     * @return the square-root of the weight matrix.
+     */
+    private RealMatrix squareRoot(RealMatrix m) {
+        if (m instanceof DiagonalMatrix) {
+            final int dim = m.getRowDimension();
+            final RealMatrix sqrtM = new DiagonalMatrix(dim);
+            for (int i = 0; i < dim; i++) {
+               sqrtM.setEntry(i, i, FastMath.sqrt(m.getEntry(i, i)));
+            }
+            return sqrtM;
+        } else {
+            final EigenDecomposition dec = new EigenDecomposition(m);
+            return dec.getSquareRoot();
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/general/AbstractScalarDifferentiableOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/general/AbstractScalarDifferentiableOptimizer.java
new file mode 100644
index 0000000..3947c2c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/general/AbstractScalarDifferentiableOptimizer.java
@@ -0,0 +1,114 @@
+/*
+ * 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.math3.optimization.general;
+
+import org.apache.commons.math3.analysis.DifferentiableMultivariateFunction;
+import org.apache.commons.math3.analysis.MultivariateVectorFunction;
+import org.apache.commons.math3.analysis.FunctionUtils;
+import org.apache.commons.math3.analysis.differentiation.MultivariateDifferentiableFunction;
+import org.apache.commons.math3.optimization.DifferentiableMultivariateOptimizer;
+import org.apache.commons.math3.optimization.GoalType;
+import org.apache.commons.math3.optimization.ConvergenceChecker;
+import org.apache.commons.math3.optimization.PointValuePair;
+import org.apache.commons.math3.optimization.direct.BaseAbstractMultivariateOptimizer;
+
+/**
+ * Base class for implementing optimizers for multivariate scalar
+ * differentiable functions.
+ * It contains boiler-plate code for dealing with gradient evaluation.
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 2.0
+ */
+@Deprecated
+public abstract class AbstractScalarDifferentiableOptimizer
+    extends BaseAbstractMultivariateOptimizer<DifferentiableMultivariateFunction>
+    implements DifferentiableMultivariateOptimizer {
+    /**
+     * Objective function gradient.
+     */
+    private MultivariateVectorFunction gradient;
+
+    /**
+     * Simple constructor with default settings.
+     * The convergence check is set to a
+     * {@link org.apache.commons.math3.optimization.SimpleValueChecker
+     * SimpleValueChecker}.
+     * @deprecated See {@link org.apache.commons.math3.optimization.SimpleValueChecker#SimpleValueChecker()}
+     */
+    @Deprecated
+    protected AbstractScalarDifferentiableOptimizer() {}
+
+    /**
+     * @param checker Convergence checker.
+     */
+    protected AbstractScalarDifferentiableOptimizer(ConvergenceChecker<PointValuePair> checker) {
+        super(checker);
+    }
+
+    /**
+     * Compute the gradient vector.
+     *
+     * @param evaluationPoint Point at which the gradient must be evaluated.
+     * @return the gradient at the specified point.
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException
+     * if the allowed number of evaluations is exceeded.
+     */
+    protected double[] computeObjectiveGradient(final double[] evaluationPoint) {
+        return gradient.value(evaluationPoint);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected PointValuePair optimizeInternal(int maxEval,
+                                              final DifferentiableMultivariateFunction f,
+                                              final GoalType goalType,
+                                              final double[] startPoint) {
+        // Store optimization problem characteristics.
+        gradient = f.gradient();
+
+        return super.optimizeInternal(maxEval, f, goalType, startPoint);
+    }
+
+    /**
+     * Optimize an objective function.
+     *
+     * @param f Objective function.
+     * @param goalType Type of optimization goal: either
+     * {@link GoalType#MAXIMIZE} or {@link GoalType#MINIMIZE}.
+     * @param startPoint Start point for optimization.
+     * @param maxEval Maximum number of function evaluations.
+     * @return the point/value pair giving the optimal value for objective
+     * function.
+     * @throws org.apache.commons.math3.exception.DimensionMismatchException
+     * if the start point dimension is wrong.
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException
+     * if the maximal number of evaluations is exceeded.
+     * @throws org.apache.commons.math3.exception.NullArgumentException if
+     * any argument is {@code null}.
+     */
+    public PointValuePair optimize(final int maxEval,
+                                   final MultivariateDifferentiableFunction f,
+                                   final GoalType goalType,
+                                   final double[] startPoint) {
+        return optimizeInternal(maxEval,
+                                FunctionUtils.toDifferentiableMultivariateFunction(f),
+                                goalType,
+                                startPoint);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/general/ConjugateGradientFormula.java b/src/main/java/org/apache/commons/math3/optimization/general/ConjugateGradientFormula.java
new file mode 100644
index 0000000..5fee40a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/general/ConjugateGradientFormula.java
@@ -0,0 +1,50 @@
+/*
+ * 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.math3.optimization.general;
+
+/**
+ * Available choices of update formulas for the &beta; parameter
+ * in {@link NonLinearConjugateGradientOptimizer}.
+ * <p>
+ * The &beta; parameter is used to compute the successive conjugate
+ * search directions. For non-linear conjugate gradients, there are
+ * two formulas to compute &beta;:
+ * <ul>
+ *   <li>Fletcher-Reeves formula</li>
+ *   <li>Polak-Ribi&egrave;re formula</li>
+ * </ul>
+ * On the one hand, the Fletcher-Reeves formula is guaranteed to converge
+ * if the start point is close enough of the optimum whether the
+ * Polak-Ribi&egrave;re formula may not converge in rare cases. On the
+ * other hand, the Polak-Ribi&egrave;re formula is often faster when it
+ * does converge. Polak-Ribi&egrave;re is often used.
+ * <p>
+ * @see NonLinearConjugateGradientOptimizer
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 2.0
+ */
+@Deprecated
+public enum ConjugateGradientFormula {
+
+    /** Fletcher-Reeves formula. */
+    FLETCHER_REEVES,
+
+    /** Polak-Ribi&egrave;re formula. */
+    POLAK_RIBIERE
+
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/general/GaussNewtonOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/general/GaussNewtonOptimizer.java
new file mode 100644
index 0000000..464a0f0
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/general/GaussNewtonOptimizer.java
@@ -0,0 +1,194 @@
+/*
+ * 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.math3.optimization.general;
+
+import org.apache.commons.math3.exception.ConvergenceException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.MathInternalError;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.linear.ArrayRealVector;
+import org.apache.commons.math3.linear.BlockRealMatrix;
+import org.apache.commons.math3.linear.DecompositionSolver;
+import org.apache.commons.math3.linear.LUDecomposition;
+import org.apache.commons.math3.linear.QRDecomposition;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.linear.SingularMatrixException;
+import org.apache.commons.math3.optimization.ConvergenceChecker;
+import org.apache.commons.math3.optimization.SimpleVectorValueChecker;
+import org.apache.commons.math3.optimization.PointVectorValuePair;
+
+/**
+ * Gauss-Newton least-squares solver.
+ * <p>
+ * This class solve a least-square problem by solving the normal equations
+ * of the linearized problem at each iteration. Either LU decomposition or
+ * QR decomposition can be used to solve the normal equations. LU decomposition
+ * is faster but QR decomposition is more robust for difficult problems.
+ * </p>
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 2.0
+ *
+ */
+@Deprecated
+public class GaussNewtonOptimizer extends AbstractLeastSquaresOptimizer {
+    /** Indicator for using LU decomposition. */
+    private final boolean useLU;
+
+    /**
+     * Simple constructor with default settings.
+     * The normal equations will be solved using LU decomposition and the
+     * convergence check is set to a {@link SimpleVectorValueChecker}
+     * with default tolerances.
+     * @deprecated See {@link SimpleVectorValueChecker#SimpleVectorValueChecker()}
+     */
+    @Deprecated
+    public GaussNewtonOptimizer() {
+        this(true);
+    }
+
+    /**
+     * Simple constructor with default settings.
+     * The normal equations will be solved using LU decomposition.
+     *
+     * @param checker Convergence checker.
+     */
+    public GaussNewtonOptimizer(ConvergenceChecker<PointVectorValuePair> checker) {
+        this(true, checker);
+    }
+
+    /**
+     * Simple constructor with default settings.
+     * The convergence check is set to a {@link SimpleVectorValueChecker}
+     * with default tolerances.
+     *
+     * @param useLU If {@code true}, the normal equations will be solved
+     * using LU decomposition, otherwise they will be solved using QR
+     * decomposition.
+     * @deprecated See {@link SimpleVectorValueChecker#SimpleVectorValueChecker()}
+     */
+    @Deprecated
+    public GaussNewtonOptimizer(final boolean useLU) {
+        this(useLU, new SimpleVectorValueChecker());
+    }
+
+    /**
+     * @param useLU If {@code true}, the normal equations will be solved
+     * using LU decomposition, otherwise they will be solved using QR
+     * decomposition.
+     * @param checker Convergence checker.
+     */
+    public GaussNewtonOptimizer(final boolean useLU,
+                                ConvergenceChecker<PointVectorValuePair> checker) {
+        super(checker);
+        this.useLU = useLU;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public PointVectorValuePair doOptimize() {
+        final ConvergenceChecker<PointVectorValuePair> checker
+            = getConvergenceChecker();
+
+        // Computation will be useless without a checker (see "for-loop").
+        if (checker == null) {
+            throw new NullArgumentException();
+        }
+
+        final double[] targetValues = getTarget();
+        final int nR = targetValues.length; // Number of observed data.
+
+        final RealMatrix weightMatrix = getWeight();
+        // Diagonal of the weight matrix.
+        final double[] residualsWeights = new double[nR];
+        for (int i = 0; i < nR; i++) {
+            residualsWeights[i] = weightMatrix.getEntry(i, i);
+        }
+
+        final double[] currentPoint = getStartPoint();
+        final int nC = currentPoint.length;
+
+        // iterate until convergence is reached
+        PointVectorValuePair current = null;
+        int iter = 0;
+        for (boolean converged = false; !converged;) {
+            ++iter;
+
+            // evaluate the objective function and its jacobian
+            PointVectorValuePair previous = current;
+            // Value of the objective function at "currentPoint".
+            final double[] currentObjective = computeObjectiveValue(currentPoint);
+            final double[] currentResiduals = computeResiduals(currentObjective);
+            final RealMatrix weightedJacobian = computeWeightedJacobian(currentPoint);
+            current = new PointVectorValuePair(currentPoint, currentObjective);
+
+            // build the linear problem
+            final double[]   b = new double[nC];
+            final double[][] a = new double[nC][nC];
+            for (int i = 0; i < nR; ++i) {
+
+                final double[] grad   = weightedJacobian.getRow(i);
+                final double weight   = residualsWeights[i];
+                final double residual = currentResiduals[i];
+
+                // compute the normal equation
+                final double wr = weight * residual;
+                for (int j = 0; j < nC; ++j) {
+                    b[j] += wr * grad[j];
+                }
+
+                // build the contribution matrix for measurement i
+                for (int k = 0; k < nC; ++k) {
+                    double[] ak = a[k];
+                    double wgk = weight * grad[k];
+                    for (int l = 0; l < nC; ++l) {
+                        ak[l] += wgk * grad[l];
+                    }
+                }
+            }
+
+            try {
+                // solve the linearized least squares problem
+                RealMatrix mA = new BlockRealMatrix(a);
+                DecompositionSolver solver = useLU ?
+                        new LUDecomposition(mA).getSolver() :
+                        new QRDecomposition(mA).getSolver();
+                final double[] dX = solver.solve(new ArrayRealVector(b, false)).toArray();
+                // update the estimated parameters
+                for (int i = 0; i < nC; ++i) {
+                    currentPoint[i] += dX[i];
+                }
+            } catch (SingularMatrixException e) {
+                throw new ConvergenceException(LocalizedFormats.UNABLE_TO_SOLVE_SINGULAR_PROBLEM);
+            }
+
+            // Check convergence.
+            if (previous != null) {
+                converged = checker.converged(iter, previous, current);
+                if (converged) {
+                    cost = computeCost(currentResiduals);
+                    // Update (deprecated) "point" field.
+                    point = current.getPoint();
+                    return current;
+                }
+            }
+        }
+        // Must never happen.
+        throw new MathInternalError();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/general/LevenbergMarquardtOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/general/LevenbergMarquardtOptimizer.java
new file mode 100644
index 0000000..a29cafc
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/general/LevenbergMarquardtOptimizer.java
@@ -0,0 +1,943 @@
+/*
+ * 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.math3.optimization.general;
+
+import java.util.Arrays;
+
+import org.apache.commons.math3.exception.ConvergenceException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.optimization.PointVectorValuePair;
+import org.apache.commons.math3.optimization.ConvergenceChecker;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.util.Precision;
+import org.apache.commons.math3.util.FastMath;
+
+
+/**
+ * This class solves a least squares problem using the Levenberg-Marquardt algorithm.
+ *
+ * <p>This implementation <em>should</em> work even for over-determined systems
+ * (i.e. systems having more point than equations). Over-determined systems
+ * are solved by ignoring the point which have the smallest impact according
+ * to their jacobian column norm. Only the rank of the matrix and some loop bounds
+ * are changed to implement this.</p>
+ *
+ * <p>The resolution engine is a simple translation of the MINPACK <a
+ * href="http://www.netlib.org/minpack/lmder.f">lmder</a> routine with minor
+ * changes. The changes include the over-determined resolution, the use of
+ * inherited convergence checker and the Q.R. decomposition which has been
+ * rewritten following the algorithm described in the
+ * P. Lascaux and R. Theodor book <i>Analyse num&eacute;rique matricielle
+ * appliqu&eacute;e &agrave; l'art de l'ing&eacute;nieur</i>, Masson 1986.</p>
+ * <p>The authors of the original fortran version are:
+ * <ul>
+ * <li>Argonne National Laboratory. MINPACK project. March 1980</li>
+ * <li>Burton S. Garbow</li>
+ * <li>Kenneth E. Hillstrom</li>
+ * <li>Jorge J. More</li>
+ * </ul>
+ * The redistribution policy for MINPACK is available <a
+ * href="http://www.netlib.org/minpack/disclaimer">here</a>, for convenience, it
+ * is reproduced below.</p>
+ *
+ * <table border="0" width="80%" cellpadding="10" align="center" bgcolor="#E0E0E0">
+ * <tr><td>
+ *    Minpack Copyright Notice (1999) University of Chicago.
+ *    All rights reserved
+ * </td></tr>
+ * <tr><td>
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * <ol>
+ *  <li>Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.</li>
+ * <li>Redistributions in binary form must reproduce the above
+ *     copyright notice, this list of conditions and the following
+ *     disclaimer in the documentation and/or other materials provided
+ *     with the distribution.</li>
+ * <li>The end-user documentation included with the redistribution, if any,
+ *     must include the following acknowledgment:
+ *     <code>This product includes software developed by the University of
+ *           Chicago, as Operator of Argonne National Laboratory.</code>
+ *     Alternately, this acknowledgment may appear in the software itself,
+ *     if and wherever such third-party acknowledgments normally appear.</li>
+ * <li><strong>WARRANTY DISCLAIMER. THE SOFTWARE IS SUPPLIED "AS IS"
+ *     WITHOUT WARRANTY OF ANY KIND. THE COPYRIGHT HOLDER, THE
+ *     UNITED STATES, THE UNITED STATES DEPARTMENT OF ENERGY, AND
+ *     THEIR EMPLOYEES: (1) DISCLAIM ANY WARRANTIES, EXPRESS OR
+ *     IMPLIED, INCLUDING BUT NOT LIMITED TO ANY IMPLIED WARRANTIES
+ *     OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE
+ *     OR NON-INFRINGEMENT, (2) DO NOT ASSUME ANY LEGAL LIABILITY
+ *     OR RESPONSIBILITY FOR THE ACCURACY, COMPLETENESS, OR
+ *     USEFULNESS OF THE SOFTWARE, (3) DO NOT REPRESENT THAT USE OF
+ *     THE SOFTWARE WOULD NOT INFRINGE PRIVATELY OWNED RIGHTS, (4)
+ *     DO NOT WARRANT THAT THE SOFTWARE WILL FUNCTION
+ *     UNINTERRUPTED, THAT IT IS ERROR-FREE OR THAT ANY ERRORS WILL
+ *     BE CORRECTED.</strong></li>
+ * <li><strong>LIMITATION OF LIABILITY. IN NO EVENT WILL THE COPYRIGHT
+ *     HOLDER, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF
+ *     ENERGY, OR THEIR EMPLOYEES: BE LIABLE FOR ANY INDIRECT,
+ *     INCIDENTAL, CONSEQUENTIAL, SPECIAL OR PUNITIVE DAMAGES OF
+ *     ANY KIND OR NATURE, INCLUDING BUT NOT LIMITED TO LOSS OF
+ *     PROFITS OR LOSS OF DATA, FOR ANY REASON WHATSOEVER, WHETHER
+ *     SUCH LIABILITY IS ASSERTED ON THE BASIS OF CONTRACT, TORT
+ *     (INCLUDING NEGLIGENCE OR STRICT LIABILITY), OR OTHERWISE,
+ *     EVEN IF ANY OF SAID PARTIES HAS BEEN WARNED OF THE
+ *     POSSIBILITY OF SUCH LOSS OR DAMAGES.</strong></li>
+ * <ol></td></tr>
+ * </table>
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 2.0
+ *
+ */
+@Deprecated
+public class LevenbergMarquardtOptimizer extends AbstractLeastSquaresOptimizer {
+    /** Number of solved point. */
+    private int solvedCols;
+    /** Diagonal elements of the R matrix in the Q.R. decomposition. */
+    private double[] diagR;
+    /** Norms of the columns of the jacobian matrix. */
+    private double[] jacNorm;
+    /** Coefficients of the Householder transforms vectors. */
+    private double[] beta;
+    /** Columns permutation array. */
+    private int[] permutation;
+    /** Rank of the jacobian matrix. */
+    private int rank;
+    /** Levenberg-Marquardt parameter. */
+    private double lmPar;
+    /** Parameters evolution direction associated with lmPar. */
+    private double[] lmDir;
+    /** Positive input variable used in determining the initial step bound. */
+    private final double initialStepBoundFactor;
+    /** Desired relative error in the sum of squares. */
+    private final double costRelativeTolerance;
+    /**  Desired relative error in the approximate solution parameters. */
+    private final double parRelativeTolerance;
+    /** Desired max cosine on the orthogonality between the function vector
+     * and the columns of the jacobian. */
+    private final double orthoTolerance;
+    /** Threshold for QR ranking. */
+    private final double qrRankingThreshold;
+    /** Weighted residuals. */
+    private double[] weightedResidual;
+    /** Weighted Jacobian. */
+    private double[][] weightedJacobian;
+
+    /**
+     * Build an optimizer for least squares problems with default values
+     * for all the tuning parameters (see the {@link
+     * #LevenbergMarquardtOptimizer(double,double,double,double,double)
+     * other contructor}.
+     * The default values for the algorithm settings are:
+     * <ul>
+     *  <li>Initial step bound factor: 100</li>
+     *  <li>Cost relative tolerance: 1e-10</li>
+     *  <li>Parameters relative tolerance: 1e-10</li>
+     *  <li>Orthogonality tolerance: 1e-10</li>
+     *  <li>QR ranking threshold: {@link Precision#SAFE_MIN}</li>
+     * </ul>
+     */
+    public LevenbergMarquardtOptimizer() {
+        this(100, 1e-10, 1e-10, 1e-10, Precision.SAFE_MIN);
+    }
+
+    /**
+     * Constructor that allows the specification of a custom convergence
+     * checker.
+     * Note that all the usual convergence checks will be <em>disabled</em>.
+     * The default values for the algorithm settings are:
+     * <ul>
+     *  <li>Initial step bound factor: 100</li>
+     *  <li>Cost relative tolerance: 1e-10</li>
+     *  <li>Parameters relative tolerance: 1e-10</li>
+     *  <li>Orthogonality tolerance: 1e-10</li>
+     *  <li>QR ranking threshold: {@link Precision#SAFE_MIN}</li>
+     * </ul>
+     *
+     * @param checker Convergence checker.
+     */
+    public LevenbergMarquardtOptimizer(ConvergenceChecker<PointVectorValuePair> checker) {
+        this(100, checker, 1e-10, 1e-10, 1e-10, Precision.SAFE_MIN);
+    }
+
+    /**
+     * Constructor that allows the specification of a custom convergence
+     * checker, in addition to the standard ones.
+     *
+     * @param initialStepBoundFactor Positive input variable used in
+     * determining the initial step bound. This bound is set to the
+     * product of initialStepBoundFactor and the euclidean norm of
+     * {@code diag * x} if non-zero, or else to {@code initialStepBoundFactor}
+     * itself. In most cases factor should lie in the interval
+     * {@code (0.1, 100.0)}. {@code 100} is a generally recommended value.
+     * @param checker Convergence checker.
+     * @param costRelativeTolerance Desired relative error in the sum of
+     * squares.
+     * @param parRelativeTolerance Desired relative error in the approximate
+     * solution parameters.
+     * @param orthoTolerance Desired max cosine on the orthogonality between
+     * the function vector and the columns of the Jacobian.
+     * @param threshold Desired threshold for QR ranking. If the squared norm
+     * of a column vector is smaller or equal to this threshold during QR
+     * decomposition, it is considered to be a zero vector and hence the rank
+     * of the matrix is reduced.
+     */
+    public LevenbergMarquardtOptimizer(double initialStepBoundFactor,
+                                       ConvergenceChecker<PointVectorValuePair> checker,
+                                       double costRelativeTolerance,
+                                       double parRelativeTolerance,
+                                       double orthoTolerance,
+                                       double threshold) {
+        super(checker);
+        this.initialStepBoundFactor = initialStepBoundFactor;
+        this.costRelativeTolerance = costRelativeTolerance;
+        this.parRelativeTolerance = parRelativeTolerance;
+        this.orthoTolerance = orthoTolerance;
+        this.qrRankingThreshold = threshold;
+    }
+
+    /**
+     * Build an optimizer for least squares problems with default values
+     * for some of the tuning parameters (see the {@link
+     * #LevenbergMarquardtOptimizer(double,double,double,double,double)
+     * other contructor}.
+     * The default values for the algorithm settings are:
+     * <ul>
+     *  <li>Initial step bound factor}: 100</li>
+     *  <li>QR ranking threshold}: {@link Precision#SAFE_MIN}</li>
+     * </ul>
+     *
+     * @param costRelativeTolerance Desired relative error in the sum of
+     * squares.
+     * @param parRelativeTolerance Desired relative error in the approximate
+     * solution parameters.
+     * @param orthoTolerance Desired max cosine on the orthogonality between
+     * the function vector and the columns of the Jacobian.
+     */
+    public LevenbergMarquardtOptimizer(double costRelativeTolerance,
+                                       double parRelativeTolerance,
+                                       double orthoTolerance) {
+        this(100,
+             costRelativeTolerance, parRelativeTolerance, orthoTolerance,
+             Precision.SAFE_MIN);
+    }
+
+    /**
+     * The arguments control the behaviour of the default convergence checking
+     * procedure.
+     * Additional criteria can defined through the setting of a {@link
+     * ConvergenceChecker}.
+     *
+     * @param initialStepBoundFactor Positive input variable used in
+     * determining the initial step bound. This bound is set to the
+     * product of initialStepBoundFactor and the euclidean norm of
+     * {@code diag * x} if non-zero, or else to {@code initialStepBoundFactor}
+     * itself. In most cases factor should lie in the interval
+     * {@code (0.1, 100.0)}. {@code 100} is a generally recommended value.
+     * @param costRelativeTolerance Desired relative error in the sum of
+     * squares.
+     * @param parRelativeTolerance Desired relative error in the approximate
+     * solution parameters.
+     * @param orthoTolerance Desired max cosine on the orthogonality between
+     * the function vector and the columns of the Jacobian.
+     * @param threshold Desired threshold for QR ranking. If the squared norm
+     * of a column vector is smaller or equal to this threshold during QR
+     * decomposition, it is considered to be a zero vector and hence the rank
+     * of the matrix is reduced.
+     */
+    public LevenbergMarquardtOptimizer(double initialStepBoundFactor,
+                                       double costRelativeTolerance,
+                                       double parRelativeTolerance,
+                                       double orthoTolerance,
+                                       double threshold) {
+        super(null); // No custom convergence criterion.
+        this.initialStepBoundFactor = initialStepBoundFactor;
+        this.costRelativeTolerance = costRelativeTolerance;
+        this.parRelativeTolerance = parRelativeTolerance;
+        this.orthoTolerance = orthoTolerance;
+        this.qrRankingThreshold = threshold;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected PointVectorValuePair doOptimize() {
+        final int nR = getTarget().length; // Number of observed data.
+        final double[] currentPoint = getStartPoint();
+        final int nC = currentPoint.length; // Number of parameters.
+
+        // arrays shared with the other private methods
+        solvedCols  = FastMath.min(nR, nC);
+        diagR       = new double[nC];
+        jacNorm     = new double[nC];
+        beta        = new double[nC];
+        permutation = new int[nC];
+        lmDir       = new double[nC];
+
+        // local point
+        double   delta   = 0;
+        double   xNorm   = 0;
+        double[] diag    = new double[nC];
+        double[] oldX    = new double[nC];
+        double[] oldRes  = new double[nR];
+        double[] oldObj  = new double[nR];
+        double[] qtf     = new double[nR];
+        double[] work1   = new double[nC];
+        double[] work2   = new double[nC];
+        double[] work3   = new double[nC];
+
+        final RealMatrix weightMatrixSqrt = getWeightSquareRoot();
+
+        // Evaluate the function at the starting point and calculate its norm.
+        double[] currentObjective = computeObjectiveValue(currentPoint);
+        double[] currentResiduals = computeResiduals(currentObjective);
+        PointVectorValuePair current = new PointVectorValuePair(currentPoint, currentObjective);
+        double currentCost = computeCost(currentResiduals);
+
+        // Outer loop.
+        lmPar = 0;
+        boolean firstIteration = true;
+        int iter = 0;
+        final ConvergenceChecker<PointVectorValuePair> checker = getConvergenceChecker();
+        while (true) {
+            ++iter;
+            final PointVectorValuePair previous = current;
+
+            // QR decomposition of the jacobian matrix
+            qrDecomposition(computeWeightedJacobian(currentPoint));
+
+            weightedResidual = weightMatrixSqrt.operate(currentResiduals);
+            for (int i = 0; i < nR; i++) {
+                qtf[i] = weightedResidual[i];
+            }
+
+            // compute Qt.res
+            qTy(qtf);
+
+            // now we don't need Q anymore,
+            // so let jacobian contain the R matrix with its diagonal elements
+            for (int k = 0; k < solvedCols; ++k) {
+                int pk = permutation[k];
+                weightedJacobian[k][pk] = diagR[pk];
+            }
+
+            if (firstIteration) {
+                // scale the point according to the norms of the columns
+                // of the initial jacobian
+                xNorm = 0;
+                for (int k = 0; k < nC; ++k) {
+                    double dk = jacNorm[k];
+                    if (dk == 0) {
+                        dk = 1.0;
+                    }
+                    double xk = dk * currentPoint[k];
+                    xNorm  += xk * xk;
+                    diag[k] = dk;
+                }
+                xNorm = FastMath.sqrt(xNorm);
+
+                // initialize the step bound delta
+                delta = (xNorm == 0) ? initialStepBoundFactor : (initialStepBoundFactor * xNorm);
+            }
+
+            // check orthogonality between function vector and jacobian columns
+            double maxCosine = 0;
+            if (currentCost != 0) {
+                for (int j = 0; j < solvedCols; ++j) {
+                    int    pj = permutation[j];
+                    double s  = jacNorm[pj];
+                    if (s != 0) {
+                        double sum = 0;
+                        for (int i = 0; i <= j; ++i) {
+                            sum += weightedJacobian[i][pj] * qtf[i];
+                        }
+                        maxCosine = FastMath.max(maxCosine, FastMath.abs(sum) / (s * currentCost));
+                    }
+                }
+            }
+            if (maxCosine <= orthoTolerance) {
+                // Convergence has been reached.
+                setCost(currentCost);
+                // Update (deprecated) "point" field.
+                point = current.getPoint();
+                return current;
+            }
+
+            // rescale if necessary
+            for (int j = 0; j < nC; ++j) {
+                diag[j] = FastMath.max(diag[j], jacNorm[j]);
+            }
+
+            // Inner loop.
+            for (double ratio = 0; ratio < 1.0e-4;) {
+
+                // save the state
+                for (int j = 0; j < solvedCols; ++j) {
+                    int pj = permutation[j];
+                    oldX[pj] = currentPoint[pj];
+                }
+                final double previousCost = currentCost;
+                double[] tmpVec = weightedResidual;
+                weightedResidual = oldRes;
+                oldRes    = tmpVec;
+                tmpVec    = currentObjective;
+                currentObjective = oldObj;
+                oldObj    = tmpVec;
+
+                // determine the Levenberg-Marquardt parameter
+                determineLMParameter(qtf, delta, diag, work1, work2, work3);
+
+                // compute the new point and the norm of the evolution direction
+                double lmNorm = 0;
+                for (int j = 0; j < solvedCols; ++j) {
+                    int pj = permutation[j];
+                    lmDir[pj] = -lmDir[pj];
+                    currentPoint[pj] = oldX[pj] + lmDir[pj];
+                    double s = diag[pj] * lmDir[pj];
+                    lmNorm  += s * s;
+                }
+                lmNorm = FastMath.sqrt(lmNorm);
+                // on the first iteration, adjust the initial step bound.
+                if (firstIteration) {
+                    delta = FastMath.min(delta, lmNorm);
+                }
+
+                // Evaluate the function at x + p and calculate its norm.
+                currentObjective = computeObjectiveValue(currentPoint);
+                currentResiduals = computeResiduals(currentObjective);
+                current = new PointVectorValuePair(currentPoint, currentObjective);
+                currentCost = computeCost(currentResiduals);
+
+                // compute the scaled actual reduction
+                double actRed = -1.0;
+                if (0.1 * currentCost < previousCost) {
+                    double r = currentCost / previousCost;
+                    actRed = 1.0 - r * r;
+                }
+
+                // compute the scaled predicted reduction
+                // and the scaled directional derivative
+                for (int j = 0; j < solvedCols; ++j) {
+                    int pj = permutation[j];
+                    double dirJ = lmDir[pj];
+                    work1[j] = 0;
+                    for (int i = 0; i <= j; ++i) {
+                        work1[i] += weightedJacobian[i][pj] * dirJ;
+                    }
+                }
+                double coeff1 = 0;
+                for (int j = 0; j < solvedCols; ++j) {
+                    coeff1 += work1[j] * work1[j];
+                }
+                double pc2 = previousCost * previousCost;
+                coeff1 /= pc2;
+                double coeff2 = lmPar * lmNorm * lmNorm / pc2;
+                double preRed = coeff1 + 2 * coeff2;
+                double dirDer = -(coeff1 + coeff2);
+
+                // ratio of the actual to the predicted reduction
+                ratio = (preRed == 0) ? 0 : (actRed / preRed);
+
+                // update the step bound
+                if (ratio <= 0.25) {
+                    double tmp =
+                        (actRed < 0) ? (0.5 * dirDer / (dirDer + 0.5 * actRed)) : 0.5;
+                        if ((0.1 * currentCost >= previousCost) || (tmp < 0.1)) {
+                            tmp = 0.1;
+                        }
+                        delta = tmp * FastMath.min(delta, 10.0 * lmNorm);
+                        lmPar /= tmp;
+                } else if ((lmPar == 0) || (ratio >= 0.75)) {
+                    delta = 2 * lmNorm;
+                    lmPar *= 0.5;
+                }
+
+                // test for successful iteration.
+                if (ratio >= 1.0e-4) {
+                    // successful iteration, update the norm
+                    firstIteration = false;
+                    xNorm = 0;
+                    for (int k = 0; k < nC; ++k) {
+                        double xK = diag[k] * currentPoint[k];
+                        xNorm += xK * xK;
+                    }
+                    xNorm = FastMath.sqrt(xNorm);
+
+                    // tests for convergence.
+                    if (checker != null && checker.converged(iter, previous, current)) {
+                        setCost(currentCost);
+                        // Update (deprecated) "point" field.
+                        point = current.getPoint();
+                        return current;
+                    }
+                } else {
+                    // failed iteration, reset the previous values
+                    currentCost = previousCost;
+                    for (int j = 0; j < solvedCols; ++j) {
+                        int pj = permutation[j];
+                        currentPoint[pj] = oldX[pj];
+                    }
+                    tmpVec    = weightedResidual;
+                    weightedResidual = oldRes;
+                    oldRes    = tmpVec;
+                    tmpVec    = currentObjective;
+                    currentObjective = oldObj;
+                    oldObj    = tmpVec;
+                    // Reset "current" to previous values.
+                    current = new PointVectorValuePair(currentPoint, currentObjective);
+                }
+
+                // Default convergence criteria.
+                if ((FastMath.abs(actRed) <= costRelativeTolerance &&
+                     preRed <= costRelativeTolerance &&
+                     ratio <= 2.0) ||
+                    delta <= parRelativeTolerance * xNorm) {
+                    setCost(currentCost);
+                    // Update (deprecated) "point" field.
+                    point = current.getPoint();
+                    return current;
+                }
+
+                // tests for termination and stringent tolerances
+                // (2.2204e-16 is the machine epsilon for IEEE754)
+                if ((FastMath.abs(actRed) <= 2.2204e-16) && (preRed <= 2.2204e-16) && (ratio <= 2.0)) {
+                    throw new ConvergenceException(LocalizedFormats.TOO_SMALL_COST_RELATIVE_TOLERANCE,
+                                                   costRelativeTolerance);
+                } else if (delta <= 2.2204e-16 * xNorm) {
+                    throw new ConvergenceException(LocalizedFormats.TOO_SMALL_PARAMETERS_RELATIVE_TOLERANCE,
+                                                   parRelativeTolerance);
+                } else if (maxCosine <= 2.2204e-16)  {
+                    throw new ConvergenceException(LocalizedFormats.TOO_SMALL_ORTHOGONALITY_TOLERANCE,
+                                                   orthoTolerance);
+                }
+            }
+        }
+    }
+
+    /**
+     * Determine the Levenberg-Marquardt parameter.
+     * <p>This implementation is a translation in Java of the MINPACK
+     * <a href="http://www.netlib.org/minpack/lmpar.f">lmpar</a>
+     * routine.</p>
+     * <p>This method sets the lmPar and lmDir attributes.</p>
+     * <p>The authors of the original fortran function are:</p>
+     * <ul>
+     *   <li>Argonne National Laboratory. MINPACK project. March 1980</li>
+     *   <li>Burton  S. Garbow</li>
+     *   <li>Kenneth E. Hillstrom</li>
+     *   <li>Jorge   J. More</li>
+     * </ul>
+     * <p>Luc Maisonobe did the Java translation.</p>
+     *
+     * @param qy array containing qTy
+     * @param delta upper bound on the euclidean norm of diagR * lmDir
+     * @param diag diagonal matrix
+     * @param work1 work array
+     * @param work2 work array
+     * @param work3 work array
+     */
+    private void determineLMParameter(double[] qy, double delta, double[] diag,
+                                      double[] work1, double[] work2, double[] work3) {
+        final int nC = weightedJacobian[0].length;
+
+        // compute and store in x the gauss-newton direction, if the
+        // jacobian is rank-deficient, obtain a least squares solution
+        for (int j = 0; j < rank; ++j) {
+            lmDir[permutation[j]] = qy[j];
+        }
+        for (int j = rank; j < nC; ++j) {
+            lmDir[permutation[j]] = 0;
+        }
+        for (int k = rank - 1; k >= 0; --k) {
+            int pk = permutation[k];
+            double ypk = lmDir[pk] / diagR[pk];
+            for (int i = 0; i < k; ++i) {
+                lmDir[permutation[i]] -= ypk * weightedJacobian[i][pk];
+            }
+            lmDir[pk] = ypk;
+        }
+
+        // evaluate the function at the origin, and test
+        // for acceptance of the Gauss-Newton direction
+        double dxNorm = 0;
+        for (int j = 0; j < solvedCols; ++j) {
+            int pj = permutation[j];
+            double s = diag[pj] * lmDir[pj];
+            work1[pj] = s;
+            dxNorm += s * s;
+        }
+        dxNorm = FastMath.sqrt(dxNorm);
+        double fp = dxNorm - delta;
+        if (fp <= 0.1 * delta) {
+            lmPar = 0;
+            return;
+        }
+
+        // if the jacobian is not rank deficient, the Newton step provides
+        // a lower bound, parl, for the zero of the function,
+        // otherwise set this bound to zero
+        double sum2;
+        double parl = 0;
+        if (rank == solvedCols) {
+            for (int j = 0; j < solvedCols; ++j) {
+                int pj = permutation[j];
+                work1[pj] *= diag[pj] / dxNorm;
+            }
+            sum2 = 0;
+            for (int j = 0; j < solvedCols; ++j) {
+                int pj = permutation[j];
+                double sum = 0;
+                for (int i = 0; i < j; ++i) {
+                    sum += weightedJacobian[i][pj] * work1[permutation[i]];
+                }
+                double s = (work1[pj] - sum) / diagR[pj];
+                work1[pj] = s;
+                sum2 += s * s;
+            }
+            parl = fp / (delta * sum2);
+        }
+
+        // calculate an upper bound, paru, for the zero of the function
+        sum2 = 0;
+        for (int j = 0; j < solvedCols; ++j) {
+            int pj = permutation[j];
+            double sum = 0;
+            for (int i = 0; i <= j; ++i) {
+                sum += weightedJacobian[i][pj] * qy[i];
+            }
+            sum /= diag[pj];
+            sum2 += sum * sum;
+        }
+        double gNorm = FastMath.sqrt(sum2);
+        double paru = gNorm / delta;
+        if (paru == 0) {
+            // 2.2251e-308 is the smallest positive real for IEE754
+            paru = 2.2251e-308 / FastMath.min(delta, 0.1);
+        }
+
+        // if the input par lies outside of the interval (parl,paru),
+        // set par to the closer endpoint
+        lmPar = FastMath.min(paru, FastMath.max(lmPar, parl));
+        if (lmPar == 0) {
+            lmPar = gNorm / dxNorm;
+        }
+
+        for (int countdown = 10; countdown >= 0; --countdown) {
+
+            // evaluate the function at the current value of lmPar
+            if (lmPar == 0) {
+                lmPar = FastMath.max(2.2251e-308, 0.001 * paru);
+            }
+            double sPar = FastMath.sqrt(lmPar);
+            for (int j = 0; j < solvedCols; ++j) {
+                int pj = permutation[j];
+                work1[pj] = sPar * diag[pj];
+            }
+            determineLMDirection(qy, work1, work2, work3);
+
+            dxNorm = 0;
+            for (int j = 0; j < solvedCols; ++j) {
+                int pj = permutation[j];
+                double s = diag[pj] * lmDir[pj];
+                work3[pj] = s;
+                dxNorm += s * s;
+            }
+            dxNorm = FastMath.sqrt(dxNorm);
+            double previousFP = fp;
+            fp = dxNorm - delta;
+
+            // if the function is small enough, accept the current value
+            // of lmPar, also test for the exceptional cases where parl is zero
+            if ((FastMath.abs(fp) <= 0.1 * delta) ||
+                    ((parl == 0) && (fp <= previousFP) && (previousFP < 0))) {
+                return;
+            }
+
+            // compute the Newton correction
+            for (int j = 0; j < solvedCols; ++j) {
+                int pj = permutation[j];
+                work1[pj] = work3[pj] * diag[pj] / dxNorm;
+            }
+            for (int j = 0; j < solvedCols; ++j) {
+                int pj = permutation[j];
+                work1[pj] /= work2[j];
+                double tmp = work1[pj];
+                for (int i = j + 1; i < solvedCols; ++i) {
+                    work1[permutation[i]] -= weightedJacobian[i][pj] * tmp;
+                }
+            }
+            sum2 = 0;
+            for (int j = 0; j < solvedCols; ++j) {
+                double s = work1[permutation[j]];
+                sum2 += s * s;
+            }
+            double correction = fp / (delta * sum2);
+
+            // depending on the sign of the function, update parl or paru.
+            if (fp > 0) {
+                parl = FastMath.max(parl, lmPar);
+            } else if (fp < 0) {
+                paru = FastMath.min(paru, lmPar);
+            }
+
+            // compute an improved estimate for lmPar
+            lmPar = FastMath.max(parl, lmPar + correction);
+
+        }
+    }
+
+    /**
+     * Solve a*x = b and d*x = 0 in the least squares sense.
+     * <p>This implementation is a translation in Java of the MINPACK
+     * <a href="http://www.netlib.org/minpack/qrsolv.f">qrsolv</a>
+     * routine.</p>
+     * <p>This method sets the lmDir and lmDiag attributes.</p>
+     * <p>The authors of the original fortran function are:</p>
+     * <ul>
+     *   <li>Argonne National Laboratory. MINPACK project. March 1980</li>
+     *   <li>Burton  S. Garbow</li>
+     *   <li>Kenneth E. Hillstrom</li>
+     *   <li>Jorge   J. More</li>
+     * </ul>
+     * <p>Luc Maisonobe did the Java translation.</p>
+     *
+     * @param qy array containing qTy
+     * @param diag diagonal matrix
+     * @param lmDiag diagonal elements associated with lmDir
+     * @param work work array
+     */
+    private void determineLMDirection(double[] qy, double[] diag,
+                                      double[] lmDiag, double[] work) {
+
+        // copy R and Qty to preserve input and initialize s
+        //  in particular, save the diagonal elements of R in lmDir
+        for (int j = 0; j < solvedCols; ++j) {
+            int pj = permutation[j];
+            for (int i = j + 1; i < solvedCols; ++i) {
+                weightedJacobian[i][pj] = weightedJacobian[j][permutation[i]];
+            }
+            lmDir[j] = diagR[pj];
+            work[j]  = qy[j];
+        }
+
+        // eliminate the diagonal matrix d using a Givens rotation
+        for (int j = 0; j < solvedCols; ++j) {
+
+            // prepare the row of d to be eliminated, locating the
+            // diagonal element using p from the Q.R. factorization
+            int pj = permutation[j];
+            double dpj = diag[pj];
+            if (dpj != 0) {
+                Arrays.fill(lmDiag, j + 1, lmDiag.length, 0);
+            }
+            lmDiag[j] = dpj;
+
+            //  the transformations to eliminate the row of d
+            // modify only a single element of Qty
+            // beyond the first n, which is initially zero.
+            double qtbpj = 0;
+            for (int k = j; k < solvedCols; ++k) {
+                int pk = permutation[k];
+
+                // determine a Givens rotation which eliminates the
+                // appropriate element in the current row of d
+                if (lmDiag[k] != 0) {
+
+                    final double sin;
+                    final double cos;
+                    double rkk = weightedJacobian[k][pk];
+                    if (FastMath.abs(rkk) < FastMath.abs(lmDiag[k])) {
+                        final double cotan = rkk / lmDiag[k];
+                        sin   = 1.0 / FastMath.sqrt(1.0 + cotan * cotan);
+                        cos   = sin * cotan;
+                    } else {
+                        final double tan = lmDiag[k] / rkk;
+                        cos = 1.0 / FastMath.sqrt(1.0 + tan * tan);
+                        sin = cos * tan;
+                    }
+
+                    // compute the modified diagonal element of R and
+                    // the modified element of (Qty,0)
+                    weightedJacobian[k][pk] = cos * rkk + sin * lmDiag[k];
+                    final double temp = cos * work[k] + sin * qtbpj;
+                    qtbpj = -sin * work[k] + cos * qtbpj;
+                    work[k] = temp;
+
+                    // accumulate the tranformation in the row of s
+                    for (int i = k + 1; i < solvedCols; ++i) {
+                        double rik = weightedJacobian[i][pk];
+                        final double temp2 = cos * rik + sin * lmDiag[i];
+                        lmDiag[i] = -sin * rik + cos * lmDiag[i];
+                        weightedJacobian[i][pk] = temp2;
+                    }
+                }
+            }
+
+            // store the diagonal element of s and restore
+            // the corresponding diagonal element of R
+            lmDiag[j] = weightedJacobian[j][permutation[j]];
+            weightedJacobian[j][permutation[j]] = lmDir[j];
+        }
+
+        // solve the triangular system for z, if the system is
+        // singular, then obtain a least squares solution
+        int nSing = solvedCols;
+        for (int j = 0; j < solvedCols; ++j) {
+            if ((lmDiag[j] == 0) && (nSing == solvedCols)) {
+                nSing = j;
+            }
+            if (nSing < solvedCols) {
+                work[j] = 0;
+            }
+        }
+        if (nSing > 0) {
+            for (int j = nSing - 1; j >= 0; --j) {
+                int pj = permutation[j];
+                double sum = 0;
+                for (int i = j + 1; i < nSing; ++i) {
+                    sum += weightedJacobian[i][pj] * work[i];
+                }
+                work[j] = (work[j] - sum) / lmDiag[j];
+            }
+        }
+
+        // permute the components of z back to components of lmDir
+        for (int j = 0; j < lmDir.length; ++j) {
+            lmDir[permutation[j]] = work[j];
+        }
+    }
+
+    /**
+     * Decompose a matrix A as A.P = Q.R using Householder transforms.
+     * <p>As suggested in the P. Lascaux and R. Theodor book
+     * <i>Analyse num&eacute;rique matricielle appliqu&eacute;e &agrave;
+     * l'art de l'ing&eacute;nieur</i> (Masson, 1986), instead of representing
+     * the Householder transforms with u<sub>k</sub> unit vectors such that:
+     * <pre>
+     * H<sub>k</sub> = I - 2u<sub>k</sub>.u<sub>k</sub><sup>t</sup>
+     * </pre>
+     * we use <sub>k</sub> non-unit vectors such that:
+     * <pre>
+     * H<sub>k</sub> = I - beta<sub>k</sub>v<sub>k</sub>.v<sub>k</sub><sup>t</sup>
+     * </pre>
+     * where v<sub>k</sub> = a<sub>k</sub> - alpha<sub>k</sub> e<sub>k</sub>.
+     * The beta<sub>k</sub> coefficients are provided upon exit as recomputing
+     * them from the v<sub>k</sub> vectors would be costly.</p>
+     * <p>This decomposition handles rank deficient cases since the tranformations
+     * are performed in non-increasing columns norms order thanks to columns
+     * pivoting. The diagonal elements of the R matrix are therefore also in
+     * non-increasing absolute values order.</p>
+     *
+     * @param jacobian Weighted Jacobian matrix at the current point.
+     * @exception ConvergenceException if the decomposition cannot be performed
+     */
+    private void qrDecomposition(RealMatrix jacobian) throws ConvergenceException {
+        // Code in this class assumes that the weighted Jacobian is -(W^(1/2) J),
+        // hence the multiplication by -1.
+        weightedJacobian = jacobian.scalarMultiply(-1).getData();
+
+        final int nR = weightedJacobian.length;
+        final int nC = weightedJacobian[0].length;
+
+        // initializations
+        for (int k = 0; k < nC; ++k) {
+            permutation[k] = k;
+            double norm2 = 0;
+            for (int i = 0; i < nR; ++i) {
+                double akk = weightedJacobian[i][k];
+                norm2 += akk * akk;
+            }
+            jacNorm[k] = FastMath.sqrt(norm2);
+        }
+
+        // transform the matrix column after column
+        for (int k = 0; k < nC; ++k) {
+
+            // select the column with the greatest norm on active components
+            int nextColumn = -1;
+            double ak2 = Double.NEGATIVE_INFINITY;
+            for (int i = k; i < nC; ++i) {
+                double norm2 = 0;
+                for (int j = k; j < nR; ++j) {
+                    double aki = weightedJacobian[j][permutation[i]];
+                    norm2 += aki * aki;
+                }
+                if (Double.isInfinite(norm2) || Double.isNaN(norm2)) {
+                    throw new ConvergenceException(LocalizedFormats.UNABLE_TO_PERFORM_QR_DECOMPOSITION_ON_JACOBIAN,
+                                                   nR, nC);
+                }
+                if (norm2 > ak2) {
+                    nextColumn = i;
+                    ak2        = norm2;
+                }
+            }
+            if (ak2 <= qrRankingThreshold) {
+                rank = k;
+                return;
+            }
+            int pk                  = permutation[nextColumn];
+            permutation[nextColumn] = permutation[k];
+            permutation[k]          = pk;
+
+            // choose alpha such that Hk.u = alpha ek
+            double akk   = weightedJacobian[k][pk];
+            double alpha = (akk > 0) ? -FastMath.sqrt(ak2) : FastMath.sqrt(ak2);
+            double betak = 1.0 / (ak2 - akk * alpha);
+            beta[pk]     = betak;
+
+            // transform the current column
+            diagR[pk]        = alpha;
+            weightedJacobian[k][pk] -= alpha;
+
+            // transform the remaining columns
+            for (int dk = nC - 1 - k; dk > 0; --dk) {
+                double gamma = 0;
+                for (int j = k; j < nR; ++j) {
+                    gamma += weightedJacobian[j][pk] * weightedJacobian[j][permutation[k + dk]];
+                }
+                gamma *= betak;
+                for (int j = k; j < nR; ++j) {
+                    weightedJacobian[j][permutation[k + dk]] -= gamma * weightedJacobian[j][pk];
+                }
+            }
+        }
+        rank = solvedCols;
+    }
+
+    /**
+     * Compute the product Qt.y for some Q.R. decomposition.
+     *
+     * @param y vector to multiply (will be overwritten with the result)
+     */
+    private void qTy(double[] y) {
+        final int nR = weightedJacobian.length;
+        final int nC = weightedJacobian[0].length;
+
+        for (int k = 0; k < nC; ++k) {
+            int pk = permutation[k];
+            double gamma = 0;
+            for (int i = k; i < nR; ++i) {
+                gamma += weightedJacobian[i][pk] * y[i];
+            }
+            gamma *= beta[pk];
+            for (int i = k; i < nR; ++i) {
+                y[i] -= gamma * weightedJacobian[i][pk];
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/general/NonLinearConjugateGradientOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/general/NonLinearConjugateGradientOptimizer.java
new file mode 100644
index 0000000..ee16472
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/general/NonLinearConjugateGradientOptimizer.java
@@ -0,0 +1,311 @@
+/*
+ * 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.math3.optimization.general;
+
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.solvers.BrentSolver;
+import org.apache.commons.math3.analysis.solvers.UnivariateSolver;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.optimization.GoalType;
+import org.apache.commons.math3.optimization.PointValuePair;
+import org.apache.commons.math3.optimization.SimpleValueChecker;
+import org.apache.commons.math3.optimization.ConvergenceChecker;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Non-linear conjugate gradient optimizer.
+ * <p>
+ * This class supports both the Fletcher-Reeves and the Polak-Ribi&egrave;re
+ * update formulas for the conjugate search directions. It also supports
+ * optional preconditioning.
+ * </p>
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 2.0
+ *
+ */
+@Deprecated
+public class NonLinearConjugateGradientOptimizer
+    extends AbstractScalarDifferentiableOptimizer {
+    /** Update formula for the beta parameter. */
+    private final ConjugateGradientFormula updateFormula;
+    /** Preconditioner (may be null). */
+    private final Preconditioner preconditioner;
+    /** solver to use in the line search (may be null). */
+    private final UnivariateSolver solver;
+    /** Initial step used to bracket the optimum in line search. */
+    private double initialStep;
+    /** Current point. */
+    private double[] point;
+
+    /**
+     * Constructor with default {@link SimpleValueChecker checker},
+     * {@link BrentSolver line search solver} and
+     * {@link IdentityPreconditioner preconditioner}.
+     *
+     * @param updateFormula formula to use for updating the &beta; parameter,
+     * must be one of {@link ConjugateGradientFormula#FLETCHER_REEVES} or {@link
+     * ConjugateGradientFormula#POLAK_RIBIERE}.
+     * @deprecated See {@link SimpleValueChecker#SimpleValueChecker()}
+     */
+    @Deprecated
+    public NonLinearConjugateGradientOptimizer(final ConjugateGradientFormula updateFormula) {
+        this(updateFormula,
+             new SimpleValueChecker());
+    }
+
+    /**
+     * Constructor with default {@link BrentSolver line search solver} and
+     * {@link IdentityPreconditioner preconditioner}.
+     *
+     * @param updateFormula formula to use for updating the &beta; parameter,
+     * must be one of {@link ConjugateGradientFormula#FLETCHER_REEVES} or {@link
+     * ConjugateGradientFormula#POLAK_RIBIERE}.
+     * @param checker Convergence checker.
+     */
+    public NonLinearConjugateGradientOptimizer(final ConjugateGradientFormula updateFormula,
+                                               ConvergenceChecker<PointValuePair> checker) {
+        this(updateFormula,
+             checker,
+             new BrentSolver(),
+             new IdentityPreconditioner());
+    }
+
+
+    /**
+     * Constructor with default {@link IdentityPreconditioner preconditioner}.
+     *
+     * @param updateFormula formula to use for updating the &beta; parameter,
+     * must be one of {@link ConjugateGradientFormula#FLETCHER_REEVES} or {@link
+     * ConjugateGradientFormula#POLAK_RIBIERE}.
+     * @param checker Convergence checker.
+     * @param lineSearchSolver Solver to use during line search.
+     */
+    public NonLinearConjugateGradientOptimizer(final ConjugateGradientFormula updateFormula,
+                                               ConvergenceChecker<PointValuePair> checker,
+                                               final UnivariateSolver lineSearchSolver) {
+        this(updateFormula,
+             checker,
+             lineSearchSolver,
+             new IdentityPreconditioner());
+    }
+
+    /**
+     * @param updateFormula formula to use for updating the &beta; parameter,
+     * must be one of {@link ConjugateGradientFormula#FLETCHER_REEVES} or {@link
+     * ConjugateGradientFormula#POLAK_RIBIERE}.
+     * @param checker Convergence checker.
+     * @param lineSearchSolver Solver to use during line search.
+     * @param preconditioner Preconditioner.
+     */
+    public NonLinearConjugateGradientOptimizer(final ConjugateGradientFormula updateFormula,
+                                               ConvergenceChecker<PointValuePair> checker,
+                                               final UnivariateSolver lineSearchSolver,
+                                               final Preconditioner preconditioner) {
+        super(checker);
+
+        this.updateFormula = updateFormula;
+        solver = lineSearchSolver;
+        this.preconditioner = preconditioner;
+        initialStep = 1.0;
+    }
+
+    /**
+     * Set the initial step used to bracket the optimum in line search.
+     * <p>
+     * The initial step is a factor with respect to the search direction,
+     * which itself is roughly related to the gradient of the function
+     * </p>
+     * @param initialStep initial step used to bracket the optimum in line search,
+     * if a non-positive value is used, the initial step is reset to its
+     * default value of 1.0
+     */
+    public void setInitialStep(final double initialStep) {
+        if (initialStep <= 0) {
+            this.initialStep = 1.0;
+        } else {
+            this.initialStep = initialStep;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected PointValuePair doOptimize() {
+        final ConvergenceChecker<PointValuePair> checker = getConvergenceChecker();
+        point = getStartPoint();
+        final GoalType goal = getGoalType();
+        final int n = point.length;
+        double[] r = computeObjectiveGradient(point);
+        if (goal == GoalType.MINIMIZE) {
+            for (int i = 0; i < n; ++i) {
+                r[i] = -r[i];
+            }
+        }
+
+        // Initial search direction.
+        double[] steepestDescent = preconditioner.precondition(point, r);
+        double[] searchDirection = steepestDescent.clone();
+
+        double delta = 0;
+        for (int i = 0; i < n; ++i) {
+            delta += r[i] * searchDirection[i];
+        }
+
+        PointValuePair current = null;
+        int iter = 0;
+        int maxEval = getMaxEvaluations();
+        while (true) {
+            ++iter;
+
+            final double objective = computeObjectiveValue(point);
+            PointValuePair previous = current;
+            current = new PointValuePair(point, objective);
+            if (previous != null && checker.converged(iter, previous, current)) {
+                // We have found an optimum.
+                return current;
+            }
+
+            // Find the optimal step in the search direction.
+            final UnivariateFunction lsf = new LineSearchFunction(searchDirection);
+            final double uB = findUpperBound(lsf, 0, initialStep);
+            // XXX Last parameters is set to a value close to zero in order to
+            // work around the divergence problem in the "testCircleFitting"
+            // unit test (see MATH-439).
+            final double step = solver.solve(maxEval, lsf, 0, uB, 1e-15);
+            maxEval -= solver.getEvaluations(); // Subtract used up evaluations.
+
+            // Validate new point.
+            for (int i = 0; i < point.length; ++i) {
+                point[i] += step * searchDirection[i];
+            }
+
+            r = computeObjectiveGradient(point);
+            if (goal == GoalType.MINIMIZE) {
+                for (int i = 0; i < n; ++i) {
+                    r[i] = -r[i];
+                }
+            }
+
+            // Compute beta.
+            final double deltaOld = delta;
+            final double[] newSteepestDescent = preconditioner.precondition(point, r);
+            delta = 0;
+            for (int i = 0; i < n; ++i) {
+                delta += r[i] * newSteepestDescent[i];
+            }
+
+            final double beta;
+            if (updateFormula == ConjugateGradientFormula.FLETCHER_REEVES) {
+                beta = delta / deltaOld;
+            } else {
+                double deltaMid = 0;
+                for (int i = 0; i < r.length; ++i) {
+                    deltaMid += r[i] * steepestDescent[i];
+                }
+                beta = (delta - deltaMid) / deltaOld;
+            }
+            steepestDescent = newSteepestDescent;
+
+            // Compute conjugate search direction.
+            if (iter % n == 0 ||
+                beta < 0) {
+                // Break conjugation: reset search direction.
+                searchDirection = steepestDescent.clone();
+            } else {
+                // Compute new conjugate search direction.
+                for (int i = 0; i < n; ++i) {
+                    searchDirection[i] = steepestDescent[i] + beta * searchDirection[i];
+                }
+            }
+        }
+    }
+
+    /**
+     * Find the upper bound b ensuring bracketing of a root between a and b.
+     *
+     * @param f function whose root must be bracketed.
+     * @param a lower bound of the interval.
+     * @param h initial step to try.
+     * @return b such that f(a) and f(b) have opposite signs.
+     * @throws MathIllegalStateException if no bracket can be found.
+     */
+    private double findUpperBound(final UnivariateFunction f,
+                                  final double a, final double h) {
+        final double yA = f.value(a);
+        double yB = yA;
+        for (double step = h; step < Double.MAX_VALUE; step *= FastMath.max(2, yA / yB)) {
+            final double b = a + step;
+            yB = f.value(b);
+            if (yA * yB <= 0) {
+                return b;
+            }
+        }
+        throw new MathIllegalStateException(LocalizedFormats.UNABLE_TO_BRACKET_OPTIMUM_IN_LINE_SEARCH);
+    }
+
+    /** Default identity preconditioner. */
+    public static class IdentityPreconditioner implements Preconditioner {
+
+        /** {@inheritDoc} */
+        public double[] precondition(double[] variables, double[] r) {
+            return r.clone();
+        }
+    }
+
+    /** Internal class for line search.
+     * <p>
+     * The function represented by this class is the dot product of
+     * the objective function gradient and the search direction. Its
+     * value is zero when the gradient is orthogonal to the search
+     * direction, i.e. when the objective function value is a local
+     * extremum along the search direction.
+     * </p>
+     */
+    private class LineSearchFunction implements UnivariateFunction {
+        /** Search direction. */
+        private final double[] searchDirection;
+
+        /** Simple constructor.
+         * @param searchDirection search direction
+         */
+        LineSearchFunction(final double[] searchDirection) {
+            this.searchDirection = searchDirection;
+        }
+
+        /** {@inheritDoc} */
+        public double value(double x) {
+            // current point in the search direction
+            final double[] shiftedPoint = point.clone();
+            for (int i = 0; i < shiftedPoint.length; ++i) {
+                shiftedPoint[i] += x * searchDirection[i];
+            }
+
+            // gradient of the objective function
+            final double[] gradient = computeObjectiveGradient(shiftedPoint);
+
+            // dot product with the search direction
+            double dotProduct = 0;
+            for (int i = 0; i < gradient.length; ++i) {
+                dotProduct += gradient[i] * searchDirection[i];
+            }
+
+            return dotProduct;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/general/Preconditioner.java b/src/main/java/org/apache/commons/math3/optimization/general/Preconditioner.java
new file mode 100644
index 0000000..7142e76
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/general/Preconditioner.java
@@ -0,0 +1,46 @@
+/*
+ * 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.math3.optimization.general;
+
+/**
+ * This interface represents a preconditioner for differentiable scalar
+ * objective function optimizers.
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 2.0
+ */
+@Deprecated
+public interface Preconditioner {
+    /**
+     * Precondition a search direction.
+     * <p>
+     * The returned preconditioned search direction must be computed fast or
+     * the algorithm performances will drop drastically. A classical approach
+     * is to compute only the diagonal elements of the hessian and to divide
+     * the raw search direction by these elements if they are all positive.
+     * If at least one of them is negative, it is safer to return a clone of
+     * the raw search direction as if the hessian was the identity matrix. The
+     * rationale for this simplified choice is that a negative diagonal element
+     * means the current point is far from the optimum and preconditioning will
+     * not be efficient anyway in this case.
+     * </p>
+     * @param point current point at which the search direction was computed
+     * @param r raw search direction (i.e. opposite of the gradient)
+     * @return approximation of H<sup>-1</sup>r where H is the objective function hessian
+     */
+    double[] precondition(double[] point, double[] r);
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/general/package-info.java b/src/main/java/org/apache/commons/math3/optimization/general/package-info.java
new file mode 100644
index 0000000..ba140ce
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/general/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * This package provides optimization algorithms that require derivatives.
+ *
+ */
+package org.apache.commons.math3.optimization.general;
diff --git a/src/main/java/org/apache/commons/math3/optimization/linear/AbstractLinearOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/linear/AbstractLinearOptimizer.java
new file mode 100644
index 0000000..921d877
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/linear/AbstractLinearOptimizer.java
@@ -0,0 +1,162 @@
+/*
+ * 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.math3.optimization.linear;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.optimization.GoalType;
+import org.apache.commons.math3.optimization.PointValuePair;
+
+/**
+ * Base class for implementing linear optimizers.
+ * <p>
+ * This base class handles the boilerplate methods associated to thresholds
+ * settings and iterations counters.
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 2.0
+ */
+@Deprecated
+public abstract class AbstractLinearOptimizer implements LinearOptimizer {
+
+    /** Default maximal number of iterations allowed. */
+    public static final int DEFAULT_MAX_ITERATIONS = 100;
+
+    /**
+     * Linear objective function.
+     * @since 2.1
+     */
+    private LinearObjectiveFunction function;
+
+    /**
+     * Linear constraints.
+     * @since 2.1
+     */
+    private Collection<LinearConstraint> linearConstraints;
+
+    /**
+     * Type of optimization goal: either {@link GoalType#MAXIMIZE} or {@link GoalType#MINIMIZE}.
+     * @since 2.1
+     */
+    private GoalType goal;
+
+    /**
+     * Whether to restrict the variables to non-negative values.
+     * @since 2.1
+     */
+    private boolean nonNegative;
+
+    /** Maximal number of iterations allowed. */
+    private int maxIterations;
+
+    /** Number of iterations already performed. */
+    private int iterations;
+
+    /**
+     * Simple constructor with default settings.
+     * <p>The maximal number of evaluation is set to its default value.</p>
+     */
+    protected AbstractLinearOptimizer() {
+        setMaxIterations(DEFAULT_MAX_ITERATIONS);
+    }
+
+    /**
+     * @return {@code true} if the variables are restricted to non-negative values.
+     */
+    protected boolean restrictToNonNegative() {
+        return nonNegative;
+    }
+
+    /**
+     * @return the optimization type.
+     */
+    protected GoalType getGoalType() {
+        return goal;
+    }
+
+    /**
+     * @return the optimization type.
+     */
+    protected LinearObjectiveFunction getFunction() {
+        return function;
+    }
+
+    /**
+     * @return the optimization type.
+     */
+    protected Collection<LinearConstraint> getConstraints() {
+        return Collections.unmodifiableCollection(linearConstraints);
+    }
+
+    /** {@inheritDoc} */
+    public void setMaxIterations(int maxIterations) {
+        this.maxIterations = maxIterations;
+    }
+
+    /** {@inheritDoc} */
+    public int getMaxIterations() {
+        return maxIterations;
+    }
+
+    /** {@inheritDoc} */
+    public int getIterations() {
+        return iterations;
+    }
+
+    /**
+     * Increment the iterations counter by 1.
+     * @exception MaxCountExceededException if the maximal number of iterations is exceeded
+     */
+    protected void incrementIterationsCounter()
+        throws MaxCountExceededException {
+        if (++iterations > maxIterations) {
+            throw new MaxCountExceededException(maxIterations);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public PointValuePair optimize(final LinearObjectiveFunction f,
+                                   final Collection<LinearConstraint> constraints,
+                                   final GoalType goalType, final boolean restrictToNonNegative)
+        throws MathIllegalStateException {
+
+        // store linear problem characteristics
+        this.function          = f;
+        this.linearConstraints = constraints;
+        this.goal              = goalType;
+        this.nonNegative       = restrictToNonNegative;
+
+        iterations  = 0;
+
+        // solve the problem
+        return doOptimize();
+
+    }
+
+    /**
+     * Perform the bulk of optimization algorithm.
+     * @return the point/value pair giving the optimal value for objective function
+     * @exception MathIllegalStateException if no solution fulfilling the constraints
+     * can be found in the allowed number of iterations
+     */
+    protected abstract PointValuePair doOptimize() throws MathIllegalStateException;
+
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/linear/LinearConstraint.java b/src/main/java/org/apache/commons/math3/optimization/linear/LinearConstraint.java
new file mode 100644
index 0000000..b3d70d4
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/linear/LinearConstraint.java
@@ -0,0 +1,236 @@
+/*
+ * 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.math3.optimization.linear;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+
+import org.apache.commons.math3.linear.MatrixUtils;
+import org.apache.commons.math3.linear.RealVector;
+import org.apache.commons.math3.linear.ArrayRealVector;
+
+
+/**
+ * A linear constraint for a linear optimization problem.
+ * <p>
+ * A linear constraint has one of the forms:
+ * <ul>
+ *   <li>c<sub>1</sub>x<sub>1</sub> + ... c<sub>n</sub>x<sub>n</sub> = v</li>
+ *   <li>c<sub>1</sub>x<sub>1</sub> + ... c<sub>n</sub>x<sub>n</sub> &lt;= v</li>
+ *   <li>c<sub>1</sub>x<sub>1</sub> + ... c<sub>n</sub>x<sub>n</sub> >= v</li>
+ *   <li>l<sub>1</sub>x<sub>1</sub> + ... l<sub>n</sub>x<sub>n</sub> + l<sub>cst</sub> =
+ *       r<sub>1</sub>x<sub>1</sub> + ... r<sub>n</sub>x<sub>n</sub> + r<sub>cst</sub></li>
+ *   <li>l<sub>1</sub>x<sub>1</sub> + ... l<sub>n</sub>x<sub>n</sub> + l<sub>cst</sub> &lt;=
+ *       r<sub>1</sub>x<sub>1</sub> + ... r<sub>n</sub>x<sub>n</sub> + r<sub>cst</sub></li>
+ *   <li>l<sub>1</sub>x<sub>1</sub> + ... l<sub>n</sub>x<sub>n</sub> + l<sub>cst</sub> >=
+ *       r<sub>1</sub>x<sub>1</sub> + ... r<sub>n</sub>x<sub>n</sub> + r<sub>cst</sub></li>
+ * </ul>
+ * The c<sub>i</sub>, l<sub>i</sub> or r<sub>i</sub> are the coefficients of the constraints, the x<sub>i</sub>
+ * are the coordinates of the current point and v is the value of the constraint.
+ * </p>
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 2.0
+ */
+@Deprecated
+public class LinearConstraint implements Serializable {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -764632794033034092L;
+
+    /** Coefficients of the constraint (left hand side). */
+    private final transient RealVector coefficients;
+
+    /** Relationship between left and right hand sides (=, &lt;=, >=). */
+    private final Relationship relationship;
+
+    /** Value of the constraint (right hand side). */
+    private final double value;
+
+    /**
+     * Build a constraint involving a single linear equation.
+     * <p>
+     * A linear constraint with a single linear equation has one of the forms:
+     * <ul>
+     *   <li>c<sub>1</sub>x<sub>1</sub> + ... c<sub>n</sub>x<sub>n</sub> = v</li>
+     *   <li>c<sub>1</sub>x<sub>1</sub> + ... c<sub>n</sub>x<sub>n</sub> &lt;= v</li>
+     *   <li>c<sub>1</sub>x<sub>1</sub> + ... c<sub>n</sub>x<sub>n</sub> >= v</li>
+     * </ul>
+     * </p>
+     * @param coefficients The coefficients of the constraint (left hand side)
+     * @param relationship The type of (in)equality used in the constraint
+     * @param value The value of the constraint (right hand side)
+     */
+    public LinearConstraint(final double[] coefficients, final Relationship relationship,
+                            final double value) {
+        this(new ArrayRealVector(coefficients), relationship, value);
+    }
+
+    /**
+     * Build a constraint involving a single linear equation.
+     * <p>
+     * A linear constraint with a single linear equation has one of the forms:
+     * <ul>
+     *   <li>c<sub>1</sub>x<sub>1</sub> + ... c<sub>n</sub>x<sub>n</sub> = v</li>
+     *   <li>c<sub>1</sub>x<sub>1</sub> + ... c<sub>n</sub>x<sub>n</sub> &lt;= v</li>
+     *   <li>c<sub>1</sub>x<sub>1</sub> + ... c<sub>n</sub>x<sub>n</sub> >= v</li>
+     * </ul>
+     * </p>
+     * @param coefficients The coefficients of the constraint (left hand side)
+     * @param relationship The type of (in)equality used in the constraint
+     * @param value The value of the constraint (right hand side)
+     */
+    public LinearConstraint(final RealVector coefficients, final Relationship relationship,
+                            final double value) {
+        this.coefficients = coefficients;
+        this.relationship = relationship;
+        this.value        = value;
+    }
+
+    /**
+     * Build a constraint involving two linear equations.
+     * <p>
+     * A linear constraint with two linear equation has one of the forms:
+     * <ul>
+     *   <li>l<sub>1</sub>x<sub>1</sub> + ... l<sub>n</sub>x<sub>n</sub> + l<sub>cst</sub> =
+     *       r<sub>1</sub>x<sub>1</sub> + ... r<sub>n</sub>x<sub>n</sub> + r<sub>cst</sub></li>
+     *   <li>l<sub>1</sub>x<sub>1</sub> + ... l<sub>n</sub>x<sub>n</sub> + l<sub>cst</sub> &lt;=
+     *       r<sub>1</sub>x<sub>1</sub> + ... r<sub>n</sub>x<sub>n</sub> + r<sub>cst</sub></li>
+     *   <li>l<sub>1</sub>x<sub>1</sub> + ... l<sub>n</sub>x<sub>n</sub> + l<sub>cst</sub> >=
+     *       r<sub>1</sub>x<sub>1</sub> + ... r<sub>n</sub>x<sub>n</sub> + r<sub>cst</sub></li>
+     * </ul>
+     * </p>
+     * @param lhsCoefficients The coefficients of the linear expression on the left hand side of the constraint
+     * @param lhsConstant The constant term of the linear expression on the left hand side of the constraint
+     * @param relationship The type of (in)equality used in the constraint
+     * @param rhsCoefficients The coefficients of the linear expression on the right hand side of the constraint
+     * @param rhsConstant The constant term of the linear expression on the right hand side of the constraint
+     */
+    public LinearConstraint(final double[] lhsCoefficients, final double lhsConstant,
+                            final Relationship relationship,
+                            final double[] rhsCoefficients, final double rhsConstant) {
+        double[] sub = new double[lhsCoefficients.length];
+        for (int i = 0; i < sub.length; ++i) {
+            sub[i] = lhsCoefficients[i] - rhsCoefficients[i];
+        }
+        this.coefficients = new ArrayRealVector(sub, false);
+        this.relationship = relationship;
+        this.value        = rhsConstant - lhsConstant;
+    }
+
+    /**
+     * Build a constraint involving two linear equations.
+     * <p>
+     * A linear constraint with two linear equation has one of the forms:
+     * <ul>
+     *   <li>l<sub>1</sub>x<sub>1</sub> + ... l<sub>n</sub>x<sub>n</sub> + l<sub>cst</sub> =
+     *       r<sub>1</sub>x<sub>1</sub> + ... r<sub>n</sub>x<sub>n</sub> + r<sub>cst</sub></li>
+     *   <li>l<sub>1</sub>x<sub>1</sub> + ... l<sub>n</sub>x<sub>n</sub> + l<sub>cst</sub> &lt;=
+     *       r<sub>1</sub>x<sub>1</sub> + ... r<sub>n</sub>x<sub>n</sub> + r<sub>cst</sub></li>
+     *   <li>l<sub>1</sub>x<sub>1</sub> + ... l<sub>n</sub>x<sub>n</sub> + l<sub>cst</sub> >=
+     *       r<sub>1</sub>x<sub>1</sub> + ... r<sub>n</sub>x<sub>n</sub> + r<sub>cst</sub></li>
+     * </ul>
+     * </p>
+     * @param lhsCoefficients The coefficients of the linear expression on the left hand side of the constraint
+     * @param lhsConstant The constant term of the linear expression on the left hand side of the constraint
+     * @param relationship The type of (in)equality used in the constraint
+     * @param rhsCoefficients The coefficients of the linear expression on the right hand side of the constraint
+     * @param rhsConstant The constant term of the linear expression on the right hand side of the constraint
+     */
+    public LinearConstraint(final RealVector lhsCoefficients, final double lhsConstant,
+                            final Relationship relationship,
+                            final RealVector rhsCoefficients, final double rhsConstant) {
+        this.coefficients = lhsCoefficients.subtract(rhsCoefficients);
+        this.relationship = relationship;
+        this.value        = rhsConstant - lhsConstant;
+    }
+
+    /**
+     * Get the coefficients of the constraint (left hand side).
+     * @return coefficients of the constraint (left hand side)
+     */
+    public RealVector getCoefficients() {
+        return coefficients;
+    }
+
+    /**
+     * Get the relationship between left and right hand sides.
+     * @return relationship between left and right hand sides
+     */
+    public Relationship getRelationship() {
+        return relationship;
+    }
+
+    /**
+     * Get the value of the constraint (right hand side).
+     * @return value of the constraint (right hand side)
+     */
+    public double getValue() {
+        return value;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object other) {
+
+      if (this == other) {
+        return true;
+      }
+
+      if (other instanceof LinearConstraint) {
+          LinearConstraint rhs = (LinearConstraint) other;
+          return (relationship == rhs.relationship) &&
+                 (value        == rhs.value) &&
+                 coefficients.equals(rhs.coefficients);
+      }
+      return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return relationship.hashCode() ^
+               Double.valueOf(value).hashCode() ^
+               coefficients.hashCode();
+    }
+
+    /**
+     * Serialize the instance.
+     * @param oos stream where object should be written
+     * @throws IOException if object cannot be written to stream
+     */
+    private void writeObject(ObjectOutputStream oos)
+        throws IOException {
+        oos.defaultWriteObject();
+        MatrixUtils.serializeRealVector(coefficients, oos);
+    }
+
+    /**
+     * Deserialize the instance.
+     * @param ois stream from which the object should be read
+     * @throws ClassNotFoundException if a class in the stream cannot be found
+     * @throws IOException if object cannot be read from the stream
+     */
+    private void readObject(ObjectInputStream ois)
+      throws ClassNotFoundException, IOException {
+        ois.defaultReadObject();
+        MatrixUtils.deserializeRealVector(this, "coefficients", ois);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/linear/LinearObjectiveFunction.java b/src/main/java/org/apache/commons/math3/optimization/linear/LinearObjectiveFunction.java
new file mode 100644
index 0000000..824a139
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/linear/LinearObjectiveFunction.java
@@ -0,0 +1,150 @@
+/*
+ * 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.math3.optimization.linear;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+
+import org.apache.commons.math3.linear.MatrixUtils;
+import org.apache.commons.math3.linear.RealVector;
+import org.apache.commons.math3.linear.ArrayRealVector;
+
+/**
+ * An objective function for a linear optimization problem.
+ * <p>
+ * A linear objective function has one the form:
+ * <pre>
+ * c<sub>1</sub>x<sub>1</sub> + ... c<sub>n</sub>x<sub>n</sub> + d
+ * </pre>
+ * The c<sub>i</sub> and d are the coefficients of the equation,
+ * the x<sub>i</sub> are the coordinates of the current point.
+ * </p>
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 2.0
+ */
+@Deprecated
+public class LinearObjectiveFunction implements Serializable {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -4531815507568396090L;
+
+    /** Coefficients of the constraint (c<sub>i</sub>). */
+    private final transient RealVector coefficients;
+
+    /** Constant term of the linear equation. */
+    private final double constantTerm;
+
+    /**
+     * @param coefficients The coefficients for the linear equation being optimized
+     * @param constantTerm The constant term of the linear equation
+     */
+    public LinearObjectiveFunction(double[] coefficients, double constantTerm) {
+        this(new ArrayRealVector(coefficients), constantTerm);
+    }
+
+    /**
+     * @param coefficients The coefficients for the linear equation being optimized
+     * @param constantTerm The constant term of the linear equation
+     */
+    public LinearObjectiveFunction(RealVector coefficients, double constantTerm) {
+        this.coefficients = coefficients;
+        this.constantTerm = constantTerm;
+    }
+
+    /**
+     * Get the coefficients of the linear equation being optimized.
+     * @return coefficients of the linear equation being optimized
+     */
+    public RealVector getCoefficients() {
+        return coefficients;
+    }
+
+    /**
+     * Get the constant of the linear equation being optimized.
+     * @return constant of the linear equation being optimized
+     */
+    public double getConstantTerm() {
+        return constantTerm;
+    }
+
+    /**
+     * Compute the value of the linear equation at the current point
+     * @param point point at which linear equation must be evaluated
+     * @return value of the linear equation at the current point
+     */
+    public double getValue(final double[] point) {
+        return coefficients.dotProduct(new ArrayRealVector(point, false)) + constantTerm;
+    }
+
+    /**
+     * Compute the value of the linear equation at the current point
+     * @param point point at which linear equation must be evaluated
+     * @return value of the linear equation at the current point
+     */
+    public double getValue(final RealVector point) {
+        return coefficients.dotProduct(point) + constantTerm;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object other) {
+
+      if (this == other) {
+        return true;
+      }
+
+      if (other instanceof LinearObjectiveFunction) {
+          LinearObjectiveFunction rhs = (LinearObjectiveFunction) other;
+          return (constantTerm == rhs.constantTerm) && coefficients.equals(rhs.coefficients);
+      }
+
+      return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return Double.valueOf(constantTerm).hashCode() ^ coefficients.hashCode();
+    }
+
+    /**
+     * Serialize the instance.
+     * @param oos stream where object should be written
+     * @throws IOException if object cannot be written to stream
+     */
+    private void writeObject(ObjectOutputStream oos)
+        throws IOException {
+        oos.defaultWriteObject();
+        MatrixUtils.serializeRealVector(coefficients, oos);
+    }
+
+    /**
+     * Deserialize the instance.
+     * @param ois stream from which the object should be read
+     * @throws ClassNotFoundException if a class in the stream cannot be found
+     * @throws IOException if object cannot be read from the stream
+     */
+    private void readObject(ObjectInputStream ois)
+      throws ClassNotFoundException, IOException {
+        ois.defaultReadObject();
+        MatrixUtils.deserializeRealVector(this, "coefficients", ois);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/linear/LinearOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/linear/LinearOptimizer.java
new file mode 100644
index 0000000..610d0cb
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/linear/LinearOptimizer.java
@@ -0,0 +1,92 @@
+/*
+ * 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.math3.optimization.linear;
+
+import java.util.Collection;
+
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.optimization.GoalType;
+import org.apache.commons.math3.optimization.PointValuePair;
+
+/**
+ * This interface represents an optimization algorithm for linear problems.
+ * <p>Optimization algorithms find the input point set that either {@link GoalType
+ * maximize or minimize} an objective function. In the linear case the form of
+ * the function is restricted to
+ * <pre>
+ * c<sub>1</sub>x<sub>1</sub> + ... c<sub>n</sub>x<sub>n</sub> = v
+ * </pre>
+ * and there may be linear constraints too, of one of the forms:
+ * <ul>
+ *   <li>c<sub>1</sub>x<sub>1</sub> + ... c<sub>n</sub>x<sub>n</sub> = v</li>
+ *   <li>c<sub>1</sub>x<sub>1</sub> + ... c<sub>n</sub>x<sub>n</sub> &lt;= v</li>
+ *   <li>c<sub>1</sub>x<sub>1</sub> + ... c<sub>n</sub>x<sub>n</sub> >= v</li>
+ *   <li>l<sub>1</sub>x<sub>1</sub> + ... l<sub>n</sub>x<sub>n</sub> + l<sub>cst</sub> =
+ *       r<sub>1</sub>x<sub>1</sub> + ... r<sub>n</sub>x<sub>n</sub> + r<sub>cst</sub></li>
+ *   <li>l<sub>1</sub>x<sub>1</sub> + ... l<sub>n</sub>x<sub>n</sub> + l<sub>cst</sub> &lt;=
+ *       r<sub>1</sub>x<sub>1</sub> + ... r<sub>n</sub>x<sub>n</sub> + r<sub>cst</sub></li>
+ *   <li>l<sub>1</sub>x<sub>1</sub> + ... l<sub>n</sub>x<sub>n</sub> + l<sub>cst</sub> >=
+ *       r<sub>1</sub>x<sub>1</sub> + ... r<sub>n</sub>x<sub>n</sub> + r<sub>cst</sub></li>
+ * </ul>
+ * where the c<sub>i</sub>, l<sub>i</sub> or r<sub>i</sub> are the coefficients of
+ * the constraints, the x<sub>i</sub> are the coordinates of the current point and
+ * v is the value of the constraint.
+ * </p>
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 2.0
+ */
+@Deprecated
+public interface LinearOptimizer {
+
+    /**
+     * Set the maximal number of iterations of the algorithm.
+     * @param maxIterations maximal number of function calls
+     */
+    void setMaxIterations(int maxIterations);
+
+    /**
+     * Get the maximal number of iterations of the algorithm.
+     * @return maximal number of iterations
+     */
+    int getMaxIterations();
+
+    /**
+     * Get the number of iterations realized by the algorithm.
+     * <p>
+     * The number of evaluations corresponds to the last call to the
+     * {@link #optimize(LinearObjectiveFunction, Collection, GoalType, boolean) optimize}
+     * method. It is 0 if the method has not been called yet.
+     * </p>
+     * @return number of iterations
+     */
+    int getIterations();
+
+    /**
+     * Optimizes an objective function.
+     * @param f linear objective function
+     * @param constraints linear constraints
+     * @param goalType type of optimization goal: either {@link GoalType#MAXIMIZE} or {@link GoalType#MINIMIZE}
+     * @param restrictToNonNegative whether to restrict the variables to non-negative values
+     * @return point/value pair giving the optimal value for objective function
+     * @exception MathIllegalStateException if no solution fulfilling the constraints
+     *   can be found in the allowed number of iterations
+     */
+   PointValuePair optimize(LinearObjectiveFunction f, Collection<LinearConstraint> constraints,
+                               GoalType goalType, boolean restrictToNonNegative) throws MathIllegalStateException;
+
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/linear/NoFeasibleSolutionException.java b/src/main/java/org/apache/commons/math3/optimization/linear/NoFeasibleSolutionException.java
new file mode 100644
index 0000000..c585c3a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/linear/NoFeasibleSolutionException.java
@@ -0,0 +1,42 @@
+/*
+ * 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.math3.optimization.linear;
+
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * This class represents exceptions thrown by optimizers when no solution fulfills the constraints.
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 2.0
+ */
+@Deprecated
+public class NoFeasibleSolutionException extends MathIllegalStateException {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -3044253632189082760L;
+
+    /**
+     * Simple constructor using a default message.
+     */
+    public NoFeasibleSolutionException() {
+        super(LocalizedFormats.NO_FEASIBLE_SOLUTION);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/linear/Relationship.java b/src/main/java/org/apache/commons/math3/optimization/linear/Relationship.java
new file mode 100644
index 0000000..b1ca087
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/linear/Relationship.java
@@ -0,0 +1,68 @@
+/*
+ * 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.math3.optimization.linear;
+
+/**
+ * Types of relationships between two cells in a Solver {@link LinearConstraint}.
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 2.0
+ */
+@Deprecated
+public enum Relationship {
+
+    /** Equality relationship. */
+    EQ("="),
+
+    /** Lesser than or equal relationship. */
+    LEQ("<="),
+
+    /** Greater than or equal relationship. */
+    GEQ(">=");
+
+    /** Display string for the relationship. */
+    private final String stringValue;
+
+    /** Simple constructor.
+     * @param stringValue display string for the relationship
+     */
+    Relationship(String stringValue) {
+        this.stringValue = stringValue;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return stringValue;
+    }
+
+    /**
+     * Get the relationship obtained when multiplying all coefficients by -1.
+     * @return relationship obtained when multiplying all coefficients by -1
+     */
+    public Relationship oppositeRelationship() {
+        switch (this) {
+        case LEQ :
+            return GEQ;
+        case GEQ :
+            return LEQ;
+        default :
+            return EQ;
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/linear/SimplexSolver.java b/src/main/java/org/apache/commons/math3/optimization/linear/SimplexSolver.java
new file mode 100644
index 0000000..1e5dbda
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/linear/SimplexSolver.java
@@ -0,0 +1,238 @@
+/*
+ * 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.math3.optimization.linear;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.optimization.PointValuePair;
+import org.apache.commons.math3.util.Precision;
+
+
+/**
+ * Solves a linear problem using the Two-Phase Simplex Method.
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 2.0
+ */
+@Deprecated
+public class SimplexSolver extends AbstractLinearOptimizer {
+
+    /** Default amount of error to accept for algorithm convergence. */
+    private static final double DEFAULT_EPSILON = 1.0e-6;
+
+    /** Default amount of error to accept in floating point comparisons (as ulps). */
+    private static final int DEFAULT_ULPS = 10;
+
+    /** Amount of error to accept for algorithm convergence. */
+    private final double epsilon;
+
+    /** Amount of error to accept in floating point comparisons (as ulps). */
+    private final int maxUlps;
+
+    /**
+     * Build a simplex solver with default settings.
+     */
+    public SimplexSolver() {
+        this(DEFAULT_EPSILON, DEFAULT_ULPS);
+    }
+
+    /**
+     * Build a simplex solver with a specified accepted amount of error
+     * @param epsilon the amount of error to accept for algorithm convergence
+     * @param maxUlps amount of error to accept in floating point comparisons
+     */
+    public SimplexSolver(final double epsilon, final int maxUlps) {
+        this.epsilon = epsilon;
+        this.maxUlps = maxUlps;
+    }
+
+    /**
+     * Returns the column with the most negative coefficient in the objective function row.
+     * @param tableau simple tableau for the problem
+     * @return column with the most negative coefficient
+     */
+    private Integer getPivotColumn(SimplexTableau tableau) {
+        double minValue = 0;
+        Integer minPos = null;
+        for (int i = tableau.getNumObjectiveFunctions(); i < tableau.getWidth() - 1; i++) {
+            final double entry = tableau.getEntry(0, i);
+            // check if the entry is strictly smaller than the current minimum
+            // do not use a ulp/epsilon check
+            if (entry < minValue) {
+                minValue = entry;
+                minPos = i;
+            }
+        }
+        return minPos;
+    }
+
+    /**
+     * Returns the row with the minimum ratio as given by the minimum ratio test (MRT).
+     * @param tableau simple tableau for the problem
+     * @param col the column to test the ratio of.  See {@link #getPivotColumn(SimplexTableau)}
+     * @return row with the minimum ratio
+     */
+    private Integer getPivotRow(SimplexTableau tableau, final int col) {
+        // create a list of all the rows that tie for the lowest score in the minimum ratio test
+        List<Integer> minRatioPositions = new ArrayList<Integer>();
+        double minRatio = Double.MAX_VALUE;
+        for (int i = tableau.getNumObjectiveFunctions(); i < tableau.getHeight(); i++) {
+            final double rhs = tableau.getEntry(i, tableau.getWidth() - 1);
+            final double entry = tableau.getEntry(i, col);
+
+            if (Precision.compareTo(entry, 0d, maxUlps) > 0) {
+                final double ratio = rhs / entry;
+                // check if the entry is strictly equal to the current min ratio
+                // do not use a ulp/epsilon check
+                final int cmp = Double.compare(ratio, minRatio);
+                if (cmp == 0) {
+                    minRatioPositions.add(i);
+                } else if (cmp < 0) {
+                    minRatio = ratio;
+                    minRatioPositions = new ArrayList<Integer>();
+                    minRatioPositions.add(i);
+                }
+            }
+        }
+
+        if (minRatioPositions.size() == 0) {
+            return null;
+        } else if (minRatioPositions.size() > 1) {
+            // there's a degeneracy as indicated by a tie in the minimum ratio test
+
+            // 1. check if there's an artificial variable that can be forced out of the basis
+            if (tableau.getNumArtificialVariables() > 0) {
+                for (Integer row : minRatioPositions) {
+                    for (int i = 0; i < tableau.getNumArtificialVariables(); i++) {
+                        int column = i + tableau.getArtificialVariableOffset();
+                        final double entry = tableau.getEntry(row, column);
+                        if (Precision.equals(entry, 1d, maxUlps) && row.equals(tableau.getBasicRow(column))) {
+                            return row;
+                        }
+                    }
+                }
+            }
+
+            // 2. apply Bland's rule to prevent cycling:
+            //    take the row for which the corresponding basic variable has the smallest index
+            //
+            // see http://www.stanford.edu/class/msande310/blandrule.pdf
+            // see http://en.wikipedia.org/wiki/Bland%27s_rule (not equivalent to the above paper)
+            //
+            // Additional heuristic: if we did not get a solution after half of maxIterations
+            //                       revert to the simple case of just returning the top-most row
+            // This heuristic is based on empirical data gathered while investigating MATH-828.
+            if (getIterations() < getMaxIterations() / 2) {
+                Integer minRow = null;
+                int minIndex = tableau.getWidth();
+                final int varStart = tableau.getNumObjectiveFunctions();
+                final int varEnd = tableau.getWidth() - 1;
+                for (Integer row : minRatioPositions) {
+                    for (int i = varStart; i < varEnd && !row.equals(minRow); i++) {
+                        final Integer basicRow = tableau.getBasicRow(i);
+                        if (basicRow != null && basicRow.equals(row) && i < minIndex) {
+                            minIndex = i;
+                            minRow = row;
+                        }
+                    }
+                }
+                return minRow;
+            }
+        }
+        return minRatioPositions.get(0);
+    }
+
+    /**
+     * Runs one iteration of the Simplex method on the given model.
+     * @param tableau simple tableau for the problem
+     * @throws MaxCountExceededException if the maximal iteration count has been exceeded
+     * @throws UnboundedSolutionException if the model is found not to have a bounded solution
+     */
+    protected void doIteration(final SimplexTableau tableau)
+        throws MaxCountExceededException, UnboundedSolutionException {
+
+        incrementIterationsCounter();
+
+        Integer pivotCol = getPivotColumn(tableau);
+        Integer pivotRow = getPivotRow(tableau, pivotCol);
+        if (pivotRow == null) {
+            throw new UnboundedSolutionException();
+        }
+
+        // set the pivot element to 1
+        double pivotVal = tableau.getEntry(pivotRow, pivotCol);
+        tableau.divideRow(pivotRow, pivotVal);
+
+        // set the rest of the pivot column to 0
+        for (int i = 0; i < tableau.getHeight(); i++) {
+            if (i != pivotRow) {
+                final double multiplier = tableau.getEntry(i, pivotCol);
+                tableau.subtractRow(i, pivotRow, multiplier);
+            }
+        }
+    }
+
+    /**
+     * Solves Phase 1 of the Simplex method.
+     * @param tableau simple tableau for the problem
+     * @throws MaxCountExceededException if the maximal iteration count has been exceeded
+     * @throws UnboundedSolutionException if the model is found not to have a bounded solution
+     * @throws NoFeasibleSolutionException if there is no feasible solution
+     */
+    protected void solvePhase1(final SimplexTableau tableau)
+        throws MaxCountExceededException, UnboundedSolutionException, NoFeasibleSolutionException {
+
+        // make sure we're in Phase 1
+        if (tableau.getNumArtificialVariables() == 0) {
+            return;
+        }
+
+        while (!tableau.isOptimal()) {
+            doIteration(tableau);
+        }
+
+        // if W is not zero then we have no feasible solution
+        if (!Precision.equals(tableau.getEntry(0, tableau.getRhsOffset()), 0d, epsilon)) {
+            throw new NoFeasibleSolutionException();
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public PointValuePair doOptimize()
+        throws MaxCountExceededException, UnboundedSolutionException, NoFeasibleSolutionException {
+        final SimplexTableau tableau =
+            new SimplexTableau(getFunction(),
+                               getConstraints(),
+                               getGoalType(),
+                               restrictToNonNegative(),
+                               epsilon,
+                               maxUlps);
+
+        solvePhase1(tableau);
+        tableau.dropPhase1Objective();
+
+        while (!tableau.isOptimal()) {
+            doIteration(tableau);
+        }
+        return tableau.getSolution();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/linear/SimplexTableau.java b/src/main/java/org/apache/commons/math3/optimization/linear/SimplexTableau.java
new file mode 100644
index 0000000..321f8c0
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/linear/SimplexTableau.java
@@ -0,0 +1,637 @@
+/*
+ * 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.math3.optimization.linear;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.commons.math3.linear.Array2DRowRealMatrix;
+import org.apache.commons.math3.linear.MatrixUtils;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.linear.RealVector;
+import org.apache.commons.math3.optimization.GoalType;
+import org.apache.commons.math3.optimization.PointValuePair;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.Precision;
+
+/**
+ * A tableau for use in the Simplex method.
+ *
+ * <p>
+ * Example:
+ * <pre>
+ *   W |  Z |  x1 |  x2 |  x- | s1 |  s2 |  a1 |  RHS
+ * ---------------------------------------------------
+ *  -1    0    0     0     0     0     0     1     0   &lt;= phase 1 objective
+ *   0    1   -15   -10    0     0     0     0     0   &lt;= phase 2 objective
+ *   0    0    1     0     0     1     0     0     2   &lt;= constraint 1
+ *   0    0    0     1     0     0     1     0     3   &lt;= constraint 2
+ *   0    0    1     1     0     0     0     1     4   &lt;= constraint 3
+ * </pre>
+ * W: Phase 1 objective function</br>
+ * Z: Phase 2 objective function</br>
+ * x1 &amp; x2: Decision variables</br>
+ * x-: Extra decision variable to allow for negative values</br>
+ * s1 &amp; s2: Slack/Surplus variables</br>
+ * a1: Artificial variable</br>
+ * RHS: Right hand side</br>
+ * </p>
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 2.0
+ */
+@Deprecated
+class SimplexTableau implements Serializable {
+
+    /** Column label for negative vars. */
+    private static final String NEGATIVE_VAR_COLUMN_LABEL = "x-";
+
+    /** Default amount of error to accept in floating point comparisons (as ulps). */
+    private static final int DEFAULT_ULPS = 10;
+
+    /** The cut-off threshold to zero-out entries. */
+    private static final double CUTOFF_THRESHOLD = 1e-12;
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -1369660067587938365L;
+
+    /** Linear objective function. */
+    private final LinearObjectiveFunction f;
+
+    /** Linear constraints. */
+    private final List<LinearConstraint> constraints;
+
+    /** Whether to restrict the variables to non-negative values. */
+    private final boolean restrictToNonNegative;
+
+    /** The variables each column represents */
+    private final List<String> columnLabels = new ArrayList<String>();
+
+    /** Simple tableau. */
+    private transient RealMatrix tableau;
+
+    /** Number of decision variables. */
+    private final int numDecisionVariables;
+
+    /** Number of slack variables. */
+    private final int numSlackVariables;
+
+    /** Number of artificial variables. */
+    private int numArtificialVariables;
+
+    /** Amount of error to accept when checking for optimality. */
+    private final double epsilon;
+
+    /** Amount of error to accept in floating point comparisons. */
+    private final int maxUlps;
+
+    /**
+     * Build a tableau for a linear problem.
+     * @param f linear objective function
+     * @param constraints linear constraints
+     * @param goalType type of optimization goal: either {@link GoalType#MAXIMIZE} or {@link GoalType#MINIMIZE}
+     * @param restrictToNonNegative whether to restrict the variables to non-negative values
+     * @param epsilon amount of error to accept when checking for optimality
+     */
+    SimplexTableau(final LinearObjectiveFunction f,
+                   final Collection<LinearConstraint> constraints,
+                   final GoalType goalType, final boolean restrictToNonNegative,
+                   final double epsilon) {
+        this(f, constraints, goalType, restrictToNonNegative, epsilon, DEFAULT_ULPS);
+    }
+
+    /**
+     * Build a tableau for a linear problem.
+     * @param f linear objective function
+     * @param constraints linear constraints
+     * @param goalType type of optimization goal: either {@link GoalType#MAXIMIZE} or {@link GoalType#MINIMIZE}
+     * @param restrictToNonNegative whether to restrict the variables to non-negative values
+     * @param epsilon amount of error to accept when checking for optimality
+     * @param maxUlps amount of error to accept in floating point comparisons
+     */
+    SimplexTableau(final LinearObjectiveFunction f,
+                   final Collection<LinearConstraint> constraints,
+                   final GoalType goalType, final boolean restrictToNonNegative,
+                   final double epsilon,
+                   final int maxUlps) {
+        this.f                      = f;
+        this.constraints            = normalizeConstraints(constraints);
+        this.restrictToNonNegative  = restrictToNonNegative;
+        this.epsilon                = epsilon;
+        this.maxUlps                = maxUlps;
+        this.numDecisionVariables   = f.getCoefficients().getDimension() +
+                                      (restrictToNonNegative ? 0 : 1);
+        this.numSlackVariables      = getConstraintTypeCounts(Relationship.LEQ) +
+                                      getConstraintTypeCounts(Relationship.GEQ);
+        this.numArtificialVariables = getConstraintTypeCounts(Relationship.EQ) +
+                                      getConstraintTypeCounts(Relationship.GEQ);
+        this.tableau = createTableau(goalType == GoalType.MAXIMIZE);
+        initializeColumnLabels();
+    }
+
+    /**
+     * Initialize the labels for the columns.
+     */
+    protected void initializeColumnLabels() {
+      if (getNumObjectiveFunctions() == 2) {
+        columnLabels.add("W");
+      }
+      columnLabels.add("Z");
+      for (int i = 0; i < getOriginalNumDecisionVariables(); i++) {
+        columnLabels.add("x" + i);
+      }
+      if (!restrictToNonNegative) {
+        columnLabels.add(NEGATIVE_VAR_COLUMN_LABEL);
+      }
+      for (int i = 0; i < getNumSlackVariables(); i++) {
+        columnLabels.add("s" + i);
+      }
+      for (int i = 0; i < getNumArtificialVariables(); i++) {
+        columnLabels.add("a" + i);
+      }
+      columnLabels.add("RHS");
+    }
+
+    /**
+     * Create the tableau by itself.
+     * @param maximize if true, goal is to maximize the objective function
+     * @return created tableau
+     */
+    protected RealMatrix createTableau(final boolean maximize) {
+
+        // create a matrix of the correct size
+        int width = numDecisionVariables + numSlackVariables +
+        numArtificialVariables + getNumObjectiveFunctions() + 1; // + 1 is for RHS
+        int height = constraints.size() + getNumObjectiveFunctions();
+        Array2DRowRealMatrix matrix = new Array2DRowRealMatrix(height, width);
+
+        // initialize the objective function rows
+        if (getNumObjectiveFunctions() == 2) {
+            matrix.setEntry(0, 0, -1);
+        }
+        int zIndex = (getNumObjectiveFunctions() == 1) ? 0 : 1;
+        matrix.setEntry(zIndex, zIndex, maximize ? 1 : -1);
+        RealVector objectiveCoefficients =
+            maximize ? f.getCoefficients().mapMultiply(-1) : f.getCoefficients();
+        copyArray(objectiveCoefficients.toArray(), matrix.getDataRef()[zIndex]);
+        matrix.setEntry(zIndex, width - 1,
+            maximize ? f.getConstantTerm() : -1 * f.getConstantTerm());
+
+        if (!restrictToNonNegative) {
+            matrix.setEntry(zIndex, getSlackVariableOffset() - 1,
+                getInvertedCoefficientSum(objectiveCoefficients));
+        }
+
+        // initialize the constraint rows
+        int slackVar = 0;
+        int artificialVar = 0;
+        for (int i = 0; i < constraints.size(); i++) {
+            LinearConstraint constraint = constraints.get(i);
+            int row = getNumObjectiveFunctions() + i;
+
+            // decision variable coefficients
+            copyArray(constraint.getCoefficients().toArray(), matrix.getDataRef()[row]);
+
+            // x-
+            if (!restrictToNonNegative) {
+                matrix.setEntry(row, getSlackVariableOffset() - 1,
+                    getInvertedCoefficientSum(constraint.getCoefficients()));
+            }
+
+            // RHS
+            matrix.setEntry(row, width - 1, constraint.getValue());
+
+            // slack variables
+            if (constraint.getRelationship() == Relationship.LEQ) {
+                matrix.setEntry(row, getSlackVariableOffset() + slackVar++, 1);  // slack
+            } else if (constraint.getRelationship() == Relationship.GEQ) {
+                matrix.setEntry(row, getSlackVariableOffset() + slackVar++, -1); // excess
+            }
+
+            // artificial variables
+            if ((constraint.getRelationship() == Relationship.EQ) ||
+                    (constraint.getRelationship() == Relationship.GEQ)) {
+                matrix.setEntry(0, getArtificialVariableOffset() + artificialVar, 1);
+                matrix.setEntry(row, getArtificialVariableOffset() + artificialVar++, 1);
+                matrix.setRowVector(0, matrix.getRowVector(0).subtract(matrix.getRowVector(row)));
+            }
+        }
+
+        return matrix;
+    }
+
+    /**
+     * Get new versions of the constraints which have positive right hand sides.
+     * @param originalConstraints original (not normalized) constraints
+     * @return new versions of the constraints
+     */
+    public List<LinearConstraint> normalizeConstraints(Collection<LinearConstraint> originalConstraints) {
+        List<LinearConstraint> normalized = new ArrayList<LinearConstraint>(originalConstraints.size());
+        for (LinearConstraint constraint : originalConstraints) {
+            normalized.add(normalize(constraint));
+        }
+        return normalized;
+    }
+
+    /**
+     * Get a new equation equivalent to this one with a positive right hand side.
+     * @param constraint reference constraint
+     * @return new equation
+     */
+    private LinearConstraint normalize(final LinearConstraint constraint) {
+        if (constraint.getValue() < 0) {
+            return new LinearConstraint(constraint.getCoefficients().mapMultiply(-1),
+                                        constraint.getRelationship().oppositeRelationship(),
+                                        -1 * constraint.getValue());
+        }
+        return new LinearConstraint(constraint.getCoefficients(),
+                                    constraint.getRelationship(), constraint.getValue());
+    }
+
+    /**
+     * Get the number of objective functions in this tableau.
+     * @return 2 for Phase 1.  1 for Phase 2.
+     */
+    protected final int getNumObjectiveFunctions() {
+        return this.numArtificialVariables > 0 ? 2 : 1;
+    }
+
+    /**
+     * Get a count of constraints corresponding to a specified relationship.
+     * @param relationship relationship to count
+     * @return number of constraint with the specified relationship
+     */
+    private int getConstraintTypeCounts(final Relationship relationship) {
+        int count = 0;
+        for (final LinearConstraint constraint : constraints) {
+            if (constraint.getRelationship() == relationship) {
+                ++count;
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Get the -1 times the sum of all coefficients in the given array.
+     * @param coefficients coefficients to sum
+     * @return the -1 times the sum of all coefficients in the given array.
+     */
+    protected static double getInvertedCoefficientSum(final RealVector coefficients) {
+        double sum = 0;
+        for (double coefficient : coefficients.toArray()) {
+            sum -= coefficient;
+        }
+        return sum;
+    }
+
+    /**
+     * Checks whether the given column is basic.
+     * @param col index of the column to check
+     * @return the row that the variable is basic in.  null if the column is not basic
+     */
+    protected Integer getBasicRow(final int col) {
+        Integer row = null;
+        for (int i = 0; i < getHeight(); i++) {
+            final double entry = getEntry(i, col);
+            if (Precision.equals(entry, 1d, maxUlps) && (row == null)) {
+                row = i;
+            } else if (!Precision.equals(entry, 0d, maxUlps)) {
+                return null;
+            }
+        }
+        return row;
+    }
+
+    /**
+     * Removes the phase 1 objective function, positive cost non-artificial variables,
+     * and the non-basic artificial variables from this tableau.
+     */
+    protected void dropPhase1Objective() {
+        if (getNumObjectiveFunctions() == 1) {
+            return;
+        }
+
+        Set<Integer> columnsToDrop = new TreeSet<Integer>();
+        columnsToDrop.add(0);
+
+        // positive cost non-artificial variables
+        for (int i = getNumObjectiveFunctions(); i < getArtificialVariableOffset(); i++) {
+            final double entry = tableau.getEntry(0, i);
+            if (Precision.compareTo(entry, 0d, epsilon) > 0) {
+                columnsToDrop.add(i);
+            }
+        }
+
+        // non-basic artificial variables
+        for (int i = 0; i < getNumArtificialVariables(); i++) {
+            int col = i + getArtificialVariableOffset();
+            if (getBasicRow(col) == null) {
+                columnsToDrop.add(col);
+            }
+        }
+
+        double[][] matrix = new double[getHeight() - 1][getWidth() - columnsToDrop.size()];
+        for (int i = 1; i < getHeight(); i++) {
+            int col = 0;
+            for (int j = 0; j < getWidth(); j++) {
+                if (!columnsToDrop.contains(j)) {
+                    matrix[i - 1][col++] = tableau.getEntry(i, j);
+                }
+            }
+        }
+
+        // remove the columns in reverse order so the indices are correct
+        Integer[] drop = columnsToDrop.toArray(new Integer[columnsToDrop.size()]);
+        for (int i = drop.length - 1; i >= 0; i--) {
+            columnLabels.remove((int) drop[i]);
+        }
+
+        this.tableau = new Array2DRowRealMatrix(matrix);
+        this.numArtificialVariables = 0;
+    }
+
+    /**
+     * @param src the source array
+     * @param dest the destination array
+     */
+    private void copyArray(final double[] src, final double[] dest) {
+        System.arraycopy(src, 0, dest, getNumObjectiveFunctions(), src.length);
+    }
+
+    /**
+     * Returns whether the problem is at an optimal state.
+     * @return whether the model has been solved
+     */
+    boolean isOptimal() {
+        for (int i = getNumObjectiveFunctions(); i < getWidth() - 1; i++) {
+            final double entry = tableau.getEntry(0, i);
+            if (Precision.compareTo(entry, 0d, epsilon) < 0) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Get the current solution.
+     * @return current solution
+     */
+    protected PointValuePair getSolution() {
+      int negativeVarColumn = columnLabels.indexOf(NEGATIVE_VAR_COLUMN_LABEL);
+      Integer negativeVarBasicRow = negativeVarColumn > 0 ? getBasicRow(negativeVarColumn) : null;
+      double mostNegative = negativeVarBasicRow == null ? 0 : getEntry(negativeVarBasicRow, getRhsOffset());
+
+      Set<Integer> basicRows = new HashSet<Integer>();
+      double[] coefficients = new double[getOriginalNumDecisionVariables()];
+      for (int i = 0; i < coefficients.length; i++) {
+          int colIndex = columnLabels.indexOf("x" + i);
+          if (colIndex < 0) {
+            coefficients[i] = 0;
+            continue;
+          }
+          Integer basicRow = getBasicRow(colIndex);
+          if (basicRow != null && basicRow == 0) {
+              // if the basic row is found to be the objective function row
+              // set the coefficient to 0 -> this case handles unconstrained
+              // variables that are still part of the objective function
+              coefficients[i] = 0;
+          } else if (basicRows.contains(basicRow)) {
+              // if multiple variables can take a given value
+              // then we choose the first and set the rest equal to 0
+              coefficients[i] = 0 - (restrictToNonNegative ? 0 : mostNegative);
+          } else {
+              basicRows.add(basicRow);
+              coefficients[i] =
+                  (basicRow == null ? 0 : getEntry(basicRow, getRhsOffset())) -
+                  (restrictToNonNegative ? 0 : mostNegative);
+          }
+      }
+      return new PointValuePair(coefficients, f.getValue(coefficients));
+    }
+
+    /**
+     * Subtracts a multiple of one row from another.
+     * <p>
+     * After application of this operation, the following will hold:
+     * <pre>minuendRow = minuendRow - multiple * subtrahendRow</pre>
+     *
+     * @param dividendRow index of the row
+     * @param divisor value of the divisor
+     */
+    protected void divideRow(final int dividendRow, final double divisor) {
+        for (int j = 0; j < getWidth(); j++) {
+            tableau.setEntry(dividendRow, j, tableau.getEntry(dividendRow, j) / divisor);
+        }
+    }
+
+    /**
+     * Subtracts a multiple of one row from another.
+     * <p>
+     * After application of this operation, the following will hold:
+     * <pre>minuendRow = minuendRow - multiple * subtrahendRow</pre>
+     *
+     * @param minuendRow row index
+     * @param subtrahendRow row index
+     * @param multiple multiplication factor
+     */
+    protected void subtractRow(final int minuendRow, final int subtrahendRow,
+                               final double multiple) {
+        for (int i = 0; i < getWidth(); i++) {
+            double result = tableau.getEntry(minuendRow, i) - tableau.getEntry(subtrahendRow, i) * multiple;
+            // cut-off values smaller than the CUTOFF_THRESHOLD, otherwise may lead to numerical instabilities
+            if (FastMath.abs(result) < CUTOFF_THRESHOLD) {
+                result = 0.0;
+            }
+            tableau.setEntry(minuendRow, i, result);
+        }
+    }
+
+    /**
+     * Get the width of the tableau.
+     * @return width of the tableau
+     */
+    protected final int getWidth() {
+        return tableau.getColumnDimension();
+    }
+
+    /**
+     * Get the height of the tableau.
+     * @return height of the tableau
+     */
+    protected final int getHeight() {
+        return tableau.getRowDimension();
+    }
+
+    /**
+     * Get an entry of the tableau.
+     * @param row row index
+     * @param column column index
+     * @return entry at (row, column)
+     */
+    protected final double getEntry(final int row, final int column) {
+        return tableau.getEntry(row, column);
+    }
+
+    /**
+     * Set an entry of the tableau.
+     * @param row row index
+     * @param column column index
+     * @param value for the entry
+     */
+    protected final void setEntry(final int row, final int column,
+                                  final double value) {
+        tableau.setEntry(row, column, value);
+    }
+
+    /**
+     * Get the offset of the first slack variable.
+     * @return offset of the first slack variable
+     */
+    protected final int getSlackVariableOffset() {
+        return getNumObjectiveFunctions() + numDecisionVariables;
+    }
+
+    /**
+     * Get the offset of the first artificial variable.
+     * @return offset of the first artificial variable
+     */
+    protected final int getArtificialVariableOffset() {
+        return getNumObjectiveFunctions() + numDecisionVariables + numSlackVariables;
+    }
+
+    /**
+     * Get the offset of the right hand side.
+     * @return offset of the right hand side
+     */
+    protected final int getRhsOffset() {
+        return getWidth() - 1;
+    }
+
+    /**
+     * Get the number of decision variables.
+     * <p>
+     * If variables are not restricted to positive values, this will include 1 extra decision variable to represent
+     * the absolute value of the most negative variable.
+     *
+     * @return number of decision variables
+     * @see #getOriginalNumDecisionVariables()
+     */
+    protected final int getNumDecisionVariables() {
+        return numDecisionVariables;
+    }
+
+    /**
+     * Get the original number of decision variables.
+     * @return original number of decision variables
+     * @see #getNumDecisionVariables()
+     */
+    protected final int getOriginalNumDecisionVariables() {
+        return f.getCoefficients().getDimension();
+    }
+
+    /**
+     * Get the number of slack variables.
+     * @return number of slack variables
+     */
+    protected final int getNumSlackVariables() {
+        return numSlackVariables;
+    }
+
+    /**
+     * Get the number of artificial variables.
+     * @return number of artificial variables
+     */
+    protected final int getNumArtificialVariables() {
+        return numArtificialVariables;
+    }
+
+    /**
+     * Get the tableau data.
+     * @return tableau data
+     */
+    protected final double[][] getData() {
+        return tableau.getData();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object other) {
+
+      if (this == other) {
+        return true;
+      }
+
+      if (other instanceof SimplexTableau) {
+          SimplexTableau rhs = (SimplexTableau) other;
+          return (restrictToNonNegative  == rhs.restrictToNonNegative) &&
+                 (numDecisionVariables   == rhs.numDecisionVariables) &&
+                 (numSlackVariables      == rhs.numSlackVariables) &&
+                 (numArtificialVariables == rhs.numArtificialVariables) &&
+                 (epsilon                == rhs.epsilon) &&
+                 (maxUlps                == rhs.maxUlps) &&
+                 f.equals(rhs.f) &&
+                 constraints.equals(rhs.constraints) &&
+                 tableau.equals(rhs.tableau);
+      }
+      return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return Boolean.valueOf(restrictToNonNegative).hashCode() ^
+               numDecisionVariables ^
+               numSlackVariables ^
+               numArtificialVariables ^
+               Double.valueOf(epsilon).hashCode() ^
+               maxUlps ^
+               f.hashCode() ^
+               constraints.hashCode() ^
+               tableau.hashCode();
+    }
+
+    /**
+     * Serialize the instance.
+     * @param oos stream where object should be written
+     * @throws IOException if object cannot be written to stream
+     */
+    private void writeObject(ObjectOutputStream oos)
+        throws IOException {
+        oos.defaultWriteObject();
+        MatrixUtils.serializeRealMatrix(tableau, oos);
+    }
+
+    /**
+     * Deserialize the instance.
+     * @param ois stream from which the object should be read
+     * @throws ClassNotFoundException if a class in the stream cannot be found
+     * @throws IOException if object cannot be read from the stream
+     */
+    private void readObject(ObjectInputStream ois)
+      throws ClassNotFoundException, IOException {
+        ois.defaultReadObject();
+        MatrixUtils.deserializeRealMatrix(this, "tableau", ois);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/linear/UnboundedSolutionException.java b/src/main/java/org/apache/commons/math3/optimization/linear/UnboundedSolutionException.java
new file mode 100644
index 0000000..a8fe77b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/linear/UnboundedSolutionException.java
@@ -0,0 +1,42 @@
+/*
+ * 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.math3.optimization.linear;
+
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * This class represents exceptions thrown by optimizers when a solution escapes to infinity.
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 2.0
+ */
+@Deprecated
+public class UnboundedSolutionException extends MathIllegalStateException {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 940539497277290619L;
+
+    /**
+     * Simple constructor using a default message.
+     */
+    public UnboundedSolutionException() {
+        super(LocalizedFormats.UNBOUNDED_SOLUTION);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/linear/package-info.java b/src/main/java/org/apache/commons/math3/optimization/linear/package-info.java
new file mode 100644
index 0000000..b61b03b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/linear/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * This package provides optimization algorithms for linear constrained problems.
+ *
+ */
+package org.apache.commons.math3.optimization.linear;
diff --git a/src/main/java/org/apache/commons/math3/optimization/package-info.java b/src/main/java/org/apache/commons/math3/optimization/package-info.java
new file mode 100644
index 0000000..2831237
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/package-info.java
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+/**
+ *
+ *
+ * <h2>All classes and sub-packages of this package are deprecated.</h2>
+ *
+ * <h3>Please use their replacements, to be found under
+ *
+ * <ul>
+ *   <li>{@link org.apache.commons.math3.optim}
+ *   <li>{@link org.apache.commons.math3.fitting}
+ * </ul>
+ *
+ * </h3>
+ *
+ * <p>This package provides common interfaces for the optimization algorithms provided in
+ * sub-packages. The main interfaces defines optimizers and convergence checkers. The functions that
+ * are optimized by the algorithms provided by this package and its sub-packages are a subset of the
+ * one defined in the <code>analysis</code> package, namely the real and vector valued functions.
+ * These functions are called objective function here. When the goal is to minimize, the functions
+ * are often called cost function, this name is not used in this package.
+ *
+ * <p>Optimizers are the algorithms that will either minimize or maximize, the objective function by
+ * changing its input variables set until an optimal set is found. There are only four interfaces
+ * defining the common behavior of optimizers, one for each supported type of objective function:
+ *
+ * <ul>
+ *   <li>{@link org.apache.commons.math3.optimization.univariate.UnivariateOptimizer
+ *       UnivariateOptimizer} for {@link org.apache.commons.math3.analysis.UnivariateFunction
+ *       univariate real functions}
+ *   <li>{@link org.apache.commons.math3.optimization.MultivariateOptimizer MultivariateOptimizer}
+ *       for {@link org.apache.commons.math3.analysis.MultivariateFunction multivariate real
+ *       functions}
+ *   <li>{@link org.apache.commons.math3.optimization.MultivariateDifferentiableOptimizer
+ *       MultivariateDifferentiableOptimizer} for {@link
+ *       org.apache.commons.math3.analysis.differentiation.MultivariateDifferentiableFunction
+ *       multivariate differentiable real functions}
+ *   <li>{@link org.apache.commons.math3.optimization.MultivariateDifferentiableVectorOptimizer
+ *       MultivariateDifferentiableVectorOptimizer} for {@link
+ *       org.apache.commons.math3.analysis.differentiation.MultivariateDifferentiableVectorFunction
+ *       multivariate differentiable vectorial functions}
+ * </ul>
+ *
+ * <p>Despite there are only four types of supported optimizers, it is possible to optimize a
+ * transform a {@link org.apache.commons.math3.analysis.MultivariateVectorFunction
+ * non-differentiable multivariate vectorial function} by converting it to a {@link
+ * org.apache.commons.math3.analysis.MultivariateFunction non-differentiable multivariate real
+ * function} thanks to the {@link org.apache.commons.math3.optimization.LeastSquaresConverter
+ * LeastSquaresConverter} helper class. The transformed function can be optimized using any
+ * implementation of the {@link org.apache.commons.math3.optimization.MultivariateOptimizer
+ * MultivariateOptimizer} interface.
+ *
+ * <p>For each of the four types of supported optimizers, there is a special implementation which
+ * wraps a classical optimizer in order to add it a multi-start feature. This feature call the
+ * underlying optimizer several times in sequence with different starting points and returns the
+ * best optimum found or all optima if desired. This is a classical way to prevent being trapped
+ * into a local extremum when looking for a global one.
+ */
+package org.apache.commons.math3.optimization;
diff --git a/src/main/java/org/apache/commons/math3/optimization/univariate/BaseAbstractUnivariateOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/univariate/BaseAbstractUnivariateOptimizer.java
new file mode 100644
index 0000000..fcacd01
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/univariate/BaseAbstractUnivariateOptimizer.java
@@ -0,0 +1,162 @@
+/*
+ * 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.math3.optimization.univariate;
+
+import org.apache.commons.math3.util.Incrementor;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.optimization.GoalType;
+import org.apache.commons.math3.optimization.ConvergenceChecker;
+
+/**
+ * Provide a default implementation for several functions useful to generic
+ * optimizers.
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 2.0
+ */
+@Deprecated
+public abstract class BaseAbstractUnivariateOptimizer
+    implements UnivariateOptimizer {
+    /** Convergence checker. */
+    private final ConvergenceChecker<UnivariatePointValuePair> checker;
+    /** Evaluations counter. */
+    private final Incrementor evaluations = new Incrementor();
+    /** Optimization type */
+    private GoalType goal;
+    /** Lower end of search interval. */
+    private double searchMin;
+    /** Higher end of search interval. */
+    private double searchMax;
+    /** Initial guess . */
+    private double searchStart;
+    /** Function to optimize. */
+    private UnivariateFunction function;
+
+    /**
+     * @param checker Convergence checking procedure.
+     */
+    protected BaseAbstractUnivariateOptimizer(ConvergenceChecker<UnivariatePointValuePair> checker) {
+        this.checker = checker;
+    }
+
+    /** {@inheritDoc} */
+    public int getMaxEvaluations() {
+        return evaluations.getMaximalCount();
+    }
+
+    /** {@inheritDoc} */
+    public int getEvaluations() {
+        return evaluations.getCount();
+    }
+
+    /**
+     * @return the optimization type.
+     */
+    public GoalType getGoalType() {
+        return goal;
+    }
+    /**
+     * @return the lower end of the search interval.
+     */
+    public double getMin() {
+        return searchMin;
+    }
+    /**
+     * @return the higher end of the search interval.
+     */
+    public double getMax() {
+        return searchMax;
+    }
+    /**
+     * @return the initial guess.
+     */
+    public double getStartValue() {
+        return searchStart;
+    }
+
+    /**
+     * Compute the objective function value.
+     *
+     * @param point Point at which the objective function must be evaluated.
+     * @return the objective function value at specified point.
+     * @throws TooManyEvaluationsException if the maximal number of evaluations
+     * is exceeded.
+     */
+    protected double computeObjectiveValue(double point) {
+        try {
+            evaluations.incrementCount();
+        } catch (MaxCountExceededException e) {
+            throw new TooManyEvaluationsException(e.getMax());
+        }
+        return function.value(point);
+    }
+
+    /** {@inheritDoc} */
+    public UnivariatePointValuePair optimize(int maxEval, UnivariateFunction f,
+                                             GoalType goalType,
+                                             double min, double max,
+                                             double startValue) {
+        // Checks.
+        if (f == null) {
+            throw new NullArgumentException();
+        }
+        if (goalType == null) {
+            throw new NullArgumentException();
+        }
+
+        // Reset.
+        searchMin = min;
+        searchMax = max;
+        searchStart = startValue;
+        goal = goalType;
+        function = f;
+        evaluations.setMaximalCount(maxEval);
+        evaluations.resetCount();
+
+        // Perform computation.
+        return doOptimize();
+    }
+
+    /** {@inheritDoc} */
+    public UnivariatePointValuePair optimize(int maxEval,
+                                             UnivariateFunction f,
+                                             GoalType goalType,
+                                             double min, double max){
+        return optimize(maxEval, f, goalType, min, max, min + 0.5 * (max - min));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public ConvergenceChecker<UnivariatePointValuePair> getConvergenceChecker() {
+        return checker;
+    }
+
+    /**
+     * Method for implementing actual optimization algorithms in derived
+     * classes.
+     *
+     * @return the optimum and its corresponding function value.
+     * @throws TooManyEvaluationsException if the maximal number of evaluations
+     * is exceeded.
+     */
+    protected abstract UnivariatePointValuePair doOptimize();
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/univariate/BaseUnivariateOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/univariate/BaseUnivariateOptimizer.java
new file mode 100644
index 0000000..fcae6f1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/univariate/BaseUnivariateOptimizer.java
@@ -0,0 +1,86 @@
+/*
+ * 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.math3.optimization.univariate;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.optimization.BaseOptimizer;
+import org.apache.commons.math3.optimization.GoalType;
+
+/**
+ * This interface is mainly intended to enforce the internal coherence of
+ * Commons-Math. Users of the API are advised to base their code on
+ * the following interfaces:
+ * <ul>
+ *  <li>{@link org.apache.commons.math3.optimization.univariate.UnivariateOptimizer}</li>
+ * </ul>
+ *
+ * @param <FUNC> Type of the objective function to be optimized.
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.0
+ */
+@Deprecated
+public interface BaseUnivariateOptimizer<FUNC extends UnivariateFunction>
+    extends BaseOptimizer<UnivariatePointValuePair> {
+    /**
+     * Find an optimum in the given interval.
+     *
+     * An optimizer may require that the interval brackets a single optimum.
+     *
+     * @param f Function to optimize.
+     * @param goalType Type of optimization goal: either
+     * {@link GoalType#MAXIMIZE} or {@link GoalType#MINIMIZE}.
+     * @param min Lower bound for the interval.
+     * @param max Upper bound for the interval.
+     * @param maxEval Maximum number of function evaluations.
+     * @return a (point, value) pair where the function is optimum.
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException
+     * if the maximum evaluation count is exceeded.
+     * @throws org.apache.commons.math3.exception.ConvergenceException
+     * if the optimizer detects a convergence problem.
+     * @throws IllegalArgumentException if {@code min > max} or the endpoints
+     * do not satisfy the requirements specified by the optimizer.
+     */
+    UnivariatePointValuePair optimize(int maxEval, FUNC f, GoalType goalType,
+                                          double min, double max);
+
+    /**
+     * Find an optimum in the given interval, start at startValue.
+     * An optimizer may require that the interval brackets a single optimum.
+     *
+     * @param f Function to optimize.
+     * @param goalType Type of optimization goal: either
+     * {@link GoalType#MAXIMIZE} or {@link GoalType#MINIMIZE}.
+     * @param min Lower bound for the interval.
+     * @param max Upper bound for the interval.
+     * @param startValue Start value to use.
+     * @param maxEval Maximum number of function evaluations.
+     * @return a (point, value) pair where the function is optimum.
+     * @throws org.apache.commons.math3.exception.TooManyEvaluationsException
+     * if the maximum evaluation count is exceeded.
+     * @throws org.apache.commons.math3.exception.ConvergenceException if the
+     * optimizer detects a convergence problem.
+     * @throws IllegalArgumentException if {@code min > max} or the endpoints
+     * do not satisfy the requirements specified by the optimizer.
+     * @throws org.apache.commons.math3.exception.NullArgumentException if any
+     * argument is {@code null}.
+     */
+    UnivariatePointValuePair optimize(int maxEval, FUNC f, GoalType goalType,
+                                          double min, double max,
+                                          double startValue);
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/univariate/BracketFinder.java b/src/main/java/org/apache/commons/math3/optimization/univariate/BracketFinder.java
new file mode 100644
index 0000000..cd3057f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/univariate/BracketFinder.java
@@ -0,0 +1,289 @@
+/*
+ * 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.math3.optimization.univariate;
+
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.Incrementor;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.TooManyEvaluationsException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.optimization.GoalType;
+
+/**
+ * Provide an interval that brackets a local optimum of a function.
+ * This code is based on a Python implementation (from <em>SciPy</em>,
+ * module {@code optimize.py} v0.5).
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 2.2
+ */
+@Deprecated
+public class BracketFinder {
+    /** Tolerance to avoid division by zero. */
+    private static final double EPS_MIN = 1e-21;
+    /**
+     * Golden section.
+     */
+    private static final double GOLD = 1.618034;
+    /**
+     * Factor for expanding the interval.
+     */
+    private final double growLimit;
+    /**
+     * Counter for function evaluations.
+     */
+    private final Incrementor evaluations = new Incrementor();
+    /**
+     * Lower bound of the bracket.
+     */
+    private double lo;
+    /**
+     * Higher bound of the bracket.
+     */
+    private double hi;
+    /**
+     * Point inside the bracket.
+     */
+    private double mid;
+    /**
+     * Function value at {@link #lo}.
+     */
+    private double fLo;
+    /**
+     * Function value at {@link #hi}.
+     */
+    private double fHi;
+    /**
+     * Function value at {@link #mid}.
+     */
+    private double fMid;
+
+    /**
+     * Constructor with default values {@code 100, 50} (see the
+     * {@link #BracketFinder(double,int) other constructor}).
+     */
+    public BracketFinder() {
+        this(100, 50);
+    }
+
+    /**
+     * Create a bracketing interval finder.
+     *
+     * @param growLimit Expanding factor.
+     * @param maxEvaluations Maximum number of evaluations allowed for finding
+     * a bracketing interval.
+     */
+    public BracketFinder(double growLimit,
+                         int maxEvaluations) {
+        if (growLimit <= 0) {
+            throw new NotStrictlyPositiveException(growLimit);
+        }
+        if (maxEvaluations <= 0) {
+            throw new NotStrictlyPositiveException(maxEvaluations);
+        }
+
+        this.growLimit = growLimit;
+        evaluations.setMaximalCount(maxEvaluations);
+    }
+
+    /**
+     * Search new points that bracket a local optimum of the function.
+     *
+     * @param func Function whose optimum should be bracketed.
+     * @param goal {@link GoalType Goal type}.
+     * @param xA Initial point.
+     * @param xB Initial point.
+     * @throws TooManyEvaluationsException if the maximum number of evaluations
+     * is exceeded.
+     */
+    public void search(UnivariateFunction func, GoalType goal, double xA, double xB) {
+        evaluations.resetCount();
+        final boolean isMinim = goal == GoalType.MINIMIZE;
+
+        double fA = eval(func, xA);
+        double fB = eval(func, xB);
+        if (isMinim ?
+            fA < fB :
+            fA > fB) {
+
+            double tmp = xA;
+            xA = xB;
+            xB = tmp;
+
+            tmp = fA;
+            fA = fB;
+            fB = tmp;
+        }
+
+        double xC = xB + GOLD * (xB - xA);
+        double fC = eval(func, xC);
+
+        while (isMinim ? fC < fB : fC > fB) {
+            double tmp1 = (xB - xA) * (fB - fC);
+            double tmp2 = (xB - xC) * (fB - fA);
+
+            double val = tmp2 - tmp1;
+            double denom = FastMath.abs(val) < EPS_MIN ? 2 * EPS_MIN : 2 * val;
+
+            double w = xB - ((xB - xC) * tmp2 - (xB - xA) * tmp1) / denom;
+            double wLim = xB + growLimit * (xC - xB);
+
+            double fW;
+            if ((w - xC) * (xB - w) > 0) {
+                fW = eval(func, w);
+                if (isMinim ?
+                    fW < fC :
+                    fW > fC) {
+                    xA = xB;
+                    xB = w;
+                    fA = fB;
+                    fB = fW;
+                    break;
+                } else if (isMinim ?
+                           fW > fB :
+                           fW < fB) {
+                    xC = w;
+                    fC = fW;
+                    break;
+                }
+                w = xC + GOLD * (xC - xB);
+                fW = eval(func, w);
+            } else if ((w - wLim) * (wLim - xC) >= 0) {
+                w = wLim;
+                fW = eval(func, w);
+            } else if ((w - wLim) * (xC - w) > 0) {
+                fW = eval(func, w);
+                if (isMinim ?
+                    fW < fC :
+                    fW > fC) {
+                    xB = xC;
+                    xC = w;
+                    w = xC + GOLD * (xC - xB);
+                    fB = fC;
+                    fC =fW;
+                    fW = eval(func, w);
+                }
+            } else {
+                w = xC + GOLD * (xC - xB);
+                fW = eval(func, w);
+            }
+
+            xA = xB;
+            fA = fB;
+            xB = xC;
+            fB = fC;
+            xC = w;
+            fC = fW;
+        }
+
+        lo = xA;
+        fLo = fA;
+        mid = xB;
+        fMid = fB;
+        hi = xC;
+        fHi = fC;
+
+        if (lo > hi) {
+            double tmp = lo;
+            lo = hi;
+            hi = tmp;
+
+            tmp = fLo;
+            fLo = fHi;
+            fHi = tmp;
+        }
+    }
+
+    /**
+     * @return the number of evalutations.
+     */
+    public int getMaxEvaluations() {
+        return evaluations.getMaximalCount();
+    }
+
+    /**
+     * @return the number of evalutations.
+     */
+    public int getEvaluations() {
+        return evaluations.getCount();
+    }
+
+    /**
+     * @return the lower bound of the bracket.
+     * @see #getFLo()
+     */
+    public double getLo() {
+        return lo;
+    }
+
+    /**
+     * Get function value at {@link #getLo()}.
+     * @return function value at {@link #getLo()}
+     */
+    public double getFLo() {
+        return fLo;
+    }
+
+    /**
+     * @return the higher bound of the bracket.
+     * @see #getFHi()
+     */
+    public double getHi() {
+        return hi;
+    }
+
+    /**
+     * Get function value at {@link #getHi()}.
+     * @return function value at {@link #getHi()}
+     */
+    public double getFHi() {
+        return fHi;
+    }
+
+    /**
+     * @return a point in the middle of the bracket.
+     * @see #getFMid()
+     */
+    public double getMid() {
+        return mid;
+    }
+
+    /**
+     * Get function value at {@link #getMid()}.
+     * @return function value at {@link #getMid()}
+     */
+    public double getFMid() {
+        return fMid;
+    }
+
+    /**
+     * @param f Function.
+     * @param x Argument.
+     * @return {@code f(x)}
+     * @throws TooManyEvaluationsException if the maximal number of evaluations is
+     * exceeded.
+     */
+    private double eval(UnivariateFunction f, double x) {
+        try {
+            evaluations.incrementCount();
+        } catch (MaxCountExceededException e) {
+            throw new TooManyEvaluationsException(e.getMax());
+        }
+        return f.value(x);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/univariate/BrentOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/univariate/BrentOptimizer.java
new file mode 100644
index 0000000..763ec99
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/univariate/BrentOptimizer.java
@@ -0,0 +1,316 @@
+/*
+ * 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.math3.optimization.univariate;
+
+import org.apache.commons.math3.util.Precision;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.optimization.ConvergenceChecker;
+import org.apache.commons.math3.optimization.GoalType;
+
+/**
+ * For a function defined on some interval {@code (lo, hi)}, this class
+ * finds an approximation {@code x} to the point at which the function
+ * attains its minimum.
+ * It implements Richard Brent's algorithm (from his book "Algorithms for
+ * Minimization without Derivatives", p. 79) for finding minima of real
+ * univariate functions.
+ * <br/>
+ * This code is an adaptation, partly based on the Python code from SciPy
+ * (module "optimize.py" v0.5); the original algorithm is also modified
+ * <ul>
+ *  <li>to use an initial guess provided by the user,</li>
+ *  <li>to ensure that the best point encountered is the one returned.</li>
+ * </ul>
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 2.0
+ */
+@Deprecated
+public class BrentOptimizer extends BaseAbstractUnivariateOptimizer {
+    /**
+     * Golden section.
+     */
+    private static final double GOLDEN_SECTION = 0.5 * (3 - FastMath.sqrt(5));
+    /**
+     * Minimum relative tolerance.
+     */
+    private static final double MIN_RELATIVE_TOLERANCE = 2 * FastMath.ulp(1d);
+    /**
+     * Relative threshold.
+     */
+    private final double relativeThreshold;
+    /**
+     * Absolute threshold.
+     */
+    private final double absoluteThreshold;
+
+    /**
+     * The arguments are used implement the original stopping criterion
+     * of Brent's algorithm.
+     * {@code abs} and {@code rel} define a tolerance
+     * {@code tol = rel |x| + abs}. {@code rel} should be no smaller than
+     * <em>2 macheps</em> and preferably not much less than <em>sqrt(macheps)</em>,
+     * where <em>macheps</em> is the relative machine precision. {@code abs} must
+     * be positive.
+     *
+     * @param rel Relative threshold.
+     * @param abs Absolute threshold.
+     * @param checker Additional, user-defined, convergence checking
+     * procedure.
+     * @throws NotStrictlyPositiveException if {@code abs <= 0}.
+     * @throws NumberIsTooSmallException if {@code rel < 2 * Math.ulp(1d)}.
+     */
+    public BrentOptimizer(double rel,
+                          double abs,
+                          ConvergenceChecker<UnivariatePointValuePair> checker) {
+        super(checker);
+
+        if (rel < MIN_RELATIVE_TOLERANCE) {
+            throw new NumberIsTooSmallException(rel, MIN_RELATIVE_TOLERANCE, true);
+        }
+        if (abs <= 0) {
+            throw new NotStrictlyPositiveException(abs);
+        }
+
+        relativeThreshold = rel;
+        absoluteThreshold = abs;
+    }
+
+    /**
+     * The arguments are used for implementing the original stopping criterion
+     * of Brent's algorithm.
+     * {@code abs} and {@code rel} define a tolerance
+     * {@code tol = rel |x| + abs}. {@code rel} should be no smaller than
+     * <em>2 macheps</em> and preferably not much less than <em>sqrt(macheps)</em>,
+     * where <em>macheps</em> is the relative machine precision. {@code abs} must
+     * be positive.
+     *
+     * @param rel Relative threshold.
+     * @param abs Absolute threshold.
+     * @throws NotStrictlyPositiveException if {@code abs <= 0}.
+     * @throws NumberIsTooSmallException if {@code rel < 2 * Math.ulp(1d)}.
+     */
+    public BrentOptimizer(double rel,
+                          double abs) {
+        this(rel, abs, null);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected UnivariatePointValuePair doOptimize() {
+        final boolean isMinim = getGoalType() == GoalType.MINIMIZE;
+        final double lo = getMin();
+        final double mid = getStartValue();
+        final double hi = getMax();
+
+        // Optional additional convergence criteria.
+        final ConvergenceChecker<UnivariatePointValuePair> checker
+            = getConvergenceChecker();
+
+        double a;
+        double b;
+        if (lo < hi) {
+            a = lo;
+            b = hi;
+        } else {
+            a = hi;
+            b = lo;
+        }
+
+        double x = mid;
+        double v = x;
+        double w = x;
+        double d = 0;
+        double e = 0;
+        double fx = computeObjectiveValue(x);
+        if (!isMinim) {
+            fx = -fx;
+        }
+        double fv = fx;
+        double fw = fx;
+
+        UnivariatePointValuePair previous = null;
+        UnivariatePointValuePair current
+            = new UnivariatePointValuePair(x, isMinim ? fx : -fx);
+        // Best point encountered so far (which is the initial guess).
+        UnivariatePointValuePair best = current;
+
+        int iter = 0;
+        while (true) {
+            final double m = 0.5 * (a + b);
+            final double tol1 = relativeThreshold * FastMath.abs(x) + absoluteThreshold;
+            final double tol2 = 2 * tol1;
+
+            // Default stopping criterion.
+            final boolean stop = FastMath.abs(x - m) <= tol2 - 0.5 * (b - a);
+            if (!stop) {
+                double p = 0;
+                double q = 0;
+                double r = 0;
+                double u = 0;
+
+                if (FastMath.abs(e) > tol1) { // Fit parabola.
+                    r = (x - w) * (fx - fv);
+                    q = (x - v) * (fx - fw);
+                    p = (x - v) * q - (x - w) * r;
+                    q = 2 * (q - r);
+
+                    if (q > 0) {
+                        p = -p;
+                    } else {
+                        q = -q;
+                    }
+
+                    r = e;
+                    e = d;
+
+                    if (p > q * (a - x) &&
+                        p < q * (b - x) &&
+                        FastMath.abs(p) < FastMath.abs(0.5 * q * r)) {
+                        // Parabolic interpolation step.
+                        d = p / q;
+                        u = x + d;
+
+                        // f must not be evaluated too close to a or b.
+                        if (u - a < tol2 || b - u < tol2) {
+                            if (x <= m) {
+                                d = tol1;
+                            } else {
+                                d = -tol1;
+                            }
+                        }
+                    } else {
+                        // Golden section step.
+                        if (x < m) {
+                            e = b - x;
+                        } else {
+                            e = a - x;
+                        }
+                        d = GOLDEN_SECTION * e;
+                    }
+                } else {
+                    // Golden section step.
+                    if (x < m) {
+                        e = b - x;
+                    } else {
+                        e = a - x;
+                    }
+                    d = GOLDEN_SECTION * e;
+                }
+
+                // Update by at least "tol1".
+                if (FastMath.abs(d) < tol1) {
+                    if (d >= 0) {
+                        u = x + tol1;
+                    } else {
+                        u = x - tol1;
+                    }
+                } else {
+                    u = x + d;
+                }
+
+                double fu = computeObjectiveValue(u);
+                if (!isMinim) {
+                    fu = -fu;
+                }
+
+                // User-defined convergence checker.
+                previous = current;
+                current = new UnivariatePointValuePair(u, isMinim ? fu : -fu);
+                best = best(best,
+                            best(previous,
+                                 current,
+                                 isMinim),
+                            isMinim);
+
+                if (checker != null && checker.converged(iter, previous, current)) {
+                    return best;
+                }
+
+                // Update a, b, v, w and x.
+                if (fu <= fx) {
+                    if (u < x) {
+                        b = x;
+                    } else {
+                        a = x;
+                    }
+                    v = w;
+                    fv = fw;
+                    w = x;
+                    fw = fx;
+                    x = u;
+                    fx = fu;
+                } else {
+                    if (u < x) {
+                        a = u;
+                    } else {
+                        b = u;
+                    }
+                    if (fu <= fw ||
+                        Precision.equals(w, x)) {
+                        v = w;
+                        fv = fw;
+                        w = u;
+                        fw = fu;
+                    } else if (fu <= fv ||
+                               Precision.equals(v, x) ||
+                               Precision.equals(v, w)) {
+                        v = u;
+                        fv = fu;
+                    }
+                }
+            } else { // Default termination (Brent's criterion).
+                return best(best,
+                            best(previous,
+                                 current,
+                                 isMinim),
+                            isMinim);
+            }
+            ++iter;
+        }
+    }
+
+    /**
+     * Selects the best of two points.
+     *
+     * @param a Point and value.
+     * @param b Point and value.
+     * @param isMinim {@code true} if the selected point must be the one with
+     * the lowest value.
+     * @return the best point, or {@code null} if {@code a} and {@code b} are
+     * both {@code null}. When {@code a} and {@code b} have the same function
+     * value, {@code a} is returned.
+     */
+    private UnivariatePointValuePair best(UnivariatePointValuePair a,
+                                          UnivariatePointValuePair b,
+                                          boolean isMinim) {
+        if (a == null) {
+            return b;
+        }
+        if (b == null) {
+            return a;
+        }
+
+        if (isMinim) {
+            return a.getValue() <= b.getValue() ? a : b;
+        } else {
+            return a.getValue() >= b.getValue() ? a : b;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/univariate/SimpleUnivariateValueChecker.java b/src/main/java/org/apache/commons/math3/optimization/univariate/SimpleUnivariateValueChecker.java
new file mode 100644
index 0000000..82c50b6
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/univariate/SimpleUnivariateValueChecker.java
@@ -0,0 +1,139 @@
+/*
+ * 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.math3.optimization.univariate;
+
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.optimization.AbstractConvergenceChecker;
+
+/**
+ * Simple implementation of the
+ * {@link org.apache.commons.math3.optimization.ConvergenceChecker} interface
+ * that uses only objective function values.
+ *
+ * Convergence is considered to have been reached if either the relative
+ * difference between the objective function values is smaller than a
+ * threshold or if either the absolute difference between the objective
+ * function values is smaller than another threshold.
+ * <br/>
+ * The {@link #converged(int,UnivariatePointValuePair,UnivariatePointValuePair)
+ * converged} method will also return {@code true} if the number of iterations
+ * has been set (see {@link #SimpleUnivariateValueChecker(double,double,int)
+ * this constructor}).
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.1
+ */
+@Deprecated
+public class SimpleUnivariateValueChecker
+    extends AbstractConvergenceChecker<UnivariatePointValuePair> {
+    /**
+     * If {@link #maxIterationCount} is set to this value, the number of
+     * iterations will never cause
+     * {@link #converged(int,UnivariatePointValuePair,UnivariatePointValuePair)}
+     * to return {@code true}.
+     */
+    private static final int ITERATION_CHECK_DISABLED = -1;
+    /**
+     * Number of iterations after which the
+     * {@link #converged(int,UnivariatePointValuePair,UnivariatePointValuePair)}
+     * method will return true (unless the check is disabled).
+     */
+    private final int maxIterationCount;
+
+    /**
+     * Build an instance with default thresholds.
+     * @deprecated See {@link AbstractConvergenceChecker#AbstractConvergenceChecker()}
+     */
+    @Deprecated
+    public SimpleUnivariateValueChecker() {
+        maxIterationCount = ITERATION_CHECK_DISABLED;
+    }
+
+    /** Build an instance with specified thresholds.
+     *
+     * In order to perform only relative checks, the absolute tolerance
+     * must be set to a negative value. In order to perform only absolute
+     * checks, the relative tolerance must be set to a negative value.
+     *
+     * @param relativeThreshold relative tolerance threshold
+     * @param absoluteThreshold absolute tolerance threshold
+     */
+    public SimpleUnivariateValueChecker(final double relativeThreshold,
+                                        final double absoluteThreshold) {
+        super(relativeThreshold, absoluteThreshold);
+        maxIterationCount = ITERATION_CHECK_DISABLED;
+    }
+
+    /**
+     * Builds an instance with specified thresholds.
+     *
+     * In order to perform only relative checks, the absolute tolerance
+     * must be set to a negative value. In order to perform only absolute
+     * checks, the relative tolerance must be set to a negative value.
+     *
+     * @param relativeThreshold relative tolerance threshold
+     * @param absoluteThreshold absolute tolerance threshold
+     * @param maxIter Maximum iteration count.
+     * @throws NotStrictlyPositiveException if {@code maxIter <= 0}.
+     *
+     * @since 3.1
+     */
+    public SimpleUnivariateValueChecker(final double relativeThreshold,
+                                        final double absoluteThreshold,
+                                        final int maxIter) {
+        super(relativeThreshold, absoluteThreshold);
+
+        if (maxIter <= 0) {
+            throw new NotStrictlyPositiveException(maxIter);
+        }
+        maxIterationCount = maxIter;
+    }
+
+    /**
+     * Check if the optimization algorithm has converged considering the
+     * last two points.
+     * This method may be called several time from the same algorithm
+     * iteration with different points. This can be detected by checking the
+     * iteration number at each call if needed. Each time this method is
+     * called, the previous and current point correspond to points with the
+     * same role at each iteration, so they can be compared. As an example,
+     * simplex-based algorithms call this method for all points of the simplex,
+     * not only for the best or worst ones.
+     *
+     * @param iteration Index of current iteration
+     * @param previous Best point in the previous iteration.
+     * @param current Best point in the current iteration.
+     * @return {@code true} if the algorithm has converged.
+     */
+    @Override
+    public boolean converged(final int iteration,
+                             final UnivariatePointValuePair previous,
+                             final UnivariatePointValuePair current) {
+        if (maxIterationCount != ITERATION_CHECK_DISABLED && iteration >= maxIterationCount) {
+            return true;
+        }
+
+        final double p = previous.getValue();
+        final double c = current.getValue();
+        final double difference = FastMath.abs(p - c);
+        final double size = FastMath.max(FastMath.abs(p), FastMath.abs(c));
+        return difference <= size * getRelativeThreshold() ||
+            difference <= getAbsoluteThreshold();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/univariate/UnivariateMultiStartOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/univariate/UnivariateMultiStartOptimizer.java
new file mode 100644
index 0000000..f63beb2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/univariate/UnivariateMultiStartOptimizer.java
@@ -0,0 +1,203 @@
+/*
+ * 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.math3.optimization.univariate;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.optimization.GoalType;
+import org.apache.commons.math3.optimization.ConvergenceChecker;
+
+/**
+ * Special implementation of the {@link UnivariateOptimizer} interface
+ * adding multi-start features to an existing optimizer.
+ *
+ * This class wraps a classical optimizer to use it several times in
+ * turn with different starting points in order to avoid being trapped
+ * into a local extremum when looking for a global one.
+ *
+ * @param <FUNC> Type of the objective function to be optimized.
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.0
+ */
+@Deprecated
+public class UnivariateMultiStartOptimizer<FUNC extends UnivariateFunction>
+    implements BaseUnivariateOptimizer<FUNC> {
+    /** Underlying classical optimizer. */
+    private final BaseUnivariateOptimizer<FUNC> optimizer;
+    /** Maximal number of evaluations allowed. */
+    private int maxEvaluations;
+    /** Number of evaluations already performed for all starts. */
+    private int totalEvaluations;
+    /** Number of starts to go. */
+    private int starts;
+    /** Random generator for multi-start. */
+    private RandomGenerator generator;
+    /** Found optima. */
+    private UnivariatePointValuePair[] optima;
+
+    /**
+     * Create a multi-start optimizer from a single-start optimizer.
+     *
+     * @param optimizer Single-start optimizer to wrap.
+     * @param starts Number of starts to perform. If {@code starts == 1},
+     * the {@code optimize} methods will return the same solution as
+     * {@code optimizer} would.
+     * @param generator Random generator to use for restarts.
+     * @throws NullArgumentException if {@code optimizer} or {@code generator}
+     * is {@code null}.
+     * @throws NotStrictlyPositiveException if {@code starts < 1}.
+     */
+    public UnivariateMultiStartOptimizer(final BaseUnivariateOptimizer<FUNC> optimizer,
+                                             final int starts,
+                                             final RandomGenerator generator) {
+        if (optimizer == null ||
+                generator == null) {
+                throw new NullArgumentException();
+        }
+        if (starts < 1) {
+            throw new NotStrictlyPositiveException(starts);
+        }
+
+        this.optimizer = optimizer;
+        this.starts = starts;
+        this.generator = generator;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public ConvergenceChecker<UnivariatePointValuePair> getConvergenceChecker() {
+        return optimizer.getConvergenceChecker();
+    }
+
+    /** {@inheritDoc} */
+    public int getMaxEvaluations() {
+        return maxEvaluations;
+    }
+
+    /** {@inheritDoc} */
+    public int getEvaluations() {
+        return totalEvaluations;
+    }
+
+    /**
+     * Get all the optima found during the last call to {@link
+     * #optimize(int,UnivariateFunction,GoalType,double,double) optimize}.
+     * The optimizer stores all the optima found during a set of
+     * restarts. The {@link #optimize(int,UnivariateFunction,GoalType,double,double) optimize}
+     * method returns the best point only. This method returns all the points
+     * found at the end of each starts, including the best one already
+     * returned by the {@link #optimize(int,UnivariateFunction,GoalType,double,double) optimize}
+     * method.
+     * <br/>
+     * The returned array as one element for each start as specified
+     * in the constructor. It is ordered with the results from the
+     * runs that did converge first, sorted from best to worst
+     * objective value (i.e in ascending order if minimizing and in
+     * descending order if maximizing), followed by {@code null} elements
+     * corresponding to the runs that did not converge. This means all
+     * elements will be {@code null} if the {@link
+     * #optimize(int,UnivariateFunction,GoalType,double,double) optimize}
+     * method did throw an exception.
+     * This also means that if the first element is not {@code null}, it is
+     * the best point found across all starts.
+     *
+     * @return an array containing the optima.
+     * @throws MathIllegalStateException if {@link
+     * #optimize(int,UnivariateFunction,GoalType,double,double) optimize}
+     * has not been called.
+     */
+    public UnivariatePointValuePair[] getOptima() {
+        if (optima == null) {
+            throw new MathIllegalStateException(LocalizedFormats.NO_OPTIMUM_COMPUTED_YET);
+        }
+        return optima.clone();
+    }
+
+    /** {@inheritDoc} */
+    public UnivariatePointValuePair optimize(int maxEval, final FUNC f,
+                                                 final GoalType goal,
+                                                 final double min, final double max) {
+        return optimize(maxEval, f, goal, min, max, min + 0.5 * (max - min));
+    }
+
+    /** {@inheritDoc} */
+    public UnivariatePointValuePair optimize(int maxEval, final FUNC f,
+                                                 final GoalType goal,
+                                                 final double min, final double max,
+                                                 final double startValue) {
+        RuntimeException lastException = null;
+        optima = new UnivariatePointValuePair[starts];
+        totalEvaluations = 0;
+
+        // Multi-start loop.
+        for (int i = 0; i < starts; ++i) {
+            // CHECKSTYLE: stop IllegalCatch
+            try {
+                final double s = (i == 0) ? startValue : min + generator.nextDouble() * (max - min);
+                optima[i] = optimizer.optimize(maxEval - totalEvaluations, f, goal, min, max, s);
+            } catch (RuntimeException mue) {
+                lastException = mue;
+                optima[i] = null;
+            }
+            // CHECKSTYLE: resume IllegalCatch
+
+            totalEvaluations += optimizer.getEvaluations();
+        }
+
+        sortPairs(goal);
+
+        if (optima[0] == null) {
+            throw lastException; // cannot be null if starts >=1
+        }
+
+        // Return the point with the best objective function value.
+        return optima[0];
+    }
+
+    /**
+     * Sort the optima from best to worst, followed by {@code null} elements.
+     *
+     * @param goal Goal type.
+     */
+    private void sortPairs(final GoalType goal) {
+        Arrays.sort(optima, new Comparator<UnivariatePointValuePair>() {
+                /** {@inheritDoc} */
+                public int compare(final UnivariatePointValuePair o1,
+                                   final UnivariatePointValuePair o2) {
+                    if (o1 == null) {
+                        return (o2 == null) ? 0 : 1;
+                    } else if (o2 == null) {
+                        return -1;
+                    }
+                    final double v1 = o1.getValue();
+                    final double v2 = o2.getValue();
+                    return (goal == GoalType.MINIMIZE) ?
+                        Double.compare(v1, v2) : Double.compare(v2, v1);
+                }
+            });
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/univariate/UnivariateOptimizer.java b/src/main/java/org/apache/commons/math3/optimization/univariate/UnivariateOptimizer.java
new file mode 100644
index 0000000..e3ebbb3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/univariate/UnivariateOptimizer.java
@@ -0,0 +1,29 @@
+/*
+ * 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.math3.optimization.univariate;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+
+/**
+ * Interface for univariate optimization algorithms.
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.0
+ */
+@Deprecated
+public interface UnivariateOptimizer
+    extends BaseUnivariateOptimizer<UnivariateFunction> {}
diff --git a/src/main/java/org/apache/commons/math3/optimization/univariate/UnivariatePointValuePair.java b/src/main/java/org/apache/commons/math3/optimization/univariate/UnivariatePointValuePair.java
new file mode 100644
index 0000000..eee931c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/univariate/UnivariatePointValuePair.java
@@ -0,0 +1,68 @@
+/*
+ * 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.math3.optimization.univariate;
+
+import java.io.Serializable;
+
+/**
+ * This class holds a point and the value of an objective function at this
+ * point.
+ * This is a simple immutable container.
+ *
+ * @deprecated As of 3.1 (to be removed in 4.0).
+ * @since 3.0
+ */
+@Deprecated
+public class UnivariatePointValuePair implements Serializable {
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 1003888396256744753L;
+    /** Point. */
+    private final double point;
+    /** Value of the objective function at the point. */
+    private final double value;
+
+    /**
+     * Build a point/objective function value pair.
+     *
+     * @param point Point.
+     * @param value Value of an objective function at the point
+     */
+    public UnivariatePointValuePair(final double point,
+                                    final double value) {
+        this.point = point;
+        this.value = value;
+    }
+
+    /**
+     * Get the point.
+     *
+     * @return the point.
+     */
+    public double getPoint() {
+        return point;
+    }
+
+    /**
+     * Get the value of the objective function.
+     *
+     * @return the stored value of the objective function.
+     */
+    public double getValue() {
+        return value;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/optimization/univariate/package-info.java b/src/main/java/org/apache/commons/math3/optimization/univariate/package-info.java
new file mode 100644
index 0000000..04feb33
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/optimization/univariate/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+/**
+ *
+ *     Univariate real functions minimum finding algorithms.
+ *
+ */
+package org.apache.commons.math3.optimization.univariate;
diff --git a/src/main/java/org/apache/commons/math3/package-info.java b/src/main/java/org/apache/commons/math3/package-info.java
new file mode 100644
index 0000000..200346d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+/**
+ * Common classes used throughout the commons-math library.
+ */
+package org.apache.commons.math3;
diff --git a/src/main/java/org/apache/commons/math3/primes/PollardRho.java b/src/main/java/org/apache/commons/math3/primes/PollardRho.java
new file mode 100644
index 0000000..4cbc064
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/primes/PollardRho.java
@@ -0,0 +1,165 @@
+/*
+ * 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.math3.primes;
+
+import org.apache.commons.math3.util.FastMath;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implementation of the Pollard's rho factorization algorithm.
+ *
+ * @since 3.2
+ */
+class PollardRho {
+
+    /** Hide utility class. */
+    private PollardRho() {}
+
+    /**
+     * Factorization using Pollard's rho algorithm.
+     *
+     * @param n number to factors, must be &gt; 0
+     * @return the list of prime factors of n.
+     */
+    public static List<Integer> primeFactors(int n) {
+        final List<Integer> factors = new ArrayList<Integer>();
+
+        n = SmallPrimes.smallTrialDivision(n, factors);
+        if (1 == n) {
+            return factors;
+        }
+
+        if (SmallPrimes.millerRabinPrimeTest(n)) {
+            factors.add(n);
+            return factors;
+        }
+
+        int divisor = rhoBrent(n);
+        factors.add(divisor);
+        factors.add(n / divisor);
+        return factors;
+    }
+
+    /**
+     * Implementation of the Pollard's rho factorization algorithm.
+     *
+     * <p>This implementation follows the paper "An improved Monte Carlo factorization algorithm" by
+     * Richard P. Brent. This avoids the triple computation of f(x) typically found in Pollard's rho
+     * implementations. It also batches several gcd computation into 1.
+     *
+     * <p>The backtracking is not implemented as we deal only with semi-primes.
+     *
+     * @param n number to factor, must be semi-prime.
+     * @return a prime factor of n.
+     */
+    static int rhoBrent(final int n) {
+        final int x0 = 2;
+        final int m = 25;
+        int cst = SmallPrimes.PRIMES_LAST;
+        int y = x0;
+        int r = 1;
+        do {
+            int x = y;
+            for (int i = 0; i < r; i++) {
+                final long y2 = ((long) y) * y;
+                y = (int) ((y2 + cst) % n);
+            }
+            int k = 0;
+            do {
+                final int bound = FastMath.min(m, r - k);
+                int q = 1;
+                for (int i = -3;
+                        i < bound;
+                        i++) { // start at -3 to ensure we enter this loop at least 3 times
+                    final long y2 = ((long) y) * y;
+                    y = (int) ((y2 + cst) % n);
+                    final long divisor = FastMath.abs(x - y);
+                    if (0 == divisor) {
+                        cst += SmallPrimes.PRIMES_LAST;
+                        k = -m;
+                        y = x0;
+                        r = 1;
+                        break;
+                    }
+                    final long prod = divisor * q;
+                    q = (int) (prod % n);
+                    if (0 == q) {
+                        return gcdPositive(FastMath.abs((int) divisor), n);
+                    }
+                }
+                final int out = gcdPositive(FastMath.abs(q), n);
+                if (1 != out) {
+                    return out;
+                }
+                k += m;
+            } while (k < r);
+            r = 2 * r;
+        } while (true);
+    }
+
+    /**
+     * Gcd between two positive numbers.
+     *
+     * <p>Gets the greatest common divisor of two numbers, using the "binary gcd" method, which
+     * avoids division and modulo operations. See Knuth 4.5.2 algorithm B. This algorithm is due to
+     * Josef Stein (1961). Special cases:
+     *
+     * <ul>
+     *   <li>The result of {@code gcd(x, x)}, {@code gcd(0, x)} and {@code gcd(x, 0)} is the value
+     *       of {@code x}.
+     *   <li>The invocation {@code gcd(0, 0)} is the only one which returns {@code 0}.
+     * </ul>
+     *
+     * @param a first number, must be &ge; 0
+     * @param b second number, must be &ge; 0
+     * @return gcd(a,b)
+     */
+    static int gcdPositive(int a, int b) {
+        // both a and b must be positive, it is not checked here
+        // gdc(a,0) = a
+        if (a == 0) {
+            return b;
+        } else if (b == 0) {
+            return a;
+        }
+
+        // make a and b odd, keep in mind the common power of twos
+        final int aTwos = Integer.numberOfTrailingZeros(a);
+        a >>= aTwos;
+        final int bTwos = Integer.numberOfTrailingZeros(b);
+        b >>= bTwos;
+        final int shift = FastMath.min(aTwos, bTwos);
+
+        // a and b >0
+        // if a > b then gdc(a,b) = gcd(a-b,b)
+        // if a < b then gcd(a,b) = gcd(b-a,a)
+        // so next a is the absolute difference and next b is the minimum of current values
+        while (a != b) {
+            final int delta = a - b;
+            b = FastMath.min(a, b);
+            a = FastMath.abs(delta);
+            // for speed optimization:
+            // remove any power of two in a as b is guaranteed to be odd throughout all iterations
+            a >>= Integer.numberOfTrailingZeros(a);
+        }
+
+        // gcd(a,a) = a, just "add" the common power of twos
+        return a << shift;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/primes/Primes.java b/src/main/java/org/apache/commons/math3/primes/Primes.java
new file mode 100644
index 0000000..4b003f9
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/primes/Primes.java
@@ -0,0 +1,124 @@
+/*
+ * 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.math3.primes;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+import java.util.List;
+
+/**
+ * Methods related to prime numbers in the range of <code>int</code>:
+ *
+ * <ul>
+ *   <li>primality test
+ *   <li>prime number generation
+ *   <li>factorization
+ * </ul>
+ *
+ * @since 3.2
+ */
+public class Primes {
+
+    /** Hide utility class. */
+    private Primes() {}
+
+    /**
+     * Primality test: tells if the argument is a (provable) prime or not.
+     *
+     * <p>It uses the Miller-Rabin probabilistic test in such a way that a result is guaranteed: it
+     * uses the firsts prime numbers as successive base (see Handbook of applied cryptography by
+     * Menezes, table 4.1).
+     *
+     * @param n number to test.
+     * @return true if n is prime. (All numbers &lt; 2 return false).
+     */
+    public static boolean isPrime(int n) {
+        if (n < 2) {
+            return false;
+        }
+
+        for (int p : SmallPrimes.PRIMES) {
+            if (0 == (n % p)) {
+                return n == p;
+            }
+        }
+        return SmallPrimes.millerRabinPrimeTest(n);
+    }
+
+    /**
+     * Return the smallest prime greater than or equal to n.
+     *
+     * @param n a positive number.
+     * @return the smallest prime greater than or equal to n.
+     * @throws MathIllegalArgumentException if n &lt; 0.
+     */
+    public static int nextPrime(int n) {
+        if (n < 0) {
+            throw new MathIllegalArgumentException(LocalizedFormats.NUMBER_TOO_SMALL, n, 0);
+        }
+        if (n == 2) {
+            return 2;
+        }
+        n |= 1; // make sure n is odd
+        if (n == 1) {
+            return 2;
+        }
+
+        if (isPrime(n)) {
+            return n;
+        }
+
+        // prepare entry in the +2, +4 loop:
+        // n should not be a multiple of 3
+        final int rem = n % 3;
+        if (0 == rem) { // if n % 3 == 0
+            n += 2; // n % 3 == 2
+        } else if (1 == rem) { // if n % 3 == 1
+            // if (isPrime(n)) return n;
+            n += 4; // n % 3 == 2
+        }
+        while (true) { // this loop skips all multiple of 3
+            if (isPrime(n)) {
+                return n;
+            }
+            n += 2; // n % 3 == 1
+            if (isPrime(n)) {
+                return n;
+            }
+            n += 4; // n % 3 == 2
+        }
+    }
+
+    /**
+     * Prime factors decomposition
+     *
+     * @param n number to factorize: must be &ge; 2
+     * @return list of prime factors of n
+     * @throws MathIllegalArgumentException if n &lt; 2.
+     */
+    public static List<Integer> primeFactors(int n) {
+
+        if (n < 2) {
+            throw new MathIllegalArgumentException(LocalizedFormats.NUMBER_TOO_SMALL, n, 2);
+        }
+        // slower than trial div unless we do an awful lot of computation
+        // (then it finally gets JIT-compiled efficiently
+        // List<Integer> out = PollardRho.primeFactors(n);
+        return SmallPrimes.trialDivision(n);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/primes/SmallPrimes.java b/src/main/java/org/apache/commons/math3/primes/SmallPrimes.java
new file mode 100644
index 0000000..7439192
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/primes/SmallPrimes.java
@@ -0,0 +1,197 @@
+/*
+ * 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.math3.primes;
+
+import org.apache.commons.math3.util.FastMath;
+
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility methods to work on primes within the <code>int</code> range.
+ *
+ * @since 3.2
+ */
+class SmallPrimes {
+
+    /**
+     * The first 512 prime numbers.
+     *
+     * <p>It contains all primes smaller or equal to the cubic square of Integer.MAX_VALUE. As a
+     * result, <code>int</code> numbers which are not reduced by those primes are guaranteed to be
+     * either prime or semi prime.
+     */
+    public static final int[] PRIMES = {
+        2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89,
+        97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181,
+        191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281,
+        283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397,
+        401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503,
+        509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619,
+        631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743,
+        751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863,
+        877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997,
+        1009, 1013, 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, 1087, 1091, 1093,
+        1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213,
+        1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291, 1297, 1301, 1303,
+        1307, 1319, 1321, 1327, 1361, 1367, 1373, 1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439,
+        1447, 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511, 1523, 1531, 1543,
+        1549, 1553, 1559, 1567, 1571, 1579, 1583, 1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627,
+        1637, 1657, 1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, 1741, 1747, 1753,
+        1759, 1777, 1783, 1787, 1789, 1801, 1811, 1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877,
+        1879, 1889, 1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987, 1993, 1997, 1999,
+        2003, 2011, 2017, 2027, 2029, 2039, 2053, 2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111,
+        2113, 2129, 2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213, 2221, 2237, 2239,
+        2243, 2251, 2267, 2269, 2273, 2281, 2287, 2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347,
+        2351, 2357, 2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423, 2437, 2441, 2447,
+        2459, 2467, 2473, 2477, 2503, 2521, 2531, 2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593,
+        2609, 2617, 2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687, 2689, 2693, 2699,
+        2707, 2711, 2713, 2719, 2729, 2731, 2741, 2749, 2753, 2767, 2777, 2789, 2791, 2797, 2801,
+        2803, 2819, 2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903, 2909, 2917, 2927,
+        2939, 2953, 2957, 2963, 2969, 2971, 2999, 3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061,
+        3067, 3079, 3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181, 3187, 3191, 3203,
+        3209, 3217, 3221, 3229, 3251, 3253, 3257, 3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323,
+        3329, 3331, 3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413, 3433, 3449, 3457,
+        3461, 3463, 3467, 3469, 3491, 3499, 3511, 3517, 3527, 3529, 3533, 3539, 3541, 3547, 3557,
+        3559, 3571, 3581, 3583, 3593, 3607, 3613, 3617, 3623, 3631, 3637, 3643, 3659, 3671
+    };
+
+    /** The last number in PRIMES. */
+    public static final int PRIMES_LAST = PRIMES[PRIMES.length - 1];
+
+    /** Hide utility class. */
+    private SmallPrimes() {}
+
+    /**
+     * Extract small factors.
+     *
+     * @param n the number to factor, must be &gt; 0.
+     * @param factors the list where to add the factors.
+     * @return the part of n which remains to be factored, it is either a prime or a semi-prime
+     */
+    public static int smallTrialDivision(int n, final List<Integer> factors) {
+        for (int p : PRIMES) {
+            while (0 == n % p) {
+                n /= p;
+                factors.add(p);
+            }
+        }
+        return n;
+    }
+
+    /**
+     * Extract factors in the range <code>PRIME_LAST+2</code> to <code>maxFactors</code>.
+     *
+     * @param n the number to factorize, must be >= PRIME_LAST+2 and must not contain any factor
+     *     below PRIME_LAST+2
+     * @param maxFactor the upper bound of trial division: if it is reached, the method gives up and
+     *     returns n.
+     * @param factors the list where to add the factors.
+     * @return n or 1 if factorization is completed.
+     */
+    public static int boundedTrialDivision(int n, int maxFactor, List<Integer> factors) {
+        int f = PRIMES_LAST + 2;
+        // no check is done about n >= f
+        while (f <= maxFactor) {
+            if (0 == n % f) {
+                n /= f;
+                factors.add(f);
+                break;
+            }
+            f += 4;
+            if (0 == n % f) {
+                n /= f;
+                factors.add(f);
+                break;
+            }
+            f += 2;
+        }
+        if (n != 1) {
+            factors.add(n);
+        }
+        return n;
+    }
+
+    /**
+     * Factorization by trial division.
+     *
+     * @param n the number to factor
+     * @return the list of prime factors of n
+     */
+    public static List<Integer> trialDivision(int n) {
+        final List<Integer> factors = new ArrayList<Integer>(32);
+        n = smallTrialDivision(n, factors);
+        if (1 == n) {
+            return factors;
+        }
+        // here we are sure that n is either a prime or a semi prime
+        final int bound = (int) FastMath.sqrt(n);
+        boundedTrialDivision(n, bound, factors);
+        return factors;
+    }
+
+    /**
+     * Miller-Rabin probabilistic primality test for int type, used in such a way that a result is
+     * always guaranteed.
+     *
+     * <p>It uses the prime numbers as successive base therefore it is guaranteed to be always
+     * correct. (see Handbook of applied cryptography by Menezes, table 4.1)
+     *
+     * @param n number to test: an odd integer &ge; 3
+     * @return true if n is prime. false if n is definitely composite.
+     */
+    public static boolean millerRabinPrimeTest(final int n) {
+        final int nMinus1 = n - 1;
+        final int s = Integer.numberOfTrailingZeros(nMinus1);
+        final int r = nMinus1 >> s;
+        // r must be odd, it is not checked here
+        int t = 1;
+        if (n >= 2047) {
+            t = 2;
+        }
+        if (n >= 1373653) {
+            t = 3;
+        }
+        if (n >= 25326001) {
+            t = 4;
+        } // works up to 3.2 billion, int range stops at 2.7 so we are safe :-)
+        BigInteger br = BigInteger.valueOf(r);
+        BigInteger bn = BigInteger.valueOf(n);
+
+        for (int i = 0; i < t; i++) {
+            BigInteger a = BigInteger.valueOf(SmallPrimes.PRIMES[i]);
+            BigInteger bPow = a.modPow(br, bn);
+            int y = bPow.intValue();
+            if ((1 != y) && (y != nMinus1)) {
+                int j = 1;
+                while ((j <= s - 1) && (nMinus1 != y)) {
+                    long square = ((long) y) * y;
+                    y = (int) (square % n);
+                    if (1 == y) {
+                        return false;
+                    } // definitely composite
+                    j++;
+                }
+                if (nMinus1 != y) {
+                    return false;
+                } // definitely composite
+            }
+        }
+        return true; // definitely prime
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/primes/package-info.java b/src/main/java/org/apache/commons/math3/primes/package-info.java
new file mode 100644
index 0000000..166ee0b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/primes/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+/** Methods related to prime numbers like primality test, factor decomposition. */
+package org.apache.commons.math3.primes;
diff --git a/src/main/java/org/apache/commons/math3/random/AbstractRandomGenerator.java b/src/main/java/org/apache/commons/math3/random/AbstractRandomGenerator.java
new file mode 100644
index 0000000..ce8ad85
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/random/AbstractRandomGenerator.java
@@ -0,0 +1,250 @@
+/*
+ * 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.math3.random;
+
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Abstract class implementing the {@link RandomGenerator} interface. Default implementations for
+ * all methods other than {@link #nextDouble()} and {@link #setSeed(long)} are provided.
+ *
+ * <p>All data generation methods are based on {@code code nextDouble()}. Concrete implementations
+ * <strong>must</strong> override this method and <strong>should</strong> provide better / more
+ * performant implementations of the other methods if the underlying PRNG supplies them.
+ *
+ * @since 1.1
+ */
+public abstract class AbstractRandomGenerator implements RandomGenerator {
+
+    /**
+     * Cached random normal value. The default implementation for {@link #nextGaussian} generates
+     * pairs of values and this field caches the second value so that the full algorithm is not
+     * executed for every activation. The value {@code Double.NaN} signals that there is no cached
+     * value. Use {@link #clear} to clear the cached value.
+     */
+    private double cachedNormalDeviate = Double.NaN;
+
+    /** Construct a RandomGenerator. */
+    public AbstractRandomGenerator() {
+        super();
+    }
+
+    /**
+     * Clears the cache used by the default implementation of {@link #nextGaussian}. Implementations
+     * that do not override the default implementation of {@code nextGaussian} should call this
+     * method in the implementation of {@link #setSeed(long)}
+     */
+    public void clear() {
+        cachedNormalDeviate = Double.NaN;
+    }
+
+    /** {@inheritDoc} */
+    public void setSeed(int seed) {
+        setSeed((long) seed);
+    }
+
+    /** {@inheritDoc} */
+    public void setSeed(int[] seed) {
+        // the following number is the largest prime that fits in 32 bits (it is 2^32 - 5)
+        final long prime = 4294967291l;
+
+        long combined = 0l;
+        for (int s : seed) {
+            combined = combined * prime + s;
+        }
+        setSeed(combined);
+    }
+
+    /**
+     * Sets the seed of the underlying random number generator using a {@code long} seed. Sequences
+     * of values generated starting with the same seeds should be identical.
+     *
+     * <p>Implementations that do not override the default implementation of {@code nextGaussian}
+     * should include a call to {@link #clear} in the implementation of this method.
+     *
+     * @param seed the seed value
+     */
+    public abstract void setSeed(long seed);
+
+    /**
+     * Generates random bytes and places them into a user-supplied byte array. The number of random
+     * bytes produced is equal to the length of the byte array.
+     *
+     * <p>The default implementation fills the array with bytes extracted from random integers
+     * generated using {@link #nextInt}.
+     *
+     * @param bytes the non-null byte array in which to put the random bytes
+     */
+    public void nextBytes(byte[] bytes) {
+        int bytesOut = 0;
+        while (bytesOut < bytes.length) {
+            int randInt = nextInt();
+            for (int i = 0; i < 3; i++) {
+                if (i > 0) {
+                    randInt >>= 8;
+                }
+                bytes[bytesOut++] = (byte) randInt;
+                if (bytesOut == bytes.length) {
+                    return;
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the next pseudorandom, uniformly distributed {@code int} value from this random
+     * number generator's sequence. All 2<font size="-1"><sup>32</sup></font> possible {@code int}
+     * values should be produced with (approximately) equal probability.
+     *
+     * <p>The default implementation provided here returns
+     *
+     * <pre>
+     * <code>(int) (nextDouble() * Integer.MAX_VALUE)</code>
+     * </pre>
+     *
+     * @return the next pseudorandom, uniformly distributed {@code int} value from this random
+     *     number generator's sequence
+     */
+    public int nextInt() {
+        return (int) ((2d * nextDouble() - 1d) * Integer.MAX_VALUE);
+    }
+
+    /**
+     * Returns a pseudorandom, uniformly distributed {@code int} value between 0 (inclusive) and the
+     * specified value (exclusive), drawn from this random number generator's sequence.
+     *
+     * <p>The default implementation returns
+     *
+     * <pre>
+     * <code>(int) (nextDouble() * n</code>
+     * </pre>
+     *
+     * @param n the bound on the random number to be returned. Must be positive.
+     * @return a pseudorandom, uniformly distributed {@code int} value between 0 (inclusive) and n
+     *     (exclusive).
+     * @throws NotStrictlyPositiveException if {@code n <= 0}.
+     */
+    public int nextInt(int n) {
+        if (n <= 0) {
+            throw new NotStrictlyPositiveException(n);
+        }
+        int result = (int) (nextDouble() * n);
+        return result < n ? result : n - 1;
+    }
+
+    /**
+     * Returns the next pseudorandom, uniformly distributed {@code long} value from this random
+     * number generator's sequence. All 2<font size="-1"><sup>64</sup></font> possible {@code long}
+     * values should be produced with (approximately) equal probability.
+     *
+     * <p>The default implementation returns
+     *
+     * <pre>
+     * <code>(long) (nextDouble() * Long.MAX_VALUE)</code>
+     * </pre>
+     *
+     * @return the next pseudorandom, uniformly distributed {@code long} value from this random
+     *     number generator's sequence
+     */
+    public long nextLong() {
+        return (long) ((2d * nextDouble() - 1d) * Long.MAX_VALUE);
+    }
+
+    /**
+     * Returns the next pseudorandom, uniformly distributed {@code boolean} value from this random
+     * number generator's sequence.
+     *
+     * <p>The default implementation returns
+     *
+     * <pre>
+     * <code>nextDouble() <= 0.5</code>
+     * </pre>
+     *
+     * @return the next pseudorandom, uniformly distributed {@code boolean} value from this random
+     *     number generator's sequence
+     */
+    public boolean nextBoolean() {
+        return nextDouble() <= 0.5;
+    }
+
+    /**
+     * Returns the next pseudorandom, uniformly distributed {@code float} value between {@code 0.0}
+     * and {@code 1.0} from this random number generator's sequence.
+     *
+     * <p>The default implementation returns
+     *
+     * <pre>
+     * <code>(float) nextDouble() </code>
+     * </pre>
+     *
+     * @return the next pseudorandom, uniformly distributed {@code float} value between {@code 0.0}
+     *     and {@code 1.0} from this random number generator's sequence
+     */
+    public float nextFloat() {
+        return (float) nextDouble();
+    }
+
+    /**
+     * Returns the next pseudorandom, uniformly distributed {@code double} value between {@code 0.0}
+     * and {@code 1.0} from this random number generator's sequence.
+     *
+     * <p>This method provides the underlying source of random data used by the other methods.
+     *
+     * @return the next pseudorandom, uniformly distributed {@code double} value between {@code 0.0}
+     *     and {@code 1.0} from this random number generator's sequence
+     */
+    public abstract double nextDouble();
+
+    /**
+     * Returns the next pseudorandom, Gaussian ("normally") distributed {@code double} value with
+     * mean {@code 0.0} and standard deviation {@code 1.0} from this random number generator's
+     * sequence.
+     *
+     * <p>The default implementation uses the <em>Polar Method</em> due to G.E.P. Box, M.E. Muller
+     * and G. Marsaglia, as described in D. Knuth, <u>The Art of Computer Programming</u>, 3.4.1C.
+     *
+     * <p>The algorithm generates a pair of independent random values. One of these is cached for
+     * reuse, so the full algorithm is not executed on each activation. Implementations that do not
+     * override this method should make sure to call {@link #clear} to clear the cached value in the
+     * implementation of {@link #setSeed(long)}.
+     *
+     * @return the next pseudorandom, Gaussian ("normally") distributed {@code double} value with
+     *     mean {@code 0.0} and standard deviation {@code 1.0} from this random number generator's
+     *     sequence
+     */
+    public double nextGaussian() {
+        if (!Double.isNaN(cachedNormalDeviate)) {
+            double dev = cachedNormalDeviate;
+            cachedNormalDeviate = Double.NaN;
+            return dev;
+        }
+        double v1 = 0;
+        double v2 = 0;
+        double s = 1;
+        while (s >= 1) {
+            v1 = 2 * nextDouble() - 1;
+            v2 = 2 * nextDouble() - 1;
+            s = v1 * v1 + v2 * v2;
+        }
+        if (s != 0) {
+            s = FastMath.sqrt(-2 * FastMath.log(s) / s);
+        }
+        cachedNormalDeviate = v2 * s;
+        return v1 * s;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/random/AbstractWell.java b/src/main/java/org/apache/commons/math3/random/AbstractWell.java
new file mode 100644
index 0000000..87f2ee1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/random/AbstractWell.java
@@ -0,0 +1,216 @@
+/*
+ * 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.math3.random;
+
+import org.apache.commons.math3.util.FastMath;
+
+import java.io.Serializable;
+
+/**
+ * This abstract class implements the WELL class of pseudo-random number generator from
+ * Fran&ccedil;ois Panneton, Pierre L'Ecuyer and Makoto Matsumoto.
+ *
+ * <p>This generator is described in a paper by Fran&ccedil;ois Panneton, Pierre L'Ecuyer and Makoto
+ * Matsumoto <a href="http://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf">Improved
+ * Long-Period Generators Based on Linear Recurrences Modulo 2</a> ACM Transactions on Mathematical
+ * Software, 32, 1 (2006). The errata for the paper are in <a
+ * href="http://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng-errata.txt">wellrng-errata.txt</a>.
+ *
+ * @see <a href="http://www.iro.umontreal.ca/~panneton/WELLRNG.html">WELL Random number
+ *     generator</a>
+ * @since 2.2
+ */
+public abstract class AbstractWell extends BitsStreamGenerator implements Serializable {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -817701723016583596L;
+
+    /** Current index in the bytes pool. */
+    protected int index;
+
+    /** Bytes pool. */
+    protected final int[] v;
+
+    /**
+     * Index indirection table giving for each index its predecessor taking table size into account.
+     */
+    protected final int[] iRm1;
+
+    /**
+     * Index indirection table giving for each index its second predecessor taking table size into
+     * account.
+     */
+    protected final int[] iRm2;
+
+    /**
+     * Index indirection table giving for each index the value index + m1 taking table size into
+     * account.
+     */
+    protected final int[] i1;
+
+    /**
+     * Index indirection table giving for each index the value index + m2 taking table size into
+     * account.
+     */
+    protected final int[] i2;
+
+    /**
+     * Index indirection table giving for each index the value index + m3 taking table size into
+     * account.
+     */
+    protected final int[] i3;
+
+    /**
+     * Creates a new random number generator.
+     *
+     * <p>The instance is initialized using the current time plus the system identity hash code of
+     * this instance as the seed.
+     *
+     * @param k number of bits in the pool (not necessarily a multiple of 32)
+     * @param m1 first parameter of the algorithm
+     * @param m2 second parameter of the algorithm
+     * @param m3 third parameter of the algorithm
+     */
+    protected AbstractWell(final int k, final int m1, final int m2, final int m3) {
+        this(k, m1, m2, m3, null);
+    }
+
+    /**
+     * Creates a new random number generator using a single int seed.
+     *
+     * @param k number of bits in the pool (not necessarily a multiple of 32)
+     * @param m1 first parameter of the algorithm
+     * @param m2 second parameter of the algorithm
+     * @param m3 third parameter of the algorithm
+     * @param seed the initial seed (32 bits integer)
+     */
+    protected AbstractWell(final int k, final int m1, final int m2, final int m3, final int seed) {
+        this(k, m1, m2, m3, new int[] {seed});
+    }
+
+    /**
+     * Creates a new random number generator using an int array seed.
+     *
+     * @param k number of bits in the pool (not necessarily a multiple of 32)
+     * @param m1 first parameter of the algorithm
+     * @param m2 second parameter of the algorithm
+     * @param m3 third parameter of the algorithm
+     * @param seed the initial seed (32 bits integers array), if null the seed of the generator will
+     *     be related to the current time
+     */
+    protected AbstractWell(
+            final int k, final int m1, final int m2, final int m3, final int[] seed) {
+
+        // the bits pool contains k bits, k = r w - p where r is the number
+        // of w bits blocks, w is the block size (always 32 in the original paper)
+        // and p is the number of unused bits in the last block
+        final int w = 32;
+        final int r = (k + w - 1) / w;
+        this.v = new int[r];
+        this.index = 0;
+
+        // precompute indirection index tables. These tables are used for optimizing access
+        // they allow saving computations like "(j + r - 2) % r" with costly modulo operations
+        iRm1 = new int[r];
+        iRm2 = new int[r];
+        i1 = new int[r];
+        i2 = new int[r];
+        i3 = new int[r];
+        for (int j = 0; j < r; ++j) {
+            iRm1[j] = (j + r - 1) % r;
+            iRm2[j] = (j + r - 2) % r;
+            i1[j] = (j + m1) % r;
+            i2[j] = (j + m2) % r;
+            i3[j] = (j + m3) % r;
+        }
+
+        // initialize the pool content
+        setSeed(seed);
+    }
+
+    /**
+     * Creates a new random number generator using a single long seed.
+     *
+     * @param k number of bits in the pool (not necessarily a multiple of 32)
+     * @param m1 first parameter of the algorithm
+     * @param m2 second parameter of the algorithm
+     * @param m3 third parameter of the algorithm
+     * @param seed the initial seed (64 bits integer)
+     */
+    protected AbstractWell(final int k, final int m1, final int m2, final int m3, final long seed) {
+        this(k, m1, m2, m3, new int[] {(int) (seed >>> 32), (int) (seed & 0xffffffffl)});
+    }
+
+    /**
+     * Reinitialize the generator as if just built with the given int seed.
+     *
+     * <p>The state of the generator is exactly the same as a new generator built with the same
+     * seed.
+     *
+     * @param seed the initial seed (32 bits integer)
+     */
+    @Override
+    public void setSeed(final int seed) {
+        setSeed(new int[] {seed});
+    }
+
+    /**
+     * Reinitialize the generator as if just built with the given int array seed.
+     *
+     * <p>The state of the generator is exactly the same as a new generator built with the same
+     * seed.
+     *
+     * @param seed the initial seed (32 bits integers array). If null the seed of the generator will
+     *     be the system time plus the system identity hash code of the instance.
+     */
+    @Override
+    public void setSeed(final int[] seed) {
+        if (seed == null) {
+            setSeed(System.currentTimeMillis() + System.identityHashCode(this));
+            return;
+        }
+
+        System.arraycopy(seed, 0, v, 0, FastMath.min(seed.length, v.length));
+
+        if (seed.length < v.length) {
+            for (int i = seed.length; i < v.length; ++i) {
+                final long l = v[i - seed.length];
+                v[i] = (int) ((1812433253l * (l ^ (l >> 30)) + i) & 0xffffffffL);
+            }
+        }
+
+        index = 0;
+        clear(); // Clear normal deviate cache
+    }
+
+    /**
+     * Reinitialize the generator as if just built with the given long seed.
+     *
+     * <p>The state of the generator is exactly the same as a new generator built with the same
+     * seed.
+     *
+     * @param seed the initial seed (64 bits integer)
+     */
+    @Override
+    public void setSeed(final long seed) {
+        setSeed(new int[] {(int) (seed >>> 32), (int) (seed & 0xffffffffl)});
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected abstract int next(final int bits);
+}
diff --git a/src/main/java/org/apache/commons/math3/random/BitsStreamGenerator.java b/src/main/java/org/apache/commons/math3/random/BitsStreamGenerator.java
new file mode 100644
index 0000000..07ab156
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/random/BitsStreamGenerator.java
@@ -0,0 +1,249 @@
+/*
+ * 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.math3.random;
+
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.util.FastMath;
+
+import java.io.Serializable;
+
+/**
+ * Base class for random number generators that generates bits streams.
+ *
+ * @since 2.0
+ */
+public abstract class BitsStreamGenerator implements RandomGenerator, Serializable {
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 20130104L;
+
+    /** Next gaussian. */
+    private double nextGaussian;
+
+    /** Creates a new random number generator. */
+    public BitsStreamGenerator() {
+        nextGaussian = Double.NaN;
+    }
+
+    /** {@inheritDoc} */
+    public abstract void setSeed(int seed);
+
+    /** {@inheritDoc} */
+    public abstract void setSeed(int[] seed);
+
+    /** {@inheritDoc} */
+    public abstract void setSeed(long seed);
+
+    /**
+     * Generate next pseudorandom number.
+     *
+     * <p>This method is the core generation algorithm. It is used by all the public generation
+     * methods for the various primitive types {@link #nextBoolean()}, {@link #nextBytes(byte[])},
+     * {@link #nextDouble()}, {@link #nextFloat()}, {@link #nextGaussian()}, {@link #nextInt()},
+     * {@link #next(int)} and {@link #nextLong()}.
+     *
+     * @param bits number of random bits to produce
+     * @return random bits generated
+     */
+    protected abstract int next(int bits);
+
+    /** {@inheritDoc} */
+    public boolean nextBoolean() {
+        return next(1) != 0;
+    }
+
+    /** {@inheritDoc} */
+    public double nextDouble() {
+        final long high = ((long) next(26)) << 26;
+        final int low = next(26);
+        return (high | low) * 0x1.0p-52d;
+    }
+
+    /** {@inheritDoc} */
+    public float nextFloat() {
+        return next(23) * 0x1.0p-23f;
+    }
+
+    /** {@inheritDoc} */
+    public double nextGaussian() {
+
+        final double random;
+        if (Double.isNaN(nextGaussian)) {
+            // generate a new pair of gaussian numbers
+            final double x = nextDouble();
+            final double y = nextDouble();
+            final double alpha = 2 * FastMath.PI * x;
+            final double r = FastMath.sqrt(-2 * FastMath.log(y));
+            random = r * FastMath.cos(alpha);
+            nextGaussian = r * FastMath.sin(alpha);
+        } else {
+            // use the second element of the pair already generated
+            random = nextGaussian;
+            nextGaussian = Double.NaN;
+        }
+
+        return random;
+    }
+
+    /** {@inheritDoc} */
+    public int nextInt() {
+        return next(32);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>This default implementation is copied from Apache Harmony java.util.Random (r929253).
+     *
+     * <p>Implementation notes:
+     *
+     * <ul>
+     *   <li>If n is a power of 2, this method returns {@code (int) ((n * (long) next(31)) >> 31)}.
+     *   <li>If n is not a power of 2, what is returned is {@code next(31) % n} with {@code
+     *       next(31)} values rejected (i.e. regenerated) until a value that is larger than the
+     *       remainder of {@code Integer.MAX_VALUE / n} is generated. Rejection of this initial
+     *       segment is necessary to ensure a uniform distribution.
+     * </ul>
+     */
+    public int nextInt(int n) throws IllegalArgumentException {
+        if (n > 0) {
+            if ((n & -n) == n) {
+                return (int) ((n * (long) next(31)) >> 31);
+            }
+            int bits;
+            int val;
+            do {
+                bits = next(31);
+                val = bits % n;
+            } while (bits - val + (n - 1) < 0);
+            return val;
+        }
+        throw new NotStrictlyPositiveException(n);
+    }
+
+    /** {@inheritDoc} */
+    public long nextLong() {
+        final long high = ((long) next(32)) << 32;
+        final long low = ((long) next(32)) & 0xffffffffL;
+        return high | low;
+    }
+
+    /**
+     * Returns a pseudorandom, uniformly distributed {@code long} value between 0 (inclusive) and
+     * the specified value (exclusive), drawn from this random number generator's sequence.
+     *
+     * @param n the bound on the random number to be returned. Must be positive.
+     * @return a pseudorandom, uniformly distributed {@code long} value between 0 (inclusive) and n
+     *     (exclusive).
+     * @throws IllegalArgumentException if n is not positive.
+     */
+    public long nextLong(long n) throws IllegalArgumentException {
+        if (n > 0) {
+            long bits;
+            long val;
+            do {
+                bits = ((long) next(31)) << 32;
+                bits |= ((long) next(32)) & 0xffffffffL;
+                val = bits % n;
+            } while (bits - val + (n - 1) < 0);
+            return val;
+        }
+        throw new NotStrictlyPositiveException(n);
+    }
+
+    /** Clears the cache used by the default implementation of {@link #nextGaussian}. */
+    public void clear() {
+        nextGaussian = Double.NaN;
+    }
+
+    /**
+     * Generates random bytes and places them into a user-supplied array.
+     *
+     * <p>The array is filled with bytes extracted from random integers. This implies that the
+     * number of random bytes generated may be larger than the length of the byte array.
+     *
+     * @param bytes Array in which to put the generated bytes. Cannot be {@code null}.
+     */
+    public void nextBytes(byte[] bytes) {
+        nextBytesFill(bytes, 0, bytes.length);
+    }
+
+    /**
+     * Generates random bytes and places them into a user-supplied array.
+     *
+     * <p>The array is filled with bytes extracted from random integers. This implies that the
+     * number of random bytes generated may be larger than the length of the byte array.
+     *
+     * @param bytes Array in which to put the generated bytes. Cannot be {@code null}.
+     * @param start Index at which to start inserting the generated bytes.
+     * @param len Number of bytes to insert.
+     * @throws OutOfRangeException if {@code start < 0} or {@code start >= bytes.length}.
+     * @throws OutOfRangeException if {@code len < 0} or {@code len > bytes.length - start}.
+     */
+    public void nextBytes(byte[] bytes, int start, int len) {
+        if (start < 0 || start >= bytes.length) {
+            throw new OutOfRangeException(start, 0, bytes.length);
+        }
+        if (len < 0 || len > bytes.length - start) {
+            throw new OutOfRangeException(len, 0, bytes.length - start);
+        }
+
+        nextBytesFill(bytes, start, len);
+    }
+
+    /**
+     * Generates random bytes and places them into a user-supplied array.
+     *
+     * <p>The array is filled with bytes extracted from random integers. This implies that the
+     * number of random bytes generated may be larger than the length of the byte array.
+     *
+     * @param bytes Array in which to put the generated bytes. Cannot be {@code null}.
+     * @param start Index at which to start inserting the generated bytes.
+     * @param len Number of bytes to insert.
+     */
+    private void nextBytesFill(byte[] bytes, int start, int len) {
+        int index = start; // Index of first insertion.
+
+        // Index of first insertion plus multiple 4 part of length (i.e. length
+        // with two least significant bits unset).
+        final int indexLoopLimit = index + (len & 0x7ffffffc);
+
+        // Start filling in the byte array, 4 bytes at a time.
+        while (index < indexLoopLimit) {
+            final int random = next(32);
+            bytes[index++] = (byte) random;
+            bytes[index++] = (byte) (random >>> 8);
+            bytes[index++] = (byte) (random >>> 16);
+            bytes[index++] = (byte) (random >>> 24);
+        }
+
+        final int indexLimit = start + len; // Index of last insertion + 1.
+
+        // Fill in the remaining bytes.
+        if (index < indexLimit) {
+            int random = next(32);
+            while (true) {
+                bytes[index++] = (byte) random;
+                if (index < indexLimit) {
+                    random >>>= 8;
+                } else {
+                    break;
+                }
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/random/CorrelatedRandomVectorGenerator.java b/src/main/java/org/apache/commons/math3/random/CorrelatedRandomVectorGenerator.java
new file mode 100644
index 0000000..6668356
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/random/CorrelatedRandomVectorGenerator.java
@@ -0,0 +1,178 @@
+/*
+ * 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.math3.random;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.linear.RectangularCholeskyDecomposition;
+
+/**
+ * A {@link RandomVectorGenerator} that generates vectors with with correlated components.
+ *
+ * <p>Random vectors with correlated components are built by combining the uncorrelated components
+ * of another random vector in such a way that the resulting correlations are the ones specified by
+ * a positive definite covariance matrix.
+ *
+ * <p>The main use for correlated random vector generation is for Monte-Carlo simulation of physical
+ * problems with several variables, for example to generate error vectors to be added to a nominal
+ * vector. A particularly interesting case is when the generated vector should be drawn from a <a
+ * href="http://en.wikipedia.org/wiki/Multivariate_normal_distribution">Multivariate Normal
+ * Distribution</a>. The approach using a Cholesky decomposition is quite usual in this case.
+ * However, it can be extended to other cases as long as the underlying random generator provides
+ * {@link NormalizedRandomGenerator normalized values} like {@link GaussianRandomGenerator} or
+ * {@link UniformRandomGenerator}.
+ *
+ * <p>Sometimes, the covariance matrix for a given simulation is not strictly positive definite.
+ * This means that the correlations are not all independent from each other. In this case, however,
+ * the non strictly positive elements found during the Cholesky decomposition of the covariance
+ * matrix should not be negative either, they should be null. Another non-conventional extension
+ * handling this case is used here. Rather than computing <code>C = U<sup>T</sup>.U</code> where
+ * <code>C</code> is the covariance matrix and <code>U</code> is an upper-triangular matrix, we
+ * compute <code>C = B.B<sup>T</sup></code> where <code>B</code> is a rectangular matrix having more
+ * rows than columns. The number of columns of <code>B</code> is the rank of the covariance matrix,
+ * and it is the dimension of the uncorrelated random vector that is needed to compute the component
+ * of the correlated vector. This class handles this situation automatically.
+ *
+ * @since 1.2
+ */
+public class CorrelatedRandomVectorGenerator implements RandomVectorGenerator {
+    /** Mean vector. */
+    private final double[] mean;
+
+    /** Underlying generator. */
+    private final NormalizedRandomGenerator generator;
+
+    /** Storage for the normalized vector. */
+    private final double[] normalized;
+
+    /** Root of the covariance matrix. */
+    private final RealMatrix root;
+
+    /**
+     * Builds a correlated random vector generator from its mean vector and covariance matrix.
+     *
+     * @param mean Expected mean values for all components.
+     * @param covariance Covariance matrix.
+     * @param small Diagonal elements threshold under which column are considered to be dependent on
+     *     previous ones and are discarded
+     * @param generator underlying generator for uncorrelated normalized components.
+     * @throws org.apache.commons.math3.linear.NonPositiveDefiniteMatrixException if the covariance
+     *     matrix is not strictly positive definite.
+     * @throws DimensionMismatchException if the mean and covariance arrays dimensions do not match.
+     */
+    public CorrelatedRandomVectorGenerator(
+            double[] mean,
+            RealMatrix covariance,
+            double small,
+            NormalizedRandomGenerator generator) {
+        int order = covariance.getRowDimension();
+        if (mean.length != order) {
+            throw new DimensionMismatchException(mean.length, order);
+        }
+        this.mean = mean.clone();
+
+        final RectangularCholeskyDecomposition decomposition =
+                new RectangularCholeskyDecomposition(covariance, small);
+        root = decomposition.getRootMatrix();
+
+        this.generator = generator;
+        normalized = new double[decomposition.getRank()];
+    }
+
+    /**
+     * Builds a null mean random correlated vector generator from its covariance matrix.
+     *
+     * @param covariance Covariance matrix.
+     * @param small Diagonal elements threshold under which column are considered to be dependent on
+     *     previous ones and are discarded.
+     * @param generator Underlying generator for uncorrelated normalized components.
+     * @throws org.apache.commons.math3.linear.NonPositiveDefiniteMatrixException if the covariance
+     *     matrix is not strictly positive definite.
+     */
+    public CorrelatedRandomVectorGenerator(
+            RealMatrix covariance, double small, NormalizedRandomGenerator generator) {
+        int order = covariance.getRowDimension();
+        mean = new double[order];
+        for (int i = 0; i < order; ++i) {
+            mean[i] = 0;
+        }
+
+        final RectangularCholeskyDecomposition decomposition =
+                new RectangularCholeskyDecomposition(covariance, small);
+        root = decomposition.getRootMatrix();
+
+        this.generator = generator;
+        normalized = new double[decomposition.getRank()];
+    }
+
+    /**
+     * Get the underlying normalized components generator.
+     *
+     * @return underlying uncorrelated components generator
+     */
+    public NormalizedRandomGenerator getGenerator() {
+        return generator;
+    }
+
+    /**
+     * Get the rank of the covariance matrix. The rank is the number of independent rows in the
+     * covariance matrix, it is also the number of columns of the root matrix.
+     *
+     * @return rank of the square matrix.
+     * @see #getRootMatrix()
+     */
+    public int getRank() {
+        return normalized.length;
+    }
+
+    /**
+     * Get the root of the covariance matrix. The root is the rectangular matrix <code>B</code> such
+     * that the covariance matrix is equal to <code>B.B<sup>T</sup></code>
+     *
+     * @return root of the square matrix
+     * @see #getRank()
+     */
+    public RealMatrix getRootMatrix() {
+        return root;
+    }
+
+    /**
+     * Generate a correlated random vector.
+     *
+     * @return a random vector as an array of double. The returned array is created at each call,
+     *     the caller can do what it wants with it.
+     */
+    public double[] nextVector() {
+
+        // generate uncorrelated vector
+        for (int i = 0; i < normalized.length; ++i) {
+            normalized[i] = generator.nextNormalizedDouble();
+        }
+
+        // compute correlated vector
+        double[] correlated = new double[mean.length];
+        for (int i = 0; i < correlated.length; ++i) {
+            correlated[i] = mean[i];
+            for (int j = 0; j < root.getColumnDimension(); ++j) {
+                correlated[i] += root.getEntry(i, j) * normalized[j];
+            }
+        }
+
+        return correlated;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/random/EmpiricalDistribution.java b/src/main/java/org/apache/commons/math3/random/EmpiricalDistribution.java
new file mode 100644
index 0000000..9ed3f4a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/random/EmpiricalDistribution.java
@@ -0,0 +1,866 @@
+/*
+ * 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.math3.random;
+
+import org.apache.commons.math3.distribution.AbstractRealDistribution;
+import org.apache.commons.math3.distribution.ConstantRealDistribution;
+import org.apache.commons.math3.distribution.NormalDistribution;
+import org.apache.commons.math3.distribution.RealDistribution;
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.exception.MathInternalError;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.ZeroException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.stat.descriptive.StatisticalSummary;
+import org.apache.commons.math3.stat.descriptive.SummaryStatistics;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents an <a href="http://http://en.wikipedia.org/wiki/Empirical_distribution_function">
+ * empirical probability distribution</a> -- a probability distribution derived from observed data
+ * without making any assumptions about the functional form of the population distribution that the
+ * data come from.
+ *
+ * <p>An <code>EmpiricalDistribution</code> maintains data structures, called <i>distribution
+ * digests</i>, that describe empirical distributions and support the following operations:
+ *
+ * <ul>
+ *   <li>loading the distribution from a file of observed data values
+ *   <li>dividing the input data into "bin ranges" and reporting bin frequency counts (data for
+ *       histogram)
+ *   <li>reporting univariate statistics describing the full set of data values as well as the
+ *       observations within each bin
+ *   <li>generating random values from the distribution
+ * </ul>
+ *
+ * Applications can use <code>EmpiricalDistribution</code> to build grouped frequency histograms
+ * representing the input data or to generate random values "like" those in the input file -- i.e.,
+ * the values generated will follow the distribution of the values in the file.
+ *
+ * <p>The implementation uses what amounts to the <a
+ * href="http://nedwww.ipac.caltech.edu/level5/March02/Silverman/Silver2_6.html">Variable Kernel
+ * Method</a> with Gaussian smoothing:
+ *
+ * <p><strong>Digesting the input file</strong>
+ *
+ * <ol>
+ *   <li>Pass the file once to compute min and max.
+ *   <li>Divide the range from min-max into <code>binCount</code> "bins."
+ *   <li>Pass the data file again, computing bin counts and univariate statistics (mean, std dev.)
+ *       for each of the bins
+ *   <li>Divide the interval (0,1) into subintervals associated with the bins, with the length of a
+ *       bin's subinterval proportional to its count.
+ * </ol>
+ *
+ * <strong>Generating random values from the distribution</strong>
+ *
+ * <ol>
+ *   <li>Generate a uniformly distributed value in (0,1)
+ *   <li>Select the subinterval to which the value belongs.
+ *   <li>Generate a random Gaussian value with mean = mean of the associated bin and std dev = std
+ *       dev of associated bin.
+ * </ol>
+ *
+ * <p>EmpiricalDistribution implements the {@link RealDistribution} interface as follows. Given x
+ * within the range of values in the dataset, let B be the bin containing x and let K be the
+ * within-bin kernel for B. Let P(B-) be the sum of the probabilities of the bins below B and let
+ * K(B) be the mass of B under K (i.e., the integral of the kernel density over B). Then set P(X <
+ * x) = P(B-) + P(B) * K(x) / K(B) where K(x) is the kernel distribution evaluated at x. This
+ * results in a cdf that matches the grouped frequency distribution at the bin endpoints and
+ * interpolates within bins using within-bin kernels. <strong>USAGE NOTES:</strong>
+ *
+ * <ul>
+ *   <li>The <code>binCount</code> is set by default to 1000. A good rule of thumb is to set the bin
+ *       count to approximately the length of the input file divided by 10.
+ *   <li>The input file <i>must</i> be a plain text file containing one valid numeric entry per
+ *       line.
+ * </ul>
+ */
+public class EmpiricalDistribution extends AbstractRealDistribution {
+
+    /** Default bin count */
+    public static final int DEFAULT_BIN_COUNT = 1000;
+
+    /** Character set for file input */
+    private static final String FILE_CHARSET = "US-ASCII";
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 5729073523949762654L;
+
+    /** RandomDataGenerator instance to use in repeated calls to getNext() */
+    protected final RandomDataGenerator randomData;
+
+    /** List of SummaryStatistics objects characterizing the bins */
+    private final List<SummaryStatistics> binStats;
+
+    /** Sample statistics */
+    private SummaryStatistics sampleStats = null;
+
+    /** Max loaded value */
+    private double max = Double.NEGATIVE_INFINITY;
+
+    /** Min loaded value */
+    private double min = Double.POSITIVE_INFINITY;
+
+    /** Grid size */
+    private double delta = 0d;
+
+    /** number of bins */
+    private final int binCount;
+
+    /** is the distribution loaded? */
+    private boolean loaded = false;
+
+    /** upper bounds of subintervals in (0,1) "belonging" to the bins */
+    private double[] upperBounds = null;
+
+    /** Creates a new EmpiricalDistribution with the default bin count. */
+    public EmpiricalDistribution() {
+        this(DEFAULT_BIN_COUNT);
+    }
+
+    /**
+     * Creates a new EmpiricalDistribution with the specified bin count.
+     *
+     * @param binCount number of bins. Must be strictly positive.
+     * @throws NotStrictlyPositiveException if {@code binCount <= 0}.
+     */
+    public EmpiricalDistribution(int binCount) {
+        this(binCount, new RandomDataGenerator());
+    }
+
+    /**
+     * Creates a new EmpiricalDistribution with the specified bin count using the provided {@link
+     * RandomGenerator} as the source of random data.
+     *
+     * @param binCount number of bins. Must be strictly positive.
+     * @param generator random data generator (may be null, resulting in default JDK generator)
+     * @throws NotStrictlyPositiveException if {@code binCount <= 0}.
+     * @since 3.0
+     */
+    public EmpiricalDistribution(int binCount, RandomGenerator generator) {
+        this(binCount, new RandomDataGenerator(generator));
+    }
+
+    /**
+     * Creates a new EmpiricalDistribution with default bin count using the provided {@link
+     * RandomGenerator} as the source of random data.
+     *
+     * @param generator random data generator (may be null, resulting in default JDK generator)
+     * @since 3.0
+     */
+    public EmpiricalDistribution(RandomGenerator generator) {
+        this(DEFAULT_BIN_COUNT, generator);
+    }
+
+    /**
+     * Creates a new EmpiricalDistribution with the specified bin count using the provided {@link
+     * RandomDataImpl} instance as the source of random data.
+     *
+     * @param binCount number of bins
+     * @param randomData random data generator (may be null, resulting in default JDK generator)
+     * @since 3.0
+     * @deprecated As of 3.1. Please use {@link #EmpiricalDistribution(int,RandomGenerator)}
+     *     instead.
+     */
+    @Deprecated
+    public EmpiricalDistribution(int binCount, RandomDataImpl randomData) {
+        this(binCount, randomData.getDelegate());
+    }
+
+    /**
+     * Creates a new EmpiricalDistribution with default bin count using the provided {@link
+     * RandomDataImpl} as the source of random data.
+     *
+     * @param randomData random data generator (may be null, resulting in default JDK generator)
+     * @since 3.0
+     * @deprecated As of 3.1. Please use {@link #EmpiricalDistribution(RandomGenerator)} instead.
+     */
+    @Deprecated
+    public EmpiricalDistribution(RandomDataImpl randomData) {
+        this(DEFAULT_BIN_COUNT, randomData);
+    }
+
+    /**
+     * Private constructor to allow lazy initialisation of the RNG contained in the {@link
+     * #randomData} instance variable.
+     *
+     * @param binCount number of bins. Must be strictly positive.
+     * @param randomData Random data generator.
+     * @throws NotStrictlyPositiveException if {@code binCount <= 0}.
+     */
+    private EmpiricalDistribution(int binCount, RandomDataGenerator randomData) {
+        super(randomData.getRandomGenerator());
+        if (binCount <= 0) {
+            throw new NotStrictlyPositiveException(binCount);
+        }
+        this.binCount = binCount;
+        this.randomData = randomData;
+        binStats = new ArrayList<SummaryStatistics>();
+    }
+
+    /**
+     * Computes the empirical distribution from the provided array of numbers.
+     *
+     * @param in the input data array
+     * @exception NullArgumentException if in is null
+     */
+    public void load(double[] in) throws NullArgumentException {
+        DataAdapter da = new ArrayDataAdapter(in);
+        try {
+            da.computeStats();
+            // new adapter for the second pass
+            fillBinStats(new ArrayDataAdapter(in));
+        } catch (IOException ex) {
+            // Can't happen
+            throw new MathInternalError();
+        }
+        loaded = true;
+    }
+
+    /**
+     * Computes the empirical distribution using data read from a URL.
+     *
+     * <p>The input file <i>must</i> be an ASCII text file containing one valid numeric entry per
+     * line.
+     *
+     * @param url url of the input file
+     * @throws IOException if an IO error occurs
+     * @throws NullArgumentException if url is null
+     * @throws ZeroException if URL contains no data
+     */
+    public void load(URL url) throws IOException, NullArgumentException, ZeroException {
+        MathUtils.checkNotNull(url);
+        Charset charset = Charset.forName(FILE_CHARSET);
+        BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream(), charset));
+        try {
+            DataAdapter da = new StreamDataAdapter(in);
+            da.computeStats();
+            if (sampleStats.getN() == 0) {
+                throw new ZeroException(LocalizedFormats.URL_CONTAINS_NO_DATA, url);
+            }
+            // new adapter for the second pass
+            in = new BufferedReader(new InputStreamReader(url.openStream(), charset));
+            fillBinStats(new StreamDataAdapter(in));
+            loaded = true;
+        } finally {
+            try {
+                in.close();
+            } catch (IOException ex) { // NOPMD
+                // ignore
+            }
+        }
+    }
+
+    /**
+     * Computes the empirical distribution from the input file.
+     *
+     * <p>The input file <i>must</i> be an ASCII text file containing one valid numeric entry per
+     * line.
+     *
+     * @param file the input file
+     * @throws IOException if an IO error occurs
+     * @throws NullArgumentException if file is null
+     */
+    public void load(File file) throws IOException, NullArgumentException {
+        MathUtils.checkNotNull(file);
+        Charset charset = Charset.forName(FILE_CHARSET);
+        InputStream is = new FileInputStream(file);
+        BufferedReader in = new BufferedReader(new InputStreamReader(is, charset));
+        try {
+            DataAdapter da = new StreamDataAdapter(in);
+            da.computeStats();
+            // new adapter for second pass
+            is = new FileInputStream(file);
+            in = new BufferedReader(new InputStreamReader(is, charset));
+            fillBinStats(new StreamDataAdapter(in));
+            loaded = true;
+        } finally {
+            try {
+                in.close();
+            } catch (IOException ex) { // NOPMD
+                // ignore
+            }
+        }
+    }
+
+    /**
+     * Provides methods for computing <code>sampleStats</code> and <code>beanStats</code>
+     * abstracting the source of data.
+     */
+    private abstract class DataAdapter {
+
+        /**
+         * Compute bin stats.
+         *
+         * @throws IOException if an error occurs computing bin stats
+         */
+        public abstract void computeBinStats() throws IOException;
+
+        /**
+         * Compute sample statistics.
+         *
+         * @throws IOException if an error occurs computing sample stats
+         */
+        public abstract void computeStats() throws IOException;
+    }
+
+    /** <code>DataAdapter</code> for data provided through some input stream */
+    private class StreamDataAdapter extends DataAdapter {
+
+        /** Input stream providing access to the data */
+        private BufferedReader inputStream;
+
+        /**
+         * Create a StreamDataAdapter from a BufferedReader
+         *
+         * @param in BufferedReader input stream
+         */
+        StreamDataAdapter(BufferedReader in) {
+            super();
+            inputStream = in;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void computeBinStats() throws IOException {
+            String str = null;
+            double val = 0.0d;
+            while ((str = inputStream.readLine()) != null) {
+                val = Double.parseDouble(str);
+                SummaryStatistics stats = binStats.get(findBin(val));
+                stats.addValue(val);
+            }
+
+            inputStream.close();
+            inputStream = null;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void computeStats() throws IOException {
+            String str = null;
+            double val = 0.0;
+            sampleStats = new SummaryStatistics();
+            while ((str = inputStream.readLine()) != null) {
+                val = Double.parseDouble(str);
+                sampleStats.addValue(val);
+            }
+            inputStream.close();
+            inputStream = null;
+        }
+    }
+
+    /** <code>DataAdapter</code> for data provided as array of doubles. */
+    private class ArrayDataAdapter extends DataAdapter {
+
+        /** Array of input data values */
+        private double[] inputArray;
+
+        /**
+         * Construct an ArrayDataAdapter from a double[] array
+         *
+         * @param in double[] array holding the data
+         * @throws NullArgumentException if in is null
+         */
+        ArrayDataAdapter(double[] in) throws NullArgumentException {
+            super();
+            MathUtils.checkNotNull(in);
+            inputArray = in;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void computeStats() throws IOException {
+            sampleStats = new SummaryStatistics();
+            for (int i = 0; i < inputArray.length; i++) {
+                sampleStats.addValue(inputArray[i]);
+            }
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void computeBinStats() throws IOException {
+            for (int i = 0; i < inputArray.length; i++) {
+                SummaryStatistics stats = binStats.get(findBin(inputArray[i]));
+                stats.addValue(inputArray[i]);
+            }
+        }
+    }
+
+    /**
+     * Fills binStats array (second pass through data file).
+     *
+     * @param da object providing access to the data
+     * @throws IOException if an IO error occurs
+     */
+    private void fillBinStats(final DataAdapter da) throws IOException {
+        // Set up grid
+        min = sampleStats.getMin();
+        max = sampleStats.getMax();
+        delta = (max - min) / ((double) binCount);
+
+        // Initialize binStats ArrayList
+        if (!binStats.isEmpty()) {
+            binStats.clear();
+        }
+        for (int i = 0; i < binCount; i++) {
+            SummaryStatistics stats = new SummaryStatistics();
+            binStats.add(i, stats);
+        }
+
+        // Filling data in binStats Array
+        da.computeBinStats();
+
+        // Assign upperBounds based on bin counts
+        upperBounds = new double[binCount];
+        upperBounds[0] = ((double) binStats.get(0).getN()) / (double) sampleStats.getN();
+        for (int i = 1; i < binCount - 1; i++) {
+            upperBounds[i] =
+                    upperBounds[i - 1]
+                            + ((double) binStats.get(i).getN()) / (double) sampleStats.getN();
+        }
+        upperBounds[binCount - 1] = 1.0d;
+    }
+
+    /**
+     * Returns the index of the bin to which the given value belongs
+     *
+     * @param value the value whose bin we are trying to find
+     * @return the index of the bin containing the value
+     */
+    private int findBin(double value) {
+        return FastMath.min(
+                FastMath.max((int) FastMath.ceil((value - min) / delta) - 1, 0), binCount - 1);
+    }
+
+    /**
+     * Generates a random value from this distribution. <strong>Preconditions:</strong>
+     *
+     * <ul>
+     *   <li>the distribution must be loaded before invoking this method
+     * </ul>
+     *
+     * @return the random value.
+     * @throws MathIllegalStateException if the distribution has not been loaded
+     */
+    public double getNextValue() throws MathIllegalStateException {
+
+        if (!loaded) {
+            throw new MathIllegalStateException(LocalizedFormats.DISTRIBUTION_NOT_LOADED);
+        }
+
+        return sample();
+    }
+
+    /**
+     * Returns a {@link StatisticalSummary} describing this distribution.
+     * <strong>Preconditions:</strong>
+     *
+     * <ul>
+     *   <li>the distribution must be loaded before invoking this method
+     * </ul>
+     *
+     * @return the sample statistics
+     * @throws IllegalStateException if the distribution has not been loaded
+     */
+    public StatisticalSummary getSampleStats() {
+        return sampleStats;
+    }
+
+    /**
+     * Returns the number of bins.
+     *
+     * @return the number of bins.
+     */
+    public int getBinCount() {
+        return binCount;
+    }
+
+    /**
+     * Returns a List of {@link SummaryStatistics} instances containing statistics describing the
+     * values in each of the bins. The list is indexed on the bin number.
+     *
+     * @return List of bin statistics.
+     */
+    public List<SummaryStatistics> getBinStats() {
+        return binStats;
+    }
+
+    /**
+     * Returns a fresh copy of the array of upper bounds for the bins. Bins are: <br>
+     * [min,upperBounds[0]],(upperBounds[0],upperBounds[1]],..., (upperBounds[binCount-2],
+     * upperBounds[binCount-1] = max].
+     *
+     * <p>Note: In versions 1.0-2.0 of commons-math, this method incorrectly returned the array of
+     * probability generator upper bounds now returned by {@link #getGeneratorUpperBounds()}.
+     *
+     * @return array of bin upper bounds
+     * @since 2.1
+     */
+    public double[] getUpperBounds() {
+        double[] binUpperBounds = new double[binCount];
+        for (int i = 0; i < binCount - 1; i++) {
+            binUpperBounds[i] = min + delta * (i + 1);
+        }
+        binUpperBounds[binCount - 1] = max;
+        return binUpperBounds;
+    }
+
+    /**
+     * Returns a fresh copy of the array of upper bounds of the subintervals of [0,1] used in
+     * generating data from the empirical distribution. Subintervals correspond to bins with lengths
+     * proportional to bin counts. <strong>Preconditions:</strong>
+     *
+     * <ul>
+     *   <li>the distribution must be loaded before invoking this method
+     * </ul>
+     *
+     * <p>In versions 1.0-2.0 of commons-math, this array was (incorrectly) returned by {@link
+     * #getUpperBounds()}.
+     *
+     * @since 2.1
+     * @return array of upper bounds of subintervals used in data generation
+     * @throws NullPointerException unless a {@code load} method has been called beforehand.
+     */
+    public double[] getGeneratorUpperBounds() {
+        int len = upperBounds.length;
+        double[] out = new double[len];
+        System.arraycopy(upperBounds, 0, out, 0, len);
+        return out;
+    }
+
+    /**
+     * Property indicating whether or not the distribution has been loaded.
+     *
+     * @return true if the distribution has been loaded
+     */
+    public boolean isLoaded() {
+        return loaded;
+    }
+
+    /**
+     * Reseeds the random number generator used by {@link #getNextValue()}.
+     *
+     * @param seed random generator seed
+     * @since 3.0
+     */
+    public void reSeed(long seed) {
+        randomData.reSeed(seed);
+    }
+
+    // Distribution methods ---------------------------
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.1
+     */
+    @Override
+    public double probability(double x) {
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>Returns the kernel density normalized so that its integral over each bin equals the bin
+     * mass.
+     *
+     * <p>Algorithm description:
+     *
+     * <ol>
+     *   <li>Find the bin B that x belongs to.
+     *   <li>Compute K(B) = the mass of B with respect to the within-bin kernel (i.e., the integral
+     *       of the kernel density over B).
+     *   <li>Return k(x) * P(B) / K(B), where k is the within-bin kernel density and P(B) is the
+     *       mass of B.
+     * </ol>
+     *
+     * @since 3.1
+     */
+    public double density(double x) {
+        if (x < min || x > max) {
+            return 0d;
+        }
+        final int binIndex = findBin(x);
+        final RealDistribution kernel = getKernel(binStats.get(binIndex));
+        return kernel.density(x) * pB(binIndex) / kB(binIndex);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>Algorithm description:
+     *
+     * <ol>
+     *   <li>Find the bin B that x belongs to.
+     *   <li>Compute P(B) = the mass of B and P(B-) = the combined mass of the bins below B.
+     *   <li>Compute K(B) = the probability mass of B with respect to the within-bin kernel and
+     *       K(B-) = the kernel distribution evaluated at the lower endpoint of B
+     *   <li>Return P(B-) + P(B) * [K(x) - K(B-)] / K(B) where K(x) is the within-bin kernel
+     *       distribution function evaluated at x.
+     * </ol>
+     *
+     * If K is a constant distribution, we return P(B-) + P(B) (counting the full mass of B).
+     *
+     * @since 3.1
+     */
+    public double cumulativeProbability(double x) {
+        if (x < min) {
+            return 0d;
+        } else if (x >= max) {
+            return 1d;
+        }
+        final int binIndex = findBin(x);
+        final double pBminus = pBminus(binIndex);
+        final double pB = pB(binIndex);
+        final RealDistribution kernel = k(x);
+        if (kernel instanceof ConstantRealDistribution) {
+            if (x < kernel.getNumericalMean()) {
+                return pBminus;
+            } else {
+                return pBminus + pB;
+            }
+        }
+        final double[] binBounds = getUpperBounds();
+        final double kB = kB(binIndex);
+        final double lower = binIndex == 0 ? min : binBounds[binIndex - 1];
+        final double withinBinCum =
+                (kernel.cumulativeProbability(x) - kernel.cumulativeProbability(lower)) / kB;
+        return pBminus + pB * withinBinCum;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>Algorithm description:
+     *
+     * <ol>
+     *   <li>Find the smallest i such that the sum of the masses of the bins through i is at least
+     *       p.
+     *   <li>Let K be the within-bin kernel distribution for bin i.</br> Let K(B) be the mass of B
+     *       under K. <br>
+     *       Let K(B-) be K evaluated at the lower endpoint of B (the combined mass of the bins
+     *       below B under K).<br>
+     *       Let P(B) be the probability of bin i.<br>
+     *       Let P(B-) be the sum of the bin masses below bin i. <br>
+     *       Let pCrit = p - P(B-)<br>
+     *   <li>Return the inverse of K evaluated at <br>
+     *       K(B-) + pCrit * K(B) / P(B)
+     * </ol>
+     *
+     * @since 3.1
+     */
+    @Override
+    public double inverseCumulativeProbability(final double p) throws OutOfRangeException {
+        if (p < 0.0 || p > 1.0) {
+            throw new OutOfRangeException(p, 0, 1);
+        }
+
+        if (p == 0.0) {
+            return getSupportLowerBound();
+        }
+
+        if (p == 1.0) {
+            return getSupportUpperBound();
+        }
+
+        int i = 0;
+        while (cumBinP(i) < p) {
+            i++;
+        }
+
+        final RealDistribution kernel = getKernel(binStats.get(i));
+        final double kB = kB(i);
+        final double[] binBounds = getUpperBounds();
+        final double lower = i == 0 ? min : binBounds[i - 1];
+        final double kBminus = kernel.cumulativeProbability(lower);
+        final double pB = pB(i);
+        final double pBminus = pBminus(i);
+        final double pCrit = p - pBminus;
+        if (pCrit <= 0) {
+            return lower;
+        }
+        return kernel.inverseCumulativeProbability(kBminus + pCrit * kB / pB);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.1
+     */
+    public double getNumericalMean() {
+        return sampleStats.getMean();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.1
+     */
+    public double getNumericalVariance() {
+        return sampleStats.getVariance();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.1
+     */
+    public double getSupportLowerBound() {
+        return min;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.1
+     */
+    public double getSupportUpperBound() {
+        return max;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.1
+     */
+    public boolean isSupportLowerBoundInclusive() {
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.1
+     */
+    public boolean isSupportUpperBoundInclusive() {
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.1
+     */
+    public boolean isSupportConnected() {
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.1
+     */
+    @Override
+    public void reseedRandomGenerator(long seed) {
+        randomData.reSeed(seed);
+    }
+
+    /**
+     * The probability of bin i.
+     *
+     * @param i the index of the bin
+     * @return the probability that selection begins in bin i
+     */
+    private double pB(int i) {
+        return i == 0 ? upperBounds[0] : upperBounds[i] - upperBounds[i - 1];
+    }
+
+    /**
+     * The combined probability of the bins up to but not including bin i.
+     *
+     * @param i the index of the bin
+     * @return the probability that selection begins in a bin below bin i.
+     */
+    private double pBminus(int i) {
+        return i == 0 ? 0 : upperBounds[i - 1];
+    }
+
+    /**
+     * Mass of bin i under the within-bin kernel of the bin.
+     *
+     * @param i index of the bin
+     * @return the difference in the within-bin kernel cdf between the upper and lower endpoints of
+     *     bin i
+     */
+    @SuppressWarnings("deprecation")
+    private double kB(int i) {
+        final double[] binBounds = getUpperBounds();
+        final RealDistribution kernel = getKernel(binStats.get(i));
+        return i == 0
+                ? kernel.cumulativeProbability(min, binBounds[0])
+                : kernel.cumulativeProbability(binBounds[i - 1], binBounds[i]);
+    }
+
+    /**
+     * The within-bin kernel of the bin that x belongs to.
+     *
+     * @param x the value to locate within a bin
+     * @return the within-bin kernel of the bin containing x
+     */
+    private RealDistribution k(double x) {
+        final int binIndex = findBin(x);
+        return getKernel(binStats.get(binIndex));
+    }
+
+    /**
+     * The combined probability of the bins up to and including binIndex.
+     *
+     * @param binIndex maximum bin index
+     * @return sum of the probabilities of bins through binIndex
+     */
+    private double cumBinP(int binIndex) {
+        return upperBounds[binIndex];
+    }
+
+    /**
+     * The within-bin smoothing kernel. Returns a Gaussian distribution parameterized by {@code
+     * bStats}, unless the bin contains only one observation, in which case a constant distribution
+     * is returned.
+     *
+     * @param bStats summary statistics for the bin
+     * @return within-bin kernel parameterized by bStats
+     */
+    protected RealDistribution getKernel(SummaryStatistics bStats) {
+        if (bStats.getN() == 1 || bStats.getVariance() == 0) {
+            return new ConstantRealDistribution(bStats.getMean());
+        } else {
+            return new NormalDistribution(
+                    randomData.getRandomGenerator(),
+                    bStats.getMean(),
+                    bStats.getStandardDeviation(),
+                    NormalDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/random/GaussianRandomGenerator.java b/src/main/java/org/apache/commons/math3/random/GaussianRandomGenerator.java
new file mode 100644
index 0000000..33eec56
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/random/GaussianRandomGenerator.java
@@ -0,0 +1,49 @@
+/*
+ * 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.math3.random;
+
+/**
+ * This class is a gaussian normalized random generator for scalars.
+ *
+ * <p>This class is a simple wrapper around the {@link RandomGenerator#nextGaussian} method.
+ *
+ * @since 1.2
+ */
+public class GaussianRandomGenerator implements NormalizedRandomGenerator {
+
+    /** Underlying generator. */
+    private final RandomGenerator generator;
+
+    /**
+     * Create a new generator.
+     *
+     * @param generator underlying random generator to use
+     */
+    public GaussianRandomGenerator(final RandomGenerator generator) {
+        this.generator = generator;
+    }
+
+    /**
+     * Generate a random scalar with null mean and unit standard deviation.
+     *
+     * @return a random scalar with null mean and unit standard deviation
+     */
+    public double nextNormalizedDouble() {
+        return generator.nextGaussian();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/random/HaltonSequenceGenerator.java b/src/main/java/org/apache/commons/math3/random/HaltonSequenceGenerator.java
new file mode 100644
index 0000000..2b1c623
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/random/HaltonSequenceGenerator.java
@@ -0,0 +1,195 @@
+/*
+ * 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.math3.random;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * Implementation of a Halton sequence.
+ *
+ * <p>A Halton sequence is a low-discrepancy sequence generating points in the interval [0, 1]
+ * according to
+ *
+ * <pre>
+ *   H(n) = d_0 / b + d_1 / b^2 .... d_j / b^j+1
+ *
+ *   with
+ *
+ *   n = d_j * b^j-1 + ... d_1 * b + d_0 * b^0
+ * </pre>
+ *
+ * For higher dimensions, subsequent prime numbers are used as base, e.g. { 2, 3, 5 } for a Halton
+ * sequence in R^3.
+ *
+ * <p>Halton sequences are known to suffer from linear correlation for larger prime numbers, thus
+ * the individual digits are usually scrambled. This implementation already comes with support for
+ * up to 40 dimensions with optimal weight numbers from <a
+ * href="http://etd.lib.fsu.edu/theses/available/etd-07062004-140409/unrestricted/dissertation1.pdf">
+ * H. Chi: Scrambled quasirandom sequences and their applications</a>.
+ *
+ * <p>The generator supports two modes:
+ *
+ * <ul>
+ *   <li>sequential generation of points: {@link #nextVector()}
+ *   <li>random access to the i-th point in the sequence: {@link #skipTo(int)}
+ * </ul>
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Halton_sequence">Halton sequence (Wikipedia)</a>
+ * @see <a href="https://lirias.kuleuven.be/bitstream/123456789/131168/1/mcm2005_bartv.pdf">On the
+ *     Halton sequence and its scramblings</a>
+ * @since 3.3
+ */
+public class HaltonSequenceGenerator implements RandomVectorGenerator {
+
+    /** The first 40 primes. */
+    private static final int[] PRIMES =
+            new int[] {
+                2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79,
+                83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167,
+                173
+            };
+
+    /** The optimal weights used for scrambling of the first 40 dimension. */
+    private static final int[] WEIGHTS =
+            new int[] {
+                1, 2, 3, 3, 8, 11, 12, 14, 7, 18, 12, 13, 17, 18, 29, 14, 18, 43, 41, 44, 40, 30,
+                47, 65, 71, 28, 40, 60, 79, 89, 56, 50, 52, 61, 108, 56, 66, 63, 60, 66
+            };
+
+    /** Space dimension. */
+    private final int dimension;
+
+    /** The current index in the sequence. */
+    private int count = 0;
+
+    /** The base numbers for each component. */
+    private final int[] base;
+
+    /** The scrambling weights for each component. */
+    private final int[] weight;
+
+    /**
+     * Construct a new Halton sequence generator for the given space dimension.
+     *
+     * @param dimension the space dimension
+     * @throws OutOfRangeException if the space dimension is outside the allowed range of [1, 40]
+     */
+    public HaltonSequenceGenerator(final int dimension) throws OutOfRangeException {
+        this(dimension, PRIMES, WEIGHTS);
+    }
+
+    /**
+     * Construct a new Halton sequence generator with the given base numbers and weights for each
+     * dimension. The length of the bases array defines the space dimension and is required to be
+     * &gt; 0.
+     *
+     * @param dimension the space dimension
+     * @param bases the base number for each dimension, entries should be (pairwise) prime, may not
+     *     be null
+     * @param weights the weights used during scrambling, may be null in which case no scrambling
+     *     will be performed
+     * @throws NullArgumentException if base is null
+     * @throws OutOfRangeException if the space dimension is outside the range [1, len], where len
+     *     refers to the length of the bases array
+     * @throws DimensionMismatchException if weights is non-null and the length of the input arrays
+     *     differ
+     */
+    public HaltonSequenceGenerator(final int dimension, final int[] bases, final int[] weights)
+            throws NullArgumentException, OutOfRangeException, DimensionMismatchException {
+
+        MathUtils.checkNotNull(bases);
+
+        if (dimension < 1 || dimension > bases.length) {
+            throw new OutOfRangeException(dimension, 1, PRIMES.length);
+        }
+
+        if (weights != null && weights.length != bases.length) {
+            throw new DimensionMismatchException(weights.length, bases.length);
+        }
+
+        this.dimension = dimension;
+        this.base = bases.clone();
+        this.weight = weights == null ? null : weights.clone();
+        count = 0;
+    }
+
+    /** {@inheritDoc} */
+    public double[] nextVector() {
+        final double[] v = new double[dimension];
+        for (int i = 0; i < dimension; i++) {
+            int index = count;
+            double f = 1.0 / base[i];
+
+            int j = 0;
+            while (index > 0) {
+                final int digit = scramble(i, j, base[i], index % base[i]);
+                v[i] += f * digit;
+                index /= base[i]; // floor( index / base )
+                f /= base[i];
+            }
+        }
+        count++;
+        return v;
+    }
+
+    /**
+     * Performs scrambling of digit {@code d_j} according to the formula:
+     *
+     * <pre>
+     *   ( weight_i * d_j ) mod base
+     * </pre>
+     *
+     * Implementations can override this method to do a different scrambling.
+     *
+     * @param i the dimension index
+     * @param j the digit index
+     * @param b the base for this dimension
+     * @param digit the j-th digit
+     * @return the scrambled digit
+     */
+    protected int scramble(final int i, final int j, final int b, final int digit) {
+        return weight != null ? (weight[i] * digit) % b : digit;
+    }
+
+    /**
+     * Skip to the i-th point in the Halton sequence.
+     *
+     * <p>This operation can be performed in O(1).
+     *
+     * @param index the index in the sequence to skip to
+     * @return the i-th point in the Halton sequence
+     * @throws NotPositiveException if index &lt; 0
+     */
+    public double[] skipTo(final int index) throws NotPositiveException {
+        count = index;
+        return nextVector();
+    }
+
+    /**
+     * Returns the index i of the next point in the Halton sequence that will be returned by calling
+     * {@link #nextVector()}.
+     *
+     * @return the index of the next point
+     */
+    public int getNextIndex() {
+        return count;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/random/ISAACRandom.java b/src/main/java/org/apache/commons/math3/random/ISAACRandom.java
new file mode 100644
index 0000000..8d10af5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/random/ISAACRandom.java
@@ -0,0 +1,279 @@
+/*
+ * 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.math3.random;
+
+import org.apache.commons.math3.util.FastMath;
+
+import java.io.Serializable;
+
+/**
+ * <a href="http://burtleburtle.net/bob/rand/isaacafa.html">ISAAC: a fast cryptographic
+ * pseudo-random number generator</a> <br>
+ * ISAAC (Indirection, Shift, Accumulate, Add, and Count) generates 32-bit random numbers. ISAAC has
+ * been designed to be cryptographically secure and is inspired by RC4. Cycles are guaranteed to be
+ * at least 2<sup>40</sup> values long, and they are 2<sup>8295</sup> values long on average. The
+ * results are uniformly distributed, unbiased, and unpredictable unless you know the seed. <br>
+ * This code is based (with minor changes and improvements) on the original implementation of the
+ * algorithm by Bob Jenkins. <br>
+ *
+ * @since 3.0
+ */
+public class ISAACRandom extends BitsStreamGenerator implements Serializable {
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 7288197941165002400L;
+
+    /** Log of size of rsl[] and mem[] */
+    private static final int SIZE_L = 8;
+
+    /** Size of rsl[] and mem[] */
+    private static final int SIZE = 1 << SIZE_L;
+
+    /** Half-size of rsl[] and mem[] */
+    private static final int H_SIZE = SIZE >> 1;
+
+    /** For pseudo-random lookup */
+    private static final int MASK = SIZE - 1 << 2;
+
+    /** The golden ratio */
+    private static final int GLD_RATIO = 0x9e3779b9;
+
+    /** The results given to the user */
+    private final int[] rsl = new int[SIZE];
+
+    /** The internal state */
+    private final int[] mem = new int[SIZE];
+
+    /** Count through the results in rsl[] */
+    private int count;
+
+    /** Accumulator */
+    private int isaacA;
+
+    /** The last result */
+    private int isaacB;
+
+    /** Counter, guarantees cycle is at least 2^40 */
+    private int isaacC;
+
+    /** Service variable. */
+    private final int[] arr = new int[8];
+
+    /** Service variable. */
+    private int isaacX;
+
+    /** Service variable. */
+    private int isaacI;
+
+    /** Service variable. */
+    private int isaacJ;
+
+    /**
+     * Creates a new ISAAC random number generator. <br>
+     * The instance is initialized using a combination of the current time and system hash code of
+     * the instance as the seed.
+     */
+    public ISAACRandom() {
+        setSeed(System.currentTimeMillis() + System.identityHashCode(this));
+    }
+
+    /**
+     * Creates a new ISAAC random number generator using a single long seed.
+     *
+     * @param seed Initial seed.
+     */
+    public ISAACRandom(long seed) {
+        setSeed(seed);
+    }
+
+    /**
+     * Creates a new ISAAC random number generator using an int array seed.
+     *
+     * @param seed Initial seed. If {@code null}, the seed will be related to the current time.
+     */
+    public ISAACRandom(int[] seed) {
+        setSeed(seed);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setSeed(int seed) {
+        setSeed(new int[] {seed});
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setSeed(long seed) {
+        setSeed(new int[] {(int) (seed >>> 32), (int) (seed & 0xffffffffL)});
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setSeed(int[] seed) {
+        if (seed == null) {
+            setSeed(System.currentTimeMillis() + System.identityHashCode(this));
+            return;
+        }
+        final int seedLen = seed.length;
+        final int rslLen = rsl.length;
+        System.arraycopy(seed, 0, rsl, 0, FastMath.min(seedLen, rslLen));
+        if (seedLen < rslLen) {
+            for (int j = seedLen; j < rslLen; j++) {
+                long k = rsl[j - seedLen];
+                rsl[j] = (int) (0x6c078965L * (k ^ k >> 30) + j & 0xffffffffL);
+            }
+        }
+        initState();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected int next(int bits) {
+        if (count < 0) {
+            isaac();
+            count = SIZE - 1;
+        }
+        return rsl[count--] >>> 32 - bits;
+    }
+
+    /** Generate 256 results */
+    private void isaac() {
+        isaacI = 0;
+        isaacJ = H_SIZE;
+        isaacB += ++isaacC;
+        while (isaacI < H_SIZE) {
+            isaac2();
+        }
+        isaacJ = 0;
+        while (isaacJ < H_SIZE) {
+            isaac2();
+        }
+    }
+
+    /** Intermediate internal loop. */
+    private void isaac2() {
+        isaacX = mem[isaacI];
+        isaacA ^= isaacA << 13;
+        isaacA += mem[isaacJ++];
+        isaac3();
+        isaacX = mem[isaacI];
+        isaacA ^= isaacA >>> 6;
+        isaacA += mem[isaacJ++];
+        isaac3();
+        isaacX = mem[isaacI];
+        isaacA ^= isaacA << 2;
+        isaacA += mem[isaacJ++];
+        isaac3();
+        isaacX = mem[isaacI];
+        isaacA ^= isaacA >>> 16;
+        isaacA += mem[isaacJ++];
+        isaac3();
+    }
+
+    /** Lowest level internal loop. */
+    private void isaac3() {
+        mem[isaacI] = mem[(isaacX & MASK) >> 2] + isaacA + isaacB;
+        isaacB = mem[(mem[isaacI] >> SIZE_L & MASK) >> 2] + isaacX;
+        rsl[isaacI++] = isaacB;
+    }
+
+    /** Initialize, or reinitialize, this instance of rand. */
+    private void initState() {
+        isaacA = 0;
+        isaacB = 0;
+        isaacC = 0;
+        for (int j = 0; j < arr.length; j++) {
+            arr[j] = GLD_RATIO;
+        }
+        for (int j = 0; j < 4; j++) {
+            shuffle();
+        }
+        // fill in mem[] with messy stuff
+        for (int j = 0; j < SIZE; j += 8) {
+            arr[0] += rsl[j];
+            arr[1] += rsl[j + 1];
+            arr[2] += rsl[j + 2];
+            arr[3] += rsl[j + 3];
+            arr[4] += rsl[j + 4];
+            arr[5] += rsl[j + 5];
+            arr[6] += rsl[j + 6];
+            arr[7] += rsl[j + 7];
+            shuffle();
+            setState(j);
+        }
+        // second pass makes all of seed affect all of mem
+        for (int j = 0; j < SIZE; j += 8) {
+            arr[0] += mem[j];
+            arr[1] += mem[j + 1];
+            arr[2] += mem[j + 2];
+            arr[3] += mem[j + 3];
+            arr[4] += mem[j + 4];
+            arr[5] += mem[j + 5];
+            arr[6] += mem[j + 6];
+            arr[7] += mem[j + 7];
+            shuffle();
+            setState(j);
+        }
+        isaac();
+        count = SIZE - 1;
+        clear();
+    }
+
+    /** Shuffle array. */
+    private void shuffle() {
+        arr[0] ^= arr[1] << 11;
+        arr[3] += arr[0];
+        arr[1] += arr[2];
+        arr[1] ^= arr[2] >>> 2;
+        arr[4] += arr[1];
+        arr[2] += arr[3];
+        arr[2] ^= arr[3] << 8;
+        arr[5] += arr[2];
+        arr[3] += arr[4];
+        arr[3] ^= arr[4] >>> 16;
+        arr[6] += arr[3];
+        arr[4] += arr[5];
+        arr[4] ^= arr[5] << 10;
+        arr[7] += arr[4];
+        arr[5] += arr[6];
+        arr[5] ^= arr[6] >>> 4;
+        arr[0] += arr[5];
+        arr[6] += arr[7];
+        arr[6] ^= arr[7] << 8;
+        arr[1] += arr[6];
+        arr[7] += arr[0];
+        arr[7] ^= arr[0] >>> 9;
+        arr[2] += arr[7];
+        arr[0] += arr[1];
+    }
+
+    /**
+     * Set the state by copying the internal arrays.
+     *
+     * @param start First index into {@link #mem} array.
+     */
+    private void setState(int start) {
+        mem[start] = arr[0];
+        mem[start + 1] = arr[1];
+        mem[start + 2] = arr[2];
+        mem[start + 3] = arr[3];
+        mem[start + 4] = arr[4];
+        mem[start + 5] = arr[5];
+        mem[start + 6] = arr[6];
+        mem[start + 7] = arr[7];
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/random/JDKRandomGenerator.java b/src/main/java/org/apache/commons/math3/random/JDKRandomGenerator.java
new file mode 100644
index 0000000..e9fe954
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/random/JDKRandomGenerator.java
@@ -0,0 +1,55 @@
+/*
+ * 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.math3.random;
+
+import java.util.Random;
+
+/**
+ * Extension of <code>java.util.Random</code> to implement {@link RandomGenerator}.
+ *
+ * @since 1.1
+ */
+public class JDKRandomGenerator extends Random implements RandomGenerator {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -7745277476784028798L;
+
+    /** Create a new JDKRandomGenerator with a default seed. */
+    public JDKRandomGenerator() {
+        super();
+    }
+
+    /**
+     * Create a new JDKRandomGenerator with the given seed.
+     *
+     * @param seed initial seed
+     * @since 3.6
+     */
+    public JDKRandomGenerator(int seed) {
+        setSeed(seed);
+    }
+
+    /** {@inheritDoc} */
+    public void setSeed(int seed) {
+        setSeed((long) seed);
+    }
+
+    /** {@inheritDoc} */
+    public void setSeed(int[] seed) {
+        setSeed(RandomGeneratorFactory.convertToLong(seed));
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/random/MersenneTwister.java b/src/main/java/org/apache/commons/math3/random/MersenneTwister.java
new file mode 100644
index 0000000..44ce85c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/random/MersenneTwister.java
@@ -0,0 +1,275 @@
+/*
+ * 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.math3.random;
+
+import org.apache.commons.math3.util.FastMath;
+
+import java.io.Serializable;
+
+/**
+ * This class implements a powerful pseudo-random number generator developed by Makoto Matsumoto and
+ * Takuji Nishimura during 1996-1997.
+ *
+ * <p>This generator features an extremely long period (2<sup>19937</sup>-1) and 623-dimensional
+ * equidistribution up to 32 bits accuracy. The home page for this generator is located at <a
+ * href="http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html">
+ * http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html</a>.
+ *
+ * <p>This generator is described in a paper by Makoto Matsumoto and Takuji Nishimura in 1998: <a
+ * href="http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/ARTICLES/mt.pdf">Mersenne Twister: A
+ * 623-Dimensionally Equidistributed Uniform Pseudo-Random Number Generator</a>, ACM Transactions on
+ * Modeling and Computer Simulation, Vol. 8, No. 1, January 1998, pp 3--30
+ *
+ * <p>This class is mainly a Java port of the 2002-01-26 version of the generator written in C by
+ * Makoto Matsumoto and Takuji Nishimura. Here is their original copyright:
+ *
+ * <table border="0" width="80%" cellpadding="10" align="center" bgcolor="#E0E0E0">
+ * <tr><td>Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
+ *     All rights reserved.</td></tr>
+ *
+ * <tr><td>Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * <ol>
+ *   <li>Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.</li>
+ *   <li>Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.</li>
+ *   <li>The names of its contributors may not be used to endorse or promote
+ *       products derived from this software without specific prior written
+ *       permission.</li>
+ * </ol></td></tr>
+ *
+ * <tr><td><strong>THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.</strong></td></tr>
+ * </table>
+ *
+ * @since 2.0
+ */
+public class MersenneTwister extends BitsStreamGenerator implements Serializable {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 8661194735290153518L;
+
+    /** Size of the bytes pool. */
+    private static final int N = 624;
+
+    /** Period second parameter. */
+    private static final int M = 397;
+
+    /** X * MATRIX_A for X = {0, 1}. */
+    private static final int[] MAG01 = {0x0, 0x9908b0df};
+
+    /** Bytes pool. */
+    private int[] mt;
+
+    /** Current index in the bytes pool. */
+    private int mti;
+
+    /**
+     * Creates a new random number generator.
+     *
+     * <p>The instance is initialized using the current time plus the system identity hash code of
+     * this instance as the seed.
+     */
+    public MersenneTwister() {
+        mt = new int[N];
+        setSeed(System.currentTimeMillis() + System.identityHashCode(this));
+    }
+
+    /**
+     * Creates a new random number generator using a single int seed.
+     *
+     * @param seed the initial seed (32 bits integer)
+     */
+    public MersenneTwister(int seed) {
+        mt = new int[N];
+        setSeed(seed);
+    }
+
+    /**
+     * Creates a new random number generator using an int array seed.
+     *
+     * @param seed the initial seed (32 bits integers array), if null the seed of the generator will
+     *     be related to the current time
+     */
+    public MersenneTwister(int[] seed) {
+        mt = new int[N];
+        setSeed(seed);
+    }
+
+    /**
+     * Creates a new random number generator using a single long seed.
+     *
+     * @param seed the initial seed (64 bits integer)
+     */
+    public MersenneTwister(long seed) {
+        mt = new int[N];
+        setSeed(seed);
+    }
+
+    /**
+     * Reinitialize the generator as if just built with the given int seed.
+     *
+     * <p>The state of the generator is exactly the same as a new generator built with the same
+     * seed.
+     *
+     * @param seed the initial seed (32 bits integer)
+     */
+    @Override
+    public void setSeed(int seed) {
+        // we use a long masked by 0xffffffffL as a poor man unsigned int
+        long longMT = seed;
+        // NB: unlike original C code, we are working with java longs, the cast below makes masking
+        // unnecessary
+        mt[0] = (int) longMT;
+        for (mti = 1; mti < N; ++mti) {
+            // See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier.
+            // initializer from the 2002-01-09 C version by Makoto Matsumoto
+            longMT = (1812433253l * (longMT ^ (longMT >> 30)) + mti) & 0xffffffffL;
+            mt[mti] = (int) longMT;
+        }
+
+        clear(); // Clear normal deviate cache
+    }
+
+    /**
+     * Reinitialize the generator as if just built with the given int array seed.
+     *
+     * <p>The state of the generator is exactly the same as a new generator built with the same
+     * seed.
+     *
+     * @param seed the initial seed (32 bits integers array), if null the seed of the generator will
+     *     be the current system time plus the system identity hash code of this instance
+     */
+    @Override
+    public void setSeed(int[] seed) {
+
+        if (seed == null) {
+            setSeed(System.currentTimeMillis() + System.identityHashCode(this));
+            return;
+        }
+
+        setSeed(19650218);
+        int i = 1;
+        int j = 0;
+
+        for (int k = FastMath.max(N, seed.length); k != 0; k--) {
+            long l0 = (mt[i] & 0x7fffffffl) | ((mt[i] < 0) ? 0x80000000l : 0x0l);
+            long l1 = (mt[i - 1] & 0x7fffffffl) | ((mt[i - 1] < 0) ? 0x80000000l : 0x0l);
+            long l = (l0 ^ ((l1 ^ (l1 >> 30)) * 1664525l)) + seed[j] + j; // non linear
+            mt[i] = (int) (l & 0xffffffffl);
+            i++;
+            j++;
+            if (i >= N) {
+                mt[0] = mt[N - 1];
+                i = 1;
+            }
+            if (j >= seed.length) {
+                j = 0;
+            }
+        }
+
+        for (int k = N - 1; k != 0; k--) {
+            long l0 = (mt[i] & 0x7fffffffl) | ((mt[i] < 0) ? 0x80000000l : 0x0l);
+            long l1 = (mt[i - 1] & 0x7fffffffl) | ((mt[i - 1] < 0) ? 0x80000000l : 0x0l);
+            long l = (l0 ^ ((l1 ^ (l1 >> 30)) * 1566083941l)) - i; // non linear
+            mt[i] = (int) (l & 0xffffffffL);
+            i++;
+            if (i >= N) {
+                mt[0] = mt[N - 1];
+                i = 1;
+            }
+        }
+
+        mt[0] = 0x80000000; // MSB is 1; assuring non-zero initial array
+
+        clear(); // Clear normal deviate cache
+    }
+
+    /**
+     * Reinitialize the generator as if just built with the given long seed.
+     *
+     * <p>The state of the generator is exactly the same as a new generator built with the same
+     * seed.
+     *
+     * @param seed the initial seed (64 bits integer)
+     */
+    @Override
+    public void setSeed(long seed) {
+        setSeed(new int[] {(int) (seed >>> 32), (int) (seed & 0xffffffffl)});
+    }
+
+    /**
+     * Generate next pseudorandom number.
+     *
+     * <p>This method is the core generation algorithm. It is used by all the public generation
+     * methods for the various primitive types {@link #nextBoolean()}, {@link #nextBytes(byte[])},
+     * {@link #nextDouble()}, {@link #nextFloat()}, {@link #nextGaussian()}, {@link #nextInt()},
+     * {@link #next(int)} and {@link #nextLong()}.
+     *
+     * @param bits number of random bits to produce
+     * @return random bits generated
+     */
+    @Override
+    protected int next(int bits) {
+
+        int y;
+
+        if (mti >= N) { // generate N words at one time
+            int mtNext = mt[0];
+            for (int k = 0; k < N - M; ++k) {
+                int mtCurr = mtNext;
+                mtNext = mt[k + 1];
+                y = (mtCurr & 0x80000000) | (mtNext & 0x7fffffff);
+                mt[k] = mt[k + M] ^ (y >>> 1) ^ MAG01[y & 0x1];
+            }
+            for (int k = N - M; k < N - 1; ++k) {
+                int mtCurr = mtNext;
+                mtNext = mt[k + 1];
+                y = (mtCurr & 0x80000000) | (mtNext & 0x7fffffff);
+                mt[k] = mt[k + (M - N)] ^ (y >>> 1) ^ MAG01[y & 0x1];
+            }
+            y = (mtNext & 0x80000000) | (mt[0] & 0x7fffffff);
+            mt[N - 1] = mt[M - 1] ^ (y >>> 1) ^ MAG01[y & 0x1];
+
+            mti = 0;
+        }
+
+        y = mt[mti++];
+
+        // tempering
+        y ^= y >>> 11;
+        y ^= (y << 7) & 0x9d2c5680;
+        y ^= (y << 15) & 0xefc60000;
+        y ^= y >>> 18;
+
+        return y >>> (32 - bits);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/random/NormalizedRandomGenerator.java b/src/main/java/org/apache/commons/math3/random/NormalizedRandomGenerator.java
new file mode 100644
index 0000000..2abcc72
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/random/NormalizedRandomGenerator.java
@@ -0,0 +1,38 @@
+/*
+ * 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.math3.random;
+
+/**
+ * This interface represent a normalized random generator for scalars. Normalized generator provide
+ * null mean and unit standard deviation scalars.
+ *
+ * @since 1.2
+ */
+public interface NormalizedRandomGenerator {
+
+    /**
+     * Generate a random scalar with null mean and unit standard deviation.
+     *
+     * <p>This method does <strong>not</strong> specify the shape of the distribution, it is the
+     * implementing class that provides it. The only contract here is to generate numbers with null
+     * mean and unit standard deviation.
+     *
+     * @return a random scalar with null mean and unit standard deviation
+     */
+    double nextNormalizedDouble();
+}
diff --git a/src/main/java/org/apache/commons/math3/random/RandomAdaptor.java b/src/main/java/org/apache/commons/math3/random/RandomAdaptor.java
new file mode 100644
index 0000000..e7030d0
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/random/RandomAdaptor.java
@@ -0,0 +1,182 @@
+/*
+ * 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.math3.random;
+
+import java.util.Random;
+
+/**
+ * Extension of <code>java.util.Random</code> wrapping a {@link RandomGenerator}.
+ *
+ * @since 1.1
+ */
+public class RandomAdaptor extends Random implements RandomGenerator {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 2306581345647615033L;
+
+    /** Wrapped randomGenerator instance */
+    private final RandomGenerator randomGenerator;
+
+    /** Prevent instantiation without a generator argument */
+    @SuppressWarnings("unused")
+    private RandomAdaptor() {
+        randomGenerator = null;
+    }
+
+    /**
+     * Construct a RandomAdaptor wrapping the supplied RandomGenerator.
+     *
+     * @param randomGenerator the wrapped generator
+     */
+    public RandomAdaptor(RandomGenerator randomGenerator) {
+        this.randomGenerator = randomGenerator;
+    }
+
+    /**
+     * Factory method to create a <code>Random</code> using the supplied <code>RandomGenerator
+     * </code>.
+     *
+     * @param randomGenerator wrapped RandomGenerator instance
+     * @return a Random instance wrapping the RandomGenerator
+     */
+    public static Random createAdaptor(RandomGenerator randomGenerator) {
+        return new RandomAdaptor(randomGenerator);
+    }
+
+    /**
+     * Returns the next pseudorandom, uniformly distributed <code>boolean</code> value from this
+     * random number generator's sequence.
+     *
+     * @return the next pseudorandom, uniformly distributed <code>boolean</code> value from this
+     *     random number generator's sequence
+     */
+    @Override
+    public boolean nextBoolean() {
+        return randomGenerator.nextBoolean();
+    }
+
+    /**
+     * Generates random bytes and places them into a user-supplied byte array. The number of random
+     * bytes produced is equal to the length of the byte array.
+     *
+     * @param bytes the non-null byte array in which to put the random bytes
+     */
+    @Override
+    public void nextBytes(byte[] bytes) {
+        randomGenerator.nextBytes(bytes);
+    }
+
+    /**
+     * Returns the next pseudorandom, uniformly distributed <code>double</code> value between <code>
+     * 0.0</code> and <code>1.0</code> from this random number generator's sequence.
+     *
+     * @return the next pseudorandom, uniformly distributed <code>double</code> value between <code>
+     *     0.0</code> and <code>1.0</code> from this random number generator's sequence
+     */
+    @Override
+    public double nextDouble() {
+        return randomGenerator.nextDouble();
+    }
+
+    /**
+     * Returns the next pseudorandom, uniformly distributed <code>float</code> value between <code>
+     * 0.0</code> and <code>1.0</code> from this random number generator's sequence.
+     *
+     * @return the next pseudorandom, uniformly distributed <code>float</code> value between <code>
+     *     0.0</code> and <code>1.0</code> from this random number generator's sequence
+     */
+    @Override
+    public float nextFloat() {
+        return randomGenerator.nextFloat();
+    }
+
+    /**
+     * Returns the next pseudorandom, Gaussian ("normally") distributed <code>double</code> value
+     * with mean <code>0.0</code> and standard deviation <code>1.0</code> from this random number
+     * generator's sequence.
+     *
+     * @return the next pseudorandom, Gaussian ("normally") distributed <code>double</code> value
+     *     with mean <code>0.0</code> and standard deviation <code>1.0</code> from this random
+     *     number generator's sequence
+     */
+    @Override
+    public double nextGaussian() {
+        return randomGenerator.nextGaussian();
+    }
+
+    /**
+     * Returns the next pseudorandom, uniformly distributed <code>int</code> value from this random
+     * number generator's sequence. All 2<font size="-1"><sup>32</sup></font> possible {@code int}
+     * values should be produced with (approximately) equal probability.
+     *
+     * @return the next pseudorandom, uniformly distributed <code>int</code> value from this random
+     *     number generator's sequence
+     */
+    @Override
+    public int nextInt() {
+        return randomGenerator.nextInt();
+    }
+
+    /**
+     * Returns a pseudorandom, uniformly distributed {@code int} value between 0 (inclusive) and the
+     * specified value (exclusive), drawn from this random number generator's sequence.
+     *
+     * @param n the bound on the random number to be returned. Must be positive.
+     * @return a pseudorandom, uniformly distributed {@code int} value between 0 (inclusive) and n
+     *     (exclusive).
+     * @throws IllegalArgumentException if n is not positive.
+     */
+    @Override
+    public int nextInt(int n) {
+        return randomGenerator.nextInt(n);
+    }
+
+    /**
+     * Returns the next pseudorandom, uniformly distributed <code>long</code> value from this random
+     * number generator's sequence. All 2<font size="-1"><sup>64</sup></font> possible {@code long}
+     * values should be produced with (approximately) equal probability.
+     *
+     * @return the next pseudorandom, uniformly distributed <code>long</code> value from this random
+     *     number generator's sequence
+     */
+    @Override
+    public long nextLong() {
+        return randomGenerator.nextLong();
+    }
+
+    /** {@inheritDoc} */
+    public void setSeed(int seed) {
+        if (randomGenerator != null) { // required to avoid NPE in constructor
+            randomGenerator.setSeed(seed);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void setSeed(int[] seed) {
+        if (randomGenerator != null) { // required to avoid NPE in constructor
+            randomGenerator.setSeed(seed);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setSeed(long seed) {
+        if (randomGenerator != null) { // required to avoid NPE in constructor
+            randomGenerator.setSeed(seed);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/random/RandomData.java b/src/main/java/org/apache/commons/math3/random/RandomData.java
new file mode 100644
index 0000000..98662e3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/random/RandomData.java
@@ -0,0 +1,245 @@
+/*
+ * 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.math3.random;
+
+import org.apache.commons.math3.exception.NotANumberException;
+import org.apache.commons.math3.exception.NotFiniteNumberException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+
+import java.util.Collection;
+
+/**
+ * Random data generation utilities.
+ *
+ * @deprecated to be removed in 4.0. Use {@link RandomDataGenerator} directly
+ */
+@Deprecated
+public interface RandomData {
+    /**
+     * Generates a random string of hex characters of length {@code len}.
+     *
+     * <p>The generated string will be random, but not cryptographically secure. To generate
+     * cryptographically secure strings, use {@link #nextSecureHexString(int)}.
+     *
+     * @param len the length of the string to be generated
+     * @return a random string of hex characters of length {@code len}
+     * @throws NotStrictlyPositiveException if {@code len <= 0}
+     */
+    String nextHexString(int len) throws NotStrictlyPositiveException;
+
+    /**
+     * Generates a uniformly distributed random integer between {@code lower} and {@code upper}
+     * (endpoints included).
+     *
+     * <p>The generated integer will be random, but not cryptographically secure. To generate
+     * cryptographically secure integer sequences, use {@link #nextSecureInt(int, int)}.
+     *
+     * @param lower lower bound for generated integer
+     * @param upper upper bound for generated integer
+     * @return a random integer greater than or equal to {@code lower} and less than or equal to
+     *     {@code upper}
+     * @throws NumberIsTooLargeException if {@code lower >= upper}
+     */
+    int nextInt(int lower, int upper) throws NumberIsTooLargeException;
+
+    /**
+     * Generates a uniformly distributed random long integer between {@code lower} and {@code upper}
+     * (endpoints included).
+     *
+     * <p>The generated long integer values will be random, but not cryptographically secure. To
+     * generate cryptographically secure sequences of longs, use {@link #nextSecureLong(long,
+     * long)}.
+     *
+     * @param lower lower bound for generated long integer
+     * @param upper upper bound for generated long integer
+     * @return a random long integer greater than or equal to {@code lower} and less than or equal
+     *     to {@code upper}
+     * @throws NumberIsTooLargeException if {@code lower >= upper}
+     */
+    long nextLong(long lower, long upper) throws NumberIsTooLargeException;
+
+    /**
+     * Generates a random string of hex characters from a secure random sequence.
+     *
+     * <p>If cryptographic security is not required, use {@link #nextHexString(int)}.
+     *
+     * @param len the length of the string to be generated
+     * @return a random string of hex characters of length {@code len}
+     * @throws NotStrictlyPositiveException if {@code len <= 0}
+     */
+    String nextSecureHexString(int len) throws NotStrictlyPositiveException;
+
+    /**
+     * Generates a uniformly distributed random integer between {@code lower} and {@code upper}
+     * (endpoints included) from a secure random sequence.
+     *
+     * <p>Sequences of integers generated using this method will be cryptographically secure. If
+     * cryptographic security is not required, {@link #nextInt(int, int)} should be used instead of
+     * this method.
+     *
+     * <p><strong>Definition</strong>: <a
+     * href="http://en.wikipedia.org/wiki/Cryptographically_secure_pseudo-random_number_generator">
+     * Secure Random Sequence</a>
+     *
+     * @param lower lower bound for generated integer
+     * @param upper upper bound for generated integer
+     * @return a random integer greater than or equal to {@code lower} and less than or equal to
+     *     {@code upper}.
+     * @throws NumberIsTooLargeException if {@code lower >= upper}.
+     */
+    int nextSecureInt(int lower, int upper) throws NumberIsTooLargeException;
+
+    /**
+     * Generates a uniformly distributed random long integer between {@code lower} and {@code upper}
+     * (endpoints included) from a secure random sequence.
+     *
+     * <p>Sequences of long values generated using this method will be cryptographically secure. If
+     * cryptographic security is not required, {@link #nextLong(long, long)} should be used instead
+     * of this method.
+     *
+     * <p><strong>Definition</strong>: <a
+     * href="http://en.wikipedia.org/wiki/Cryptographically_secure_pseudo-random_number_generator">
+     * Secure Random Sequence</a>
+     *
+     * @param lower lower bound for generated integer
+     * @param upper upper bound for generated integer
+     * @return a random long integer greater than or equal to {@code lower} and less than or equal
+     *     to {@code upper}.
+     * @throws NumberIsTooLargeException if {@code lower >= upper}.
+     */
+    long nextSecureLong(long lower, long upper) throws NumberIsTooLargeException;
+
+    /**
+     * Generates a random value from the Poisson distribution with the given mean.
+     *
+     * <p><strong>Definition</strong>: <a
+     * href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda366j.htm">Poisson
+     * Distribution</a>
+     *
+     * @param mean the mean of the Poisson distribution
+     * @return a random value following the specified Poisson distribution
+     * @throws NotStrictlyPositiveException if {@code mean <= 0}.
+     */
+    long nextPoisson(double mean) throws NotStrictlyPositiveException;
+
+    /**
+     * Generates a random value from the Normal (or Gaussian) distribution with specified mean and
+     * standard deviation.
+     *
+     * <p><strong>Definition</strong>: <a
+     * href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda3661.htm">Normal
+     * Distribution</a>
+     *
+     * @param mu the mean of the distribution
+     * @param sigma the standard deviation of the distribution
+     * @return a random value following the specified Gaussian distribution
+     * @throws NotStrictlyPositiveException if {@code sigma <= 0}.
+     */
+    double nextGaussian(double mu, double sigma) throws NotStrictlyPositiveException;
+
+    /**
+     * Generates a random value from the exponential distribution with specified mean.
+     *
+     * <p><strong>Definition</strong>: <a
+     * href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda3667.htm">Exponential
+     * Distribution</a>
+     *
+     * @param mean the mean of the distribution
+     * @return a random value following the specified exponential distribution
+     * @throws NotStrictlyPositiveException if {@code mean <= 0}.
+     */
+    double nextExponential(double mean) throws NotStrictlyPositiveException;
+
+    /**
+     * Generates a uniformly distributed random value from the open interval {@code (lower, upper)}
+     * (i.e., endpoints excluded).
+     *
+     * <p><strong>Definition</strong>: <a
+     * href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda3662.htm">Uniform
+     * Distribution</a> {@code lower} and {@code upper - lower} are the <a href =
+     * "http://www.itl.nist.gov/div898/handbook/eda/section3/eda364.htm"> location and scale
+     * parameters</a>, respectively.
+     *
+     * @param lower the exclusive lower bound of the support
+     * @param upper the exclusive upper bound of the support
+     * @return a uniformly distributed random value between lower and upper (exclusive)
+     * @throws NumberIsTooLargeException if {@code lower >= upper}
+     * @throws NotFiniteNumberException if one of the bounds is infinite
+     * @throws NotANumberException if one of the bounds is NaN
+     */
+    double nextUniform(double lower, double upper)
+            throws NumberIsTooLargeException, NotFiniteNumberException, NotANumberException;
+
+    /**
+     * Generates a uniformly distributed random value from the interval {@code (lower, upper)} or
+     * the interval {@code [lower, upper)}. The lower bound is thus optionally included, while the
+     * upper bound is always excluded.
+     *
+     * <p><strong>Definition</strong>: <a
+     * href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda3662.htm">Uniform
+     * Distribution</a> {@code lower} and {@code upper - lower} are the <a href =
+     * "http://www.itl.nist.gov/div898/handbook/eda/section3/eda364.htm"> location and scale
+     * parameters</a>, respectively.
+     *
+     * @param lower the lower bound of the support
+     * @param upper the exclusive upper bound of the support
+     * @param lowerInclusive {@code true} if the lower bound is inclusive
+     * @return uniformly distributed random value in the {@code (lower, upper)} interval, if {@code
+     *     lowerInclusive} is {@code false}, or in the {@code [lower, upper)} interval, if {@code
+     *     lowerInclusive} is {@code true}
+     * @throws NumberIsTooLargeException if {@code lower >= upper}
+     * @throws NotFiniteNumberException if one of the bounds is infinite
+     * @throws NotANumberException if one of the bounds is NaN
+     */
+    double nextUniform(double lower, double upper, boolean lowerInclusive)
+            throws NumberIsTooLargeException, NotFiniteNumberException, NotANumberException;
+
+    /**
+     * Generates an integer array of length {@code k} whose entries are selected randomly, without
+     * repetition, from the integers {@code 0, ..., n - 1} (inclusive).
+     *
+     * <p>Generated arrays represent permutations of {@code n} taken {@code k} at a time.
+     *
+     * @param n the domain of the permutation
+     * @param k the size of the permutation
+     * @return a random {@code k}-permutation of {@code n}, as an array of integers
+     * @throws NumberIsTooLargeException if {@code k > n}.
+     * @throws NotStrictlyPositiveException if {@code k <= 0}.
+     */
+    int[] nextPermutation(int n, int k)
+            throws NumberIsTooLargeException, NotStrictlyPositiveException;
+
+    /**
+     * Returns an array of {@code k} objects selected randomly from the Collection {@code c}.
+     *
+     * <p>Sampling from {@code c} is without replacement; but if {@code c} contains identical
+     * objects, the sample may include repeats. If all elements of {@code c} are distinct, the
+     * resulting object array represents a <a
+     * href="http://rkb.home.cern.ch/rkb/AN16pp/node250.html#SECTION0002500000000000000000">Simple
+     * Random Sample</a> of size {@code k} from the elements of {@code c}.
+     *
+     * @param c the collection to be sampled
+     * @param k the size of the sample
+     * @return a random sample of {@code k} elements from {@code c}
+     * @throws NumberIsTooLargeException if {@code k > c.size()}.
+     * @throws NotStrictlyPositiveException if {@code k <= 0}.
+     */
+    Object[] nextSample(Collection<?> c, int k)
+            throws NumberIsTooLargeException, NotStrictlyPositiveException;
+}
diff --git a/src/main/java/org/apache/commons/math3/random/RandomDataGenerator.java b/src/main/java/org/apache/commons/math3/random/RandomDataGenerator.java
new file mode 100644
index 0000000..5d48580
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/random/RandomDataGenerator.java
@@ -0,0 +1,780 @@
+/*
+ * 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.math3.random;
+
+import org.apache.commons.math3.distribution.BetaDistribution;
+import org.apache.commons.math3.distribution.BinomialDistribution;
+import org.apache.commons.math3.distribution.CauchyDistribution;
+import org.apache.commons.math3.distribution.ChiSquaredDistribution;
+import org.apache.commons.math3.distribution.ExponentialDistribution;
+import org.apache.commons.math3.distribution.FDistribution;
+import org.apache.commons.math3.distribution.GammaDistribution;
+import org.apache.commons.math3.distribution.HypergeometricDistribution;
+import org.apache.commons.math3.distribution.PascalDistribution;
+import org.apache.commons.math3.distribution.PoissonDistribution;
+import org.apache.commons.math3.distribution.TDistribution;
+import org.apache.commons.math3.distribution.UniformIntegerDistribution;
+import org.apache.commons.math3.distribution.WeibullDistribution;
+import org.apache.commons.math3.distribution.ZipfDistribution;
+import org.apache.commons.math3.exception.MathInternalError;
+import org.apache.commons.math3.exception.NotANumberException;
+import org.apache.commons.math3.exception.NotFiniteNumberException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.MathArrays;
+
+import java.io.Serializable;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.SecureRandom;
+import java.util.Collection;
+
+/**
+ * Implements the {@link RandomData} interface using a {@link RandomGenerator} instance to generate
+ * non-secure data and a {@link java.security.SecureRandom} instance to provide data for the <code>
+ * nextSecureXxx</code> methods. If no <code>RandomGenerator</code> is provided in the constructor,
+ * the default is to use a {@link Well19937c} generator. To plug in a different implementation,
+ * either implement <code>RandomGenerator</code> directly or extend {@link AbstractRandomGenerator}.
+ *
+ * <p>Supports reseeding the underlying pseudo-random number generator (PRNG). The <code>
+ * SecurityProvider</code> and <code>Algorithm</code> used by the <code>SecureRandom</code> instance
+ * can also be reset.
+ *
+ * <p>For details on the default PRNGs, see {@link java.util.Random} and {@link
+ * java.security.SecureRandom}.
+ *
+ * <p><strong>Usage Notes</strong>:
+ *
+ * <ul>
+ *   <li>Instance variables are used to maintain <code>RandomGenerator</code> and <code>SecureRandom
+ *       </code> instances used in data generation. Therefore, to generate a random sequence of
+ *       values or strings, you should use just <strong>one</strong> <code>RandomDataImpl</code>
+ *       instance repeatedly.
+ *   <li>The "secure" methods are *much* slower. These should be used only when a cryptographically
+ *       secure random sequence is required. A secure random sequence is a sequence of pseudo-random
+ *       values which, in addition to being well-dispersed (so no subsequence of values is an any
+ *       more likely than other subsequence of the the same length), also has the additional
+ *       property that knowledge of values generated up to any point in the sequence does not make
+ *       it any easier to predict subsequent values.
+ *   <li>When a new <code>RandomDataImpl</code> is created, the underlying random number generators
+ *       are <strong>not</strong> initialized. If you do not explicitly seed the default non-secure
+ *       generator, it is seeded with the current time in milliseconds plus the system identity hash
+ *       code on first use. The same holds for the secure generator. If you provide a <code>
+ *       RandomGenerator</code> to the constructor, however, this generator is not reseeded by the
+ *       constructor nor is it reseeded on first use.
+ *   <li>The <code>reSeed</code> and <code>reSeedSecure</code> methods delegate to the corresponding
+ *       methods on the underlying <code>RandomGenerator</code> and <code>SecureRandom</code>
+ *       instances. Therefore, <code>reSeed(long)</code> fully resets the initial state of the
+ *       non-secure random number generator (so that reseeding with a specific value always results
+ *       in the same subsequent random sequence); whereas reSeedSecure(long) does
+ *       <strong>not</strong> reinitialize the secure random number generator (so secure sequences
+ *       started with calls to reseedSecure(long) won't be identical).
+ *   <li>This implementation is not synchronized. The underlying <code>RandomGenerator</code> or
+ *       <code>SecureRandom</code> instances are not protected by synchronization and are not
+ *       guaranteed to be thread-safe. Therefore, if an instance of this class is concurrently
+ *       utilized by multiple threads, it is the responsibility of client code to synchronize access
+ *       to seeding and data generation methods.
+ * </ul>
+ *
+ * @since 3.1
+ */
+public class RandomDataGenerator implements RandomData, Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -626730818244969716L;
+
+    /** underlying random number generator */
+    private RandomGenerator rand = null;
+
+    /** underlying secure random number generator */
+    private RandomGenerator secRand = null;
+
+    /**
+     * Construct a RandomDataGenerator, using a default random generator as the source of
+     * randomness.
+     *
+     * <p>The default generator is a {@link Well19937c} seeded with {@code
+     * System.currentTimeMillis() + System.identityHashCode(this))}. The generator is initialized
+     * and seeded on first use.
+     */
+    public RandomDataGenerator() {}
+
+    /**
+     * Construct a RandomDataGenerator using the supplied {@link RandomGenerator} as the source of
+     * (non-secure) random data.
+     *
+     * @param rand the source of (non-secure) random data (may be null, resulting in the default
+     *     generator)
+     */
+    public RandomDataGenerator(RandomGenerator rand) {
+        this.rand = rand;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p><strong>Algorithm Description:</strong> hex strings are generated using a 2-step process.
+     *
+     * <ol>
+     *   <li>{@code len / 2 + 1} binary bytes are generated using the underlying Random
+     *   <li>Each binary byte is translated into 2 hex digits
+     * </ol>
+     *
+     * @param len the desired string length.
+     * @return the random string.
+     * @throws NotStrictlyPositiveException if {@code len <= 0}.
+     */
+    public String nextHexString(int len) throws NotStrictlyPositiveException {
+        if (len <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.LENGTH, len);
+        }
+
+        // Get a random number generator
+        RandomGenerator ran = getRandomGenerator();
+
+        // Initialize output buffer
+        StringBuilder outBuffer = new StringBuilder();
+
+        // Get int(len/2)+1 random bytes
+        byte[] randomBytes = new byte[(len / 2) + 1];
+        ran.nextBytes(randomBytes);
+
+        // Convert each byte to 2 hex digits
+        for (int i = 0; i < randomBytes.length; i++) {
+            Integer c = Integer.valueOf(randomBytes[i]);
+
+            /*
+             * Add 128 to byte value to make interval 0-255 before doing hex
+             * conversion. This guarantees <= 2 hex digits from toHexString()
+             * toHexString would otherwise add 2^32 to negative arguments.
+             */
+            String hex = Integer.toHexString(c.intValue() + 128);
+
+            // Make sure we add 2 hex digits for each byte
+            if (hex.length() == 1) {
+                hex = "0" + hex;
+            }
+            outBuffer.append(hex);
+        }
+        return outBuffer.toString().substring(0, len);
+    }
+
+    /** {@inheritDoc} */
+    public int nextInt(final int lower, final int upper) throws NumberIsTooLargeException {
+        return new UniformIntegerDistribution(getRandomGenerator(), lower, upper).sample();
+    }
+
+    /** {@inheritDoc} */
+    public long nextLong(final long lower, final long upper) throws NumberIsTooLargeException {
+        if (lower >= upper) {
+            throw new NumberIsTooLargeException(
+                    LocalizedFormats.LOWER_BOUND_NOT_BELOW_UPPER_BOUND, lower, upper, false);
+        }
+        final long max = (upper - lower) + 1;
+        if (max <= 0) {
+            // the range is too wide to fit in a positive long (larger than 2^63); as it covers
+            // more than half the long range, we use directly a simple rejection method
+            final RandomGenerator rng = getRandomGenerator();
+            while (true) {
+                final long r = rng.nextLong();
+                if (r >= lower && r <= upper) {
+                    return r;
+                }
+            }
+        } else if (max < Integer.MAX_VALUE) {
+            // we can shift the range and generate directly a positive int
+            return lower + getRandomGenerator().nextInt((int) max);
+        } else {
+            // we can shift the range and generate directly a positive long
+            return lower + nextLong(getRandomGenerator(), max);
+        }
+    }
+
+    /**
+     * Returns a pseudorandom, uniformly distributed {@code long} value between 0 (inclusive) and
+     * the specified value (exclusive), drawn from this random number generator's sequence.
+     *
+     * @param rng random generator to use
+     * @param n the bound on the random number to be returned. Must be positive.
+     * @return a pseudorandom, uniformly distributed {@code long} value between 0 (inclusive) and n
+     *     (exclusive).
+     * @throws IllegalArgumentException if n is not positive.
+     */
+    private static long nextLong(final RandomGenerator rng, final long n)
+            throws IllegalArgumentException {
+        if (n > 0) {
+            final byte[] byteArray = new byte[8];
+            long bits;
+            long val;
+            do {
+                rng.nextBytes(byteArray);
+                bits = 0;
+                for (final byte b : byteArray) {
+                    bits = (bits << 8) | (((long) b) & 0xffL);
+                }
+                bits &= 0x7fffffffffffffffL;
+                val = bits % n;
+            } while (bits - val + (n - 1) < 0);
+            return val;
+        }
+        throw new NotStrictlyPositiveException(n);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p><strong>Algorithm Description:</strong> hex strings are generated in 40-byte segments
+     * using a 3-step process.
+     *
+     * <ol>
+     *   <li>20 random bytes are generated using the underlying <code>SecureRandom</code>.
+     *   <li>SHA-1 hash is applied to yield a 20-byte binary digest.
+     *   <li>Each byte of the binary digest is converted to 2 hex digits.
+     * </ol>
+     *
+     * @throws NotStrictlyPositiveException if {@code len <= 0}
+     */
+    public String nextSecureHexString(int len) throws NotStrictlyPositiveException {
+        if (len <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.LENGTH, len);
+        }
+
+        // Get SecureRandom and setup Digest provider
+        final RandomGenerator secRan = getSecRan();
+        MessageDigest alg = null;
+        try {
+            alg = MessageDigest.getInstance("SHA-1");
+        } catch (NoSuchAlgorithmException ex) {
+            // this should never happen
+            throw new MathInternalError(ex);
+        }
+        alg.reset();
+
+        // Compute number of iterations required (40 bytes each)
+        int numIter = (len / 40) + 1;
+
+        StringBuilder outBuffer = new StringBuilder();
+        for (int iter = 1; iter < numIter + 1; iter++) {
+            byte[] randomBytes = new byte[40];
+            secRan.nextBytes(randomBytes);
+            alg.update(randomBytes);
+
+            // Compute hash -- will create 20-byte binary hash
+            byte[] hash = alg.digest();
+
+            // Loop over the hash, converting each byte to 2 hex digits
+            for (int i = 0; i < hash.length; i++) {
+                Integer c = Integer.valueOf(hash[i]);
+
+                /*
+                 * Add 128 to byte value to make interval 0-255 This guarantees
+                 * <= 2 hex digits from toHexString() toHexString would
+                 * otherwise add 2^32 to negative arguments
+                 */
+                String hex = Integer.toHexString(c.intValue() + 128);
+
+                // Keep strings uniform length -- guarantees 40 bytes
+                if (hex.length() == 1) {
+                    hex = "0" + hex;
+                }
+                outBuffer.append(hex);
+            }
+        }
+        return outBuffer.toString().substring(0, len);
+    }
+
+    /** {@inheritDoc} */
+    public int nextSecureInt(final int lower, final int upper) throws NumberIsTooLargeException {
+        return new UniformIntegerDistribution(getSecRan(), lower, upper).sample();
+    }
+
+    /** {@inheritDoc} */
+    public long nextSecureLong(final long lower, final long upper)
+            throws NumberIsTooLargeException {
+        if (lower >= upper) {
+            throw new NumberIsTooLargeException(
+                    LocalizedFormats.LOWER_BOUND_NOT_BELOW_UPPER_BOUND, lower, upper, false);
+        }
+        final RandomGenerator rng = getSecRan();
+        final long max = (upper - lower) + 1;
+        if (max <= 0) {
+            // the range is too wide to fit in a positive long (larger than 2^63); as it covers
+            // more than half the long range, we use directly a simple rejection method
+            while (true) {
+                final long r = rng.nextLong();
+                if (r >= lower && r <= upper) {
+                    return r;
+                }
+            }
+        } else if (max < Integer.MAX_VALUE) {
+            // we can shift the range and generate directly a positive int
+            return lower + rng.nextInt((int) max);
+        } else {
+            // we can shift the range and generate directly a positive long
+            return lower + nextLong(rng, max);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p><strong>Algorithm Description</strong>:
+     *
+     * <ul>
+     *   <li>For small means, uses simulation of a Poisson process using Uniform deviates, as
+     *       described <a href="http://irmi.epfl.ch/cmos/Pmmi/interactive/rng7.htm">here.</a> The
+     *       Poisson process (and hence value returned) is bounded by 1000 * mean.
+     *   <li>For large means, uses the rejection algorithm described in <br>
+     *       Devroye, Luc. (1981).<i>The Computer Generation of Poisson Random Variables</i>
+     *       <strong>Computing</strong> vol. 26 pp. 197-207.
+     * </ul>
+     *
+     * @throws NotStrictlyPositiveException if {@code len <= 0}
+     */
+    public long nextPoisson(double mean) throws NotStrictlyPositiveException {
+        return new PoissonDistribution(
+                        getRandomGenerator(),
+                        mean,
+                        PoissonDistribution.DEFAULT_EPSILON,
+                        PoissonDistribution.DEFAULT_MAX_ITERATIONS)
+                .sample();
+    }
+
+    /** {@inheritDoc} */
+    public double nextGaussian(double mu, double sigma) throws NotStrictlyPositiveException {
+        if (sigma <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.STANDARD_DEVIATION, sigma);
+        }
+        return sigma * getRandomGenerator().nextGaussian() + mu;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p><strong>Algorithm Description</strong>: Uses the Algorithm SA (Ahrens) from p. 876 in:
+     * [1]: Ahrens, J. H. and Dieter, U. (1972). Computer methods for sampling from the exponential
+     * and normal distributions. Communications of the ACM, 15, 873-882.
+     */
+    public double nextExponential(double mean) throws NotStrictlyPositiveException {
+        return new ExponentialDistribution(
+                        getRandomGenerator(),
+                        mean,
+                        ExponentialDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY)
+                .sample();
+    }
+
+    /**
+     * Generates a random value from the {@link
+     * org.apache.commons.math3.distribution.GammaDistribution Gamma Distribution}.
+     *
+     * <p>This implementation uses the following algorithms:
+     *
+     * <p>For 0 < shape < 1: <br>
+     * Ahrens, J. H. and Dieter, U., <i>Computer methods for sampling from gamma, beta, Poisson and
+     * binomial distributions.</i> Computing, 12, 223-246, 1974.
+     *
+     * <p>For shape >= 1: <br>
+     * Marsaglia and Tsang, <i>A Simple Method for Generating Gamma Variables.</i> ACM Transactions
+     * on Mathematical Software, Volume 26 Issue 3, September, 2000.
+     *
+     * @param shape the median of the Gamma distribution
+     * @param scale the scale parameter of the Gamma distribution
+     * @return random value sampled from the Gamma(shape, scale) distribution
+     * @throws NotStrictlyPositiveException if {@code shape <= 0} or {@code scale <= 0}.
+     */
+    public double nextGamma(double shape, double scale) throws NotStrictlyPositiveException {
+        return new GammaDistribution(
+                        getRandomGenerator(),
+                        shape,
+                        scale,
+                        GammaDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY)
+                .sample();
+    }
+
+    /**
+     * Generates a random value from the {@link HypergeometricDistribution Hypergeometric
+     * Distribution}.
+     *
+     * @param populationSize the population size of the Hypergeometric distribution
+     * @param numberOfSuccesses number of successes in the population of the Hypergeometric
+     *     distribution
+     * @param sampleSize the sample size of the Hypergeometric distribution
+     * @return random value sampled from the Hypergeometric(numberOfSuccesses, sampleSize)
+     *     distribution
+     * @throws NumberIsTooLargeException if {@code numberOfSuccesses > populationSize}, or {@code
+     *     sampleSize > populationSize}.
+     * @throws NotStrictlyPositiveException if {@code populationSize <= 0}.
+     * @throws NotPositiveException if {@code numberOfSuccesses < 0}.
+     */
+    public int nextHypergeometric(int populationSize, int numberOfSuccesses, int sampleSize)
+            throws NotPositiveException, NotStrictlyPositiveException, NumberIsTooLargeException {
+        return new HypergeometricDistribution(
+                        getRandomGenerator(), populationSize, numberOfSuccesses, sampleSize)
+                .sample();
+    }
+
+    /**
+     * Generates a random value from the {@link PascalDistribution Pascal Distribution}.
+     *
+     * @param r the number of successes of the Pascal distribution
+     * @param p the probability of success of the Pascal distribution
+     * @return random value sampled from the Pascal(r, p) distribution
+     * @throws NotStrictlyPositiveException if the number of successes is not positive
+     * @throws OutOfRangeException if the probability of success is not in the range {@code [0, 1]}.
+     */
+    public int nextPascal(int r, double p)
+            throws NotStrictlyPositiveException, OutOfRangeException {
+        return new PascalDistribution(getRandomGenerator(), r, p).sample();
+    }
+
+    /**
+     * Generates a random value from the {@link TDistribution T Distribution}.
+     *
+     * @param df the degrees of freedom of the T distribution
+     * @return random value from the T(df) distribution
+     * @throws NotStrictlyPositiveException if {@code df <= 0}
+     */
+    public double nextT(double df) throws NotStrictlyPositiveException {
+        return new TDistribution(
+                        getRandomGenerator(), df, TDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY)
+                .sample();
+    }
+
+    /**
+     * Generates a random value from the {@link WeibullDistribution Weibull Distribution}.
+     *
+     * @param shape the shape parameter of the Weibull distribution
+     * @param scale the scale parameter of the Weibull distribution
+     * @return random value sampled from the Weibull(shape, size) distribution
+     * @throws NotStrictlyPositiveException if {@code shape <= 0} or {@code scale <= 0}.
+     */
+    public double nextWeibull(double shape, double scale) throws NotStrictlyPositiveException {
+        return new WeibullDistribution(
+                        getRandomGenerator(),
+                        shape,
+                        scale,
+                        WeibullDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY)
+                .sample();
+    }
+
+    /**
+     * Generates a random value from the {@link ZipfDistribution Zipf Distribution}.
+     *
+     * @param numberOfElements the number of elements of the ZipfDistribution
+     * @param exponent the exponent of the ZipfDistribution
+     * @return random value sampled from the Zipf(numberOfElements, exponent) distribution
+     * @exception NotStrictlyPositiveException if {@code numberOfElements <= 0} or {@code exponent
+     *     <= 0}.
+     */
+    public int nextZipf(int numberOfElements, double exponent) throws NotStrictlyPositiveException {
+        return new ZipfDistribution(getRandomGenerator(), numberOfElements, exponent).sample();
+    }
+
+    /**
+     * Generates a random value from the {@link BetaDistribution Beta Distribution}.
+     *
+     * @param alpha first distribution shape parameter
+     * @param beta second distribution shape parameter
+     * @return random value sampled from the beta(alpha, beta) distribution
+     */
+    public double nextBeta(double alpha, double beta) {
+        return new BetaDistribution(
+                        getRandomGenerator(),
+                        alpha,
+                        beta,
+                        BetaDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY)
+                .sample();
+    }
+
+    /**
+     * Generates a random value from the {@link BinomialDistribution Binomial Distribution}.
+     *
+     * @param numberOfTrials number of trials of the Binomial distribution
+     * @param probabilityOfSuccess probability of success of the Binomial distribution
+     * @return random value sampled from the Binomial(numberOfTrials, probabilityOfSuccess)
+     *     distribution
+     */
+    public int nextBinomial(int numberOfTrials, double probabilityOfSuccess) {
+        return new BinomialDistribution(getRandomGenerator(), numberOfTrials, probabilityOfSuccess)
+                .sample();
+    }
+
+    /**
+     * Generates a random value from the {@link CauchyDistribution Cauchy Distribution}.
+     *
+     * @param median the median of the Cauchy distribution
+     * @param scale the scale parameter of the Cauchy distribution
+     * @return random value sampled from the Cauchy(median, scale) distribution
+     */
+    public double nextCauchy(double median, double scale) {
+        return new CauchyDistribution(
+                        getRandomGenerator(),
+                        median,
+                        scale,
+                        CauchyDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY)
+                .sample();
+    }
+
+    /**
+     * Generates a random value from the {@link ChiSquaredDistribution ChiSquare Distribution}.
+     *
+     * @param df the degrees of freedom of the ChiSquare distribution
+     * @return random value sampled from the ChiSquare(df) distribution
+     */
+    public double nextChiSquare(double df) {
+        return new ChiSquaredDistribution(
+                        getRandomGenerator(),
+                        df,
+                        ChiSquaredDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY)
+                .sample();
+    }
+
+    /**
+     * Generates a random value from the {@link FDistribution F Distribution}.
+     *
+     * @param numeratorDf the numerator degrees of freedom of the F distribution
+     * @param denominatorDf the denominator degrees of freedom of the F distribution
+     * @return random value sampled from the F(numeratorDf, denominatorDf) distribution
+     * @throws NotStrictlyPositiveException if {@code numeratorDf <= 0} or {@code denominatorDf <=
+     *     0}.
+     */
+    public double nextF(double numeratorDf, double denominatorDf)
+            throws NotStrictlyPositiveException {
+        return new FDistribution(
+                        getRandomGenerator(),
+                        numeratorDf,
+                        denominatorDf,
+                        FDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY)
+                .sample();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p><strong>Algorithm Description</strong>: scales the output of Random.nextDouble(), but
+     * rejects 0 values (i.e., will generate another random double if Random.nextDouble() returns
+     * 0). This is necessary to provide a symmetric output interval (both endpoints excluded).
+     *
+     * @throws NumberIsTooLargeException if {@code lower >= upper}
+     * @throws NotFiniteNumberException if one of the bounds is infinite
+     * @throws NotANumberException if one of the bounds is NaN
+     */
+    public double nextUniform(double lower, double upper)
+            throws NumberIsTooLargeException, NotFiniteNumberException, NotANumberException {
+        return nextUniform(lower, upper, false);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p><strong>Algorithm Description</strong>: if the lower bound is excluded, scales the output
+     * of Random.nextDouble(), but rejects 0 values (i.e., will generate another random double if
+     * Random.nextDouble() returns 0). This is necessary to provide a symmetric output interval
+     * (both endpoints excluded).
+     *
+     * @throws NumberIsTooLargeException if {@code lower >= upper}
+     * @throws NotFiniteNumberException if one of the bounds is infinite
+     * @throws NotANumberException if one of the bounds is NaN
+     */
+    public double nextUniform(double lower, double upper, boolean lowerInclusive)
+            throws NumberIsTooLargeException, NotFiniteNumberException, NotANumberException {
+
+        if (lower >= upper) {
+            throw new NumberIsTooLargeException(
+                    LocalizedFormats.LOWER_BOUND_NOT_BELOW_UPPER_BOUND, lower, upper, false);
+        }
+
+        if (Double.isInfinite(lower)) {
+            throw new NotFiniteNumberException(LocalizedFormats.INFINITE_BOUND, lower);
+        }
+        if (Double.isInfinite(upper)) {
+            throw new NotFiniteNumberException(LocalizedFormats.INFINITE_BOUND, upper);
+        }
+
+        if (Double.isNaN(lower) || Double.isNaN(upper)) {
+            throw new NotANumberException();
+        }
+
+        final RandomGenerator generator = getRandomGenerator();
+
+        // ensure nextDouble() isn't 0.0
+        double u = generator.nextDouble();
+        while (!lowerInclusive && u <= 0.0) {
+            u = generator.nextDouble();
+        }
+
+        return u * upper + (1.0 - u) * lower;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>This method calls {@link MathArrays#shuffle(int[],RandomGenerator) MathArrays.shuffle} in
+     * order to create a random shuffle of the set of natural numbers {@code { 0, 1, ..., n - 1 }}.
+     *
+     * @throws NumberIsTooLargeException if {@code k > n}.
+     * @throws NotStrictlyPositiveException if {@code k <= 0}.
+     */
+    public int[] nextPermutation(int n, int k)
+            throws NumberIsTooLargeException, NotStrictlyPositiveException {
+        if (k > n) {
+            throw new NumberIsTooLargeException(LocalizedFormats.PERMUTATION_EXCEEDS_N, k, n, true);
+        }
+        if (k <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.PERMUTATION_SIZE, k);
+        }
+
+        int[] index = MathArrays.natural(n);
+        MathArrays.shuffle(index, getRandomGenerator());
+
+        // Return a new array containing the first "k" entries of "index".
+        return MathArrays.copyOf(index, k);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>This method calls {@link #nextPermutation(int,int) nextPermutation(c.size(), k)} in order
+     * to sample the collection.
+     */
+    public Object[] nextSample(Collection<?> c, int k)
+            throws NumberIsTooLargeException, NotStrictlyPositiveException {
+
+        int len = c.size();
+        if (k > len) {
+            throw new NumberIsTooLargeException(
+                    LocalizedFormats.SAMPLE_SIZE_EXCEEDS_COLLECTION_SIZE, k, len, true);
+        }
+        if (k <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.NUMBER_OF_SAMPLES, k);
+        }
+
+        Object[] objects = c.toArray();
+        int[] index = nextPermutation(len, k);
+        Object[] result = new Object[k];
+        for (int i = 0; i < k; i++) {
+            result[i] = objects[index[i]];
+        }
+        return result;
+    }
+
+    /**
+     * Reseeds the random number generator with the supplied seed.
+     *
+     * <p>Will create and initialize if null.
+     *
+     * @param seed the seed value to use
+     */
+    public void reSeed(long seed) {
+        getRandomGenerator().setSeed(seed);
+    }
+
+    /**
+     * Reseeds the secure random number generator with the current time in milliseconds.
+     *
+     * <p>Will create and initialize if null.
+     */
+    public void reSeedSecure() {
+        getSecRan().setSeed(System.currentTimeMillis());
+    }
+
+    /**
+     * Reseeds the secure random number generator with the supplied seed.
+     *
+     * <p>Will create and initialize if null.
+     *
+     * @param seed the seed value to use
+     */
+    public void reSeedSecure(long seed) {
+        getSecRan().setSeed(seed);
+    }
+
+    /**
+     * Reseeds the random number generator with {@code System.currentTimeMillis() +
+     * System.identityHashCode(this))}.
+     */
+    public void reSeed() {
+        getRandomGenerator().setSeed(System.currentTimeMillis() + System.identityHashCode(this));
+    }
+
+    /**
+     * Sets the PRNG algorithm for the underlying SecureRandom instance using the Security Provider
+     * API. The Security Provider API is defined in <a href =
+     * "http://java.sun.com/j2se/1.3/docs/guide/security/CryptoSpec.html#AppA"> Java Cryptography
+     * Architecture API Specification & Reference.</a>
+     *
+     * <p><strong>USAGE NOTE:</strong> This method carries <i>significant</i> overhead and may take
+     * several seconds to execute.
+     *
+     * @param algorithm the name of the PRNG algorithm
+     * @param provider the name of the provider
+     * @throws NoSuchAlgorithmException if the specified algorithm is not available
+     * @throws NoSuchProviderException if the specified provider is not installed
+     */
+    public void setSecureAlgorithm(String algorithm, String provider)
+            throws NoSuchAlgorithmException, NoSuchProviderException {
+        secRand =
+                RandomGeneratorFactory.createRandomGenerator(
+                        SecureRandom.getInstance(algorithm, provider));
+    }
+
+    /**
+     * Returns the RandomGenerator used to generate non-secure random data.
+     *
+     * <p>Creates and initializes a default generator if null. Uses a {@link Well19937c} generator
+     * with {@code System.currentTimeMillis() + System.identityHashCode(this))} as the default seed.
+     *
+     * @return the Random used to generate random data
+     * @since 3.2
+     */
+    public RandomGenerator getRandomGenerator() {
+        if (rand == null) {
+            initRan();
+        }
+        return rand;
+    }
+
+    /**
+     * Sets the default generator to a {@link Well19937c} generator seeded with {@code
+     * System.currentTimeMillis() + System.identityHashCode(this))}.
+     */
+    private void initRan() {
+        rand = new Well19937c(System.currentTimeMillis() + System.identityHashCode(this));
+    }
+
+    /**
+     * Returns the SecureRandom used to generate secure random data.
+     *
+     * <p>Creates and initializes if null. Uses {@code System.currentTimeMillis() +
+     * System.identityHashCode(this)} as the default seed.
+     *
+     * @return the SecureRandom used to generate secure random data, wrapped in a {@link
+     *     RandomGenerator}.
+     */
+    private RandomGenerator getSecRan() {
+        if (secRand == null) {
+            secRand = RandomGeneratorFactory.createRandomGenerator(new SecureRandom());
+            secRand.setSeed(System.currentTimeMillis() + System.identityHashCode(this));
+        }
+        return secRand;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/random/RandomDataImpl.java b/src/main/java/org/apache/commons/math3/random/RandomDataImpl.java
new file mode 100644
index 0000000..d5749e9
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/random/RandomDataImpl.java
@@ -0,0 +1,544 @@
+/*
+ * 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.math3.random;
+
+import org.apache.commons.math3.distribution.IntegerDistribution;
+import org.apache.commons.math3.distribution.RealDistribution;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NotANumberException;
+import org.apache.commons.math3.exception.NotFiniteNumberException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+
+import java.io.Serializable;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.util.Collection;
+
+/**
+ * Generates random deviates and other random data using a {@link RandomGenerator} instance to
+ * generate non-secure data and a {@link java.security.SecureRandom} instance to provide data for
+ * the <code>nextSecureXxx</code> methods. If no <code>RandomGenerator</code> is provided in the
+ * constructor, the default is to use a {@link Well19937c} generator. To plug in a different
+ * implementation, either implement <code>RandomGenerator</code> directly or extend {@link
+ * AbstractRandomGenerator}.
+ *
+ * <p>Supports reseeding the underlying pseudo-random number generator (PRNG). The <code>
+ * SecurityProvider</code> and <code>Algorithm</code> used by the <code>SecureRandom</code> instance
+ * can also be reset.
+ *
+ * <p>For details on the default PRNGs, see {@link java.util.Random} and {@link
+ * java.security.SecureRandom}.
+ *
+ * <p><strong>Usage Notes</strong>:
+ *
+ * <ul>
+ *   <li>Instance variables are used to maintain <code>RandomGenerator</code> and <code>SecureRandom
+ *       </code> instances used in data generation. Therefore, to generate a random sequence of
+ *       values or strings, you should use just <strong>one</strong> <code>RandomDataGenerator
+ *       </code> instance repeatedly.
+ *   <li>The "secure" methods are *much* slower. These should be used only when a cryptographically
+ *       secure random sequence is required. A secure random sequence is a sequence of pseudo-random
+ *       values which, in addition to being well-dispersed (so no subsequence of values is an any
+ *       more likely than other subsequence of the the same length), also has the additional
+ *       property that knowledge of values generated up to any point in the sequence does not make
+ *       it any easier to predict subsequent values.
+ *   <li>When a new <code>RandomDataGenerator</code> is created, the underlying random number
+ *       generators are <strong>not</strong> initialized. If you do not explicitly seed the default
+ *       non-secure generator, it is seeded with the current time in milliseconds plus the system
+ *       identity hash code on first use. The same holds for the secure generator. If you provide a
+ *       <code>RandomGenerator</code> to the constructor, however, this generator is not reseeded by
+ *       the constructor nor is it reseeded on first use.
+ *   <li>The <code>reSeed</code> and <code>reSeedSecure</code> methods delegate to the corresponding
+ *       methods on the underlying <code>RandomGenerator</code> and <code>SecureRandom</code>
+ *       instances. Therefore, <code>reSeed(long)</code> fully resets the initial state of the
+ *       non-secure random number generator (so that reseeding with a specific value always results
+ *       in the same subsequent random sequence); whereas reSeedSecure(long) does
+ *       <strong>not</strong> reinitialize the secure random number generator (so secure sequences
+ *       started with calls to reseedSecure(long) won't be identical).
+ *   <li>This implementation is not synchronized. The underlying <code>RandomGenerator</code> or
+ *       <code>SecureRandom</code> instances are not protected by synchronization and are not
+ *       guaranteed to be thread-safe. Therefore, if an instance of this class is concurrently
+ *       utilized by multiple threads, it is the responsibility of client code to synchronize access
+ *       to seeding and data generation methods.
+ * </ul>
+ *
+ * @deprecated to be removed in 4.0. Use {@link RandomDataGenerator} instead
+ */
+@Deprecated
+public class RandomDataImpl implements RandomData, Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -626730818244969716L;
+
+    /** RandomDataGenerator delegate */
+    private final RandomDataGenerator delegate;
+
+    /**
+     * Construct a RandomDataImpl, using a default random generator as the source of randomness.
+     *
+     * <p>The default generator is a {@link Well19937c} seeded with {@code
+     * System.currentTimeMillis() + System.identityHashCode(this))}. The generator is initialized
+     * and seeded on first use.
+     */
+    public RandomDataImpl() {
+        delegate = new RandomDataGenerator();
+    }
+
+    /**
+     * Construct a RandomDataImpl using the supplied {@link RandomGenerator} as the source of
+     * (non-secure) random data.
+     *
+     * @param rand the source of (non-secure) random data (may be null, resulting in the default
+     *     generator)
+     * @since 1.1
+     */
+    public RandomDataImpl(RandomGenerator rand) {
+        delegate = new RandomDataGenerator(rand);
+    }
+
+    /**
+     * @return the delegate object.
+     * @deprecated To be removed in 4.0.
+     */
+    @Deprecated
+    RandomDataGenerator getDelegate() {
+        return delegate;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p><strong>Algorithm Description:</strong> hex strings are generated using a 2-step process.
+     *
+     * <ol>
+     *   <li>{@code len / 2 + 1} binary bytes are generated using the underlying Random
+     *   <li>Each binary byte is translated into 2 hex digits
+     * </ol>
+     *
+     * @param len the desired string length.
+     * @return the random string.
+     * @throws NotStrictlyPositiveException if {@code len <= 0}.
+     */
+    public String nextHexString(int len) throws NotStrictlyPositiveException {
+        return delegate.nextHexString(len);
+    }
+
+    /** {@inheritDoc} */
+    public int nextInt(int lower, int upper) throws NumberIsTooLargeException {
+        return delegate.nextInt(lower, upper);
+    }
+
+    /** {@inheritDoc} */
+    public long nextLong(long lower, long upper) throws NumberIsTooLargeException {
+        return delegate.nextLong(lower, upper);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p><strong>Algorithm Description:</strong> hex strings are generated in 40-byte segments
+     * using a 3-step process.
+     *
+     * <ol>
+     *   <li>20 random bytes are generated using the underlying <code>SecureRandom</code>.
+     *   <li>SHA-1 hash is applied to yield a 20-byte binary digest.
+     *   <li>Each byte of the binary digest is converted to 2 hex digits.
+     * </ol>
+     */
+    public String nextSecureHexString(int len) throws NotStrictlyPositiveException {
+        return delegate.nextSecureHexString(len);
+    }
+
+    /** {@inheritDoc} */
+    public int nextSecureInt(int lower, int upper) throws NumberIsTooLargeException {
+        return delegate.nextSecureInt(lower, upper);
+    }
+
+    /** {@inheritDoc} */
+    public long nextSecureLong(long lower, long upper) throws NumberIsTooLargeException {
+        return delegate.nextSecureLong(lower, upper);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p><strong>Algorithm Description</strong>:
+     *
+     * <ul>
+     *   <li>For small means, uses simulation of a Poisson process using Uniform deviates, as
+     *       described <a href="http://irmi.epfl.ch/cmos/Pmmi/interactive/rng7.htm">here.</a> The
+     *       Poisson process (and hence value returned) is bounded by 1000 * mean.
+     *   <li>For large means, uses the rejection algorithm described in <br>
+     *       Devroye, Luc. (1981).<i>The Computer Generation of Poisson Random Variables</i>
+     *       <strong>Computing</strong> vol. 26 pp. 197-207.
+     * </ul>
+     */
+    public long nextPoisson(double mean) throws NotStrictlyPositiveException {
+        return delegate.nextPoisson(mean);
+    }
+
+    /** {@inheritDoc} */
+    public double nextGaussian(double mu, double sigma) throws NotStrictlyPositiveException {
+        return delegate.nextGaussian(mu, sigma);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p><strong>Algorithm Description</strong>: Uses the Algorithm SA (Ahrens) from p. 876 in:
+     * [1]: Ahrens, J. H. and Dieter, U. (1972). Computer methods for sampling from the exponential
+     * and normal distributions. Communications of the ACM, 15, 873-882.
+     */
+    public double nextExponential(double mean) throws NotStrictlyPositiveException {
+        return delegate.nextExponential(mean);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p><strong>Algorithm Description</strong>: scales the output of Random.nextDouble(), but
+     * rejects 0 values (i.e., will generate another random double if Random.nextDouble() returns
+     * 0). This is necessary to provide a symmetric output interval (both endpoints excluded).
+     */
+    public double nextUniform(double lower, double upper)
+            throws NumberIsTooLargeException, NotFiniteNumberException, NotANumberException {
+        return delegate.nextUniform(lower, upper);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p><strong>Algorithm Description</strong>: if the lower bound is excluded, scales the output
+     * of Random.nextDouble(), but rejects 0 values (i.e., will generate another random double if
+     * Random.nextDouble() returns 0). This is necessary to provide a symmetric output interval
+     * (both endpoints excluded).
+     *
+     * @since 3.0
+     */
+    public double nextUniform(double lower, double upper, boolean lowerInclusive)
+            throws NumberIsTooLargeException, NotFiniteNumberException, NotANumberException {
+        return delegate.nextUniform(lower, upper, lowerInclusive);
+    }
+
+    /**
+     * Generates a random value from the {@link
+     * org.apache.commons.math3.distribution.BetaDistribution Beta Distribution}. This
+     * implementation uses {@link #nextInversionDeviate(RealDistribution) inversion} to generate
+     * random values.
+     *
+     * @param alpha first distribution shape parameter
+     * @param beta second distribution shape parameter
+     * @return random value sampled from the beta(alpha, beta) distribution
+     * @since 2.2
+     */
+    public double nextBeta(double alpha, double beta) {
+        return delegate.nextBeta(alpha, beta);
+    }
+
+    /**
+     * Generates a random value from the {@link
+     * org.apache.commons.math3.distribution.BinomialDistribution Binomial Distribution}. This
+     * implementation uses {@link #nextInversionDeviate(RealDistribution) inversion} to generate
+     * random values.
+     *
+     * @param numberOfTrials number of trials of the Binomial distribution
+     * @param probabilityOfSuccess probability of success of the Binomial distribution
+     * @return random value sampled from the Binomial(numberOfTrials, probabilityOfSuccess)
+     *     distribution
+     * @since 2.2
+     */
+    public int nextBinomial(int numberOfTrials, double probabilityOfSuccess) {
+        return delegate.nextBinomial(numberOfTrials, probabilityOfSuccess);
+    }
+
+    /**
+     * Generates a random value from the {@link
+     * org.apache.commons.math3.distribution.CauchyDistribution Cauchy Distribution}. This
+     * implementation uses {@link #nextInversionDeviate(RealDistribution) inversion} to generate
+     * random values.
+     *
+     * @param median the median of the Cauchy distribution
+     * @param scale the scale parameter of the Cauchy distribution
+     * @return random value sampled from the Cauchy(median, scale) distribution
+     * @since 2.2
+     */
+    public double nextCauchy(double median, double scale) {
+        return delegate.nextCauchy(median, scale);
+    }
+
+    /**
+     * Generates a random value from the {@link
+     * org.apache.commons.math3.distribution.ChiSquaredDistribution ChiSquare Distribution}. This
+     * implementation uses {@link #nextInversionDeviate(RealDistribution) inversion} to generate
+     * random values.
+     *
+     * @param df the degrees of freedom of the ChiSquare distribution
+     * @return random value sampled from the ChiSquare(df) distribution
+     * @since 2.2
+     */
+    public double nextChiSquare(double df) {
+        return delegate.nextChiSquare(df);
+    }
+
+    /**
+     * Generates a random value from the {@link org.apache.commons.math3.distribution.FDistribution
+     * F Distribution}. This implementation uses {@link #nextInversionDeviate(RealDistribution)
+     * inversion} to generate random values.
+     *
+     * @param numeratorDf the numerator degrees of freedom of the F distribution
+     * @param denominatorDf the denominator degrees of freedom of the F distribution
+     * @return random value sampled from the F(numeratorDf, denominatorDf) distribution
+     * @throws NotStrictlyPositiveException if {@code numeratorDf <= 0} or {@code denominatorDf <=
+     *     0}.
+     * @since 2.2
+     */
+    public double nextF(double numeratorDf, double denominatorDf)
+            throws NotStrictlyPositiveException {
+        return delegate.nextF(numeratorDf, denominatorDf);
+    }
+
+    /**
+     * Generates a random value from the {@link
+     * org.apache.commons.math3.distribution.GammaDistribution Gamma Distribution}.
+     *
+     * <p>This implementation uses the following algorithms:
+     *
+     * <p>For 0 < shape < 1: <br>
+     * Ahrens, J. H. and Dieter, U., <i>Computer methods for sampling from gamma, beta, Poisson and
+     * binomial distributions.</i> Computing, 12, 223-246, 1974.
+     *
+     * <p>For shape >= 1: <br>
+     * Marsaglia and Tsang, <i>A Simple Method for Generating Gamma Variables.</i> ACM Transactions
+     * on Mathematical Software, Volume 26 Issue 3, September, 2000.
+     *
+     * @param shape the median of the Gamma distribution
+     * @param scale the scale parameter of the Gamma distribution
+     * @return random value sampled from the Gamma(shape, scale) distribution
+     * @throws NotStrictlyPositiveException if {@code shape <= 0} or {@code scale <= 0}.
+     * @since 2.2
+     */
+    public double nextGamma(double shape, double scale) throws NotStrictlyPositiveException {
+        return delegate.nextGamma(shape, scale);
+    }
+
+    /**
+     * Generates a random value from the {@link
+     * org.apache.commons.math3.distribution.HypergeometricDistribution Hypergeometric
+     * Distribution}. This implementation uses {@link #nextInversionDeviate(IntegerDistribution)
+     * inversion} to generate random values.
+     *
+     * @param populationSize the population size of the Hypergeometric distribution
+     * @param numberOfSuccesses number of successes in the population of the Hypergeometric
+     *     distribution
+     * @param sampleSize the sample size of the Hypergeometric distribution
+     * @return random value sampled from the Hypergeometric(numberOfSuccesses, sampleSize)
+     *     distribution
+     * @throws NumberIsTooLargeException if {@code numberOfSuccesses > populationSize}, or {@code
+     *     sampleSize > populationSize}.
+     * @throws NotStrictlyPositiveException if {@code populationSize <= 0}.
+     * @throws NotPositiveException if {@code numberOfSuccesses < 0}.
+     * @since 2.2
+     */
+    public int nextHypergeometric(int populationSize, int numberOfSuccesses, int sampleSize)
+            throws NotPositiveException, NotStrictlyPositiveException, NumberIsTooLargeException {
+        return delegate.nextHypergeometric(populationSize, numberOfSuccesses, sampleSize);
+    }
+
+    /**
+     * Generates a random value from the {@link
+     * org.apache.commons.math3.distribution.PascalDistribution Pascal Distribution}. This
+     * implementation uses {@link #nextInversionDeviate(IntegerDistribution) inversion} to generate
+     * random values.
+     *
+     * @param r the number of successes of the Pascal distribution
+     * @param p the probability of success of the Pascal distribution
+     * @return random value sampled from the Pascal(r, p) distribution
+     * @since 2.2
+     * @throws NotStrictlyPositiveException if the number of successes is not positive
+     * @throws OutOfRangeException if the probability of success is not in the range {@code [0, 1]}.
+     */
+    public int nextPascal(int r, double p)
+            throws NotStrictlyPositiveException, OutOfRangeException {
+        return delegate.nextPascal(r, p);
+    }
+
+    /**
+     * Generates a random value from the {@link org.apache.commons.math3.distribution.TDistribution
+     * T Distribution}. This implementation uses {@link #nextInversionDeviate(RealDistribution)
+     * inversion} to generate random values.
+     *
+     * @param df the degrees of freedom of the T distribution
+     * @return random value from the T(df) distribution
+     * @since 2.2
+     * @throws NotStrictlyPositiveException if {@code df <= 0}
+     */
+    public double nextT(double df) throws NotStrictlyPositiveException {
+        return delegate.nextT(df);
+    }
+
+    /**
+     * Generates a random value from the {@link
+     * org.apache.commons.math3.distribution.WeibullDistribution Weibull Distribution}. This
+     * implementation uses {@link #nextInversionDeviate(RealDistribution) inversion} to generate
+     * random values.
+     *
+     * @param shape the shape parameter of the Weibull distribution
+     * @param scale the scale parameter of the Weibull distribution
+     * @return random value sampled from the Weibull(shape, size) distribution
+     * @since 2.2
+     * @throws NotStrictlyPositiveException if {@code shape <= 0} or {@code scale <= 0}.
+     */
+    public double nextWeibull(double shape, double scale) throws NotStrictlyPositiveException {
+        return delegate.nextWeibull(shape, scale);
+    }
+
+    /**
+     * Generates a random value from the {@link
+     * org.apache.commons.math3.distribution.ZipfDistribution Zipf Distribution}. This
+     * implementation uses {@link #nextInversionDeviate(IntegerDistribution) inversion} to generate
+     * random values.
+     *
+     * @param numberOfElements the number of elements of the ZipfDistribution
+     * @param exponent the exponent of the ZipfDistribution
+     * @return random value sampled from the Zipf(numberOfElements, exponent) distribution
+     * @since 2.2
+     * @exception NotStrictlyPositiveException if {@code numberOfElements <= 0} or {@code exponent
+     *     <= 0}.
+     */
+    public int nextZipf(int numberOfElements, double exponent) throws NotStrictlyPositiveException {
+        return delegate.nextZipf(numberOfElements, exponent);
+    }
+
+    /**
+     * Reseeds the random number generator with the supplied seed.
+     *
+     * <p>Will create and initialize if null.
+     *
+     * @param seed the seed value to use
+     */
+    public void reSeed(long seed) {
+        delegate.reSeed(seed);
+    }
+
+    /**
+     * Reseeds the secure random number generator with the current time in milliseconds.
+     *
+     * <p>Will create and initialize if null.
+     */
+    public void reSeedSecure() {
+        delegate.reSeedSecure();
+    }
+
+    /**
+     * Reseeds the secure random number generator with the supplied seed.
+     *
+     * <p>Will create and initialize if null.
+     *
+     * @param seed the seed value to use
+     */
+    public void reSeedSecure(long seed) {
+        delegate.reSeedSecure(seed);
+    }
+
+    /**
+     * Reseeds the random number generator with {@code System.currentTimeMillis() +
+     * System.identityHashCode(this))}.
+     */
+    public void reSeed() {
+        delegate.reSeed();
+    }
+
+    /**
+     * Sets the PRNG algorithm for the underlying SecureRandom instance using the Security Provider
+     * API. The Security Provider API is defined in <a href =
+     * "http://java.sun.com/j2se/1.3/docs/guide/security/CryptoSpec.html#AppA"> Java Cryptography
+     * Architecture API Specification & Reference.</a>
+     *
+     * <p><strong>USAGE NOTE:</strong> This method carries <i>significant</i> overhead and may take
+     * several seconds to execute.
+     *
+     * @param algorithm the name of the PRNG algorithm
+     * @param provider the name of the provider
+     * @throws NoSuchAlgorithmException if the specified algorithm is not available
+     * @throws NoSuchProviderException if the specified provider is not installed
+     */
+    public void setSecureAlgorithm(String algorithm, String provider)
+            throws NoSuchAlgorithmException, NoSuchProviderException {
+        delegate.setSecureAlgorithm(algorithm, provider);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>Uses a 2-cycle permutation shuffle. The shuffling process is described <a
+     * href="http://www.maths.abdn.ac.uk/~igc/tch/mx4002/notes/node83.html">here</a>.
+     */
+    public int[] nextPermutation(int n, int k)
+            throws NotStrictlyPositiveException, NumberIsTooLargeException {
+        return delegate.nextPermutation(n, k);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p><strong>Algorithm Description</strong>: Uses a 2-cycle permutation shuffle to generate a
+     * random permutation of <code>c.size()</code> and then returns the elements whose indexes
+     * correspond to the elements of the generated permutation. This technique is described, and
+     * proven to generate random samples <a
+     * href="http://www.maths.abdn.ac.uk/~igc/tch/mx4002/notes/node83.html">here</a>
+     */
+    public Object[] nextSample(Collection<?> c, int k)
+            throws NotStrictlyPositiveException, NumberIsTooLargeException {
+        return delegate.nextSample(c, k);
+    }
+
+    /**
+     * Generate a random deviate from the given distribution using the <a
+     * href="http://en.wikipedia.org/wiki/Inverse_transform_sampling">inversion method.</a>
+     *
+     * @param distribution Continuous distribution to generate a random value from
+     * @return a random value sampled from the given distribution
+     * @throws MathIllegalArgumentException if the underlynig distribution throws one
+     * @since 2.2
+     * @deprecated use the distribution's sample() method
+     */
+    @Deprecated
+    public double nextInversionDeviate(RealDistribution distribution)
+            throws MathIllegalArgumentException {
+        return distribution.inverseCumulativeProbability(nextUniform(0, 1));
+    }
+
+    /**
+     * Generate a random deviate from the given distribution using the <a
+     * href="http://en.wikipedia.org/wiki/Inverse_transform_sampling">inversion method.</a>
+     *
+     * @param distribution Integer distribution to generate a random value from
+     * @return a random value sampled from the given distribution
+     * @throws MathIllegalArgumentException if the underlynig distribution throws one
+     * @since 2.2
+     * @deprecated use the distribution's sample() method
+     */
+    @Deprecated
+    public int nextInversionDeviate(IntegerDistribution distribution)
+            throws MathIllegalArgumentException {
+        return distribution.inverseCumulativeProbability(nextUniform(0, 1));
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/random/RandomGenerator.java b/src/main/java/org/apache/commons/math3/random/RandomGenerator.java
new file mode 100644
index 0000000..aeba5e3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/random/RandomGenerator.java
@@ -0,0 +1,130 @@
+/*
+ * 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.math3.random;
+
+/**
+ * Interface extracted from <code>java.util.Random</code>. This interface is implemented by {@link
+ * AbstractRandomGenerator}.
+ *
+ * @since 1.1
+ */
+public interface RandomGenerator {
+
+    /**
+     * Sets the seed of the underlying random number generator using an <code>int</code> seed.
+     *
+     * <p>Sequences of values generated starting with the same seeds should be identical.
+     *
+     * @param seed the seed value
+     */
+    void setSeed(int seed);
+
+    /**
+     * Sets the seed of the underlying random number generator using an <code>int</code> array seed.
+     *
+     * <p>Sequences of values generated starting with the same seeds should be identical.
+     *
+     * @param seed the seed value
+     */
+    void setSeed(int[] seed);
+
+    /**
+     * Sets the seed of the underlying random number generator using a <code>long</code> seed.
+     *
+     * <p>Sequences of values generated starting with the same seeds should be identical.
+     *
+     * @param seed the seed value
+     */
+    void setSeed(long seed);
+
+    /**
+     * Generates random bytes and places them into a user-supplied byte array. The number of random
+     * bytes produced is equal to the length of the byte array.
+     *
+     * @param bytes the non-null byte array in which to put the random bytes
+     */
+    void nextBytes(byte[] bytes);
+
+    /**
+     * Returns the next pseudorandom, uniformly distributed <code>int</code> value from this random
+     * number generator's sequence. All 2<font size="-1"><sup>32</sup></font> possible {@code int}
+     * values should be produced with (approximately) equal probability.
+     *
+     * @return the next pseudorandom, uniformly distributed <code>int</code> value from this random
+     *     number generator's sequence
+     */
+    int nextInt();
+
+    /**
+     * Returns a pseudorandom, uniformly distributed {@code int} value between 0 (inclusive) and the
+     * specified value (exclusive), drawn from this random number generator's sequence.
+     *
+     * @param n the bound on the random number to be returned. Must be positive.
+     * @return a pseudorandom, uniformly distributed {@code int} value between 0 (inclusive) and n
+     *     (exclusive).
+     * @throws IllegalArgumentException if n is not positive.
+     */
+    int nextInt(int n);
+
+    /**
+     * Returns the next pseudorandom, uniformly distributed <code>long</code> value from this random
+     * number generator's sequence. All 2<font size="-1"><sup>64</sup></font> possible {@code long}
+     * values should be produced with (approximately) equal probability.
+     *
+     * @return the next pseudorandom, uniformly distributed <code>long</code> value from this random
+     *     number generator's sequence
+     */
+    long nextLong();
+
+    /**
+     * Returns the next pseudorandom, uniformly distributed <code>boolean</code> value from this
+     * random number generator's sequence.
+     *
+     * @return the next pseudorandom, uniformly distributed <code>boolean</code> value from this
+     *     random number generator's sequence
+     */
+    boolean nextBoolean();
+
+    /**
+     * Returns the next pseudorandom, uniformly distributed <code>float</code> value between <code>
+     * 0.0</code> and <code>1.0</code> from this random number generator's sequence.
+     *
+     * @return the next pseudorandom, uniformly distributed <code>float</code> value between <code>
+     *     0.0</code> and <code>1.0</code> from this random number generator's sequence
+     */
+    float nextFloat();
+
+    /**
+     * Returns the next pseudorandom, uniformly distributed <code>double</code> value between <code>
+     * 0.0</code> and <code>1.0</code> from this random number generator's sequence.
+     *
+     * @return the next pseudorandom, uniformly distributed <code>double</code> value between <code>
+     *     0.0</code> and <code>1.0</code> from this random number generator's sequence
+     */
+    double nextDouble();
+
+    /**
+     * Returns the next pseudorandom, Gaussian ("normally") distributed <code>double</code> value
+     * with mean <code>0.0</code> and standard deviation <code>1.0</code> from this random number
+     * generator's sequence.
+     *
+     * @return the next pseudorandom, Gaussian ("normally") distributed <code>double</code> value
+     *     with mean <code>0.0</code> and standard deviation <code>1.0</code> from this random
+     *     number generator's sequence
+     */
+    double nextGaussian();
+}
diff --git a/src/main/java/org/apache/commons/math3/random/RandomGeneratorFactory.java b/src/main/java/org/apache/commons/math3/random/RandomGeneratorFactory.java
new file mode 100644
index 0000000..b610b44
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/random/RandomGeneratorFactory.java
@@ -0,0 +1,118 @@
+/*
+ * 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.math3.random;
+
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+
+import java.util.Random;
+
+/**
+ * Utilities for creating {@link RandomGenerator} instances.
+ *
+ * @since 3.3
+ */
+public class RandomGeneratorFactory {
+    /** Class contains only static methods. */
+    private RandomGeneratorFactory() {}
+
+    /**
+     * Creates a {@link RandomDataGenerator} instance that wraps a {@link Random} instance.
+     *
+     * @param rng JDK {@link Random} instance that will generate the the random data.
+     * @return the given RNG, wrapped in a {@link RandomGenerator}.
+     */
+    public static RandomGenerator createRandomGenerator(final Random rng) {
+        return new RandomGenerator() {
+            /** {@inheritDoc} */
+            public void setSeed(int seed) {
+                rng.setSeed((long) seed);
+            }
+
+            /** {@inheritDoc} */
+            public void setSeed(int[] seed) {
+                rng.setSeed(convertToLong(seed));
+            }
+
+            /** {@inheritDoc} */
+            public void setSeed(long seed) {
+                rng.setSeed(seed);
+            }
+
+            /** {@inheritDoc} */
+            public void nextBytes(byte[] bytes) {
+                rng.nextBytes(bytes);
+            }
+
+            /** {@inheritDoc} */
+            public int nextInt() {
+                return rng.nextInt();
+            }
+
+            /** {@inheritDoc} */
+            public int nextInt(int n) {
+                if (n <= 0) {
+                    throw new NotStrictlyPositiveException(n);
+                }
+                return rng.nextInt(n);
+            }
+
+            /** {@inheritDoc} */
+            public long nextLong() {
+                return rng.nextLong();
+            }
+
+            /** {@inheritDoc} */
+            public boolean nextBoolean() {
+                return rng.nextBoolean();
+            }
+
+            /** {@inheritDoc} */
+            public float nextFloat() {
+                return rng.nextFloat();
+            }
+
+            /** {@inheritDoc} */
+            public double nextDouble() {
+                return rng.nextDouble();
+            }
+
+            /** {@inheritDoc} */
+            public double nextGaussian() {
+                return rng.nextGaussian();
+            }
+        };
+    }
+
+    /**
+     * Converts seed from one representation to another.
+     *
+     * @param seed Original seed.
+     * @return the converted seed.
+     */
+    public static long convertToLong(int[] seed) {
+        // The following number is the largest prime that fits
+        // in 32 bits (i.e. 2^32 - 5).
+        final long prime = 4294967291l;
+
+        long combined = 0l;
+        for (int s : seed) {
+            combined = combined * prime + s;
+        }
+
+        return combined;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/random/RandomVectorGenerator.java b/src/main/java/org/apache/commons/math3/random/RandomVectorGenerator.java
new file mode 100644
index 0000000..55571ef
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/random/RandomVectorGenerator.java
@@ -0,0 +1,33 @@
+/*
+ * 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.math3.random;
+
+/**
+ * This interface represents a random generator for whole vectors.
+ *
+ * @since 1.2
+ */
+public interface RandomVectorGenerator {
+
+    /**
+     * Generate a random vector.
+     *
+     * @return a random vector as an array of double.
+     */
+    double[] nextVector();
+}
diff --git a/src/main/java/org/apache/commons/math3/random/SobolSequenceGenerator.java b/src/main/java/org/apache/commons/math3/random/SobolSequenceGenerator.java
new file mode 100644
index 0000000..7b91c7b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/random/SobolSequenceGenerator.java
@@ -0,0 +1,329 @@
+/*
+ * 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.math3.random;
+
+import org.apache.commons.math3.exception.MathInternalError;
+import org.apache.commons.math3.exception.MathParseException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.util.FastMath;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.NoSuchElementException;
+import java.util.StringTokenizer;
+
+/**
+ * Implementation of a Sobol sequence.
+ *
+ * <p>A Sobol sequence is a low-discrepancy sequence with the property that for all values of N, its
+ * subsequence (x1, ... xN) has a low discrepancy. It can be used to generate pseudo-random points
+ * in a space S, which are equi-distributed.
+ *
+ * <p>The implementation already comes with support for up to 1000 dimensions with direction numbers
+ * calculated from <a href="http://web.maths.unsw.edu.au/~fkuo/sobol/">Stephen Joe and Frances
+ * Kuo</a>.
+ *
+ * <p>The generator supports two modes:
+ *
+ * <ul>
+ *   <li>sequential generation of points: {@link #nextVector()}
+ *   <li>random access to the i-th point in the sequence: {@link #skipTo(int)}
+ * </ul>
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Sobol_sequence">Sobol sequence (Wikipedia)</a>
+ * @see <a href="http://web.maths.unsw.edu.au/~fkuo/sobol/">Sobol sequence direction numbers</a>
+ * @since 3.3
+ */
+public class SobolSequenceGenerator implements RandomVectorGenerator {
+
+    /** The number of bits to use. */
+    private static final int BITS = 52;
+
+    /** The scaling factor. */
+    private static final double SCALE = FastMath.pow(2, BITS);
+
+    /** The maximum supported space dimension. */
+    private static final int MAX_DIMENSION = 1000;
+
+    /** The resource containing the direction numbers. */
+    private static final String RESOURCE_NAME =
+            "/assets/org/apache/commons/math3/random/new-joe-kuo-6.1000";
+
+    /** Character set for file input. */
+    private static final String FILE_CHARSET = "US-ASCII";
+
+    /** Space dimension. */
+    private final int dimension;
+
+    /** The current index in the sequence. */
+    private int count = 0;
+
+    /** The direction vector for each component. */
+    private final long[][] direction;
+
+    /** The current state. */
+    private final long[] x;
+
+    /**
+     * Construct a new Sobol sequence generator for the given space dimension.
+     *
+     * @param dimension the space dimension
+     * @throws OutOfRangeException if the space dimension is outside the allowed range of [1, 1000]
+     */
+    public SobolSequenceGenerator(final int dimension) throws OutOfRangeException {
+        if (dimension < 1 || dimension > MAX_DIMENSION) {
+            throw new OutOfRangeException(dimension, 1, MAX_DIMENSION);
+        }
+
+        // initialize the other dimensions with direction numbers from a resource
+        final InputStream is = getClass().getResourceAsStream(RESOURCE_NAME);
+        if (is == null) {
+            throw new MathInternalError();
+        }
+
+        this.dimension = dimension;
+
+        // init data structures
+        direction = new long[dimension][BITS + 1];
+        x = new long[dimension];
+
+        try {
+            initFromStream(is);
+        } catch (IOException e) {
+            // the internal resource file could not be read -> should not happen
+            throw new MathInternalError();
+        } catch (MathParseException e) {
+            // the internal resource file could not be parsed -> should not happen
+            throw new MathInternalError();
+        } finally {
+            try {
+                is.close();
+            } catch (IOException e) { // NOPMD
+                // ignore
+            }
+        }
+    }
+
+    /**
+     * Construct a new Sobol sequence generator for the given space dimension with direction vectors
+     * loaded from the given stream.
+     *
+     * <p>The expected format is identical to the files available from <a
+     * href="http://web.maths.unsw.edu.au/~fkuo/sobol/">Stephen Joe and Frances Kuo</a>. The first
+     * line will be ignored as it is assumed to contain only the column headers. The columns are:
+     *
+     * <ul>
+     *   <li>d: the dimension
+     *   <li>s: the degree of the primitive polynomial
+     *   <li>a: the number representing the coefficients
+     *   <li>m: the list of initial direction numbers
+     * </ul>
+     *
+     * Example:
+     *
+     * <pre>
+     * d       s       a       m_i
+     * 2       1       0       1
+     * 3       2       1       1 3
+     * </pre>
+     *
+     * <p>The input stream <i>must</i> be an ASCII text containing one valid direction vector per
+     * line.
+     *
+     * @param dimension the space dimension
+     * @param is the stream to read the direction vectors from
+     * @throws NotStrictlyPositiveException if the space dimension is &lt; 1
+     * @throws OutOfRangeException if the space dimension is outside the range [1, max], where max
+     *     refers to the maximum dimension found in the input stream
+     * @throws MathParseException if the content in the stream could not be parsed successfully
+     * @throws IOException if an error occurs while reading from the input stream
+     */
+    public SobolSequenceGenerator(final int dimension, final InputStream is)
+            throws NotStrictlyPositiveException, MathParseException, IOException {
+
+        if (dimension < 1) {
+            throw new NotStrictlyPositiveException(dimension);
+        }
+
+        this.dimension = dimension;
+
+        // init data structures
+        direction = new long[dimension][BITS + 1];
+        x = new long[dimension];
+
+        // initialize the other dimensions with direction numbers from the stream
+        int lastDimension = initFromStream(is);
+        if (lastDimension < dimension) {
+            throw new OutOfRangeException(dimension, 1, lastDimension);
+        }
+    }
+
+    /**
+     * Load the direction vector for each dimension from the given stream.
+     *
+     * <p>The input stream <i>must</i> be an ASCII text containing one valid direction vector per
+     * line.
+     *
+     * @param is the input stream to read the direction vector from
+     * @return the last dimension that has been read from the input stream
+     * @throws IOException if the stream could not be read
+     * @throws MathParseException if the content could not be parsed successfully
+     */
+    private int initFromStream(final InputStream is) throws MathParseException, IOException {
+
+        // special case: dimension 1 -> use unit initialization
+        for (int i = 1; i <= BITS; i++) {
+            direction[0][i] = 1l << (BITS - i);
+        }
+
+        final Charset charset = Charset.forName(FILE_CHARSET);
+        final BufferedReader reader = new BufferedReader(new InputStreamReader(is, charset));
+        int dim = -1;
+
+        try {
+            // ignore first line
+            reader.readLine();
+
+            int lineNumber = 2;
+            int index = 1;
+            String line = null;
+            while ((line = reader.readLine()) != null) {
+                StringTokenizer st = new StringTokenizer(line, " ");
+                try {
+                    dim = Integer.parseInt(st.nextToken());
+                    if (dim >= 2 && dim <= dimension) { // we have found the right dimension
+                        final int s = Integer.parseInt(st.nextToken());
+                        final int a = Integer.parseInt(st.nextToken());
+                        final int[] m = new int[s + 1];
+                        for (int i = 1; i <= s; i++) {
+                            m[i] = Integer.parseInt(st.nextToken());
+                        }
+                        initDirectionVector(index++, a, m);
+                    }
+
+                    if (dim > dimension) {
+                        return dim;
+                    }
+                } catch (NoSuchElementException e) {
+                    throw new MathParseException(line, lineNumber);
+                } catch (NumberFormatException e) {
+                    throw new MathParseException(line, lineNumber);
+                }
+                lineNumber++;
+            }
+        } finally {
+            reader.close();
+        }
+
+        return dim;
+    }
+
+    /**
+     * Calculate the direction numbers from the given polynomial.
+     *
+     * @param d the dimension, zero-based
+     * @param a the coefficients of the primitive polynomial
+     * @param m the initial direction numbers
+     */
+    private void initDirectionVector(final int d, final int a, final int[] m) {
+        final int s = m.length - 1;
+        for (int i = 1; i <= s; i++) {
+            direction[d][i] = ((long) m[i]) << (BITS - i);
+        }
+        for (int i = s + 1; i <= BITS; i++) {
+            direction[d][i] = direction[d][i - s] ^ (direction[d][i - s] >> s);
+            for (int k = 1; k <= s - 1; k++) {
+                direction[d][i] ^= ((a >> (s - 1 - k)) & 1) * direction[d][i - k];
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    public double[] nextVector() {
+        final double[] v = new double[dimension];
+        if (count == 0) {
+            count++;
+            return v;
+        }
+
+        // find the index c of the rightmost 0
+        int c = 1;
+        int value = count - 1;
+        while ((value & 1) == 1) {
+            value >>= 1;
+            c++;
+        }
+
+        for (int i = 0; i < dimension; i++) {
+            x[i] ^= direction[i][c];
+            v[i] = (double) x[i] / SCALE;
+        }
+        count++;
+        return v;
+    }
+
+    /**
+     * Skip to the i-th point in the Sobol sequence.
+     *
+     * <p>This operation can be performed in O(1).
+     *
+     * @param index the index in the sequence to skip to
+     * @return the i-th point in the Sobol sequence
+     * @throws NotPositiveException if index &lt; 0
+     */
+    public double[] skipTo(final int index) throws NotPositiveException {
+        if (index == 0) {
+            // reset x vector
+            Arrays.fill(x, 0);
+        } else {
+            final int i = index - 1;
+            final long grayCode = i ^ (i >> 1); // compute the gray code of i = i XOR floor(i / 2)
+            for (int j = 0; j < dimension; j++) {
+                long result = 0;
+                for (int k = 1; k <= BITS; k++) {
+                    final long shift = grayCode >> (k - 1);
+                    if (shift == 0) {
+                        // stop, as all remaining bits will be zero
+                        break;
+                    }
+                    // the k-th bit of i
+                    final long ik = shift & 1;
+                    result ^= ik * direction[j][k];
+                }
+                x[j] = result;
+            }
+        }
+        count = index;
+        return nextVector();
+    }
+
+    /**
+     * Returns the index i of the next point in the Sobol sequence that will be returned by calling
+     * {@link #nextVector()}.
+     *
+     * @return the index of the next point
+     */
+    public int getNextIndex() {
+        return count;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/random/StableRandomGenerator.java b/src/main/java/org/apache/commons/math3/random/StableRandomGenerator.java
new file mode 100644
index 0000000..b43a770
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/random/StableRandomGenerator.java
@@ -0,0 +1,143 @@
+/*
+ * 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.math3.random;
+
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * This class provides a stable normalized random generator. It samples from a stable distribution
+ * with location parameter 0 and scale 1.
+ *
+ * <p>The implementation uses the Chambers-Mallows-Stuck method as described in <i>Handbook of
+ * computational statistics: concepts and methods</i> by James E. Gentle, Wolfgang H&auml;rdle,
+ * Yuichi Mori.
+ *
+ * @since 3.0
+ */
+public class StableRandomGenerator implements NormalizedRandomGenerator {
+    /** Underlying generator. */
+    private final RandomGenerator generator;
+
+    /** stability parameter */
+    private final double alpha;
+
+    /** skewness parameter */
+    private final double beta;
+
+    /** cache of expression value used in generation */
+    private final double zeta;
+
+    /**
+     * Create a new generator.
+     *
+     * @param generator underlying random generator to use
+     * @param alpha Stability parameter. Must be in range (0, 2]
+     * @param beta Skewness parameter. Must be in range [-1, 1]
+     * @throws NullArgumentException if generator is null
+     * @throws OutOfRangeException if {@code alpha <= 0} or {@code alpha > 2} or {@code beta < -1}
+     *     or {@code beta > 1}
+     */
+    public StableRandomGenerator(
+            final RandomGenerator generator, final double alpha, final double beta)
+            throws NullArgumentException, OutOfRangeException {
+        if (generator == null) {
+            throw new NullArgumentException();
+        }
+
+        if (!(alpha > 0d && alpha <= 2d)) {
+            throw new OutOfRangeException(LocalizedFormats.OUT_OF_RANGE_LEFT, alpha, 0, 2);
+        }
+
+        if (!(beta >= -1d && beta <= 1d)) {
+            throw new OutOfRangeException(LocalizedFormats.OUT_OF_RANGE_SIMPLE, beta, -1, 1);
+        }
+
+        this.generator = generator;
+        this.alpha = alpha;
+        this.beta = beta;
+        if (alpha < 2d && beta != 0d) {
+            zeta = beta * FastMath.tan(FastMath.PI * alpha / 2);
+        } else {
+            zeta = 0d;
+        }
+    }
+
+    /**
+     * Generate a random scalar with zero location and unit scale.
+     *
+     * @return a random scalar with zero location and unit scale
+     */
+    public double nextNormalizedDouble() {
+        // we need 2 uniform random numbers to calculate omega and phi
+        double omega = -FastMath.log(generator.nextDouble());
+        double phi = FastMath.PI * (generator.nextDouble() - 0.5);
+
+        // Normal distribution case (Box-Muller algorithm)
+        if (alpha == 2d) {
+            return FastMath.sqrt(2d * omega) * FastMath.sin(phi);
+        }
+
+        double x;
+        // when beta = 0, zeta is zero as well
+        // Thus we can exclude it from the formula
+        if (beta == 0d) {
+            // Cauchy distribution case
+            if (alpha == 1d) {
+                x = FastMath.tan(phi);
+            } else {
+                x =
+                        FastMath.pow(omega * FastMath.cos((1 - alpha) * phi), 1d / alpha - 1d)
+                                * FastMath.sin(alpha * phi)
+                                / FastMath.pow(FastMath.cos(phi), 1d / alpha);
+            }
+        } else {
+            // Generic stable distribution
+            double cosPhi = FastMath.cos(phi);
+            // to avoid rounding errors around alpha = 1
+            if (FastMath.abs(alpha - 1d) > 1e-8) {
+                double alphaPhi = alpha * phi;
+                double invAlphaPhi = phi - alphaPhi;
+                x =
+                        (FastMath.sin(alphaPhi) + zeta * FastMath.cos(alphaPhi))
+                                / cosPhi
+                                * (FastMath.cos(invAlphaPhi) + zeta * FastMath.sin(invAlphaPhi))
+                                / FastMath.pow(omega * cosPhi, (1 - alpha) / alpha);
+            } else {
+                double betaPhi = FastMath.PI / 2 + beta * phi;
+                x =
+                        2d
+                                / FastMath.PI
+                                * (betaPhi * FastMath.tan(phi)
+                                        - beta
+                                                * FastMath.log(
+                                                        FastMath.PI
+                                                                / 2d
+                                                                * omega
+                                                                * cosPhi
+                                                                / betaPhi));
+
+                if (alpha != 1d) {
+                    x += beta * FastMath.tan(FastMath.PI * alpha / 2);
+                }
+            }
+        }
+        return x;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/random/SynchronizedRandomGenerator.java b/src/main/java/org/apache/commons/math3/random/SynchronizedRandomGenerator.java
new file mode 100644
index 0000000..410ec3c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/random/SynchronizedRandomGenerator.java
@@ -0,0 +1,95 @@
+/*
+ * 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.math3.random;
+
+/**
+ * Any {@link RandomGenerator} implementation can be thread-safe if it is used through an instance
+ * of this class. This is achieved by enclosing calls to the methods of the actual generator inside
+ * the overridden {@code synchronized} methods of this class.
+ *
+ * @since 3.1
+ */
+public class SynchronizedRandomGenerator implements RandomGenerator {
+    /** Object to which all calls will be delegated. */
+    private final RandomGenerator wrapped;
+
+    /**
+     * Creates a synchronized wrapper for the given {@code RandomGenerator} instance.
+     *
+     * @param rng Generator whose methods will be called through their corresponding overridden
+     *     synchronized version. To ensure thread-safety, the wrapped generator <em>must</em> not be
+     *     used directly.
+     */
+    public SynchronizedRandomGenerator(RandomGenerator rng) {
+        wrapped = rng;
+    }
+
+    /** {@inheritDoc} */
+    public synchronized void setSeed(int seed) {
+        wrapped.setSeed(seed);
+    }
+
+    /** {@inheritDoc} */
+    public synchronized void setSeed(int[] seed) {
+        wrapped.setSeed(seed);
+    }
+
+    /** {@inheritDoc} */
+    public synchronized void setSeed(long seed) {
+        wrapped.setSeed(seed);
+    }
+
+    /** {@inheritDoc} */
+    public synchronized void nextBytes(byte[] bytes) {
+        wrapped.nextBytes(bytes);
+    }
+
+    /** {@inheritDoc} */
+    public synchronized int nextInt() {
+        return wrapped.nextInt();
+    }
+
+    /** {@inheritDoc} */
+    public synchronized int nextInt(int n) {
+        return wrapped.nextInt(n);
+    }
+
+    /** {@inheritDoc} */
+    public synchronized long nextLong() {
+        return wrapped.nextLong();
+    }
+
+    /** {@inheritDoc} */
+    public synchronized boolean nextBoolean() {
+        return wrapped.nextBoolean();
+    }
+
+    /** {@inheritDoc} */
+    public synchronized float nextFloat() {
+        return wrapped.nextFloat();
+    }
+
+    /** {@inheritDoc} */
+    public synchronized double nextDouble() {
+        return wrapped.nextDouble();
+    }
+
+    /** {@inheritDoc} */
+    public synchronized double nextGaussian() {
+        return wrapped.nextGaussian();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/random/UncorrelatedRandomVectorGenerator.java b/src/main/java/org/apache/commons/math3/random/UncorrelatedRandomVectorGenerator.java
new file mode 100644
index 0000000..ec38c4d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/random/UncorrelatedRandomVectorGenerator.java
@@ -0,0 +1,91 @@
+/*
+ * 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.math3.random;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+
+import java.util.Arrays;
+
+/**
+ * A {@link RandomVectorGenerator} that generates vectors with uncorrelated components. Components
+ * of generated vectors follow (independent) Gaussian distributions, with parameters supplied in the
+ * constructor.
+ *
+ * @since 1.2
+ */
+public class UncorrelatedRandomVectorGenerator implements RandomVectorGenerator {
+
+    /** Underlying scalar generator. */
+    private final NormalizedRandomGenerator generator;
+
+    /** Mean vector. */
+    private final double[] mean;
+
+    /** Standard deviation vector. */
+    private final double[] standardDeviation;
+
+    /**
+     * Simple constructor.
+     *
+     * <p>Build an uncorrelated random vector generator from its mean and standard deviation
+     * vectors.
+     *
+     * @param mean expected mean values for each component
+     * @param standardDeviation standard deviation for each component
+     * @param generator underlying generator for uncorrelated normalized components
+     */
+    public UncorrelatedRandomVectorGenerator(
+            double[] mean, double[] standardDeviation, NormalizedRandomGenerator generator) {
+        if (mean.length != standardDeviation.length) {
+            throw new DimensionMismatchException(mean.length, standardDeviation.length);
+        }
+        this.mean = mean.clone();
+        this.standardDeviation = standardDeviation.clone();
+        this.generator = generator;
+    }
+
+    /**
+     * Simple constructor.
+     *
+     * <p>Build a null mean random and unit standard deviation uncorrelated vector generator
+     *
+     * @param dimension dimension of the vectors to generate
+     * @param generator underlying generator for uncorrelated normalized components
+     */
+    public UncorrelatedRandomVectorGenerator(int dimension, NormalizedRandomGenerator generator) {
+        mean = new double[dimension];
+        standardDeviation = new double[dimension];
+        Arrays.fill(standardDeviation, 1.0);
+        this.generator = generator;
+    }
+
+    /**
+     * Generate an uncorrelated random vector.
+     *
+     * @return a random vector as a newly built array of double
+     */
+    public double[] nextVector() {
+
+        double[] random = new double[mean.length];
+        for (int i = 0; i < random.length; ++i) {
+            random[i] = mean[i] + standardDeviation[i] * generator.nextNormalizedDouble();
+        }
+
+        return random;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/random/UniformRandomGenerator.java b/src/main/java/org/apache/commons/math3/random/UniformRandomGenerator.java
new file mode 100644
index 0000000..4db7f8e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/random/UniformRandomGenerator.java
@@ -0,0 +1,58 @@
+/*
+ * 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.math3.random;
+
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * This class implements a normalized uniform random generator.
+ *
+ * <p>Since it is a normalized random generator, it generates values from a uniform distribution
+ * with mean equal to 0 and standard deviation equal to 1. Generated values fall in the range
+ * [-&#x0221A;3, +&#x0221A;3].
+ *
+ * @since 1.2
+ */
+public class UniformRandomGenerator implements NormalizedRandomGenerator {
+
+    /** Square root of three. */
+    private static final double SQRT3 = FastMath.sqrt(3.0);
+
+    /** Underlying generator. */
+    private final RandomGenerator generator;
+
+    /**
+     * Create a new generator.
+     *
+     * @param generator underlying random generator to use
+     */
+    public UniformRandomGenerator(RandomGenerator generator) {
+        this.generator = generator;
+    }
+
+    /**
+     * Generate a random scalar with null mean and unit standard deviation.
+     *
+     * <p>The number generated is uniformly distributed between -&sqrt;(3) and +&sqrt;(3).
+     *
+     * @return a random scalar with null mean and unit standard deviation
+     */
+    public double nextNormalizedDouble() {
+        return SQRT3 * (2 * generator.nextDouble() - 1.0);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/random/UnitSphereRandomVectorGenerator.java b/src/main/java/org/apache/commons/math3/random/UnitSphereRandomVectorGenerator.java
new file mode 100644
index 0000000..ddcaa97
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/random/UnitSphereRandomVectorGenerator.java
@@ -0,0 +1,74 @@
+/*
+ * 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.math3.random;
+
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Generate random vectors isotropically located on the surface of a sphere.
+ *
+ * @since 2.1
+ */
+public class UnitSphereRandomVectorGenerator implements RandomVectorGenerator {
+    /** RNG used for generating the individual components of the vectors. */
+    private final RandomGenerator rand;
+
+    /** Space dimension. */
+    private final int dimension;
+
+    /**
+     * @param dimension Space dimension.
+     * @param rand RNG for the individual components of the vectors.
+     */
+    public UnitSphereRandomVectorGenerator(final int dimension, final RandomGenerator rand) {
+        this.dimension = dimension;
+        this.rand = rand;
+    }
+
+    /**
+     * Create an object that will use a default RNG ({@link MersenneTwister}), in order to generate
+     * the individual components.
+     *
+     * @param dimension Space dimension.
+     */
+    public UnitSphereRandomVectorGenerator(final int dimension) {
+        this(dimension, new MersenneTwister());
+    }
+
+    /** {@inheritDoc} */
+    public double[] nextVector() {
+        final double[] v = new double[dimension];
+
+        // See http://mathworld.wolfram.com/SpherePointPicking.html for example.
+        // Pick a point by choosing a standard Gaussian for each element, and then
+        // normalizing to unit length.
+        double normSq = 0;
+        for (int i = 0; i < dimension; i++) {
+            final double comp = rand.nextGaussian();
+            v[i] = comp;
+            normSq += comp * comp;
+        }
+
+        final double f = 1 / FastMath.sqrt(normSq);
+        for (int i = 0; i < dimension; i++) {
+            v[i] *= f;
+        }
+
+        return v;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/random/ValueServer.java b/src/main/java/org/apache/commons/math3/random/ValueServer.java
new file mode 100644
index 0000000..9c15292
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/random/ValueServer.java
@@ -0,0 +1,465 @@
+/*
+ * 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.math3.random;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.ZeroException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * Generates values for use in simulation applications.
+ *
+ * <p>How values are generated is determined by the <code>mode</code> property.
+ *
+ * <p>Supported <code>mode</code> values are:
+ *
+ * <ul>
+ *   <li>DIGEST_MODE -- uses an empirical distribution
+ *   <li>REPLAY_MODE -- replays data from <code>valuesFileURL</code>
+ *   <li>UNIFORM_MODE -- generates uniformly distributed random values with mean = <code>mu</code>
+ *   <li>EXPONENTIAL_MODE -- generates exponentially distributed random values with mean = <code>mu
+ *       </code>
+ *   <li>GAUSSIAN_MODE -- generates Gaussian distributed random values with mean = <code>mu</code>
+ *       and standard deviation = <code>sigma</code>
+ *   <li>CONSTANT_MODE -- returns <code>mu</code> every time.
+ * </ul>
+ */
+public class ValueServer {
+
+    /** Use empirical distribution. */
+    public static final int DIGEST_MODE = 0;
+
+    /** Replay data from valuesFilePath. */
+    public static final int REPLAY_MODE = 1;
+
+    /** Uniform random deviates with mean = &mu;. */
+    public static final int UNIFORM_MODE = 2;
+
+    /** Exponential random deviates with mean = &mu;. */
+    public static final int EXPONENTIAL_MODE = 3;
+
+    /** Gaussian random deviates with mean = &mu;, std dev = &sigma;. */
+    public static final int GAUSSIAN_MODE = 4;
+
+    /** Always return mu */
+    public static final int CONSTANT_MODE = 5;
+
+    /** mode determines how values are generated. */
+    private int mode = 5;
+
+    /** URI to raw data values. */
+    private URL valuesFileURL = null;
+
+    /** Mean for use with non-data-driven modes. */
+    private double mu = 0.0;
+
+    /** Standard deviation for use with GAUSSIAN_MODE. */
+    private double sigma = 0.0;
+
+    /** Empirical probability distribution for use with DIGEST_MODE. */
+    private EmpiricalDistribution empiricalDistribution = null;
+
+    /** File pointer for REPLAY_MODE. */
+    private BufferedReader filePointer = null;
+
+    /** RandomDataImpl to use for random data generation. */
+    private final RandomDataGenerator randomData;
+
+    // Data generation modes ======================================
+
+    /** Creates new ValueServer */
+    public ValueServer() {
+        randomData = new RandomDataGenerator();
+    }
+
+    /**
+     * Construct a ValueServer instance using a RandomDataImpl as its source of random data.
+     *
+     * @param randomData the RandomDataImpl instance used to source random data
+     * @since 3.0
+     * @deprecated use {@link #ValueServer(RandomGenerator)}
+     */
+    @Deprecated
+    public ValueServer(RandomDataImpl randomData) {
+        this.randomData = randomData.getDelegate();
+    }
+
+    /**
+     * Construct a ValueServer instance using a RandomGenerator as its source of random data.
+     *
+     * @since 3.1
+     * @param generator source of random data
+     */
+    public ValueServer(RandomGenerator generator) {
+        this.randomData = new RandomDataGenerator(generator);
+    }
+
+    /**
+     * Returns the next generated value, generated according to the mode value (see MODE constants).
+     *
+     * @return generated value
+     * @throws IOException in REPLAY_MODE if a file I/O error occurs
+     * @throws MathIllegalStateException if mode is not recognized
+     * @throws MathIllegalArgumentException if the underlying random generator thwrows one
+     */
+    public double getNext()
+            throws IOException, MathIllegalStateException, MathIllegalArgumentException {
+        switch (mode) {
+            case DIGEST_MODE:
+                return getNextDigest();
+            case REPLAY_MODE:
+                return getNextReplay();
+            case UNIFORM_MODE:
+                return getNextUniform();
+            case EXPONENTIAL_MODE:
+                return getNextExponential();
+            case GAUSSIAN_MODE:
+                return getNextGaussian();
+            case CONSTANT_MODE:
+                return mu;
+            default:
+                throw new MathIllegalStateException(
+                        LocalizedFormats.UNKNOWN_MODE,
+                        mode,
+                        "DIGEST_MODE",
+                        DIGEST_MODE,
+                        "REPLAY_MODE",
+                        REPLAY_MODE,
+                        "UNIFORM_MODE",
+                        UNIFORM_MODE,
+                        "EXPONENTIAL_MODE",
+                        EXPONENTIAL_MODE,
+                        "GAUSSIAN_MODE",
+                        GAUSSIAN_MODE,
+                        "CONSTANT_MODE",
+                        CONSTANT_MODE);
+        }
+    }
+
+    /**
+     * Fills the input array with values generated using getNext() repeatedly.
+     *
+     * @param values array to be filled
+     * @throws IOException in REPLAY_MODE if a file I/O error occurs
+     * @throws MathIllegalStateException if mode is not recognized
+     * @throws MathIllegalArgumentException if the underlying random generator thwrows one
+     */
+    public void fill(double[] values)
+            throws IOException, MathIllegalStateException, MathIllegalArgumentException {
+        for (int i = 0; i < values.length; i++) {
+            values[i] = getNext();
+        }
+    }
+
+    /**
+     * Returns an array of length <code>length</code> with values generated using getNext()
+     * repeatedly.
+     *
+     * @param length length of output array
+     * @return array of generated values
+     * @throws IOException in REPLAY_MODE if a file I/O error occurs
+     * @throws MathIllegalStateException if mode is not recognized
+     * @throws MathIllegalArgumentException if the underlying random generator thwrows one
+     */
+    public double[] fill(int length)
+            throws IOException, MathIllegalStateException, MathIllegalArgumentException {
+        double[] out = new double[length];
+        for (int i = 0; i < length; i++) {
+            out[i] = getNext();
+        }
+        return out;
+    }
+
+    /**
+     * Computes the empirical distribution using values from the file in <code>valuesFileURL</code>,
+     * using the default number of bins.
+     *
+     * <p><code>valuesFileURL</code> must exist and be readable by *this at runtime.
+     *
+     * <p>This method must be called before using <code>getNext()</code> with <code>
+     * mode = DIGEST_MODE</code>
+     *
+     * @throws IOException if an I/O error occurs reading the input file
+     * @throws NullArgumentException if the {@code valuesFileURL} has not been set
+     * @throws ZeroException if URL contains no data
+     */
+    public void computeDistribution() throws IOException, ZeroException, NullArgumentException {
+        computeDistribution(EmpiricalDistribution.DEFAULT_BIN_COUNT);
+    }
+
+    /**
+     * Computes the empirical distribution using values from the file in <code>valuesFileURL</code>
+     * and <code>binCount</code> bins.
+     *
+     * <p><code>valuesFileURL</code> must exist and be readable by this process at runtime.
+     *
+     * <p>This method must be called before using <code>getNext()</code> with <code>
+     * mode = DIGEST_MODE</code>
+     *
+     * @param binCount the number of bins used in computing the empirical distribution
+     * @throws NullArgumentException if the {@code valuesFileURL} has not been set
+     * @throws IOException if an error occurs reading the input file
+     * @throws ZeroException if URL contains no data
+     */
+    public void computeDistribution(int binCount)
+            throws NullArgumentException, IOException, ZeroException {
+        empiricalDistribution =
+                new EmpiricalDistribution(binCount, randomData.getRandomGenerator());
+        empiricalDistribution.load(valuesFileURL);
+        mu = empiricalDistribution.getSampleStats().getMean();
+        sigma = empiricalDistribution.getSampleStats().getStandardDeviation();
+    }
+
+    /**
+     * Returns the data generation mode. See {@link ValueServer the class javadoc} for description
+     * of the valid values of this property.
+     *
+     * @return Value of property mode.
+     */
+    public int getMode() {
+        return mode;
+    }
+
+    /**
+     * Sets the data generation mode.
+     *
+     * @param mode New value of the data generation mode.
+     */
+    public void setMode(int mode) {
+        this.mode = mode;
+    }
+
+    /**
+     * Returns the URL for the file used to build the empirical distribution when using {@link
+     * #DIGEST_MODE}.
+     *
+     * @return Values file URL.
+     */
+    public URL getValuesFileURL() {
+        return valuesFileURL;
+    }
+
+    /**
+     * Sets the {@link #getValuesFileURL() values file URL} using a string URL representation.
+     *
+     * @param url String representation for new valuesFileURL.
+     * @throws MalformedURLException if url is not well formed
+     */
+    public void setValuesFileURL(String url) throws MalformedURLException {
+        this.valuesFileURL = new URL(url);
+    }
+
+    /**
+     * Sets the the {@link #getValuesFileURL() values file URL}.
+     *
+     * <p>The values file <i>must</i> be an ASCII text file containing one valid numeric entry per
+     * line.
+     *
+     * @param url URL of the values file.
+     */
+    public void setValuesFileURL(URL url) {
+        this.valuesFileURL = url;
+    }
+
+    /**
+     * Returns the {@link EmpiricalDistribution} used when operating in {@value #DIGEST_MODE}.
+     *
+     * @return EmpircalDistribution built by {@link #computeDistribution()}
+     */
+    public EmpiricalDistribution getEmpiricalDistribution() {
+        return empiricalDistribution;
+    }
+
+    /**
+     * Resets REPLAY_MODE file pointer to the beginning of the <code>valuesFileURL</code>.
+     *
+     * @throws IOException if an error occurs opening the file
+     * @throws NullPointerException if the {@code valuesFileURL} has not been set.
+     */
+    public void resetReplayFile() throws IOException {
+        if (filePointer != null) {
+            try {
+                filePointer.close();
+                filePointer = null;
+            } catch (IOException ex) { // NOPMD
+                // ignore
+            }
+        }
+        filePointer =
+                new BufferedReader(new InputStreamReader(valuesFileURL.openStream(), "UTF-8"));
+    }
+
+    /**
+     * Closes {@code valuesFileURL} after use in REPLAY_MODE.
+     *
+     * @throws IOException if an error occurs closing the file
+     */
+    public void closeReplayFile() throws IOException {
+        if (filePointer != null) {
+            filePointer.close();
+            filePointer = null;
+        }
+    }
+
+    /**
+     * Returns the mean used when operating in {@link #GAUSSIAN_MODE}, {@link #EXPONENTIAL_MODE} or
+     * {@link #UNIFORM_MODE}. When operating in {@link #CONSTANT_MODE}, this is the constant value
+     * always returned. Calling {@link #computeDistribution()} sets this value to the overall mean
+     * of the values in the {@link #getValuesFileURL() values file}.
+     *
+     * @return Mean used in data generation.
+     */
+    public double getMu() {
+        return mu;
+    }
+
+    /**
+     * Sets the {@link #getMu() mean} used in data generation. Note that calling this method after
+     * {@link #computeDistribution()} has been called will have no effect on data generated in
+     * {@link #DIGEST_MODE}.
+     *
+     * @param mu new Mean value.
+     */
+    public void setMu(double mu) {
+        this.mu = mu;
+    }
+
+    /**
+     * Returns the standard deviation used when operating in {@link #GAUSSIAN_MODE}. Calling {@link
+     * #computeDistribution()} sets this value to the overall standard deviation of the values in
+     * the {@link #getValuesFileURL() values file}. This property has no effect when the data
+     * generation mode is not {@link #GAUSSIAN_MODE}.
+     *
+     * @return Standard deviation used when operating in {@link #GAUSSIAN_MODE}.
+     */
+    public double getSigma() {
+        return sigma;
+    }
+
+    /**
+     * Sets the {@link #getSigma() standard deviation} used in {@link #GAUSSIAN_MODE}.
+     *
+     * @param sigma New standard deviation.
+     */
+    public void setSigma(double sigma) {
+        this.sigma = sigma;
+    }
+
+    /**
+     * Reseeds the random data generator.
+     *
+     * @param seed Value with which to reseed the {@link RandomDataImpl} used to generate random
+     *     data.
+     */
+    public void reSeed(long seed) {
+        randomData.reSeed(seed);
+    }
+
+    // ------------- private methods ---------------------------------
+
+    /**
+     * Gets a random value in DIGEST_MODE.
+     *
+     * <p><strong>Preconditions</strong>:
+     *
+     * <ul>
+     *   <li>Before this method is called, <code>computeDistribution()</code> must have completed
+     *       successfully; otherwise an <code>IllegalStateException</code> will be thrown
+     * </ul>
+     *
+     * @return next random value from the empirical distribution digest
+     * @throws MathIllegalStateException if digest has not been initialized
+     */
+    private double getNextDigest() throws MathIllegalStateException {
+        if ((empiricalDistribution == null) || (empiricalDistribution.getBinStats().size() == 0)) {
+            throw new MathIllegalStateException(LocalizedFormats.DIGEST_NOT_INITIALIZED);
+        }
+        return empiricalDistribution.getNextValue();
+    }
+
+    /**
+     * Gets next sequential value from the <code>valuesFileURL</code>.
+     *
+     * <p>Throws an IOException if the read fails.
+     *
+     * <p>This method will open the <code>valuesFileURL</code> if there is no replay file open.
+     *
+     * <p>The <code>valuesFileURL</code> will be closed and reopened to wrap around from EOF to BOF
+     * if EOF is encountered. EOFException (which is a kind of IOException) may still be thrown if
+     * the <code>valuesFileURL</code> is empty.
+     *
+     * @return next value from the replay file
+     * @throws IOException if there is a problem reading from the file
+     * @throws MathIllegalStateException if URL contains no data
+     * @throws NumberFormatException if an invalid numeric string is encountered in the file
+     */
+    private double getNextReplay() throws IOException, MathIllegalStateException {
+        String str = null;
+        if (filePointer == null) {
+            resetReplayFile();
+        }
+        if ((str = filePointer.readLine()) == null) {
+            // we have probably reached end of file, wrap around from EOF to BOF
+            closeReplayFile();
+            resetReplayFile();
+            if ((str = filePointer.readLine()) == null) {
+                throw new MathIllegalStateException(
+                        LocalizedFormats.URL_CONTAINS_NO_DATA, valuesFileURL);
+            }
+        }
+        return Double.parseDouble(str);
+    }
+
+    /**
+     * Gets a uniformly distributed random value with mean = mu.
+     *
+     * @return random uniform value
+     * @throws MathIllegalArgumentException if the underlying random generator thwrows one
+     */
+    private double getNextUniform() throws MathIllegalArgumentException {
+        return randomData.nextUniform(0, 2 * mu);
+    }
+
+    /**
+     * Gets an exponentially distributed random value with mean = mu.
+     *
+     * @return random exponential value
+     * @throws MathIllegalArgumentException if the underlying random generator thwrows one
+     */
+    private double getNextExponential() throws MathIllegalArgumentException {
+        return randomData.nextExponential(mu);
+    }
+
+    /**
+     * Gets a Gaussian distributed random value with mean = mu and standard deviation = sigma.
+     *
+     * @return random Gaussian value
+     * @throws MathIllegalArgumentException if the underlying random generator thwrows one
+     */
+    private double getNextGaussian() throws MathIllegalArgumentException {
+        return randomData.nextGaussian(mu, sigma);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/random/Well1024a.java b/src/main/java/org/apache/commons/math3/random/Well1024a.java
new file mode 100644
index 0000000..72c7f16
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/random/Well1024a.java
@@ -0,0 +1,110 @@
+/*
+ * 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.math3.random;
+
+/**
+ * This class implements the WELL1024a pseudo-random number generator from Fran&ccedil;ois Panneton,
+ * Pierre L'Ecuyer and Makoto Matsumoto.
+ *
+ * <p>This generator is described in a paper by Fran&ccedil;ois Panneton, Pierre L'Ecuyer and Makoto
+ * Matsumoto <a href="http://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf">Improved
+ * Long-Period Generators Based on Linear Recurrences Modulo 2</a> ACM Transactions on Mathematical
+ * Software, 32, 1 (2006). The errata for the paper are in <a
+ * href="http://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng-errata.txt">wellrng-errata.txt</a>.
+ *
+ * @see <a href="http://www.iro.umontreal.ca/~panneton/WELLRNG.html">WELL Random number
+ *     generator</a>
+ * @since 2.2
+ */
+public class Well1024a extends AbstractWell {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 5680173464174485492L;
+
+    /** Number of bits in the pool. */
+    private static final int K = 1024;
+
+    /** First parameter of the algorithm. */
+    private static final int M1 = 3;
+
+    /** Second parameter of the algorithm. */
+    private static final int M2 = 24;
+
+    /** Third parameter of the algorithm. */
+    private static final int M3 = 10;
+
+    /**
+     * Creates a new random number generator.
+     *
+     * <p>The instance is initialized using the current time as the seed.
+     */
+    public Well1024a() {
+        super(K, M1, M2, M3);
+    }
+
+    /**
+     * Creates a new random number generator using a single int seed.
+     *
+     * @param seed the initial seed (32 bits integer)
+     */
+    public Well1024a(int seed) {
+        super(K, M1, M2, M3, seed);
+    }
+
+    /**
+     * Creates a new random number generator using an int array seed.
+     *
+     * @param seed the initial seed (32 bits integers array), if null the seed of the generator will
+     *     be related to the current time
+     */
+    public Well1024a(int[] seed) {
+        super(K, M1, M2, M3, seed);
+    }
+
+    /**
+     * Creates a new random number generator using a single long seed.
+     *
+     * @param seed the initial seed (64 bits integer)
+     */
+    public Well1024a(long seed) {
+        super(K, M1, M2, M3, seed);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected int next(final int bits) {
+
+        final int indexRm1 = iRm1[index];
+
+        final int v0 = v[index];
+        final int vM1 = v[i1[index]];
+        final int vM2 = v[i2[index]];
+        final int vM3 = v[i3[index]];
+
+        final int z0 = v[indexRm1];
+        final int z1 = v0 ^ (vM1 ^ (vM1 >>> 8));
+        final int z2 = (vM2 ^ (vM2 << 19)) ^ (vM3 ^ (vM3 << 14));
+        final int z3 = z1 ^ z2;
+        final int z4 = (z0 ^ (z0 << 11)) ^ (z1 ^ (z1 << 7)) ^ (z2 ^ (z2 << 13));
+
+        v[index] = z3;
+        v[indexRm1] = z4;
+        index = indexRm1;
+
+        return z4 >>> (32 - bits);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/random/Well19937a.java b/src/main/java/org/apache/commons/math3/random/Well19937a.java
new file mode 100644
index 0000000..a93358b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/random/Well19937a.java
@@ -0,0 +1,112 @@
+/*
+ * 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.math3.random;
+
+/**
+ * This class implements the WELL19937a pseudo-random number generator from Fran&ccedil;ois
+ * Panneton, Pierre L'Ecuyer and Makoto Matsumoto.
+ *
+ * <p>This generator is described in a paper by Fran&ccedil;ois Panneton, Pierre L'Ecuyer and Makoto
+ * Matsumoto <a href="http://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf">Improved
+ * Long-Period Generators Based on Linear Recurrences Modulo 2</a> ACM Transactions on Mathematical
+ * Software, 32, 1 (2006). The errata for the paper are in <a
+ * href="http://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng-errata.txt">wellrng-errata.txt</a>.
+ *
+ * @see <a href="http://www.iro.umontreal.ca/~panneton/WELLRNG.html">WELL Random number
+ *     generator</a>
+ * @since 2.2
+ */
+public class Well19937a extends AbstractWell {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -7462102162223815419L;
+
+    /** Number of bits in the pool. */
+    private static final int K = 19937;
+
+    /** First parameter of the algorithm. */
+    private static final int M1 = 70;
+
+    /** Second parameter of the algorithm. */
+    private static final int M2 = 179;
+
+    /** Third parameter of the algorithm. */
+    private static final int M3 = 449;
+
+    /**
+     * Creates a new random number generator.
+     *
+     * <p>The instance is initialized using the current time as the seed.
+     */
+    public Well19937a() {
+        super(K, M1, M2, M3);
+    }
+
+    /**
+     * Creates a new random number generator using a single int seed.
+     *
+     * @param seed the initial seed (32 bits integer)
+     */
+    public Well19937a(int seed) {
+        super(K, M1, M2, M3, seed);
+    }
+
+    /**
+     * Creates a new random number generator using an int array seed.
+     *
+     * @param seed the initial seed (32 bits integers array), if null the seed of the generator will
+     *     be related to the current time
+     */
+    public Well19937a(int[] seed) {
+        super(K, M1, M2, M3, seed);
+    }
+
+    /**
+     * Creates a new random number generator using a single long seed.
+     *
+     * @param seed the initial seed (64 bits integer)
+     */
+    public Well19937a(long seed) {
+        super(K, M1, M2, M3, seed);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected int next(final int bits) {
+
+        final int indexRm1 = iRm1[index];
+        final int indexRm2 = iRm2[index];
+
+        final int v0 = v[index];
+        final int vM1 = v[i1[index]];
+        final int vM2 = v[i2[index]];
+        final int vM3 = v[i3[index]];
+
+        final int z0 = (0x80000000 & v[indexRm1]) ^ (0x7FFFFFFF & v[indexRm2]);
+        final int z1 = (v0 ^ (v0 << 25)) ^ (vM1 ^ (vM1 >>> 27));
+        final int z2 = (vM2 >>> 9) ^ (vM3 ^ (vM3 >>> 1));
+        final int z3 = z1 ^ z2;
+        final int z4 = z0 ^ (z1 ^ (z1 << 9)) ^ (z2 ^ (z2 << 21)) ^ (z3 ^ (z3 >>> 21));
+
+        v[index] = z3;
+        v[indexRm1] = z4;
+        v[indexRm2] &= 0x80000000;
+        index = indexRm1;
+
+        return z4 >>> (32 - bits);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/random/Well19937c.java b/src/main/java/org/apache/commons/math3/random/Well19937c.java
new file mode 100644
index 0000000..a699195
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/random/Well19937c.java
@@ -0,0 +1,117 @@
+/*
+ * 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.math3.random;
+
+/**
+ * This class implements the WELL19937c pseudo-random number generator from Fran&ccedil;ois
+ * Panneton, Pierre L'Ecuyer and Makoto Matsumoto.
+ *
+ * <p>This generator is described in a paper by Fran&ccedil;ois Panneton, Pierre L'Ecuyer and Makoto
+ * Matsumoto <a href="http://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf">Improved
+ * Long-Period Generators Based on Linear Recurrences Modulo 2</a> ACM Transactions on Mathematical
+ * Software, 32, 1 (2006). The errata for the paper are in <a
+ * href="http://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng-errata.txt">wellrng-errata.txt</a>.
+ *
+ * @see <a href="http://www.iro.umontreal.ca/~panneton/WELLRNG.html">WELL Random number
+ *     generator</a>
+ * @since 2.2
+ */
+public class Well19937c extends AbstractWell {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -7203498180754925124L;
+
+    /** Number of bits in the pool. */
+    private static final int K = 19937;
+
+    /** First parameter of the algorithm. */
+    private static final int M1 = 70;
+
+    /** Second parameter of the algorithm. */
+    private static final int M2 = 179;
+
+    /** Third parameter of the algorithm. */
+    private static final int M3 = 449;
+
+    /**
+     * Creates a new random number generator.
+     *
+     * <p>The instance is initialized using the current time as the seed.
+     */
+    public Well19937c() {
+        super(K, M1, M2, M3);
+    }
+
+    /**
+     * Creates a new random number generator using a single int seed.
+     *
+     * @param seed the initial seed (32 bits integer)
+     */
+    public Well19937c(int seed) {
+        super(K, M1, M2, M3, seed);
+    }
+
+    /**
+     * Creates a new random number generator using an int array seed.
+     *
+     * @param seed the initial seed (32 bits integers array), if null the seed of the generator will
+     *     be related to the current time
+     */
+    public Well19937c(int[] seed) {
+        super(K, M1, M2, M3, seed);
+    }
+
+    /**
+     * Creates a new random number generator using a single long seed.
+     *
+     * @param seed the initial seed (64 bits integer)
+     */
+    public Well19937c(long seed) {
+        super(K, M1, M2, M3, seed);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected int next(final int bits) {
+
+        final int indexRm1 = iRm1[index];
+        final int indexRm2 = iRm2[index];
+
+        final int v0 = v[index];
+        final int vM1 = v[i1[index]];
+        final int vM2 = v[i2[index]];
+        final int vM3 = v[i3[index]];
+
+        final int z0 = (0x80000000 & v[indexRm1]) ^ (0x7FFFFFFF & v[indexRm2]);
+        final int z1 = (v0 ^ (v0 << 25)) ^ (vM1 ^ (vM1 >>> 27));
+        final int z2 = (vM2 >>> 9) ^ (vM3 ^ (vM3 >>> 1));
+        final int z3 = z1 ^ z2;
+        int z4 = z0 ^ (z1 ^ (z1 << 9)) ^ (z2 ^ (z2 << 21)) ^ (z3 ^ (z3 >>> 21));
+
+        v[index] = z3;
+        v[indexRm1] = z4;
+        v[indexRm2] &= 0x80000000;
+        index = indexRm1;
+
+        // add Matsumoto-Kurita tempering
+        // to get a maximally-equidistributed generator
+        z4 ^= (z4 << 7) & 0xe46e1700;
+        z4 ^= (z4 << 15) & 0x9b868000;
+
+        return z4 >>> (32 - bits);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/random/Well44497a.java b/src/main/java/org/apache/commons/math3/random/Well44497a.java
new file mode 100644
index 0000000..7a4f34c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/random/Well44497a.java
@@ -0,0 +1,115 @@
+/*
+ * 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.math3.random;
+
+/**
+ * This class implements the WELL44497a pseudo-random number generator from Fran&ccedil;ois
+ * Panneton, Pierre L'Ecuyer and Makoto Matsumoto.
+ *
+ * <p>This generator is described in a paper by Fran&ccedil;ois Panneton, Pierre L'Ecuyer and Makoto
+ * Matsumoto <a href="http://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf">Improved
+ * Long-Period Generators Based on Linear Recurrences Modulo 2</a> ACM Transactions on Mathematical
+ * Software, 32, 1 (2006). The errata for the paper are in <a
+ * href="http://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng-errata.txt">wellrng-errata.txt</a>.
+ *
+ * @see <a href="http://www.iro.umontreal.ca/~panneton/WELLRNG.html">WELL Random number
+ *     generator</a>
+ * @since 2.2
+ */
+public class Well44497a extends AbstractWell {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -3859207588353972099L;
+
+    /** Number of bits in the pool. */
+    private static final int K = 44497;
+
+    /** First parameter of the algorithm. */
+    private static final int M1 = 23;
+
+    /** Second parameter of the algorithm. */
+    private static final int M2 = 481;
+
+    /** Third parameter of the algorithm. */
+    private static final int M3 = 229;
+
+    /**
+     * Creates a new random number generator.
+     *
+     * <p>The instance is initialized using the current time as the seed.
+     */
+    public Well44497a() {
+        super(K, M1, M2, M3);
+    }
+
+    /**
+     * Creates a new random number generator using a single int seed.
+     *
+     * @param seed the initial seed (32 bits integer)
+     */
+    public Well44497a(int seed) {
+        super(K, M1, M2, M3, seed);
+    }
+
+    /**
+     * Creates a new random number generator using an int array seed.
+     *
+     * @param seed the initial seed (32 bits integers array), if null the seed of the generator will
+     *     be related to the current time
+     */
+    public Well44497a(int[] seed) {
+        super(K, M1, M2, M3, seed);
+    }
+
+    /**
+     * Creates a new random number generator using a single long seed.
+     *
+     * @param seed the initial seed (64 bits integer)
+     */
+    public Well44497a(long seed) {
+        super(K, M1, M2, M3, seed);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected int next(final int bits) {
+
+        final int indexRm1 = iRm1[index];
+        final int indexRm2 = iRm2[index];
+
+        final int v0 = v[index];
+        final int vM1 = v[i1[index]];
+        final int vM2 = v[i2[index]];
+        final int vM3 = v[i3[index]];
+
+        // the values below include the errata of the original article
+        final int z0 = (0xFFFF8000 & v[indexRm1]) ^ (0x00007FFF & v[indexRm2]);
+        final int z1 = (v0 ^ (v0 << 24)) ^ (vM1 ^ (vM1 >>> 30));
+        final int z2 = (vM2 ^ (vM2 << 10)) ^ (vM3 << 26);
+        final int z3 = z1 ^ z2;
+        final int z2Prime = ((z2 << 9) ^ (z2 >>> 23)) & 0xfbffffff;
+        final int z2Second = ((z2 & 0x00020000) != 0) ? (z2Prime ^ 0xb729fcec) : z2Prime;
+        final int z4 = z0 ^ (z1 ^ (z1 >>> 20)) ^ z2Second ^ z3;
+
+        v[index] = z3;
+        v[indexRm1] = z4;
+        v[indexRm2] &= 0xFFFF8000;
+        index = indexRm1;
+
+        return z4 >>> (32 - bits);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/random/Well44497b.java b/src/main/java/org/apache/commons/math3/random/Well44497b.java
new file mode 100644
index 0000000..9da51a5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/random/Well44497b.java
@@ -0,0 +1,122 @@
+/*
+ * 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.math3.random;
+
+/**
+ * This class implements the WELL44497b pseudo-random number generator from Fran&ccedil;ois
+ * Panneton, Pierre L'Ecuyer and Makoto Matsumoto.
+ *
+ * <p>This generator is described in a paper by Fran&ccedil;ois Panneton, Pierre L'Ecuyer and Makoto
+ * Matsumoto <a href="http://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf">Improved
+ * Long-Period Generators Based on Linear Recurrences Modulo 2</a> ACM Transactions on Mathematical
+ * Software, 32, 1 (2006). The errata for the paper are in <a
+ * href="http://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng-errata.txt">wellrng-errata.txt</a>.
+ *
+ * @see <a href="http://www.iro.umontreal.ca/~panneton/WELLRNG.html">WELL Random number
+ *     generator</a>
+ * @since 2.2
+ */
+public class Well44497b extends AbstractWell {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 4032007538246675492L;
+
+    /** Number of bits in the pool. */
+    private static final int K = 44497;
+
+    /** First parameter of the algorithm. */
+    private static final int M1 = 23;
+
+    /** Second parameter of the algorithm. */
+    private static final int M2 = 481;
+
+    /** Third parameter of the algorithm. */
+    private static final int M3 = 229;
+
+    /**
+     * Creates a new random number generator.
+     *
+     * <p>The instance is initialized using the current time as the seed.
+     */
+    public Well44497b() {
+        super(K, M1, M2, M3);
+    }
+
+    /**
+     * Creates a new random number generator using a single int seed.
+     *
+     * @param seed the initial seed (32 bits integer)
+     */
+    public Well44497b(int seed) {
+        super(K, M1, M2, M3, seed);
+    }
+
+    /**
+     * Creates a new random number generator using an int array seed.
+     *
+     * @param seed the initial seed (32 bits integers array), if null the seed of the generator will
+     *     be related to the current time
+     */
+    public Well44497b(int[] seed) {
+        super(K, M1, M2, M3, seed);
+    }
+
+    /**
+     * Creates a new random number generator using a single long seed.
+     *
+     * @param seed the initial seed (64 bits integer)
+     */
+    public Well44497b(long seed) {
+        super(K, M1, M2, M3, seed);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected int next(final int bits) {
+
+        // compute raw value given by WELL44497a generator
+        // which is NOT maximally-equidistributed
+        final int indexRm1 = iRm1[index];
+        final int indexRm2 = iRm2[index];
+
+        final int v0 = v[index];
+        final int vM1 = v[i1[index]];
+        final int vM2 = v[i2[index]];
+        final int vM3 = v[i3[index]];
+
+        // the values below include the errata of the original article
+        final int z0 = (0xFFFF8000 & v[indexRm1]) ^ (0x00007FFF & v[indexRm2]);
+        final int z1 = (v0 ^ (v0 << 24)) ^ (vM1 ^ (vM1 >>> 30));
+        final int z2 = (vM2 ^ (vM2 << 10)) ^ (vM3 << 26);
+        final int z3 = z1 ^ z2;
+        final int z2Prime = ((z2 << 9) ^ (z2 >>> 23)) & 0xfbffffff;
+        final int z2Second = ((z2 & 0x00020000) != 0) ? (z2Prime ^ 0xb729fcec) : z2Prime;
+        int z4 = z0 ^ (z1 ^ (z1 >>> 20)) ^ z2Second ^ z3;
+
+        v[index] = z3;
+        v[indexRm1] = z4;
+        v[indexRm2] &= 0xFFFF8000;
+        index = indexRm1;
+
+        // add Matsumoto-Kurita tempering
+        // to get a maximally-equidistributed generator
+        z4 ^= (z4 << 7) & 0x93dd1400;
+        z4 ^= (z4 << 15) & 0xfa118000;
+
+        return z4 >>> (32 - bits);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/random/Well512a.java b/src/main/java/org/apache/commons/math3/random/Well512a.java
new file mode 100644
index 0000000..d09cbb1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/random/Well512a.java
@@ -0,0 +1,111 @@
+/*
+ * 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.math3.random;
+
+/**
+ * This class implements the WELL512a pseudo-random number generator from Fran&ccedil;ois Panneton,
+ * Pierre L'Ecuyer and Makoto Matsumoto.
+ *
+ * <p>This generator is described in a paper by Fran&ccedil;ois Panneton, Pierre L'Ecuyer and Makoto
+ * Matsumoto <a href="http://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf">Improved
+ * Long-Period Generators Based on Linear Recurrences Modulo 2</a> ACM Transactions on Mathematical
+ * Software, 32, 1 (2006). The errata for the paper are in <a
+ * href="http://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng-errata.txt">wellrng-errata.txt</a>.
+ *
+ * @see <a href="http://www.iro.umontreal.ca/~panneton/WELLRNG.html">WELL Random number
+ *     generator</a>
+ * @since 2.2
+ */
+public class Well512a extends AbstractWell {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -6104179812103820574L;
+
+    /** Number of bits in the pool. */
+    private static final int K = 512;
+
+    /** First parameter of the algorithm. */
+    private static final int M1 = 13;
+
+    /** Second parameter of the algorithm. */
+    private static final int M2 = 9;
+
+    /** Third parameter of the algorithm. */
+    private static final int M3 = 5;
+
+    /**
+     * Creates a new random number generator.
+     *
+     * <p>The instance is initialized using the current time as the seed.
+     */
+    public Well512a() {
+        super(K, M1, M2, M3);
+    }
+
+    /**
+     * Creates a new random number generator using a single int seed.
+     *
+     * @param seed the initial seed (32 bits integer)
+     */
+    public Well512a(int seed) {
+        super(K, M1, M2, M3, seed);
+    }
+
+    /**
+     * Creates a new random number generator using an int array seed.
+     *
+     * @param seed the initial seed (32 bits integers array), if null the seed of the generator will
+     *     be related to the current time
+     */
+    public Well512a(int[] seed) {
+        super(K, M1, M2, M3, seed);
+    }
+
+    /**
+     * Creates a new random number generator using a single long seed.
+     *
+     * @param seed the initial seed (64 bits integer)
+     */
+    public Well512a(long seed) {
+        super(K, M1, M2, M3, seed);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected int next(final int bits) {
+
+        final int indexRm1 = iRm1[index];
+
+        final int vi = v[index];
+        final int vi1 = v[i1[index]];
+        final int vi2 = v[i2[index]];
+        final int z0 = v[indexRm1];
+
+        // the values below include the errata of the original article
+        final int z1 = (vi ^ (vi << 16)) ^ (vi1 ^ (vi1 << 15));
+        final int z2 = vi2 ^ (vi2 >>> 11);
+        final int z3 = z1 ^ z2;
+        final int z4 =
+                (z0 ^ (z0 << 2)) ^ (z1 ^ (z1 << 18)) ^ (z2 << 28) ^ (z3 ^ ((z3 << 5) & 0xda442d24));
+
+        v[index] = z3;
+        v[indexRm1] = z4;
+        index = indexRm1;
+
+        return z4 >>> (32 - bits);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/random/package-info.java b/src/main/java/org/apache/commons/math3/random/package-info.java
new file mode 100644
index 0000000..212b018
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/random/package-info.java
@@ -0,0 +1,115 @@
+/*
+ * 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.
+ */
+/**
+ * Random number and random data generators.
+ *
+ * <p>Commons-math provides a few pseudo random number generators. The top level interface is
+ * RandomGenerator. It is implemented by three classes:
+ *
+ * <ul>
+ *   <li>{@link org.apache.commons.math3.random.JDKRandomGenerator JDKRandomGenerator} that extends
+ *       the JDK provided generator
+ *   <li>AbstractRandomGenerator as a helper for users generators
+ *   <li>BitStreamGenerator which is an abstract class for several generators and which in turn is
+ *       extended by:
+ *       <ul>
+ *         <li>{@link org.apache.commons.math3.random.MersenneTwister MersenneTwister}
+ *         <li>{@link org.apache.commons.math3.random.Well512a Well512a}
+ *         <li>{@link org.apache.commons.math3.random.Well1024a Well1024a}
+ *         <li>{@link org.apache.commons.math3.random.Well19937a Well19937a}
+ *         <li>{@link org.apache.commons.math3.random.Well19937c Well19937c}
+ *         <li>{@link org.apache.commons.math3.random.Well44497a Well44497a}
+ *         <li>{@link org.apache.commons.math3.random.Well44497b Well44497b}
+ *       </ul>
+ * </ul>
+ *
+ * <p>The JDK provided generator is a simple one that can be used only for very simple needs. The
+ * Mersenne Twister is a fast generator with very good properties well suited for Monte-Carlo
+ * simulation. It is equidistributed for generating vectors up to dimension 623 and has a huge
+ * period: 2<sup>19937</sup> - 1 (which is a Mersenne prime). This generator is described in a paper
+ * by Makoto Matsumoto and Takuji Nishimura in 1998: <a
+ * href="http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/ARTICLES/mt.pdf">Mersenne Twister: A
+ * 623-Dimensionally Equidistributed Uniform Pseudo-Random Number Generator</a>, ACM Transactions on
+ * Modeling and Computer Simulation, Vol. 8, No. 1, January 1998, pp 3--30. The WELL generators are
+ * a family of generators with period ranging from 2<sup>512</sup> - 1 to 2<sup>44497</sup> - 1
+ * (this last one is also a Mersenne prime) with even better properties than Mersenne Twister. These
+ * generators are described in a paper by Fran&ccedil;ois Panneton, Pierre L'Ecuyer and Makoto
+ * Matsumoto <a href="http://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng.pdf">Improved
+ * Long-Period Generators Based on Linear Recurrences Modulo 2</a> ACM Transactions on Mathematical
+ * Software, 32, 1 (2006). The errata for the paper are in <a
+ * href="http://www.iro.umontreal.ca/~lecuyer/myftp/papers/wellrng-errata.txt">wellrng-errata.txt</a>.
+ *
+ * <p>For simple sampling, any of these generators is sufficient. For Monte-Carlo simulations the
+ * JDK generator does not have any of the good mathematical properties of the other generators, so
+ * it should be avoided. The Mersenne twister and WELL generators have equidistribution properties
+ * proven according to their bits pool size which is directly linked to their period (all of them
+ * have maximal period, i.e. a generator with size n pool has a period 2<sup>n</sup>-1). They also
+ * have equidistribution properties for 32 bits blocks up to s/32 dimension where s is their pool
+ * size. So WELL19937c for exemple is equidistributed up to dimension 623 (19937/32). This means a
+ * Monte-Carlo simulation generating a vector of n variables at each iteration has some guarantees
+ * on the properties of the vector as long as its dimension does not exceed the limit. However,
+ * since we use bits from two successive 32 bits generated integers to create one double, this limit
+ * is smaller when the variables are of type double. so for Monte-Carlo simulation where less the 16
+ * doubles are generated at each round, WELL1024 may be sufficient. If a larger number of doubles
+ * are needed a generator with a larger pool would be useful.
+ *
+ * <p>The WELL generators are more modern then MersenneTwister (the paper describing than has been
+ * published in 2006 instead of 1998) and fix some of its (few) drawbacks. If initialization array
+ * contains many zero bits, MersenneTwister may take a very long time (several hundreds of thousands
+ * of iterations to reach a steady state with a balanced number of zero and one in its bits pool).
+ * So the WELL generators are better to <i>escape zeroland</i> as explained by the WELL generators
+ * creators. The Well19937a and Well44497a generator are not maximally equidistributed (i.e. there
+ * are some dimensions or bits blocks size for which they are not equidistributed). The Well512a,
+ * Well1024a, Well19937c and Well44497b are maximally equidistributed for blocks size up to 32 bits
+ * (they should behave correctly also for double based on more than 32 bits blocks, but
+ * equidistribution is not proven at these blocks sizes).
+ *
+ * <p>The MersenneTwister generator uses a 624 elements integer array, so it consumes less than 2.5
+ * kilobytes. The WELL generators use 6 integer arrays with a size equal to the pool size, so for
+ * example the WELL44497b generator uses about 33 kilobytes. This may be important if a very large
+ * number of generator instances were used at the same time.
+ *
+ * <p>All generators are quite fast. As an example, here are some comparisons, obtained on a 64 bits
+ * JVM on a linux computer with a 2008 processor (AMD phenom Quad 9550 at 2.2 GHz). The generation
+ * rate for MersenneTwister was about 27 millions doubles per second (remember we generate two 32
+ * bits integers for each double). Generation rates for other PRNG, relative to MersenneTwister:
+ *
+ * <p>
+ *
+ * <table border="1" align="center">
+ *          <tr BGCOLOR="#CCCCFF"><td colspan="2"><font size="+2">Example of performances</font></td></tr>
+ *          <tr BGCOLOR="#EEEEFF"><font size="+1"><td>Name</td><td>generation rate (relative to MersenneTwister)</td></font></tr>
+ *          <tr><td>{@link org.apache.commons.math3.random.MersenneTwister MersenneTwister}</td><td>1</td></tr>
+ *          <tr><td>{@link org.apache.commons.math3.random.JDKRandomGenerator JDKRandomGenerator}</td><td>between 0.96 and 1.16</td></tr>
+ *          <tr><td>{@link org.apache.commons.math3.random.Well512a Well512a}</td><td>between 0.85 and 0.88</td></tr>
+ *          <tr><td>{@link org.apache.commons.math3.random.Well1024a Well1024a}</td><td>between 0.63 and 0.73</td></tr>
+ *          <tr><td>{@link org.apache.commons.math3.random.Well19937a Well19937a}</td><td>between 0.70 and 0.71</td></tr>
+ *          <tr><td>{@link org.apache.commons.math3.random.Well19937c Well19937c}</td><td>between 0.57 and 0.71</td></tr>
+ *          <tr><td>{@link org.apache.commons.math3.random.Well44497a Well44497a}</td><td>between 0.69 and 0.71</td></tr>
+ *          <tr><td>{@link org.apache.commons.math3.random.Well44497b Well44497b}</td><td>between 0.65 and 0.71</td></tr>
+ *        </table>
+ *
+ * <p>So for most simulation problems, the better generators like {@link
+ * org.apache.commons.math3.random.Well19937c Well19937c} and {@link
+ * org.apache.commons.math3.random.Well44497b Well44497b} are probably very good choices.
+ *
+ * <p>Note that <em>none</em> of these generators are suitable for cryptography. They are devoted to
+ * simulation, and to generate very long series with strong properties on the series as a whole
+ * (equidistribution, no correlation ...). They do not attempt to create small series but with very
+ * strong properties of unpredictability as needed in cryptography.
+ */
+package org.apache.commons.math3.random;
diff --git a/src/main/java/org/apache/commons/math3/special/BesselJ.java b/src/main/java/org/apache/commons/math3/special/BesselJ.java
new file mode 100644
index 0000000..61aa4ff
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/special/BesselJ.java
@@ -0,0 +1,661 @@
+/*
+ * 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.math3.special;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.exception.ConvergenceException;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * This class provides computation methods related to Bessel functions of the first kind. Detailed
+ * descriptions of these functions are available in <a
+ * href="http://en.wikipedia.org/wiki/Bessel_function">Wikipedia</a>, <a
+ * href="http://en.wikipedia.org/wiki/Abramowitz_and_Stegun">Abrabowitz and Stegun</a> (Ch. 9-11),
+ * and <a href="http://dlmf.nist.gov/">DLMF</a> (Ch. 10).
+ *
+ * <p>This implementation is based on the rjbesl Fortran routine at <a
+ * href="http://www.netlib.org/specfun/rjbesl">Netlib</a>.
+ *
+ * <p>From the Fortran code:
+ *
+ * <p>This program is based on a program written by David J. Sookne (2) that computes values of the
+ * Bessel functions J or I of real argument and integer order. Modifications include the restriction
+ * of the computation to the J Bessel function of non-negative real argument, the extension of the
+ * computation to arbitrary positive order, and the elimination of most underflow.
+ *
+ * <p>References:
+ *
+ * <ul>
+ *   <li>"A Note on Backward Recurrence Algorithms," Olver, F. W. J., and Sookne, D. J., Math. Comp.
+ *       26, 1972, pp 941-947.
+ *   <li>"Bessel Functions of Real Argument and Integer Order," Sookne, D. J., NBS Jour. of Res. B.
+ *       77B, 1973, pp 125-132.
+ * </ul>
+ *
+ * @since 3.4
+ */
+public class BesselJ implements UnivariateFunction {
+
+    // ---------------------------------------------------------------------
+    // Mathematical constants
+    // ---------------------------------------------------------------------
+
+    /** -2 / pi */
+    private static final double PI2 = 0.636619772367581343075535;
+
+    /** first few significant digits of 2pi */
+    private static final double TOWPI1 = 6.28125;
+
+    /** 2pi - TWOPI1 to working precision */
+    private static final double TWOPI2 = 1.935307179586476925286767e-3;
+
+    /** TOWPI1 + TWOPI2 */
+    private static final double TWOPI = TOWPI1 + TWOPI2;
+
+    // ---------------------------------------------------------------------
+    // Machine-dependent parameters
+    // ---------------------------------------------------------------------
+
+    /**
+     * 10.0^K, where K is the largest integer such that ENTEN is machine-representable in working
+     * precision
+     */
+    private static final double ENTEN = 1.0e308;
+
+    /**
+     * Decimal significance desired. Should be set to (INT(log_{10}(2) * (it)+1)). Setting NSIG
+     * lower will result in decreased accuracy while setting NSIG higher will increase CPU time
+     * without increasing accuracy. The truncation error is limited to a relative error of
+     * T=.5(10^(-NSIG)).
+     */
+    private static final double ENSIG = 1.0e16;
+
+    /** 10.0 ** (-K) for the smallest integer K such that K >= NSIG/4 */
+    private static final double RTNSIG = 1.0e-4;
+
+    /** Smallest ABS(X) such that X/4 does not underflow */
+    private static final double ENMTEN = 8.90e-308;
+
+    /** Minimum acceptable value for x */
+    private static final double X_MIN = 0.0;
+
+    /**
+     * Upper limit on the magnitude of x. If abs(x) = n, then at least n iterations of the backward
+     * recursion will be executed. The value of 10.0 ** 4 is used on every machine.
+     */
+    private static final double X_MAX = 1.0e4;
+
+    /** First 25 factorials as doubles */
+    private static final double[] FACT = {
+        1.0,
+        1.0,
+        2.0,
+        6.0,
+        24.0,
+        120.0,
+        720.0,
+        5040.0,
+        40320.0,
+        362880.0,
+        3628800.0,
+        39916800.0,
+        479001600.0,
+        6227020800.0,
+        87178291200.0,
+        1.307674368e12,
+        2.0922789888e13,
+        3.55687428096e14,
+        6.402373705728e15,
+        1.21645100408832e17,
+        2.43290200817664e18,
+        5.109094217170944e19,
+        1.12400072777760768e21,
+        2.585201673888497664e22,
+        6.2044840173323943936e23
+    };
+
+    /** Order of the function computed when {@link #value(double)} is used */
+    private final double order;
+
+    /**
+     * Create a new BesselJ with the given order.
+     *
+     * @param order order of the function computed when using {@link #value(double)}.
+     */
+    public BesselJ(double order) {
+        this.order = order;
+    }
+
+    /**
+     * Returns the value of the constructed Bessel function of the first kind, for the passed
+     * argument.
+     *
+     * @param x Argument
+     * @return Value of the Bessel function at x
+     * @throws MathIllegalArgumentException if {@code x} is too large relative to {@code order}
+     * @throws ConvergenceException if the algorithm fails to converge
+     */
+    public double value(double x) throws MathIllegalArgumentException, ConvergenceException {
+        return BesselJ.value(order, x);
+    }
+
+    /**
+     * Returns the first Bessel function, \(J_{order}(x)\).
+     *
+     * @param order Order of the Bessel function
+     * @param x Argument
+     * @return Value of the Bessel function of the first kind, \(J_{order}(x)\)
+     * @throws MathIllegalArgumentException if {@code x} is too large relative to {@code order}
+     * @throws ConvergenceException if the algorithm fails to converge
+     */
+    public static double value(double order, double x)
+            throws MathIllegalArgumentException, ConvergenceException {
+        final int n = (int) order;
+        final double alpha = order - n;
+        final int nb = n + 1;
+        final BesselJResult res = rjBesl(x, alpha, nb);
+
+        if (res.nVals >= nb) {
+            return res.vals[n];
+        } else if (res.nVals < 0) {
+            throw new MathIllegalArgumentException(
+                    LocalizedFormats.BESSEL_FUNCTION_BAD_ARGUMENT, order, x);
+        } else if (FastMath.abs(res.vals[res.nVals - 1]) < 1e-100) {
+            return res.vals[n]; // underflow; return value (will be zero)
+        }
+        throw new ConvergenceException(
+                LocalizedFormats.BESSEL_FUNCTION_FAILED_CONVERGENCE, order, x);
+    }
+
+    /**
+     * Encapsulates the results returned by {@link BesselJ#rjBesl(double, double, int)}.
+     *
+     * <p>{@link #getVals()} returns the computed function values. {@link #getnVals()} is the number
+     * of values among those returned by {@link #getnVals()} that can be considered accurate.
+     *
+     * <p>
+     *
+     * <ul>
+     *   <li>nVals < 0: An argument is out of range. For example, nb <= 0, alpha < 0 or > 1, or x is
+     *       too large. In this case, b(0) is set to zero, the remainder of the b-vector is not
+     *       calculated, and nVals is set to MIN(nb,0) - 1 so that nVals != nb.
+     *   <li>nb > nVals > 0: Not all requested function values could be calculated accurately. This
+     *       usually occurs because nb is much larger than abs(x). In this case, b(n) is calculated
+     *       to the desired accuracy for n < nVals, but precision is lost for nVals < n <= nb. If
+     *       b(n) does not vanish for n > nVals (because it is too small to be represented), and
+     *       b(n)/b(nVals) = \(10^{-k}\), then only the first NSIG-k significant figures of b(n) can
+     *       be trusted.
+     * </ul>
+     */
+    public static class BesselJResult {
+
+        /** Bessel function values */
+        private final double[] vals;
+
+        /** Valid value count */
+        private final int nVals;
+
+        /**
+         * Create a new BesselJResult with the given values and valid value count.
+         *
+         * @param b values
+         * @param n count of valid values
+         */
+        public BesselJResult(double[] b, int n) {
+            vals = MathArrays.copyOf(b, b.length);
+            nVals = n;
+        }
+
+        /**
+         * @return the computed function values
+         */
+        public double[] getVals() {
+            return MathArrays.copyOf(vals, vals.length);
+        }
+
+        /**
+         * @return the number of valid function values (normally the same as the length of the array
+         *     returned by {@link #getnVals()})
+         */
+        public int getnVals() {
+            return nVals;
+        }
+    }
+
+    /**
+     * Calculates Bessel functions \(J_{n+alpha}(x)\) for non-negative argument x, and non-negative
+     * order n + alpha.
+     *
+     * <p>Before using the output vector, the user should check that nVals = nb, i.e., all orders
+     * have been calculated to the desired accuracy. See BesselResult class javadoc for details on
+     * return values.
+     *
+     * @param x non-negative real argument for which J's are to be calculated
+     * @param alpha fractional part of order for which J's or exponentially scaled J's (\(J\cdot
+     *     e^{x}\)) are to be calculated. 0 <= alpha < 1.0.
+     * @param nb integer number of functions to be calculated, nb > 0. The first function calculated
+     *     is of order alpha, and the last is of order nb - 1 + alpha.
+     * @return BesselJResult a vector of the functions \(J_{alpha}(x)\) through
+     *     \(J_{nb-1+alpha}(x)\), or the corresponding exponentially scaled functions and an integer
+     *     output variable indicating possible errors
+     */
+    public static BesselJResult rjBesl(double x, double alpha, int nb) {
+        final double[] b = new double[nb];
+
+        int ncalc = 0;
+        double alpem = 0;
+        double alp2em = 0;
+
+        // ---------------------------------------------------------------------
+        // Check for out of range arguments.
+        // ---------------------------------------------------------------------
+        final int magx = (int) x;
+        if ((nb > 0) && (x >= X_MIN) && (x <= X_MAX) && (alpha >= 0) && (alpha < 1)) {
+            // ---------------------------------------------------------------------
+            // Initialize result array to zero.
+            // ---------------------------------------------------------------------
+            ncalc = nb;
+            for (int i = 0; i < nb; ++i) {
+                b[i] = 0;
+            }
+
+            // ---------------------------------------------------------------------
+            // Branch to use 2-term ascending series for small X and asymptotic
+            // form for large X when NB is not too large.
+            // ---------------------------------------------------------------------
+            double tempa;
+            double tempb;
+            double tempc;
+            double tover;
+            if (x < RTNSIG) {
+                // ---------------------------------------------------------------------
+                // Two-term ascending series for small X.
+                // ---------------------------------------------------------------------
+                tempa = 1;
+                alpem = 1 + alpha;
+                double halfx = 0;
+                if (x > ENMTEN) {
+                    halfx = 0.5 * x;
+                }
+                if (alpha != 0) {
+                    tempa = FastMath.pow(halfx, alpha) / (alpha * Gamma.gamma(alpha));
+                }
+                tempb = 0;
+                if (x + 1 > 1) {
+                    tempb = -halfx * halfx;
+                }
+                b[0] = tempa + (tempa * tempb / alpem);
+                if ((x != 0) && (b[0] == 0)) {
+                    ncalc = 0;
+                }
+                if (nb != 1) {
+                    if (x <= 0) {
+                        for (int n = 1; n < nb; ++n) {
+                            b[n] = 0;
+                        }
+                    } else {
+                        // ---------------------------------------------------------------------
+                        // Calculate higher order functions.
+                        // ---------------------------------------------------------------------
+                        tempc = halfx;
+                        tover = tempb != 0 ? ENMTEN / tempb : 2 * ENMTEN / x;
+                        for (int n = 1; n < nb; ++n) {
+                            tempa /= alpem;
+                            alpem += 1;
+                            tempa *= tempc;
+                            if (tempa <= tover * alpem) {
+                                tempa = 0;
+                            }
+                            b[n] = tempa + (tempa * tempb / alpem);
+                            if ((b[n] == 0) && (ncalc > n)) {
+                                ncalc = n;
+                            }
+                        }
+                    }
+                }
+            } else if ((x > 25.0) && (nb <= magx + 1)) {
+                // ---------------------------------------------------------------------
+                // Asymptotic series for X > 25
+                // ---------------------------------------------------------------------
+                final double xc = FastMath.sqrt(PI2 / x);
+                final double mul = 0.125 / x;
+                final double xin = mul * mul;
+                int m = 0;
+                if (x >= 130.0) {
+                    m = 4;
+                } else if (x >= 35.0) {
+                    m = 8;
+                } else {
+                    m = 11;
+                }
+
+                final double xm = 4.0 * m;
+                // ---------------------------------------------------------------------
+                // Argument reduction for SIN and COS routines.
+                // ---------------------------------------------------------------------
+                double t = (double) ((int) ((x / TWOPI) + 0.5));
+                final double z = x - t * TOWPI1 - t * TWOPI2 - (alpha + 0.5) / PI2;
+                double vsin = FastMath.sin(z);
+                double vcos = FastMath.cos(z);
+                double gnu = 2 * alpha;
+                double capq;
+                double capp;
+                double s;
+                double t1;
+                double xk;
+                for (int i = 1; i <= 2; i++) {
+                    s = (xm - 1 - gnu) * (xm - 1 + gnu) * xin * 0.5;
+                    t = (gnu - (xm - 3.0)) * (gnu + (xm - 3.0));
+                    capp = (s * t) / FACT[2 * m];
+                    t1 = (gnu - (xm + 1)) * (gnu + (xm + 1));
+                    capq = (s * t1) / FACT[2 * m + 1];
+                    xk = xm;
+                    int k = 2 * m;
+                    t1 = t;
+
+                    for (int j = 2; j <= m; j++) {
+                        xk -= 4.0;
+                        s = (xk - 1 - gnu) * (xk - 1 + gnu);
+                        t = (gnu - (xk - 3.0)) * (gnu + (xk - 3.0));
+                        capp = (capp + 1 / FACT[k - 2]) * s * t * xin;
+                        capq = (capq + 1 / FACT[k - 1]) * s * t1 * xin;
+                        k -= 2;
+                        t1 = t;
+                    }
+
+                    capp += 1;
+                    capq = (capq + 1) * ((gnu * gnu) - 1) * (0.125 / x);
+                    b[i - 1] = xc * (capp * vcos - capq * vsin);
+                    if (nb == 1) {
+                        return new BesselJResult(MathArrays.copyOf(b, b.length), ncalc);
+                    }
+                    t = vsin;
+                    vsin = -vcos;
+                    vcos = t;
+                    gnu += 2.0;
+                }
+
+                // ---------------------------------------------------------------------
+                // If NB > 2, compute J(X,ORDER+I) I = 2, NB-1
+                // ---------------------------------------------------------------------
+                if (nb > 2) {
+                    gnu = 2 * alpha + 2.0;
+                    for (int j = 2; j < nb; ++j) {
+                        b[j] = gnu * b[j - 1] / x - b[j - 2];
+                        gnu += 2.0;
+                    }
+                }
+            } else {
+                // ---------------------------------------------------------------------
+                // Use recurrence to generate results. First initialize the
+                // calculation of P*S.
+                // ---------------------------------------------------------------------
+                final int nbmx = nb - magx;
+                int n = magx + 1;
+                int nstart = 0;
+                int nend = 0;
+                double en = 2 * (n + alpha);
+                double plast = 1;
+                double p = en / x;
+                double pold;
+                // ---------------------------------------------------------------------
+                // Calculate general significance test.
+                // ---------------------------------------------------------------------
+                double test = 2 * ENSIG;
+                boolean readyToInitialize = false;
+                if (nbmx >= 3) {
+                    // ---------------------------------------------------------------------
+                    // Calculate P*S until N = NB-1. Check for possible
+                    // overflow.
+                    // ---------------------------------------------------------------------
+                    tover = ENTEN / ENSIG;
+                    nstart = magx + 2;
+                    nend = nb - 1;
+                    en = 2 * (nstart - 1 + alpha);
+                    double psave;
+                    double psavel;
+                    for (int k = nstart; k <= nend; k++) {
+                        n = k;
+                        en += 2.0;
+                        pold = plast;
+                        plast = p;
+                        p = (en * plast / x) - pold;
+                        if (p > tover) {
+                            // ---------------------------------------------------------------------
+                            // To avoid overflow, divide P*S by TOVER. Calculate
+                            // P*S until
+                            // ABS(P) > 1.
+                            // ---------------------------------------------------------------------
+                            tover = ENTEN;
+                            p /= tover;
+                            plast /= tover;
+                            psave = p;
+                            psavel = plast;
+                            nstart = n + 1;
+                            do {
+                                n += 1;
+                                en += 2.0;
+                                pold = plast;
+                                plast = p;
+                                p = (en * plast / x) - pold;
+                            } while (p <= 1);
+                            tempb = en / x;
+                            // ---------------------------------------------------------------------
+                            // Calculate backward test and find NCALC, the
+                            // highest N such that
+                            // the test is passed.
+                            // ---------------------------------------------------------------------
+                            test = pold * plast * (0.5 - 0.5 / (tempb * tempb));
+                            test /= ENSIG;
+                            p = plast * tover;
+                            n -= 1;
+                            en -= 2.0;
+                            nend = FastMath.min(nb, n);
+                            for (int l = nstart; l <= nend; l++) {
+                                pold = psavel;
+                                psavel = psave;
+                                psave = (en * psavel / x) - pold;
+                                if (psave * psavel > test) {
+                                    ncalc = l - 1;
+                                    readyToInitialize = true;
+                                    break;
+                                }
+                            }
+                            ncalc = nend;
+                            readyToInitialize = true;
+                            break;
+                        }
+                    }
+                    if (!readyToInitialize) {
+                        n = nend;
+                        en = 2 * (n + alpha);
+                        // ---------------------------------------------------------------------
+                        // Calculate special significance test for NBMX > 2.
+                        // ---------------------------------------------------------------------
+                        test =
+                                FastMath.max(
+                                        test, FastMath.sqrt(plast * ENSIG) * FastMath.sqrt(2 * p));
+                    }
+                }
+                // ---------------------------------------------------------------------
+                // Calculate P*S until significance test passes.
+                // ---------------------------------------------------------------------
+                if (!readyToInitialize) {
+                    do {
+                        n += 1;
+                        en += 2.0;
+                        pold = plast;
+                        plast = p;
+                        p = (en * plast / x) - pold;
+                    } while (p < test);
+                }
+                // ---------------------------------------------------------------------
+                // Initialize the backward recursion and the normalization sum.
+                // ---------------------------------------------------------------------
+                n += 1;
+                en += 2.0;
+                tempb = 0;
+                tempa = 1 / p;
+                int m = (2 * n) - 4 * (n / 2);
+                double sum = 0;
+                double em = (double) (n / 2);
+                alpem = em - 1 + alpha;
+                alp2em = 2 * em + alpha;
+                if (m != 0) {
+                    sum = tempa * alpem * alp2em / em;
+                }
+                nend = n - nb;
+
+                boolean readyToNormalize = false;
+                boolean calculatedB0 = false;
+
+                // ---------------------------------------------------------------------
+                // Recur backward via difference equation, calculating (but not
+                // storing) B(N), until N = NB.
+                // ---------------------------------------------------------------------
+                for (int l = 1; l <= nend; l++) {
+                    n -= 1;
+                    en -= 2.0;
+                    tempc = tempb;
+                    tempb = tempa;
+                    tempa = (en * tempb / x) - tempc;
+                    m = 2 - m;
+                    if (m != 0) {
+                        em -= 1;
+                        alp2em = 2 * em + alpha;
+                        if (n == 1) {
+                            break;
+                        }
+                        alpem = em - 1 + alpha;
+                        if (alpem == 0) {
+                            alpem = 1;
+                        }
+                        sum = (sum + tempa * alp2em) * alpem / em;
+                    }
+                }
+
+                // ---------------------------------------------------------------------
+                // Store B(NB).
+                // ---------------------------------------------------------------------
+                b[n - 1] = tempa;
+                if (nend >= 0) {
+                    if (nb <= 1) {
+                        alp2em = alpha;
+                        if (alpha + 1 == 1) {
+                            alp2em = 1;
+                        }
+                        sum += b[0] * alp2em;
+                        readyToNormalize = true;
+                    } else {
+                        // ---------------------------------------------------------------------
+                        // Calculate and store B(NB-1).
+                        // ---------------------------------------------------------------------
+                        n -= 1;
+                        en -= 2.0;
+                        b[n - 1] = (en * tempa / x) - tempb;
+                        if (n == 1) {
+                            calculatedB0 = true;
+                        } else {
+                            m = 2 - m;
+                            if (m != 0) {
+                                em -= 1;
+                                alp2em = 2 * em + alpha;
+                                alpem = em - 1 + alpha;
+                                if (alpem == 0) {
+                                    alpem = 1;
+                                }
+
+                                sum = (sum + (b[n - 1] * alp2em)) * alpem / em;
+                            }
+                        }
+                    }
+                }
+                if (!readyToNormalize && !calculatedB0) {
+                    nend = n - 2;
+                    if (nend != 0) {
+                        // ---------------------------------------------------------------------
+                        // Calculate via difference equation and store B(N),
+                        // until N = 2.
+                        // ---------------------------------------------------------------------
+
+                        for (int l = 1; l <= nend; l++) {
+                            n -= 1;
+                            en -= 2.0;
+                            b[n - 1] = (en * b[n] / x) - b[n + 1];
+                            m = 2 - m;
+                            if (m != 0) {
+                                em -= 1;
+                                alp2em = 2 * em + alpha;
+                                alpem = em - 1 + alpha;
+                                if (alpem == 0) {
+                                    alpem = 1;
+                                }
+
+                                sum = (sum + b[n - 1] * alp2em) * alpem / em;
+                            }
+                        }
+                    }
+                }
+                // ---------------------------------------------------------------------
+                // Calculate b[0]
+                // ---------------------------------------------------------------------
+                if (!readyToNormalize) {
+                    if (!calculatedB0) {
+                        b[0] = 2.0 * (alpha + 1) * b[1] / x - b[2];
+                    }
+                    em -= 1;
+                    alp2em = 2 * em + alpha;
+                    if (alp2em == 0) {
+                        alp2em = 1;
+                    }
+                    sum += b[0] * alp2em;
+                }
+                // ---------------------------------------------------------------------
+                // Normalize. Divide all B(N) by sum.
+                // ---------------------------------------------------------------------
+
+                if (FastMath.abs(alpha) > 1e-16) {
+                    sum *= Gamma.gamma(alpha) * FastMath.pow(x * 0.5, -alpha);
+                }
+                tempa = ENMTEN;
+                if (sum > 1) {
+                    tempa *= sum;
+                }
+
+                for (n = 0; n < nb; n++) {
+                    if (FastMath.abs(b[n]) < tempa) {
+                        b[n] = 0;
+                    }
+                    b[n] /= sum;
+                }
+            }
+            // ---------------------------------------------------------------------
+            // Error return -- X, NB, or ALPHA is out of range.
+            // ---------------------------------------------------------------------
+        } else {
+            if (b.length > 0) {
+                b[0] = 0;
+            }
+            ncalc = FastMath.min(nb, 0) - 1;
+        }
+        return new BesselJResult(MathArrays.copyOf(b, b.length), ncalc);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/special/Beta.java b/src/main/java/org/apache/commons/math3/special/Beta.java
new file mode 100644
index 0000000..2f6b6da
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/special/Beta.java
@@ -0,0 +1,478 @@
+/*
+ * 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.math3.special;
+
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.util.ContinuedFraction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * This is a utility class that provides computation methods related to the Beta family of
+ * functions.
+ *
+ * <p>Implementation of {@link #logBeta(double, double)} is based on the algorithms described in
+ *
+ * <ul>
+ *   <li><a href="http://dx.doi.org/10.1145/22721.23109">Didonato and Morris (1986)</a>,
+ *       <em>Computation of the Incomplete Gamma Function Ratios and their Inverse</em>, TOMS 12(4),
+ *       377-393,
+ *   <li><a href="http://dx.doi.org/10.1145/131766.131776">Didonato and Morris (1992)</a>,
+ *       <em>Algorithm 708: Significant Digit Computation of the Incomplete Beta Function
+ *       Ratios</em>, TOMS 18(3), 360-373,
+ * </ul>
+ *
+ * and implemented in the <a href="http://www.dtic.mil/docs/citations/ADA476840">NSWC Library of
+ * Mathematical Functions</a>, available <a
+ * href="http://www.ualberta.ca/CNS/RESEARCH/Software/NumericalNSWC/site.html">here</a>. This
+ * library is "approved for public release", and the <a
+ * href="http://www.dtic.mil/dtic/pdf/announcements/CopyrightGuidance.pdf">Copyright guidance</a>
+ * indicates that unless otherwise stated in the code, all FORTRAN functions in this library are
+ * license free. Since no such notice appears in the code these functions can safely be ported to
+ * Commons-Math.
+ */
+public class Beta {
+    /** Maximum allowed numerical error. */
+    private static final double DEFAULT_EPSILON = 1E-14;
+
+    /** The constant value of ½log 2π. */
+    private static final double HALF_LOG_TWO_PI = .9189385332046727;
+
+    /**
+     * <p>
+     * The coefficients of the series expansion of the Δ function. This function
+     * is defined as follows
+     * </p>
+     * <center>Δ(x) = log Γ(x) - (x - 0.5) log a + a - 0.5 log 2π,</center>
+     * <p>
+     * see equation (23) in Didonato and Morris (1992). The series expansion,
+     * which applies for x ≥ 10, reads
+     * </p>
+     * <pre>
+     *                 14
+     *                ====
+     *             1  \                2 n
+     *     Δ(x) = ---  >    d  (10 / x)
+     *             x  /      n
+     *                ====
+     *                n = 0
+     * <pre>
+     */
+    private static final double[] DELTA = {
+        .833333333333333333333333333333E-01,
+        -.277777777777777777777777752282E-04,
+        .793650793650793650791732130419E-07,
+        -.595238095238095232389839236182E-09,
+        .841750841750832853294451671990E-11,
+        -.191752691751854612334149171243E-12,
+        .641025640510325475730918472625E-14,
+        -.295506514125338232839867823991E-15,
+        .179643716359402238723287696452E-16,
+        -.139228964661627791231203060395E-17,
+        .133802855014020915603275339093E-18,
+        -.154246009867966094273710216533E-19,
+        .197701992980957427278370133333E-20,
+        -.234065664793997056856992426667E-21,
+        .171348014966398575409015466667E-22
+    };
+
+    /** Default constructor. Prohibit instantiation. */
+    private Beta() {}
+
+    /**
+     * Returns the <a href="http://mathworld.wolfram.com/RegularizedBetaFunction.html">regularized
+     * beta function</a> I(x, a, b).
+     *
+     * @param x Value.
+     * @param a Parameter {@code a}.
+     * @param b Parameter {@code b}.
+     * @return the regularized beta function I(x, a, b).
+     * @throws org.apache.commons.math3.exception.MaxCountExceededException if the algorithm fails
+     *     to converge.
+     */
+    public static double regularizedBeta(double x, double a, double b) {
+        return regularizedBeta(x, a, b, DEFAULT_EPSILON, Integer.MAX_VALUE);
+    }
+
+    /**
+     * Returns the <a href="http://mathworld.wolfram.com/RegularizedBetaFunction.html">regularized
+     * beta function</a> I(x, a, b).
+     *
+     * @param x Value.
+     * @param a Parameter {@code a}.
+     * @param b Parameter {@code b}.
+     * @param epsilon When the absolute value of the nth item in the series is less than epsilon the
+     *     approximation ceases to calculate further elements in the series.
+     * @return the regularized beta function I(x, a, b)
+     * @throws org.apache.commons.math3.exception.MaxCountExceededException if the algorithm fails
+     *     to converge.
+     */
+    public static double regularizedBeta(double x, double a, double b, double epsilon) {
+        return regularizedBeta(x, a, b, epsilon, Integer.MAX_VALUE);
+    }
+
+    /**
+     * Returns the regularized beta function I(x, a, b).
+     *
+     * @param x the value.
+     * @param a Parameter {@code a}.
+     * @param b Parameter {@code b}.
+     * @param maxIterations Maximum number of "iterations" to complete.
+     * @return the regularized beta function I(x, a, b)
+     * @throws org.apache.commons.math3.exception.MaxCountExceededException if the algorithm fails
+     *     to converge.
+     */
+    public static double regularizedBeta(double x, double a, double b, int maxIterations) {
+        return regularizedBeta(x, a, b, DEFAULT_EPSILON, maxIterations);
+    }
+
+    /**
+     * Returns the regularized beta function I(x, a, b).
+     *
+     * <p>The implementation of this method is based on:
+     *
+     * <ul>
+     *   <li><a href="http://mathworld.wolfram.com/RegularizedBetaFunction.html">Regularized Beta
+     *       Function</a>.
+     *   <li><a href="http://functions.wolfram.com/06.21.10.0001.01">Regularized Beta Function</a>.
+     * </ul>
+     *
+     * @param x the value.
+     * @param a Parameter {@code a}.
+     * @param b Parameter {@code b}.
+     * @param epsilon When the absolute value of the nth item in the series is less than epsilon the
+     *     approximation ceases to calculate further elements in the series.
+     * @param maxIterations Maximum number of "iterations" to complete.
+     * @return the regularized beta function I(x, a, b)
+     * @throws org.apache.commons.math3.exception.MaxCountExceededException if the algorithm fails
+     *     to converge.
+     */
+    public static double regularizedBeta(
+            double x, final double a, final double b, double epsilon, int maxIterations) {
+        double ret;
+
+        if (Double.isNaN(x)
+                || Double.isNaN(a)
+                || Double.isNaN(b)
+                || x < 0
+                || x > 1
+                || a <= 0
+                || b <= 0) {
+            ret = Double.NaN;
+        } else if (x > (a + 1) / (2 + b + a) && 1 - x <= (b + 1) / (2 + b + a)) {
+            ret = 1 - regularizedBeta(1 - x, b, a, epsilon, maxIterations);
+        } else {
+            ContinuedFraction fraction =
+                    new ContinuedFraction() {
+
+                        /** {@inheritDoc} */
+                        @Override
+                        protected double getB(int n, double x) {
+                            double ret;
+                            double m;
+                            if (n % 2 == 0) { // even
+                                m = n / 2.0;
+                                ret = (m * (b - m) * x) / ((a + (2 * m) - 1) * (a + (2 * m)));
+                            } else {
+                                m = (n - 1.0) / 2.0;
+                                ret =
+                                        -((a + m) * (a + b + m) * x)
+                                                / ((a + (2 * m)) * (a + (2 * m) + 1.0));
+                            }
+                            return ret;
+                        }
+
+                        /** {@inheritDoc} */
+                        @Override
+                        protected double getA(int n, double x) {
+                            return 1.0;
+                        }
+                    };
+            ret =
+                    FastMath.exp(
+                                    (a * FastMath.log(x))
+                                            + (b * FastMath.log1p(-x))
+                                            - FastMath.log(a)
+                                            - logBeta(a, b))
+                            * 1.0
+                            / fraction.evaluate(x, epsilon, maxIterations);
+        }
+
+        return ret;
+    }
+
+    /**
+     * Returns the natural logarithm of the beta function B(a, b).
+     *
+     * <p>The implementation of this method is based on:
+     *
+     * <ul>
+     *   <li><a href="http://mathworld.wolfram.com/BetaFunction.html">Beta Function</a>, equation
+     *       (1).
+     * </ul>
+     *
+     * @param a Parameter {@code a}.
+     * @param b Parameter {@code b}.
+     * @param epsilon This parameter is ignored.
+     * @param maxIterations This parameter is ignored.
+     * @return log(B(a, b)).
+     * @deprecated as of version 3.1, this method is deprecated as the computation of the beta
+     *     function is no longer iterative; it will be removed in version 4.0. Current
+     *     implementation of this method internally calls {@link #logBeta(double, double)}.
+     */
+    @Deprecated
+    public static double logBeta(double a, double b, double epsilon, int maxIterations) {
+
+        return logBeta(a, b);
+    }
+
+    /**
+     * Returns the value of log Γ(a + b) for 1 ≤ a, b ≤ 2. Based on the <em>NSWC Library of
+     * Mathematics Subroutines</em> double precision implementation, {@code DGSMLN}. In {@code
+     * BetaTest.testLogGammaSum()}, this private method is accessed through reflection.
+     *
+     * @param a First argument.
+     * @param b Second argument.
+     * @return the value of {@code log(Gamma(a + b))}.
+     * @throws OutOfRangeException if {@code a} or {@code b} is lower than {@code 1.0} or greater
+     *     than {@code 2.0}.
+     */
+    private static double logGammaSum(final double a, final double b) throws OutOfRangeException {
+
+        if ((a < 1.0) || (a > 2.0)) {
+            throw new OutOfRangeException(a, 1.0, 2.0);
+        }
+        if ((b < 1.0) || (b > 2.0)) {
+            throw new OutOfRangeException(b, 1.0, 2.0);
+        }
+
+        final double x = (a - 1.0) + (b - 1.0);
+        if (x <= 0.5) {
+            return Gamma.logGamma1p(1.0 + x);
+        } else if (x <= 1.5) {
+            return Gamma.logGamma1p(x) + FastMath.log1p(x);
+        } else {
+            return Gamma.logGamma1p(x - 1.0) + FastMath.log(x * (1.0 + x));
+        }
+    }
+
+    /**
+     * Returns the value of log[Γ(b) / Γ(a + b)] for a ≥ 0 and b ≥ 10. Based on the <em>NSWC Library
+     * of Mathematics Subroutines</em> double precision implementation, {@code DLGDIV}. In {@code
+     * BetaTest.testLogGammaMinusLogGammaSum()}, this private method is accessed through reflection.
+     *
+     * @param a First argument.
+     * @param b Second argument.
+     * @return the value of {@code log(Gamma(b) / Gamma(a + b))}.
+     * @throws NumberIsTooSmallException if {@code a < 0.0} or {@code b < 10.0}.
+     */
+    private static double logGammaMinusLogGammaSum(final double a, final double b)
+            throws NumberIsTooSmallException {
+
+        if (a < 0.0) {
+            throw new NumberIsTooSmallException(a, 0.0, true);
+        }
+        if (b < 10.0) {
+            throw new NumberIsTooSmallException(b, 10.0, true);
+        }
+
+        /*
+         * d = a + b - 0.5
+         */
+        final double d;
+        final double w;
+        if (a <= b) {
+            d = b + (a - 0.5);
+            w = deltaMinusDeltaSum(a, b);
+        } else {
+            d = a + (b - 0.5);
+            w = deltaMinusDeltaSum(b, a);
+        }
+
+        final double u = d * FastMath.log1p(a / b);
+        final double v = a * (FastMath.log(b) - 1.0);
+
+        return u <= v ? (w - u) - v : (w - v) - u;
+    }
+
+    /**
+     * Returns the value of Δ(b) - Δ(a + b), with 0 ≤ a ≤ b and b ≥ 10. Based on equations (26),
+     * (27) and (28) in Didonato and Morris (1992).
+     *
+     * @param a First argument.
+     * @param b Second argument.
+     * @return the value of {@code Delta(b) - Delta(a + b)}
+     * @throws OutOfRangeException if {@code a < 0} or {@code a > b}
+     * @throws NumberIsTooSmallException if {@code b < 10}
+     */
+    private static double deltaMinusDeltaSum(final double a, final double b)
+            throws OutOfRangeException, NumberIsTooSmallException {
+
+        if ((a < 0) || (a > b)) {
+            throw new OutOfRangeException(a, 0, b);
+        }
+        if (b < 10) {
+            throw new NumberIsTooSmallException(b, 10, true);
+        }
+
+        final double h = a / b;
+        final double p = h / (1.0 + h);
+        final double q = 1.0 / (1.0 + h);
+        final double q2 = q * q;
+        /*
+         * s[i] = 1 + q + ... - q**(2 * i)
+         */
+        final double[] s = new double[DELTA.length];
+        s[0] = 1.0;
+        for (int i = 1; i < s.length; i++) {
+            s[i] = 1.0 + (q + q2 * s[i - 1]);
+        }
+        /*
+         * w = Delta(b) - Delta(a + b)
+         */
+        final double sqrtT = 10.0 / b;
+        final double t = sqrtT * sqrtT;
+        double w = DELTA[DELTA.length - 1] * s[s.length - 1];
+        for (int i = DELTA.length - 2; i >= 0; i--) {
+            w = t * w + DELTA[i] * s[i];
+        }
+        return w * p / b;
+    }
+
+    /**
+     * Returns the value of Δ(p) + Δ(q) - Δ(p + q), with p, q ≥ 10. Based on the <em>NSWC Library of
+     * Mathematics Subroutines</em> double precision implementation, {@code DBCORR}. In {@code
+     * BetaTest.testSumDeltaMinusDeltaSum()}, this private method is accessed through reflection.
+     *
+     * @param p First argument.
+     * @param q Second argument.
+     * @return the value of {@code Delta(p) + Delta(q) - Delta(p + q)}.
+     * @throws NumberIsTooSmallException if {@code p < 10.0} or {@code q < 10.0}.
+     */
+    private static double sumDeltaMinusDeltaSum(final double p, final double q) {
+
+        if (p < 10.0) {
+            throw new NumberIsTooSmallException(p, 10.0, true);
+        }
+        if (q < 10.0) {
+            throw new NumberIsTooSmallException(q, 10.0, true);
+        }
+
+        final double a = FastMath.min(p, q);
+        final double b = FastMath.max(p, q);
+        final double sqrtT = 10.0 / a;
+        final double t = sqrtT * sqrtT;
+        double z = DELTA[DELTA.length - 1];
+        for (int i = DELTA.length - 2; i >= 0; i--) {
+            z = t * z + DELTA[i];
+        }
+        return z / a + deltaMinusDeltaSum(a, b);
+    }
+
+    /**
+     * Returns the value of log B(p, q) for 0 ≤ x ≤ 1 and p, q > 0. Based on the <em>NSWC Library of
+     * Mathematics Subroutines</em> implementation, {@code DBETLN}.
+     *
+     * @param p First argument.
+     * @param q Second argument.
+     * @return the value of {@code log(Beta(p, q))}, {@code NaN} if {@code p <= 0} or {@code q <=
+     *     0}.
+     */
+    public static double logBeta(final double p, final double q) {
+        if (Double.isNaN(p) || Double.isNaN(q) || (p <= 0.0) || (q <= 0.0)) {
+            return Double.NaN;
+        }
+
+        final double a = FastMath.min(p, q);
+        final double b = FastMath.max(p, q);
+        if (a >= 10.0) {
+            final double w = sumDeltaMinusDeltaSum(a, b);
+            final double h = a / b;
+            final double c = h / (1.0 + h);
+            final double u = -(a - 0.5) * FastMath.log(c);
+            final double v = b * FastMath.log1p(h);
+            if (u <= v) {
+                return (((-0.5 * FastMath.log(b) + HALF_LOG_TWO_PI) + w) - u) - v;
+            } else {
+                return (((-0.5 * FastMath.log(b) + HALF_LOG_TWO_PI) + w) - v) - u;
+            }
+        } else if (a > 2.0) {
+            if (b > 1000.0) {
+                final int n = (int) FastMath.floor(a - 1.0);
+                double prod = 1.0;
+                double ared = a;
+                for (int i = 0; i < n; i++) {
+                    ared -= 1.0;
+                    prod *= ared / (1.0 + ared / b);
+                }
+                return (FastMath.log(prod) - n * FastMath.log(b))
+                        + (Gamma.logGamma(ared) + logGammaMinusLogGammaSum(ared, b));
+            } else {
+                double prod1 = 1.0;
+                double ared = a;
+                while (ared > 2.0) {
+                    ared -= 1.0;
+                    final double h = ared / b;
+                    prod1 *= h / (1.0 + h);
+                }
+                if (b < 10.0) {
+                    double prod2 = 1.0;
+                    double bred = b;
+                    while (bred > 2.0) {
+                        bred -= 1.0;
+                        prod2 *= bred / (ared + bred);
+                    }
+                    return FastMath.log(prod1)
+                            + FastMath.log(prod2)
+                            + (Gamma.logGamma(ared)
+                                    + (Gamma.logGamma(bred) - logGammaSum(ared, bred)));
+                } else {
+                    return FastMath.log(prod1)
+                            + Gamma.logGamma(ared)
+                            + logGammaMinusLogGammaSum(ared, b);
+                }
+            }
+        } else if (a >= 1.0) {
+            if (b > 2.0) {
+                if (b < 10.0) {
+                    double prod = 1.0;
+                    double bred = b;
+                    while (bred > 2.0) {
+                        bred -= 1.0;
+                        prod *= bred / (a + bred);
+                    }
+                    return FastMath.log(prod)
+                            + (Gamma.logGamma(a) + (Gamma.logGamma(bred) - logGammaSum(a, bred)));
+                } else {
+                    return Gamma.logGamma(a) + logGammaMinusLogGammaSum(a, b);
+                }
+            } else {
+                return Gamma.logGamma(a) + Gamma.logGamma(b) - logGammaSum(a, b);
+            }
+        } else {
+            if (b >= 10.0) {
+                return Gamma.logGamma(a) + logGammaMinusLogGammaSum(a, b);
+            } else {
+                // The following command is the original NSWC implementation.
+                // return Gamma.logGamma(a) +
+                // (Gamma.logGamma(b) - Gamma.logGamma(a + b));
+                // The following command turns out to be more accurate.
+                return FastMath.log(Gamma.gamma(a) * Gamma.gamma(b) / Gamma.gamma(a + b));
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/special/Erf.java b/src/main/java/org/apache/commons/math3/special/Erf.java
new file mode 100644
index 0000000..325b15c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/special/Erf.java
@@ -0,0 +1,227 @@
+/*
+ * 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.math3.special;
+
+import org.apache.commons.math3.util.FastMath;
+
+/** This is a utility class that provides computation methods related to the error functions. */
+public class Erf {
+
+    /**
+     * The number {@code X_CRIT} is used by {@link #erf(double, double)} internally. This number
+     * solves {@code erf(x)=0.5} within 1ulp. More precisely, the current implementations of {@link
+     * #erf(double)} and {@link #erfc(double)} satisfy:<br>
+     * {@code erf(X_CRIT) < 0.5},<br>
+     * {@code erf(Math.nextUp(X_CRIT) > 0.5},<br>
+     * {@code erfc(X_CRIT) = 0.5}, and<br>
+     * {@code erfc(Math.nextUp(X_CRIT) < 0.5}
+     */
+    private static final double X_CRIT = 0.4769362762044697;
+
+    /** Default constructor. Prohibit instantiation. */
+    private Erf() {}
+
+    /**
+     * Returns the error function.
+     *
+     * <p>erf(x) = 2/&radic;&pi; <sub>0</sub>&int;<sup>x</sup> e<sup>-t<sup>2</sup></sup>dt
+     *
+     * <p>This implementation computes erf(x) using the {@link Gamma#regularizedGammaP(double,
+     * double, double, int) regularized gamma function}, following <a
+     * href="http://mathworld.wolfram.com/Erf.html">Erf</a>, equation (3)
+     *
+     * <p>The value returned is always between -1 and 1 (inclusive). If {@code abs(x) > 40}, then
+     * {@code erf(x)} is indistinguishable from either 1 or -1 as a double, so the appropriate
+     * extreme value is returned.
+     *
+     * @param x the value.
+     * @return the error function erf(x)
+     * @throws org.apache.commons.math3.exception.MaxCountExceededException if the algorithm fails
+     *     to converge.
+     * @see Gamma#regularizedGammaP(double, double, double, int)
+     */
+    public static double erf(double x) {
+        if (FastMath.abs(x) > 40) {
+            return x > 0 ? 1 : -1;
+        }
+        final double ret = Gamma.regularizedGammaP(0.5, x * x, 1.0e-15, 10000);
+        return x < 0 ? -ret : ret;
+    }
+
+    /**
+     * Returns the complementary error function.
+     *
+     * <p>erfc(x) = 2/&radic;&pi; <sub>x</sub>&int;<sup>&infin;</sup> e<sup>-t<sup>2</sup></sup>dt
+     * <br>
+     * = 1 - {@link #erf(double) erf(x)}
+     *
+     * <p>This implementation computes erfc(x) using the {@link Gamma#regularizedGammaQ(double,
+     * double, double, int) regularized gamma function}, following <a
+     * href="http://mathworld.wolfram.com/Erf.html">Erf</a>, equation (3).
+     *
+     * <p>The value returned is always between 0 and 2 (inclusive). If {@code abs(x) > 40}, then
+     * {@code erf(x)} is indistinguishable from either 0 or 2 as a double, so the appropriate
+     * extreme value is returned.
+     *
+     * @param x the value
+     * @return the complementary error function erfc(x)
+     * @throws org.apache.commons.math3.exception.MaxCountExceededException if the algorithm fails
+     *     to converge.
+     * @see Gamma#regularizedGammaQ(double, double, double, int)
+     * @since 2.2
+     */
+    public static double erfc(double x) {
+        if (FastMath.abs(x) > 40) {
+            return x > 0 ? 0 : 2;
+        }
+        final double ret = Gamma.regularizedGammaQ(0.5, x * x, 1.0e-15, 10000);
+        return x < 0 ? 2 - ret : ret;
+    }
+
+    /**
+     * Returns the difference between erf(x1) and erf(x2).
+     *
+     * <p>The implementation uses either erf(double) or erfc(double) depending on which provides the
+     * most precise result.
+     *
+     * @param x1 the first value
+     * @param x2 the second value
+     * @return erf(x2) - erf(x1)
+     */
+    public static double erf(double x1, double x2) {
+        if (x1 > x2) {
+            return -erf(x2, x1);
+        }
+
+        return x1 < -X_CRIT
+                ? x2 < 0.0 ? erfc(-x2) - erfc(-x1) : erf(x2) - erf(x1)
+                : x2 > X_CRIT && x1 > 0.0 ? erfc(x1) - erfc(x2) : erf(x2) - erf(x1);
+    }
+
+    /**
+     * Returns the inverse erf.
+     *
+     * <p>This implementation is described in the paper: <a
+     * href="http://people.maths.ox.ac.uk/gilesm/files/gems_erfinv.pdf">Approximating the erfinv
+     * function</a> by Mike Giles, Oxford-Man Institute of Quantitative Finance, which was published
+     * in GPU Computing Gems, volume 2, 2010. The source code is available <a
+     * href="http://gpucomputing.net/?q=node/1828">here</a>.
+     *
+     * @param x the value
+     * @return t such that x = erf(t)
+     * @since 3.2
+     */
+    public static double erfInv(final double x) {
+
+        // beware that the logarithm argument must be
+        // commputed as (1.0 - x) * (1.0 + x),
+        // it must NOT be simplified as 1.0 - x * x as this
+        // would induce rounding errors near the boundaries +/-1
+        double w = -FastMath.log((1.0 - x) * (1.0 + x));
+        double p;
+
+        if (w < 6.25) {
+            w -= 3.125;
+            p = -3.6444120640178196996e-21;
+            p = -1.685059138182016589e-19 + p * w;
+            p = 1.2858480715256400167e-18 + p * w;
+            p = 1.115787767802518096e-17 + p * w;
+            p = -1.333171662854620906e-16 + p * w;
+            p = 2.0972767875968561637e-17 + p * w;
+            p = 6.6376381343583238325e-15 + p * w;
+            p = -4.0545662729752068639e-14 + p * w;
+            p = -8.1519341976054721522e-14 + p * w;
+            p = 2.6335093153082322977e-12 + p * w;
+            p = -1.2975133253453532498e-11 + p * w;
+            p = -5.4154120542946279317e-11 + p * w;
+            p = 1.051212273321532285e-09 + p * w;
+            p = -4.1126339803469836976e-09 + p * w;
+            p = -2.9070369957882005086e-08 + p * w;
+            p = 4.2347877827932403518e-07 + p * w;
+            p = -1.3654692000834678645e-06 + p * w;
+            p = -1.3882523362786468719e-05 + p * w;
+            p = 0.0001867342080340571352 + p * w;
+            p = -0.00074070253416626697512 + p * w;
+            p = -0.0060336708714301490533 + p * w;
+            p = 0.24015818242558961693 + p * w;
+            p = 1.6536545626831027356 + p * w;
+        } else if (w < 16.0) {
+            w = FastMath.sqrt(w) - 3.25;
+            p = 2.2137376921775787049e-09;
+            p = 9.0756561938885390979e-08 + p * w;
+            p = -2.7517406297064545428e-07 + p * w;
+            p = 1.8239629214389227755e-08 + p * w;
+            p = 1.5027403968909827627e-06 + p * w;
+            p = -4.013867526981545969e-06 + p * w;
+            p = 2.9234449089955446044e-06 + p * w;
+            p = 1.2475304481671778723e-05 + p * w;
+            p = -4.7318229009055733981e-05 + p * w;
+            p = 6.8284851459573175448e-05 + p * w;
+            p = 2.4031110387097893999e-05 + p * w;
+            p = -0.0003550375203628474796 + p * w;
+            p = 0.00095328937973738049703 + p * w;
+            p = -0.0016882755560235047313 + p * w;
+            p = 0.0024914420961078508066 + p * w;
+            p = -0.0037512085075692412107 + p * w;
+            p = 0.005370914553590063617 + p * w;
+            p = 1.0052589676941592334 + p * w;
+            p = 3.0838856104922207635 + p * w;
+        } else if (!Double.isInfinite(w)) {
+            w = FastMath.sqrt(w) - 5.0;
+            p = -2.7109920616438573243e-11;
+            p = -2.5556418169965252055e-10 + p * w;
+            p = 1.5076572693500548083e-09 + p * w;
+            p = -3.7894654401267369937e-09 + p * w;
+            p = 7.6157012080783393804e-09 + p * w;
+            p = -1.4960026627149240478e-08 + p * w;
+            p = 2.9147953450901080826e-08 + p * w;
+            p = -6.7711997758452339498e-08 + p * w;
+            p = 2.2900482228026654717e-07 + p * w;
+            p = -9.9298272942317002539e-07 + p * w;
+            p = 4.5260625972231537039e-06 + p * w;
+            p = -1.9681778105531670567e-05 + p * w;
+            p = 7.5995277030017761139e-05 + p * w;
+            p = -0.00021503011930044477347 + p * w;
+            p = -0.00013871931833623122026 + p * w;
+            p = 1.0103004648645343977 + p * w;
+            p = 4.8499064014085844221 + p * w;
+        } else {
+            // this branch does not appears in the original code, it
+            // was added because the previous branch does not handle
+            // x = +/-1 correctly. In this case, w is positive infinity
+            // and as the first coefficient (-2.71e-11) is negative.
+            // Once the first multiplication is done, p becomes negative
+            // infinity and remains so throughout the polynomial evaluation.
+            // So the branch above incorrectly returns negative infinity
+            // instead of the correct positive infinity.
+            p = Double.POSITIVE_INFINITY;
+        }
+
+        return p * x;
+    }
+
+    /**
+     * Returns the inverse erfc.
+     *
+     * @param x the value
+     * @return t such that x = erfc(t)
+     * @since 3.2
+     */
+    public static double erfcInv(final double x) {
+        return erfInv(1 - x);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/special/Gamma.java b/src/main/java/org/apache/commons/math3/special/Gamma.java
new file mode 100644
index 0000000..8f7e248
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/special/Gamma.java
@@ -0,0 +1,692 @@
+/*
+ * 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.math3.special;
+
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.util.ContinuedFraction;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * This is a utility class that provides computation methods related to the &Gamma; (Gamma) family
+ * of functions.
+ *
+ * <p>Implementation of {@link #invGamma1pm1(double)} and {@link #logGamma1p(double)} is based on
+ * the algorithms described in
+ *
+ * <ul>
+ *   <li><a href="http://dx.doi.org/10.1145/22721.23109">Didonato and Morris (1986)</a>,
+ *       <em>Computation of the Incomplete Gamma Function Ratios and their Inverse</em>, TOMS 12(4),
+ *       377-393,
+ *   <li><a href="http://dx.doi.org/10.1145/131766.131776">Didonato and Morris (1992)</a>,
+ *       <em>Algorithm 708: Significant Digit Computation of the Incomplete Beta Function
+ *       Ratios</em>, TOMS 18(3), 360-373,
+ * </ul>
+ *
+ * and implemented in the <a href="http://www.dtic.mil/docs/citations/ADA476840">NSWC Library of
+ * Mathematical Functions</a>, available <a
+ * href="http://www.ualberta.ca/CNS/RESEARCH/Software/NumericalNSWC/site.html">here</a>. This
+ * library is "approved for public release", and the <a
+ * href="http://www.dtic.mil/dtic/pdf/announcements/CopyrightGuidance.pdf">Copyright guidance</a>
+ * indicates that unless otherwise stated in the code, all FORTRAN functions in this library are
+ * license free. Since no such notice appears in the code these functions can safely be ported to
+ * Commons-Math.
+ */
+public class Gamma {
+    /**
+     * <a href="http://en.wikipedia.org/wiki/Euler-Mascheroni_constant">Euler-Mascheroni
+     * constant</a>
+     *
+     * @since 2.0
+     */
+    public static final double GAMMA = 0.577215664901532860606512090082;
+
+    /**
+     * The value of the {@code g} constant in the Lanczos approximation, see {@link
+     * #lanczos(double)}.
+     *
+     * @since 3.1
+     */
+    public static final double LANCZOS_G = 607.0 / 128.0;
+
+    /** Maximum allowed numerical error. */
+    private static final double DEFAULT_EPSILON = 10e-15;
+
+    /** Lanczos coefficients */
+    private static final double[] LANCZOS = {
+        0.99999999999999709182,
+        57.156235665862923517,
+        -59.597960355475491248,
+        14.136097974741747174,
+        -0.49191381609762019978,
+        .33994649984811888699e-4,
+        .46523628927048575665e-4,
+        -.98374475304879564677e-4,
+        .15808870322491248884e-3,
+        -.21026444172410488319e-3,
+        .21743961811521264320e-3,
+        -.16431810653676389022e-3,
+        .84418223983852743293e-4,
+        -.26190838401581408670e-4,
+        .36899182659531622704e-5,
+    };
+
+    /** Avoid repeated computation of log of 2 PI in logGamma */
+    private static final double HALF_LOG_2_PI = 0.5 * FastMath.log(2.0 * FastMath.PI);
+
+    /** The constant value of &radic;(2&pi;). */
+    private static final double SQRT_TWO_PI = 2.506628274631000502;
+
+    // limits for switching algorithm in digamma
+    /** C limit. */
+    private static final double C_LIMIT = 49;
+
+    /** S limit. */
+    private static final double S_LIMIT = 1e-5;
+
+    /*
+     * Constants for the computation of double invGamma1pm1(double).
+     * Copied from DGAM1 in the NSWC library.
+     */
+
+    /** The constant {@code A0} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_A0 = .611609510448141581788E-08;
+
+    /** The constant {@code A1} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_A1 = .624730830116465516210E-08;
+
+    /** The constant {@code B1} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_B1 = .203610414066806987300E+00;
+
+    /** The constant {@code B2} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_B2 = .266205348428949217746E-01;
+
+    /** The constant {@code B3} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_B3 = .493944979382446875238E-03;
+
+    /** The constant {@code B4} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_B4 = -.851419432440314906588E-05;
+
+    /** The constant {@code B5} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_B5 = -.643045481779353022248E-05;
+
+    /** The constant {@code B6} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_B6 = .992641840672773722196E-06;
+
+    /** The constant {@code B7} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_B7 = -.607761895722825260739E-07;
+
+    /** The constant {@code B8} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_B8 = .195755836614639731882E-09;
+
+    /** The constant {@code P0} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_P0 = .6116095104481415817861E-08;
+
+    /** The constant {@code P1} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_P1 = .6871674113067198736152E-08;
+
+    /** The constant {@code P2} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_P2 = .6820161668496170657918E-09;
+
+    /** The constant {@code P3} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_P3 = .4686843322948848031080E-10;
+
+    /** The constant {@code P4} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_P4 = .1572833027710446286995E-11;
+
+    /** The constant {@code P5} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_P5 = -.1249441572276366213222E-12;
+
+    /** The constant {@code P6} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_P6 = .4343529937408594255178E-14;
+
+    /** The constant {@code Q1} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_Q1 = .3056961078365221025009E+00;
+
+    /** The constant {@code Q2} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_Q2 = .5464213086042296536016E-01;
+
+    /** The constant {@code Q3} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_Q3 = .4956830093825887312020E-02;
+
+    /** The constant {@code Q4} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_Q4 = .2692369466186361192876E-03;
+
+    /** The constant {@code C} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_C = -.422784335098467139393487909917598E+00;
+
+    /** The constant {@code C0} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_C0 = .577215664901532860606512090082402E+00;
+
+    /** The constant {@code C1} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_C1 = -.655878071520253881077019515145390E+00;
+
+    /** The constant {@code C2} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_C2 = -.420026350340952355290039348754298E-01;
+
+    /** The constant {@code C3} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_C3 = .166538611382291489501700795102105E+00;
+
+    /** The constant {@code C4} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_C4 = -.421977345555443367482083012891874E-01;
+
+    /** The constant {@code C5} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_C5 = -.962197152787697356211492167234820E-02;
+
+    /** The constant {@code C6} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_C6 = .721894324666309954239501034044657E-02;
+
+    /** The constant {@code C7} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_C7 = -.116516759185906511211397108401839E-02;
+
+    /** The constant {@code C8} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_C8 = -.215241674114950972815729963053648E-03;
+
+    /** The constant {@code C9} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_C9 = .128050282388116186153198626328164E-03;
+
+    /** The constant {@code C10} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_C10 = -.201348547807882386556893914210218E-04;
+
+    /** The constant {@code C11} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_C11 = -.125049348214267065734535947383309E-05;
+
+    /** The constant {@code C12} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_C12 = .113302723198169588237412962033074E-05;
+
+    /** The constant {@code C13} defined in {@code DGAM1}. */
+    private static final double INV_GAMMA1P_M1_C13 = -.205633841697760710345015413002057E-06;
+
+    /** Default constructor. Prohibit instantiation. */
+    private Gamma() {}
+
+    /**
+     * Returns the value of log&nbsp;&Gamma;(x) for x&nbsp;&gt;&nbsp;0.
+     *
+     * <p>For x &le; 8, the implementation is based on the double precision implementation in the
+     * <em>NSWC Library of Mathematics Subroutines</em>, {@code DGAMLN}. For x &gt; 8, the
+     * implementation is based on
+     *
+     * <ul>
+     *   <li><a href="http://mathworld.wolfram.com/GammaFunction.html">Gamma Function</a>, equation
+     *       (28).
+     *   <li><a href="http://mathworld.wolfram.com/LanczosApproximation.html">Lanczos
+     *       Approximation</a>, equations (1) through (5).
+     *   <li><a href="http://my.fit.edu/~gabdo/gamma.txt">Paul Godfrey, A note on the computation of
+     *       the convergent Lanczos complex Gamma approximation</a>
+     * </ul>
+     *
+     * @param x Argument.
+     * @return the value of {@code log(Gamma(x))}, {@code Double.NaN} if {@code x <= 0.0}.
+     */
+    public static double logGamma(double x) {
+        double ret;
+
+        if (Double.isNaN(x) || (x <= 0.0)) {
+            ret = Double.NaN;
+        } else if (x < 0.5) {
+            return logGamma1p(x) - FastMath.log(x);
+        } else if (x <= 2.5) {
+            return logGamma1p((x - 0.5) - 0.5);
+        } else if (x <= 8.0) {
+            final int n = (int) FastMath.floor(x - 1.5);
+            double prod = 1.0;
+            for (int i = 1; i <= n; i++) {
+                prod *= x - i;
+            }
+            return logGamma1p(x - (n + 1)) + FastMath.log(prod);
+        } else {
+            double sum = lanczos(x);
+            double tmp = x + LANCZOS_G + .5;
+            ret = ((x + .5) * FastMath.log(tmp)) - tmp + HALF_LOG_2_PI + FastMath.log(sum / x);
+        }
+
+        return ret;
+    }
+
+    /**
+     * Returns the regularized gamma function P(a, x).
+     *
+     * @param a Parameter.
+     * @param x Value.
+     * @return the regularized gamma function P(a, x).
+     * @throws MaxCountExceededException if the algorithm fails to converge.
+     */
+    public static double regularizedGammaP(double a, double x) {
+        return regularizedGammaP(a, x, DEFAULT_EPSILON, Integer.MAX_VALUE);
+    }
+
+    /**
+     * Returns the regularized gamma function P(a, x).
+     *
+     * <p>The implementation of this method is based on:
+     *
+     * <ul>
+     *   <li><a href="http://mathworld.wolfram.com/RegularizedGammaFunction.html">Regularized Gamma
+     *       Function</a>, equation (1)
+     *   <li><a href="http://mathworld.wolfram.com/IncompleteGammaFunction.html">Incomplete Gamma
+     *       Function</a>, equation (4).
+     *   <li><a
+     *       href="http://mathworld.wolfram.com/ConfluentHypergeometricFunctionoftheFirstKind.html">
+     *       Confluent Hypergeometric Function of the First Kind</a>, equation (1).
+     * </ul>
+     *
+     * @param a the a parameter.
+     * @param x the value.
+     * @param epsilon When the absolute value of the nth item in the series is less than epsilon the
+     *     approximation ceases to calculate further elements in the series.
+     * @param maxIterations Maximum number of "iterations" to complete.
+     * @return the regularized gamma function P(a, x)
+     * @throws MaxCountExceededException if the algorithm fails to converge.
+     */
+    public static double regularizedGammaP(double a, double x, double epsilon, int maxIterations) {
+        double ret;
+
+        if (Double.isNaN(a) || Double.isNaN(x) || (a <= 0.0) || (x < 0.0)) {
+            ret = Double.NaN;
+        } else if (x == 0.0) {
+            ret = 0.0;
+        } else if (x >= a + 1) {
+            // use regularizedGammaQ because it should converge faster in this
+            // case.
+            ret = 1.0 - regularizedGammaQ(a, x, epsilon, maxIterations);
+        } else {
+            // calculate series
+            double n = 0.0; // current element index
+            double an = 1.0 / a; // n-th element in the series
+            double sum = an; // partial sum
+            while (FastMath.abs(an / sum) > epsilon
+                    && n < maxIterations
+                    && sum < Double.POSITIVE_INFINITY) {
+                // compute next element in the series
+                n += 1.0;
+                an *= x / (a + n);
+
+                // update partial sum
+                sum += an;
+            }
+            if (n >= maxIterations) {
+                throw new MaxCountExceededException(maxIterations);
+            } else if (Double.isInfinite(sum)) {
+                ret = 1.0;
+            } else {
+                ret = FastMath.exp(-x + (a * FastMath.log(x)) - logGamma(a)) * sum;
+            }
+        }
+
+        return ret;
+    }
+
+    /**
+     * Returns the regularized gamma function Q(a, x) = 1 - P(a, x).
+     *
+     * @param a the a parameter.
+     * @param x the value.
+     * @return the regularized gamma function Q(a, x)
+     * @throws MaxCountExceededException if the algorithm fails to converge.
+     */
+    public static double regularizedGammaQ(double a, double x) {
+        return regularizedGammaQ(a, x, DEFAULT_EPSILON, Integer.MAX_VALUE);
+    }
+
+    /**
+     * Returns the regularized gamma function Q(a, x) = 1 - P(a, x).
+     *
+     * <p>The implementation of this method is based on:
+     *
+     * <ul>
+     *   <li><a href="http://mathworld.wolfram.com/RegularizedGammaFunction.html">Regularized Gamma
+     *       Function</a>, equation (1).
+     *   <li><a href="http://functions.wolfram.com/GammaBetaErf/GammaRegularized/10/0003/">
+     *       Regularized incomplete gamma function: Continued fraction representations (formula
+     *       06.08.10.0003)</a>
+     * </ul>
+     *
+     * @param a the a parameter.
+     * @param x the value.
+     * @param epsilon When the absolute value of the nth item in the series is less than epsilon the
+     *     approximation ceases to calculate further elements in the series.
+     * @param maxIterations Maximum number of "iterations" to complete.
+     * @return the regularized gamma function P(a, x)
+     * @throws MaxCountExceededException if the algorithm fails to converge.
+     */
+    public static double regularizedGammaQ(
+            final double a, double x, double epsilon, int maxIterations) {
+        double ret;
+
+        if (Double.isNaN(a) || Double.isNaN(x) || (a <= 0.0) || (x < 0.0)) {
+            ret = Double.NaN;
+        } else if (x == 0.0) {
+            ret = 1.0;
+        } else if (x < a + 1.0) {
+            // use regularizedGammaP because it should converge faster in this
+            // case.
+            ret = 1.0 - regularizedGammaP(a, x, epsilon, maxIterations);
+        } else {
+            // create continued fraction
+            ContinuedFraction cf =
+                    new ContinuedFraction() {
+
+                        /** {@inheritDoc} */
+                        @Override
+                        protected double getA(int n, double x) {
+                            return ((2.0 * n) + 1.0) - a + x;
+                        }
+
+                        /** {@inheritDoc} */
+                        @Override
+                        protected double getB(int n, double x) {
+                            return n * (a - n);
+                        }
+                    };
+
+            ret = 1.0 / cf.evaluate(x, epsilon, maxIterations);
+            ret = FastMath.exp(-x + (a * FastMath.log(x)) - logGamma(a)) * ret;
+        }
+
+        return ret;
+    }
+
+    /**
+     * Computes the digamma function of x.
+     *
+     * <p>This is an independently written implementation of the algorithm described in Jose
+     * Bernardo, Algorithm AS 103: Psi (Digamma) Function, Applied Statistics, 1976.
+     *
+     * <p>Some of the constants have been changed to increase accuracy at the moderate expense of
+     * run-time. The result should be accurate to within 10^-8 absolute tolerance for x >= 10^-5 and
+     * within 10^-8 relative tolerance for x > 0.
+     *
+     * <p>Performance for large negative values of x will be quite expensive (proportional to |x|).
+     * Accuracy for negative values of x should be about 10^-8 absolute for results less than 10^5
+     * and 10^-8 relative for results larger than that.
+     *
+     * @param x Argument.
+     * @return digamma(x) to within 10-8 relative or absolute error whichever is smaller.
+     * @see <a href="http://en.wikipedia.org/wiki/Digamma_function">Digamma</a>
+     * @see <a href="http://www.uv.es/~bernardo/1976AppStatist.pdf">Bernardo&apos;s original article
+     *     </a>
+     * @since 2.0
+     */
+    public static double digamma(double x) {
+        if (Double.isNaN(x) || Double.isInfinite(x)) {
+            return x;
+        }
+
+        if (x > 0 && x <= S_LIMIT) {
+            // use method 5 from Bernardo AS103
+            // accurate to O(x)
+            return -GAMMA - 1 / x;
+        }
+
+        if (x >= C_LIMIT) {
+            // use method 4 (accurate to O(1/x^8)
+            double inv = 1 / (x * x);
+            //            1       1        1         1
+            // log(x) -  --- - ------ + ------- - -------
+            //           2 x   12 x^2   120 x^4   252 x^6
+            return FastMath.log(x) - 0.5 / x - inv * ((1.0 / 12) + inv * (1.0 / 120 - inv / 252));
+        }
+
+        return digamma(x + 1) - 1 / x;
+    }
+
+    /**
+     * Computes the trigamma function of x. This function is derived by taking the derivative of the
+     * implementation of digamma.
+     *
+     * @param x Argument.
+     * @return trigamma(x) to within 10-8 relative or absolute error whichever is smaller
+     * @see <a href="http://en.wikipedia.org/wiki/Trigamma_function">Trigamma</a>
+     * @see Gamma#digamma(double)
+     * @since 2.0
+     */
+    public static double trigamma(double x) {
+        if (Double.isNaN(x) || Double.isInfinite(x)) {
+            return x;
+        }
+
+        if (x > 0 && x <= S_LIMIT) {
+            return 1 / (x * x);
+        }
+
+        if (x >= C_LIMIT) {
+            double inv = 1 / (x * x);
+            //  1    1      1       1       1
+            //  - + ---- + ---- - ----- + -----
+            //  x      2      3       5       7
+            //      2 x    6 x    30 x    42 x
+            return 1 / x + inv / 2 + inv / x * (1.0 / 6 - inv * (1.0 / 30 + inv / 42));
+        }
+
+        return trigamma(x + 1) + 1 / (x * x);
+    }
+
+    /**
+     * Returns the Lanczos approximation used to compute the gamma function. The Lanczos
+     * approximation is related to the Gamma function by the following equation <center> {@code
+     * gamma(x) = sqrt(2 * pi) / x * (x + g + 0.5) ^ (x + 0.5) * exp(-x - g - 0.5) * lanczos(x)},
+     * </center> where {@code g} is the Lanczos constant.
+     *
+     * @param x Argument.
+     * @return The Lanczos approximation.
+     * @see <a href="http://mathworld.wolfram.com/LanczosApproximation.html">Lanczos
+     *     Approximation</a> equations (1) through (5), and Paul Godfrey's <a
+     *     href="http://my.fit.edu/~gabdo/gamma.txt">Note on the computation of the convergent
+     *     Lanczos complex Gamma approximation</a>
+     * @since 3.1
+     */
+    public static double lanczos(final double x) {
+        double sum = 0.0;
+        for (int i = LANCZOS.length - 1; i > 0; --i) {
+            sum += LANCZOS[i] / (x + i);
+        }
+        return sum + LANCZOS[0];
+    }
+
+    /**
+     * Returns the value of 1 / &Gamma;(1 + x) - 1 for -0&#46;5 &le; x &le; 1&#46;5. This
+     * implementation is based on the double precision implementation in the <em>NSWC Library of
+     * Mathematics Subroutines</em>, {@code DGAM1}.
+     *
+     * @param x Argument.
+     * @return The value of {@code 1.0 / Gamma(1.0 + x) - 1.0}.
+     * @throws NumberIsTooSmallException if {@code x < -0.5}
+     * @throws NumberIsTooLargeException if {@code x > 1.5}
+     * @since 3.1
+     */
+    public static double invGamma1pm1(final double x) {
+
+        if (x < -0.5) {
+            throw new NumberIsTooSmallException(x, -0.5, true);
+        }
+        if (x > 1.5) {
+            throw new NumberIsTooLargeException(x, 1.5, true);
+        }
+
+        final double ret;
+        final double t = x <= 0.5 ? x : (x - 0.5) - 0.5;
+        if (t < 0.0) {
+            final double a = INV_GAMMA1P_M1_A0 + t * INV_GAMMA1P_M1_A1;
+            double b = INV_GAMMA1P_M1_B8;
+            b = INV_GAMMA1P_M1_B7 + t * b;
+            b = INV_GAMMA1P_M1_B6 + t * b;
+            b = INV_GAMMA1P_M1_B5 + t * b;
+            b = INV_GAMMA1P_M1_B4 + t * b;
+            b = INV_GAMMA1P_M1_B3 + t * b;
+            b = INV_GAMMA1P_M1_B2 + t * b;
+            b = INV_GAMMA1P_M1_B1 + t * b;
+            b = 1.0 + t * b;
+
+            double c = INV_GAMMA1P_M1_C13 + t * (a / b);
+            c = INV_GAMMA1P_M1_C12 + t * c;
+            c = INV_GAMMA1P_M1_C11 + t * c;
+            c = INV_GAMMA1P_M1_C10 + t * c;
+            c = INV_GAMMA1P_M1_C9 + t * c;
+            c = INV_GAMMA1P_M1_C8 + t * c;
+            c = INV_GAMMA1P_M1_C7 + t * c;
+            c = INV_GAMMA1P_M1_C6 + t * c;
+            c = INV_GAMMA1P_M1_C5 + t * c;
+            c = INV_GAMMA1P_M1_C4 + t * c;
+            c = INV_GAMMA1P_M1_C3 + t * c;
+            c = INV_GAMMA1P_M1_C2 + t * c;
+            c = INV_GAMMA1P_M1_C1 + t * c;
+            c = INV_GAMMA1P_M1_C + t * c;
+            if (x > 0.5) {
+                ret = t * c / x;
+            } else {
+                ret = x * ((c + 0.5) + 0.5);
+            }
+        } else {
+            double p = INV_GAMMA1P_M1_P6;
+            p = INV_GAMMA1P_M1_P5 + t * p;
+            p = INV_GAMMA1P_M1_P4 + t * p;
+            p = INV_GAMMA1P_M1_P3 + t * p;
+            p = INV_GAMMA1P_M1_P2 + t * p;
+            p = INV_GAMMA1P_M1_P1 + t * p;
+            p = INV_GAMMA1P_M1_P0 + t * p;
+
+            double q = INV_GAMMA1P_M1_Q4;
+            q = INV_GAMMA1P_M1_Q3 + t * q;
+            q = INV_GAMMA1P_M1_Q2 + t * q;
+            q = INV_GAMMA1P_M1_Q1 + t * q;
+            q = 1.0 + t * q;
+
+            double c = INV_GAMMA1P_M1_C13 + (p / q) * t;
+            c = INV_GAMMA1P_M1_C12 + t * c;
+            c = INV_GAMMA1P_M1_C11 + t * c;
+            c = INV_GAMMA1P_M1_C10 + t * c;
+            c = INV_GAMMA1P_M1_C9 + t * c;
+            c = INV_GAMMA1P_M1_C8 + t * c;
+            c = INV_GAMMA1P_M1_C7 + t * c;
+            c = INV_GAMMA1P_M1_C6 + t * c;
+            c = INV_GAMMA1P_M1_C5 + t * c;
+            c = INV_GAMMA1P_M1_C4 + t * c;
+            c = INV_GAMMA1P_M1_C3 + t * c;
+            c = INV_GAMMA1P_M1_C2 + t * c;
+            c = INV_GAMMA1P_M1_C1 + t * c;
+            c = INV_GAMMA1P_M1_C0 + t * c;
+
+            if (x > 0.5) {
+                ret = (t / x) * ((c - 0.5) - 0.5);
+            } else {
+                ret = x * c;
+            }
+        }
+
+        return ret;
+    }
+
+    /**
+     * Returns the value of log &Gamma;(1 + x) for -0&#46;5 &le; x &le; 1&#46;5. This implementation
+     * is based on the double precision implementation in the <em>NSWC Library of Mathematics
+     * Subroutines</em>, {@code DGMLN1}.
+     *
+     * @param x Argument.
+     * @return The value of {@code log(Gamma(1 + x))}.
+     * @throws NumberIsTooSmallException if {@code x < -0.5}.
+     * @throws NumberIsTooLargeException if {@code x > 1.5}.
+     * @since 3.1
+     */
+    public static double logGamma1p(final double x)
+            throws NumberIsTooSmallException, NumberIsTooLargeException {
+
+        if (x < -0.5) {
+            throw new NumberIsTooSmallException(x, -0.5, true);
+        }
+        if (x > 1.5) {
+            throw new NumberIsTooLargeException(x, 1.5, true);
+        }
+
+        return -FastMath.log1p(invGamma1pm1(x));
+    }
+
+    /**
+     * Returns the value of Γ(x). Based on the <em>NSWC Library of Mathematics Subroutines</em>
+     * double precision implementation, {@code DGAMMA}.
+     *
+     * @param x Argument.
+     * @return the value of {@code Gamma(x)}.
+     * @since 3.1
+     */
+    public static double gamma(final double x) {
+
+        if ((x == FastMath.rint(x)) && (x <= 0.0)) {
+            return Double.NaN;
+        }
+
+        final double ret;
+        final double absX = FastMath.abs(x);
+        if (absX <= 20.0) {
+            if (x >= 1.0) {
+                /*
+                 * From the recurrence relation
+                 * Gamma(x) = (x - 1) * ... * (x - n) * Gamma(x - n),
+                 * then
+                 * Gamma(t) = 1 / [1 + invGamma1pm1(t - 1)],
+                 * where t = x - n. This means that t must satisfy
+                 * -0.5 <= t - 1 <= 1.5.
+                 */
+                double prod = 1.0;
+                double t = x;
+                while (t > 2.5) {
+                    t -= 1.0;
+                    prod *= t;
+                }
+                ret = prod / (1.0 + invGamma1pm1(t - 1.0));
+            } else {
+                /*
+                 * From the recurrence relation
+                 * Gamma(x) = Gamma(x + n + 1) / [x * (x + 1) * ... * (x + n)]
+                 * then
+                 * Gamma(x + n + 1) = 1 / [1 + invGamma1pm1(x + n)],
+                 * which requires -0.5 <= x + n <= 1.5.
+                 */
+                double prod = x;
+                double t = x;
+                while (t < -0.5) {
+                    t += 1.0;
+                    prod *= t;
+                }
+                ret = 1.0 / (prod * (1.0 + invGamma1pm1(t)));
+            }
+        } else {
+            final double y = absX + LANCZOS_G + 0.5;
+            final double gammaAbs =
+                    SQRT_TWO_PI
+                            / absX
+                            * FastMath.pow(y, absX + 0.5)
+                            * FastMath.exp(-y)
+                            * lanczos(absX);
+            if (x > 0.0) {
+                ret = gammaAbs;
+            } else {
+                /*
+                 * From the reflection formula
+                 * Gamma(x) * Gamma(1 - x) * sin(pi * x) = pi,
+                 * and the recurrence relation
+                 * Gamma(1 - x) = -x * Gamma(-x),
+                 * it is found
+                 * Gamma(x) = -pi / [x * sin(pi * x) * Gamma(-x)].
+                 */
+                ret = -FastMath.PI / (x * FastMath.sin(FastMath.PI * x) * gammaAbs);
+            }
+        }
+        return ret;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/special/package-info.java b/src/main/java/org/apache/commons/math3/special/package-info.java
new file mode 100644
index 0000000..a9fc1fc
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/special/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+/** Implementations of special functions such as Beta and Gamma. */
+package org.apache.commons.math3.special;
diff --git a/src/main/java/org/apache/commons/math3/stat/Frequency.java b/src/main/java/org/apache/commons/math3/stat/Frequency.java
new file mode 100644
index 0000000..276382c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/Frequency.java
@@ -0,0 +1,664 @@
+/*
+ * 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.math3.stat;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.MathUtils;
+
+import java.io.Serializable;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+/**
+ * Maintains a frequency distribution.
+ *
+ * <p>Accepts int, long, char or Comparable values. New values added must be comparable to those
+ * that have been added, otherwise the add method will throw an IllegalArgumentException.
+ *
+ * <p>Integer values (int, long, Integer, Long) are not distinguished by type -- i.e. <code>
+ * addValue(Long.valueOf(2)), addValue(2), addValue(2l)</code> all have the same effect (similarly
+ * for arguments to <code>getCount,</code> etc.).
+ *
+ * <p>NOTE: byte and short values will be implicitly converted to int values by the compiler, thus
+ * there are no explicit overloaded methods for these primitive types.
+ *
+ * <p>char values are converted by <code>addValue</code> to Character instances. As such, these
+ * values are not comparable to integral values, so attempts to combine integral types with chars in
+ * a frequency distribution will fail.
+ *
+ * <p>Float is not coerced to Double. Since they are not Comparable with each other the user must do
+ * any necessary coercion. Float.NaN and Double.NaN are not treated specially; they may occur in
+ * input and will occur in output if appropriate. </b>
+ *
+ * <p>The values are ordered using the default (natural order), unless a <code>Comparator</code> is
+ * supplied in the constructor.
+ */
+public class Frequency implements Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -3845586908418844111L;
+
+    /** underlying collection */
+    private final SortedMap<Comparable<?>, Long> freqTable;
+
+    /** Default constructor. */
+    public Frequency() {
+        freqTable = new TreeMap<Comparable<?>, Long>();
+    }
+
+    /**
+     * Constructor allowing values Comparator to be specified.
+     *
+     * @param comparator Comparator used to order values
+     */
+    @SuppressWarnings("unchecked") // TODO is the cast OK?
+    public Frequency(Comparator<?> comparator) {
+        freqTable =
+                new TreeMap<Comparable<?>, Long>((Comparator<? super Comparable<?>>) comparator);
+    }
+
+    /**
+     * Return a string representation of this frequency distribution.
+     *
+     * @return a string representation.
+     */
+    @Override
+    public String toString() {
+        NumberFormat nf = NumberFormat.getPercentInstance();
+        StringBuilder outBuffer = new StringBuilder();
+        outBuffer.append("Value \t Freq. \t Pct. \t Cum Pct. \n");
+        Iterator<Comparable<?>> iter = freqTable.keySet().iterator();
+        while (iter.hasNext()) {
+            Comparable<?> value = iter.next();
+            outBuffer.append(value);
+            outBuffer.append('\t');
+            outBuffer.append(getCount(value));
+            outBuffer.append('\t');
+            outBuffer.append(nf.format(getPct(value)));
+            outBuffer.append('\t');
+            outBuffer.append(nf.format(getCumPct(value)));
+            outBuffer.append('\n');
+        }
+        return outBuffer.toString();
+    }
+
+    /**
+     * Adds 1 to the frequency count for v.
+     *
+     * <p>If other objects have already been added to this Frequency, v must be comparable to those
+     * that have already been added.
+     *
+     * @param v the value to add.
+     * @throws MathIllegalArgumentException if <code>v</code> is not comparable with previous
+     *     entries
+     */
+    public void addValue(Comparable<?> v) throws MathIllegalArgumentException {
+        incrementValue(v, 1);
+    }
+
+    /**
+     * Adds 1 to the frequency count for v.
+     *
+     * @param v the value to add.
+     * @throws MathIllegalArgumentException if the table contains entries not comparable to Long
+     */
+    public void addValue(int v) throws MathIllegalArgumentException {
+        addValue(Long.valueOf(v));
+    }
+
+    /**
+     * Adds 1 to the frequency count for v.
+     *
+     * @param v the value to add.
+     * @throws MathIllegalArgumentException if the table contains entries not comparable to Long
+     */
+    public void addValue(long v) throws MathIllegalArgumentException {
+        addValue(Long.valueOf(v));
+    }
+
+    /**
+     * Adds 1 to the frequency count for v.
+     *
+     * @param v the value to add.
+     * @throws MathIllegalArgumentException if the table contains entries not comparable to Char
+     */
+    public void addValue(char v) throws MathIllegalArgumentException {
+        addValue(Character.valueOf(v));
+    }
+
+    /**
+     * Increments the frequency count for v.
+     *
+     * <p>If other objects have already been added to this Frequency, v must be comparable to those
+     * that have already been added.
+     *
+     * @param v the value to add.
+     * @param increment the amount by which the value should be incremented
+     * @throws MathIllegalArgumentException if <code>v</code> is not comparable with previous
+     *     entries
+     * @since 3.1
+     */
+    public void incrementValue(Comparable<?> v, long increment)
+            throws MathIllegalArgumentException {
+        Comparable<?> obj = v;
+        if (v instanceof Integer) {
+            obj = Long.valueOf(((Integer) v).longValue());
+        }
+        try {
+            Long count = freqTable.get(obj);
+            if (count == null) {
+                freqTable.put(obj, Long.valueOf(increment));
+            } else {
+                freqTable.put(obj, Long.valueOf(count.longValue() + increment));
+            }
+        } catch (ClassCastException ex) {
+            // TreeMap will throw ClassCastException if v is not comparable
+            throw new MathIllegalArgumentException(
+                    LocalizedFormats.INSTANCES_NOT_COMPARABLE_TO_EXISTING_VALUES,
+                    v.getClass().getName());
+        }
+    }
+
+    /**
+     * Increments the frequency count for v.
+     *
+     * <p>If other objects have already been added to this Frequency, v must be comparable to those
+     * that have already been added.
+     *
+     * @param v the value to add.
+     * @param increment the amount by which the value should be incremented
+     * @throws MathIllegalArgumentException if the table contains entries not comparable to Long
+     * @since 3.3
+     */
+    public void incrementValue(int v, long increment) throws MathIllegalArgumentException {
+        incrementValue(Long.valueOf(v), increment);
+    }
+
+    /**
+     * Increments the frequency count for v.
+     *
+     * <p>If other objects have already been added to this Frequency, v must be comparable to those
+     * that have already been added.
+     *
+     * @param v the value to add.
+     * @param increment the amount by which the value should be incremented
+     * @throws MathIllegalArgumentException if the table contains entries not comparable to Long
+     * @since 3.3
+     */
+    public void incrementValue(long v, long increment) throws MathIllegalArgumentException {
+        incrementValue(Long.valueOf(v), increment);
+    }
+
+    /**
+     * Increments the frequency count for v.
+     *
+     * <p>If other objects have already been added to this Frequency, v must be comparable to those
+     * that have already been added.
+     *
+     * @param v the value to add.
+     * @param increment the amount by which the value should be incremented
+     * @throws MathIllegalArgumentException if the table contains entries not comparable to Char
+     * @since 3.3
+     */
+    public void incrementValue(char v, long increment) throws MathIllegalArgumentException {
+        incrementValue(Character.valueOf(v), increment);
+    }
+
+    /** Clears the frequency table */
+    public void clear() {
+        freqTable.clear();
+    }
+
+    /**
+     * Returns an Iterator over the set of values that have been added.
+     *
+     * <p>If added values are integral (i.e., integers, longs, Integers, or Longs), they are
+     * converted to Longs when they are added, so the objects returned by the Iterator will in this
+     * case be Longs.
+     *
+     * @return values Iterator
+     */
+    public Iterator<Comparable<?>> valuesIterator() {
+        return freqTable.keySet().iterator();
+    }
+
+    /**
+     * Return an Iterator over the set of keys and values that have been added. Using the entry set
+     * to iterate is more efficient in the case where you need to access respective counts as well
+     * as values, since it doesn't require a "get" for every key...the value is provided in the
+     * Map.Entry.
+     *
+     * <p>If added values are integral (i.e., integers, longs, Integers, or Longs), they are
+     * converted to Longs when they are added, so the values of the map entries returned by the
+     * Iterator will in this case be Longs.
+     *
+     * @return entry set Iterator
+     * @since 3.1
+     */
+    public Iterator<Map.Entry<Comparable<?>, Long>> entrySetIterator() {
+        return freqTable.entrySet().iterator();
+    }
+
+    // -------------------------------------------------------------------------
+
+    /**
+     * Returns the sum of all frequencies.
+     *
+     * @return the total frequency count.
+     */
+    public long getSumFreq() {
+        long result = 0;
+        Iterator<Long> iterator = freqTable.values().iterator();
+        while (iterator.hasNext()) {
+            result += iterator.next().longValue();
+        }
+        return result;
+    }
+
+    /**
+     * Returns the number of values equal to v. Returns 0 if the value is not comparable.
+     *
+     * @param v the value to lookup.
+     * @return the frequency of v.
+     */
+    public long getCount(Comparable<?> v) {
+        if (v instanceof Integer) {
+            return getCount(((Integer) v).longValue());
+        }
+        long result = 0;
+        try {
+            Long count = freqTable.get(v);
+            if (count != null) {
+                result = count.longValue();
+            }
+        } catch (ClassCastException ex) { // NOPMD
+            // ignore and return 0 -- ClassCastException will be thrown if value is not comparable
+        }
+        return result;
+    }
+
+    /**
+     * Returns the number of values equal to v.
+     *
+     * @param v the value to lookup.
+     * @return the frequency of v.
+     */
+    public long getCount(int v) {
+        return getCount(Long.valueOf(v));
+    }
+
+    /**
+     * Returns the number of values equal to v.
+     *
+     * @param v the value to lookup.
+     * @return the frequency of v.
+     */
+    public long getCount(long v) {
+        return getCount(Long.valueOf(v));
+    }
+
+    /**
+     * Returns the number of values equal to v.
+     *
+     * @param v the value to lookup.
+     * @return the frequency of v.
+     */
+    public long getCount(char v) {
+        return getCount(Character.valueOf(v));
+    }
+
+    /**
+     * Returns the number of values in the frequency table.
+     *
+     * @return the number of unique values that have been added to the frequency table.
+     * @see #valuesIterator()
+     */
+    public int getUniqueCount() {
+        return freqTable.keySet().size();
+    }
+
+    /**
+     * Returns the percentage of values that are equal to v (as a proportion between 0 and 1).
+     *
+     * <p>Returns <code>Double.NaN</code> if no values have been added. Returns 0 if at least one
+     * value has been added, but v is not comparable to the values set.
+     *
+     * @param v the value to lookup
+     * @return the proportion of values equal to v
+     */
+    public double getPct(Comparable<?> v) {
+        final long sumFreq = getSumFreq();
+        if (sumFreq == 0) {
+            return Double.NaN;
+        }
+        return (double) getCount(v) / (double) sumFreq;
+    }
+
+    /**
+     * Returns the percentage of values that are equal to v (as a proportion between 0 and 1).
+     *
+     * @param v the value to lookup
+     * @return the proportion of values equal to v
+     */
+    public double getPct(int v) {
+        return getPct(Long.valueOf(v));
+    }
+
+    /**
+     * Returns the percentage of values that are equal to v (as a proportion between 0 and 1).
+     *
+     * @param v the value to lookup
+     * @return the proportion of values equal to v
+     */
+    public double getPct(long v) {
+        return getPct(Long.valueOf(v));
+    }
+
+    /**
+     * Returns the percentage of values that are equal to v (as a proportion between 0 and 1).
+     *
+     * @param v the value to lookup
+     * @return the proportion of values equal to v
+     */
+    public double getPct(char v) {
+        return getPct(Character.valueOf(v));
+    }
+
+    // -----------------------------------------------------------------------------------------
+
+    /**
+     * Returns the cumulative frequency of values less than or equal to v.
+     *
+     * <p>Returns 0 if v is not comparable to the values set.
+     *
+     * @param v the value to lookup.
+     * @return the proportion of values equal to v
+     */
+    @SuppressWarnings({"rawtypes", "unchecked"})
+    public long getCumFreq(Comparable<?> v) {
+        if (getSumFreq() == 0) {
+            return 0;
+        }
+        if (v instanceof Integer) {
+            return getCumFreq(((Integer) v).longValue());
+        }
+        Comparator<Comparable<?>> c = (Comparator<Comparable<?>>) freqTable.comparator();
+        if (c == null) {
+            c = new NaturalComparator();
+        }
+        long result = 0;
+
+        try {
+            Long value = freqTable.get(v);
+            if (value != null) {
+                result = value.longValue();
+            }
+        } catch (ClassCastException ex) {
+            return result; // v is not comparable
+        }
+
+        if (c.compare(v, freqTable.firstKey()) < 0) {
+            return 0; // v is comparable, but less than first value
+        }
+
+        if (c.compare(v, freqTable.lastKey()) >= 0) {
+            return getSumFreq(); // v is comparable, but greater than the last value
+        }
+
+        Iterator<Comparable<?>> values = valuesIterator();
+        while (values.hasNext()) {
+            Comparable<?> nextValue = values.next();
+            if (c.compare(v, nextValue) > 0) {
+                result += getCount(nextValue);
+            } else {
+                return result;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Returns the cumulative frequency of values less than or equal to v.
+     *
+     * <p>Returns 0 if v is not comparable to the values set.
+     *
+     * @param v the value to lookup
+     * @return the proportion of values equal to v
+     */
+    public long getCumFreq(int v) {
+        return getCumFreq(Long.valueOf(v));
+    }
+
+    /**
+     * Returns the cumulative frequency of values less than or equal to v.
+     *
+     * <p>Returns 0 if v is not comparable to the values set.
+     *
+     * @param v the value to lookup
+     * @return the proportion of values equal to v
+     */
+    public long getCumFreq(long v) {
+        return getCumFreq(Long.valueOf(v));
+    }
+
+    /**
+     * Returns the cumulative frequency of values less than or equal to v.
+     *
+     * <p>Returns 0 if v is not comparable to the values set.
+     *
+     * @param v the value to lookup
+     * @return the proportion of values equal to v
+     */
+    public long getCumFreq(char v) {
+        return getCumFreq(Character.valueOf(v));
+    }
+
+    // ----------------------------------------------------------------------------------------------
+
+    /**
+     * Returns the cumulative percentage of values less than or equal to v (as a proportion between
+     * 0 and 1).
+     *
+     * <p>Returns <code>Double.NaN</code> if no values have been added. Returns 0 if at least one
+     * value has been added, but v is not comparable to the values set.
+     *
+     * @param v the value to lookup
+     * @return the proportion of values less than or equal to v
+     */
+    public double getCumPct(Comparable<?> v) {
+        final long sumFreq = getSumFreq();
+        if (sumFreq == 0) {
+            return Double.NaN;
+        }
+        return (double) getCumFreq(v) / (double) sumFreq;
+    }
+
+    /**
+     * Returns the cumulative percentage of values less than or equal to v (as a proportion between
+     * 0 and 1).
+     *
+     * <p>Returns 0 if v is not comparable to the values set.
+     *
+     * @param v the value to lookup
+     * @return the proportion of values less than or equal to v
+     */
+    public double getCumPct(int v) {
+        return getCumPct(Long.valueOf(v));
+    }
+
+    /**
+     * Returns the cumulative percentage of values less than or equal to v (as a proportion between
+     * 0 and 1).
+     *
+     * <p>Returns 0 if v is not comparable to the values set.
+     *
+     * @param v the value to lookup
+     * @return the proportion of values less than or equal to v
+     */
+    public double getCumPct(long v) {
+        return getCumPct(Long.valueOf(v));
+    }
+
+    /**
+     * Returns the cumulative percentage of values less than or equal to v (as a proportion between
+     * 0 and 1).
+     *
+     * <p>Returns 0 if v is not comparable to the values set.
+     *
+     * @param v the value to lookup
+     * @return the proportion of values less than or equal to v
+     */
+    public double getCumPct(char v) {
+        return getCumPct(Character.valueOf(v));
+    }
+
+    /**
+     * Returns the mode value(s) in comparator order.
+     *
+     * @return a list containing the value(s) which appear most often.
+     * @since 3.3
+     */
+    public List<Comparable<?>> getMode() {
+        long mostPopular = 0; // frequencies are always positive
+
+        // Get the max count first, so we avoid having to recreate the List each time
+        for (Long l : freqTable.values()) {
+            long frequency = l.longValue();
+            if (frequency > mostPopular) {
+                mostPopular = frequency;
+            }
+        }
+
+        List<Comparable<?>> modeList = new ArrayList<Comparable<?>>();
+        for (Entry<Comparable<?>, Long> ent : freqTable.entrySet()) {
+            long frequency = ent.getValue().longValue();
+            if (frequency == mostPopular) {
+                modeList.add(ent.getKey());
+            }
+        }
+        return modeList;
+    }
+
+    // ----------------------------------------------------------------------------------------------
+
+    /**
+     * Merge another Frequency object's counts into this instance. This Frequency's counts will be
+     * incremented (or set when not already set) by the counts represented by other.
+     *
+     * @param other the other {@link Frequency} object to be merged
+     * @throws NullArgumentException if {@code other} is null
+     * @since 3.1
+     */
+    public void merge(final Frequency other) throws NullArgumentException {
+        MathUtils.checkNotNull(other, LocalizedFormats.NULL_NOT_ALLOWED);
+
+        final Iterator<Map.Entry<Comparable<?>, Long>> iter = other.entrySetIterator();
+        while (iter.hasNext()) {
+            final Map.Entry<Comparable<?>, Long> entry = iter.next();
+            incrementValue(entry.getKey(), entry.getValue().longValue());
+        }
+    }
+
+    /**
+     * Merge a {@link Collection} of {@link Frequency} objects into this instance. This Frequency's
+     * counts will be incremented (or set when not already set) by the counts represented by each of
+     * the others.
+     *
+     * @param others the other {@link Frequency} objects to be merged
+     * @throws NullArgumentException if the collection is null
+     * @since 3.1
+     */
+    public void merge(final Collection<Frequency> others) throws NullArgumentException {
+        MathUtils.checkNotNull(others, LocalizedFormats.NULL_NOT_ALLOWED);
+
+        for (final Frequency freq : others) {
+            merge(freq);
+        }
+    }
+
+    // ----------------------------------------------------------------------------------------------
+
+    /**
+     * A Comparator that compares comparable objects using the natural order. Copied from Commons
+     * Collections ComparableComparator.
+     *
+     * @param <T> the type of the objects compared
+     */
+    private static class NaturalComparator<T extends Comparable<T>>
+            implements Comparator<Comparable<T>>, Serializable {
+
+        /** Serializable version identifier */
+        private static final long serialVersionUID = -3852193713161395148L;
+
+        /**
+         * Compare the two {@link Comparable Comparable} arguments. This method is equivalent to:
+         *
+         * <pre>(({@link Comparable Comparable})o1).{@link Comparable#compareTo compareTo}(o2)</pre>
+         *
+         * @param o1 the first object
+         * @param o2 the second object
+         * @return result of comparison
+         * @throws NullPointerException when <i>o1</i> is <code>null</code>, or when <code>
+         *     ((Comparable)o1).compareTo(o2)</code> does
+         * @throws ClassCastException when <i>o1</i> is not a {@link Comparable Comparable}, or when
+         *     <code>((Comparable)o1).compareTo(o2)</code> does
+         */
+        @SuppressWarnings("unchecked") // cast to (T) may throw ClassCastException, see Javadoc
+        public int compare(Comparable<T> o1, Comparable<T> o2) {
+            return o1.compareTo((T) o2);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((freqTable == null) ? 0 : freqTable.hashCode());
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof Frequency)) {
+            return false;
+        }
+        Frequency other = (Frequency) obj;
+        if (freqTable == null) {
+            if (other.freqTable != null) {
+                return false;
+            }
+        } else if (!freqTable.equals(other.freqTable)) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/StatUtils.java b/src/main/java/org/apache/commons/math3/stat/StatUtils.java
new file mode 100644
index 0000000..31d75e8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/StatUtils.java
@@ -0,0 +1,852 @@
+/*
+ * 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.math3.stat;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
+import org.apache.commons.math3.stat.descriptive.UnivariateStatistic;
+import org.apache.commons.math3.stat.descriptive.moment.GeometricMean;
+import org.apache.commons.math3.stat.descriptive.moment.Mean;
+import org.apache.commons.math3.stat.descriptive.moment.Variance;
+import org.apache.commons.math3.stat.descriptive.rank.Max;
+import org.apache.commons.math3.stat.descriptive.rank.Min;
+import org.apache.commons.math3.stat.descriptive.rank.Percentile;
+import org.apache.commons.math3.stat.descriptive.summary.Product;
+import org.apache.commons.math3.stat.descriptive.summary.Sum;
+import org.apache.commons.math3.stat.descriptive.summary.SumOfLogs;
+import org.apache.commons.math3.stat.descriptive.summary.SumOfSquares;
+
+import java.util.List;
+
+/**
+ * StatUtils provides static methods for computing statistics based on data stored in double[]
+ * arrays.
+ */
+public final class StatUtils {
+
+    /** sum */
+    private static final UnivariateStatistic SUM = new Sum();
+
+    /** sumSq */
+    private static final UnivariateStatistic SUM_OF_SQUARES = new SumOfSquares();
+
+    /** prod */
+    private static final UnivariateStatistic PRODUCT = new Product();
+
+    /** sumLog */
+    private static final UnivariateStatistic SUM_OF_LOGS = new SumOfLogs();
+
+    /** min */
+    private static final UnivariateStatistic MIN = new Min();
+
+    /** max */
+    private static final UnivariateStatistic MAX = new Max();
+
+    /** mean */
+    private static final UnivariateStatistic MEAN = new Mean();
+
+    /** variance */
+    private static final Variance VARIANCE = new Variance();
+
+    /** percentile */
+    private static final Percentile PERCENTILE = new Percentile();
+
+    /** geometric mean */
+    private static final GeometricMean GEOMETRIC_MEAN = new GeometricMean();
+
+    /** Private Constructor */
+    private StatUtils() {}
+
+    /**
+     * Returns the sum of the values in the input array, or <code>Double.NaN</code> if the array is
+     * empty.
+     *
+     * <p>Throws <code>IllegalArgumentException</code> if the input array is null.
+     *
+     * @param values array of values to sum
+     * @return the sum of the values or <code>Double.NaN</code> if the array is empty
+     * @throws MathIllegalArgumentException if the array is null
+     */
+    public static double sum(final double[] values) throws MathIllegalArgumentException {
+        return SUM.evaluate(values);
+    }
+
+    /**
+     * Returns the sum of the entries in the specified portion of the input array, or <code>
+     * Double.NaN</code> if the designated subarray is empty.
+     *
+     * <p>Throws <code>IllegalArgumentException</code> if the array is null.
+     *
+     * @param values the input array
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @return the sum of the values or Double.NaN if length = 0
+     * @throws MathIllegalArgumentException if the array is null or the array index parameters are
+     *     not valid
+     */
+    public static double sum(final double[] values, final int begin, final int length)
+            throws MathIllegalArgumentException {
+        return SUM.evaluate(values, begin, length);
+    }
+
+    /**
+     * Returns the sum of the squares of the entries in the input array, or <code>Double.NaN</code>
+     * if the array is empty.
+     *
+     * <p>Throws <code>IllegalArgumentException</code> if the array is null.
+     *
+     * @param values input array
+     * @return the sum of the squared values or <code>Double.NaN</code> if the array is empty
+     * @throws MathIllegalArgumentException if the array is null
+     */
+    public static double sumSq(final double[] values) throws MathIllegalArgumentException {
+        return SUM_OF_SQUARES.evaluate(values);
+    }
+
+    /**
+     * Returns the sum of the squares of the entries in the specified portion of the input array, or
+     * <code>Double.NaN</code> if the designated subarray is empty.
+     *
+     * <p>Throws <code>IllegalArgumentException</code> if the array is null.
+     *
+     * @param values the input array
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @return the sum of the squares of the values or Double.NaN if length = 0
+     * @throws MathIllegalArgumentException if the array is null or the array index parameters are
+     *     not valid
+     */
+    public static double sumSq(final double[] values, final int begin, final int length)
+            throws MathIllegalArgumentException {
+        return SUM_OF_SQUARES.evaluate(values, begin, length);
+    }
+
+    /**
+     * Returns the product of the entries in the input array, or <code>Double.NaN</code> if the
+     * array is empty.
+     *
+     * <p>Throws <code>IllegalArgumentException</code> if the array is null.
+     *
+     * @param values the input array
+     * @return the product of the values or Double.NaN if the array is empty
+     * @throws MathIllegalArgumentException if the array is null
+     */
+    public static double product(final double[] values) throws MathIllegalArgumentException {
+        return PRODUCT.evaluate(values);
+    }
+
+    /**
+     * Returns the product of the entries in the specified portion of the input array, or <code>
+     * Double.NaN</code> if the designated subarray is empty.
+     *
+     * <p>Throws <code>IllegalArgumentException</code> if the array is null.
+     *
+     * @param values the input array
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @return the product of the values or Double.NaN if length = 0
+     * @throws MathIllegalArgumentException if the array is null or the array index parameters are
+     *     not valid
+     */
+    public static double product(final double[] values, final int begin, final int length)
+            throws MathIllegalArgumentException {
+        return PRODUCT.evaluate(values, begin, length);
+    }
+
+    /**
+     * Returns the sum of the natural logs of the entries in the input array, or <code>Double.NaN
+     * </code> if the array is empty.
+     *
+     * <p>Throws <code>IllegalArgumentException</code> if the array is null.
+     *
+     * <p>See {@link org.apache.commons.math3.stat.descriptive.summary.SumOfLogs}.
+     *
+     * @param values the input array
+     * @return the sum of the natural logs of the values or Double.NaN if the array is empty
+     * @throws MathIllegalArgumentException if the array is null
+     */
+    public static double sumLog(final double[] values) throws MathIllegalArgumentException {
+        return SUM_OF_LOGS.evaluate(values);
+    }
+
+    /**
+     * Returns the sum of the natural logs of the entries in the specified portion of the input
+     * array, or <code>Double.NaN</code> if the designated subarray is empty.
+     *
+     * <p>Throws <code>IllegalArgumentException</code> if the array is null.
+     *
+     * <p>See {@link org.apache.commons.math3.stat.descriptive.summary.SumOfLogs}.
+     *
+     * @param values the input array
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @return the sum of the natural logs of the values or Double.NaN if length = 0
+     * @throws MathIllegalArgumentException if the array is null or the array index parameters are
+     *     not valid
+     */
+    public static double sumLog(final double[] values, final int begin, final int length)
+            throws MathIllegalArgumentException {
+        return SUM_OF_LOGS.evaluate(values, begin, length);
+    }
+
+    /**
+     * Returns the arithmetic mean of the entries in the input array, or <code>Double.NaN</code> if
+     * the array is empty.
+     *
+     * <p>Throws <code>IllegalArgumentException</code> if the array is null.
+     *
+     * <p>See {@link org.apache.commons.math3.stat.descriptive.moment.Mean} for details on the
+     * computing algorithm.
+     *
+     * @param values the input array
+     * @return the mean of the values or Double.NaN if the array is empty
+     * @throws MathIllegalArgumentException if the array is null
+     */
+    public static double mean(final double[] values) throws MathIllegalArgumentException {
+        return MEAN.evaluate(values);
+    }
+
+    /**
+     * Returns the arithmetic mean of the entries in the specified portion of the input array, or
+     * <code>Double.NaN</code> if the designated subarray is empty.
+     *
+     * <p>Throws <code>IllegalArgumentException</code> if the array is null.
+     *
+     * <p>See {@link org.apache.commons.math3.stat.descriptive.moment.Mean} for details on the
+     * computing algorithm.
+     *
+     * @param values the input array
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @return the mean of the values or Double.NaN if length = 0
+     * @throws MathIllegalArgumentException if the array is null or the array index parameters are
+     *     not valid
+     */
+    public static double mean(final double[] values, final int begin, final int length)
+            throws MathIllegalArgumentException {
+        return MEAN.evaluate(values, begin, length);
+    }
+
+    /**
+     * Returns the geometric mean of the entries in the input array, or <code>Double.NaN</code> if
+     * the array is empty.
+     *
+     * <p>Throws <code>IllegalArgumentException</code> if the array is null.
+     *
+     * <p>See {@link org.apache.commons.math3.stat.descriptive.moment.GeometricMean} for details on
+     * the computing algorithm.
+     *
+     * @param values the input array
+     * @return the geometric mean of the values or Double.NaN if the array is empty
+     * @throws MathIllegalArgumentException if the array is null
+     */
+    public static double geometricMean(final double[] values) throws MathIllegalArgumentException {
+        return GEOMETRIC_MEAN.evaluate(values);
+    }
+
+    /**
+     * Returns the geometric mean of the entries in the specified portion of the input array, or
+     * <code>Double.NaN</code> if the designated subarray is empty.
+     *
+     * <p>Throws <code>IllegalArgumentException</code> if the array is null.
+     *
+     * <p>See {@link org.apache.commons.math3.stat.descriptive.moment.GeometricMean} for details on
+     * the computing algorithm.
+     *
+     * @param values the input array
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @return the geometric mean of the values or Double.NaN if length = 0
+     * @throws MathIllegalArgumentException if the array is null or the array index parameters are
+     *     not valid
+     */
+    public static double geometricMean(final double[] values, final int begin, final int length)
+            throws MathIllegalArgumentException {
+        return GEOMETRIC_MEAN.evaluate(values, begin, length);
+    }
+
+    /**
+     * Returns the variance of the entries in the input array, or <code>Double.NaN</code> if the
+     * array is empty.
+     *
+     * <p>This method returns the bias-corrected sample variance (using {@code n - 1} in the
+     * denominator). Use {@link #populationVariance(double[])} for the non-bias-corrected population
+     * variance.
+     *
+     * <p>See {@link org.apache.commons.math3.stat.descriptive.moment.Variance} for details on the
+     * computing algorithm.
+     *
+     * <p>Returns 0 for a single-value (i.e. length = 1) sample.
+     *
+     * <p>Throws <code>MathIllegalArgumentException</code> if the array is null.
+     *
+     * @param values the input array
+     * @return the variance of the values or Double.NaN if the array is empty
+     * @throws MathIllegalArgumentException if the array is null
+     */
+    public static double variance(final double[] values) throws MathIllegalArgumentException {
+        return VARIANCE.evaluate(values);
+    }
+
+    /**
+     * Returns the variance of the entries in the specified portion of the input array, or <code>
+     * Double.NaN</code> if the designated subarray is empty.
+     *
+     * <p>This method returns the bias-corrected sample variance (using {@code n - 1} in the
+     * denominator). Use {@link #populationVariance(double[], int, int)} for the non-bias-corrected
+     * population variance.
+     *
+     * <p>See {@link org.apache.commons.math3.stat.descriptive.moment.Variance} for details on the
+     * computing algorithm.
+     *
+     * <p>Returns 0 for a single-value (i.e. length = 1) sample.
+     *
+     * <p>Throws <code>MathIllegalArgumentException</code> if the array is null or the array index
+     * parameters are not valid.
+     *
+     * @param values the input array
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @return the variance of the values or Double.NaN if length = 0
+     * @throws MathIllegalArgumentException if the array is null or the array index parameters are
+     *     not valid
+     */
+    public static double variance(final double[] values, final int begin, final int length)
+            throws MathIllegalArgumentException {
+        return VARIANCE.evaluate(values, begin, length);
+    }
+
+    /**
+     * Returns the variance of the entries in the specified portion of the input array, using the
+     * precomputed mean value. Returns <code>Double.NaN</code> if the designated subarray is empty.
+     *
+     * <p>This method returns the bias-corrected sample variance (using {@code n - 1} in the
+     * denominator). Use {@link #populationVariance(double[], double, int, int)} for the
+     * non-bias-corrected population variance.
+     *
+     * <p>See {@link org.apache.commons.math3.stat.descriptive.moment.Variance} for details on the
+     * computing algorithm.
+     *
+     * <p>The formula used assumes that the supplied mean value is the arithmetic mean of the sample
+     * data, not a known population parameter. This method is supplied only to save computation when
+     * the mean has already been computed.
+     *
+     * <p>Returns 0 for a single-value (i.e. length = 1) sample.
+     *
+     * <p>Throws <code>MathIllegalArgumentException</code> if the array is null or the array index
+     * parameters are not valid.
+     *
+     * @param values the input array
+     * @param mean the precomputed mean value
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @return the variance of the values or Double.NaN if length = 0
+     * @throws MathIllegalArgumentException if the array is null or the array index parameters are
+     *     not valid
+     */
+    public static double variance(
+            final double[] values, final double mean, final int begin, final int length)
+            throws MathIllegalArgumentException {
+        return VARIANCE.evaluate(values, mean, begin, length);
+    }
+
+    /**
+     * Returns the variance of the entries in the input array, using the precomputed mean value.
+     * Returns <code>Double.NaN</code> if the array is empty.
+     *
+     * <p>This method returns the bias-corrected sample variance (using {@code n - 1} in the
+     * denominator). Use {@link #populationVariance(double[], double)} for the non-bias-corrected
+     * population variance.
+     *
+     * <p>See {@link org.apache.commons.math3.stat.descriptive.moment.Variance} for details on the
+     * computing algorithm.
+     *
+     * <p>The formula used assumes that the supplied mean value is the arithmetic mean of the sample
+     * data, not a known population parameter. This method is supplied only to save computation when
+     * the mean has already been computed.
+     *
+     * <p>Returns 0 for a single-value (i.e. length = 1) sample.
+     *
+     * <p>Throws <code>MathIllegalArgumentException</code> if the array is null.
+     *
+     * @param values the input array
+     * @param mean the precomputed mean value
+     * @return the variance of the values or Double.NaN if the array is empty
+     * @throws MathIllegalArgumentException if the array is null
+     */
+    public static double variance(final double[] values, final double mean)
+            throws MathIllegalArgumentException {
+        return VARIANCE.evaluate(values, mean);
+    }
+
+    /**
+     * Returns the <a href="http://en.wikibooks.org/wiki/Statistics/Summary/Variance">population
+     * variance</a> of the entries in the input array, or <code>Double.NaN</code> if the array is
+     * empty.
+     *
+     * <p>See {@link org.apache.commons.math3.stat.descriptive.moment.Variance} for details on the
+     * formula and computing algorithm.
+     *
+     * <p>Returns 0 for a single-value (i.e. length = 1) sample.
+     *
+     * <p>Throws <code>MathIllegalArgumentException</code> if the array is null.
+     *
+     * @param values the input array
+     * @return the population variance of the values or Double.NaN if the array is empty
+     * @throws MathIllegalArgumentException if the array is null
+     */
+    public static double populationVariance(final double[] values)
+            throws MathIllegalArgumentException {
+        return new Variance(false).evaluate(values);
+    }
+
+    /**
+     * Returns the <a href="http://en.wikibooks.org/wiki/Statistics/Summary/Variance">population
+     * variance</a> of the entries in the specified portion of the input array, or <code>Double.NaN
+     * </code> if the designated subarray is empty.
+     *
+     * <p>See {@link org.apache.commons.math3.stat.descriptive.moment.Variance} for details on the
+     * computing algorithm.
+     *
+     * <p>Returns 0 for a single-value (i.e. length = 1) sample.
+     *
+     * <p>Throws <code>MathIllegalArgumentException</code> if the array is null or the array index
+     * parameters are not valid.
+     *
+     * @param values the input array
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @return the population variance of the values or Double.NaN if length = 0
+     * @throws MathIllegalArgumentException if the array is null or the array index parameters are
+     *     not valid
+     */
+    public static double populationVariance(
+            final double[] values, final int begin, final int length)
+            throws MathIllegalArgumentException {
+        return new Variance(false).evaluate(values, begin, length);
+    }
+
+    /**
+     * Returns the <a href="http://en.wikibooks.org/wiki/Statistics/Summary/Variance">population
+     * variance</a> of the entries in the specified portion of the input array, using the
+     * precomputed mean value. Returns <code>Double.NaN</code> if the designated subarray is empty.
+     *
+     * <p>See {@link org.apache.commons.math3.stat.descriptive.moment.Variance} for details on the
+     * computing algorithm.
+     *
+     * <p>The formula used assumes that the supplied mean value is the arithmetic mean of the sample
+     * data, not a known population parameter. This method is supplied only to save computation when
+     * the mean has already been computed.
+     *
+     * <p>Returns 0 for a single-value (i.e. length = 1) sample.
+     *
+     * <p>Throws <code>MathIllegalArgumentException</code> if the array is null or the array index
+     * parameters are not valid.
+     *
+     * @param values the input array
+     * @param mean the precomputed mean value
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @return the population variance of the values or Double.NaN if length = 0
+     * @throws MathIllegalArgumentException if the array is null or the array index parameters are
+     *     not valid
+     */
+    public static double populationVariance(
+            final double[] values, final double mean, final int begin, final int length)
+            throws MathIllegalArgumentException {
+        return new Variance(false).evaluate(values, mean, begin, length);
+    }
+
+    /**
+     * Returns the <a href="http://en.wikibooks.org/wiki/Statistics/Summary/Variance">population
+     * variance</a> of the entries in the input array, using the precomputed mean value. Returns
+     * <code>Double.NaN</code> if the array is empty.
+     *
+     * <p>See {@link org.apache.commons.math3.stat.descriptive.moment.Variance} for details on the
+     * computing algorithm.
+     *
+     * <p>The formula used assumes that the supplied mean value is the arithmetic mean of the sample
+     * data, not a known population parameter. This method is supplied only to save computation when
+     * the mean has already been computed.
+     *
+     * <p>Returns 0 for a single-value (i.e. length = 1) sample.
+     *
+     * <p>Throws <code>MathIllegalArgumentException</code> if the array is null.
+     *
+     * @param values the input array
+     * @param mean the precomputed mean value
+     * @return the population variance of the values or Double.NaN if the array is empty
+     * @throws MathIllegalArgumentException if the array is null
+     */
+    public static double populationVariance(final double[] values, final double mean)
+            throws MathIllegalArgumentException {
+        return new Variance(false).evaluate(values, mean);
+    }
+
+    /**
+     * Returns the maximum of the entries in the input array, or <code>Double.NaN</code> if the
+     * array is empty.
+     *
+     * <p>Throws <code>MathIllegalArgumentException</code> if the array is null.
+     *
+     * <p>
+     *
+     * <ul>
+     *   <li>The result is <code>NaN</code> iff all values are <code>NaN</code> (i.e. <code>NaN
+     *       </code> values have no impact on the value of the statistic).
+     *   <li>If any of the values equals <code>Double.POSITIVE_INFINITY</code>, the result is <code>
+     *       Double.POSITIVE_INFINITY.</code>
+     * </ul>
+     *
+     * @param values the input array
+     * @return the maximum of the values or Double.NaN if the array is empty
+     * @throws MathIllegalArgumentException if the array is null
+     */
+    public static double max(final double[] values) throws MathIllegalArgumentException {
+        return MAX.evaluate(values);
+    }
+
+    /**
+     * Returns the maximum of the entries in the specified portion of the input array, or <code>
+     * Double.NaN</code> if the designated subarray is empty.
+     *
+     * <p>Throws <code>MathIllegalArgumentException</code> if the array is null or the array index
+     * parameters are not valid.
+     *
+     * <p>
+     *
+     * <ul>
+     *   <li>The result is <code>NaN</code> iff all values are <code>NaN</code> (i.e. <code>NaN
+     *       </code> values have no impact on the value of the statistic).
+     *   <li>If any of the values equals <code>Double.POSITIVE_INFINITY</code>, the result is <code>
+     *       Double.POSITIVE_INFINITY.</code>
+     * </ul>
+     *
+     * @param values the input array
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @return the maximum of the values or Double.NaN if length = 0
+     * @throws MathIllegalArgumentException if the array is null or the array index parameters are
+     *     not valid
+     */
+    public static double max(final double[] values, final int begin, final int length)
+            throws MathIllegalArgumentException {
+        return MAX.evaluate(values, begin, length);
+    }
+
+    /**
+     * Returns the minimum of the entries in the input array, or <code>Double.NaN</code> if the
+     * array is empty.
+     *
+     * <p>Throws <code>MathIllegalArgumentException</code> if the array is null.
+     *
+     * <p>
+     *
+     * <ul>
+     *   <li>The result is <code>NaN</code> iff all values are <code>NaN</code> (i.e. <code>NaN
+     *       </code> values have no impact on the value of the statistic).
+     *   <li>If any of the values equals <code>Double.NEGATIVE_INFINITY</code>, the result is <code>
+     *       Double.NEGATIVE_INFINITY.</code>
+     * </ul>
+     *
+     * @param values the input array
+     * @return the minimum of the values or Double.NaN if the array is empty
+     * @throws MathIllegalArgumentException if the array is null
+     */
+    public static double min(final double[] values) throws MathIllegalArgumentException {
+        return MIN.evaluate(values);
+    }
+
+    /**
+     * Returns the minimum of the entries in the specified portion of the input array, or <code>
+     * Double.NaN</code> if the designated subarray is empty.
+     *
+     * <p>Throws <code>MathIllegalArgumentException</code> if the array is null or the array index
+     * parameters are not valid.
+     *
+     * <p>
+     *
+     * <ul>
+     *   <li>The result is <code>NaN</code> iff all values are <code>NaN</code> (i.e. <code>NaN
+     *       </code> values have no impact on the value of the statistic).
+     *   <li>If any of the values equals <code>Double.NEGATIVE_INFINITY</code>, the result is <code>
+     *       Double.NEGATIVE_INFINITY.</code>
+     * </ul>
+     *
+     * @param values the input array
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @return the minimum of the values or Double.NaN if length = 0
+     * @throws MathIllegalArgumentException if the array is null or the array index parameters are
+     *     not valid
+     */
+    public static double min(final double[] values, final int begin, final int length)
+            throws MathIllegalArgumentException {
+        return MIN.evaluate(values, begin, length);
+    }
+
+    /**
+     * Returns an estimate of the <code>p</code>th percentile of the values in the <code>values
+     * </code> array.
+     *
+     * <p>
+     *
+     * <ul>
+     *   <li>Returns <code>Double.NaN</code> if <code>values</code> has length <code>0</code>
+     *   <li>Returns (for any value of <code>p</code>) <code>values[0]</code> if <code>values</code>
+     *       has length <code>1</code>
+     *   <li>Throws <code>IllegalArgumentException</code> if <code>values</code> is null or p is not
+     *       a valid quantile value (p must be greater than 0 and less than or equal to 100)
+     * </ul>
+     *
+     * <p>See {@link org.apache.commons.math3.stat.descriptive.rank.Percentile} for a description of
+     * the percentile estimation algorithm used.
+     *
+     * @param values input array of values
+     * @param p the percentile value to compute
+     * @return the percentile value or Double.NaN if the array is empty
+     * @throws MathIllegalArgumentException if <code>values</code> is null or p is invalid
+     */
+    public static double percentile(final double[] values, final double p)
+            throws MathIllegalArgumentException {
+        return PERCENTILE.evaluate(values, p);
+    }
+
+    /**
+     * Returns an estimate of the <code>p</code>th percentile of the values in the <code>values
+     * </code> array, starting with the element in (0-based) position <code>begin</code> in the
+     * array and including <code>length</code> values.
+     *
+     * <p>
+     *
+     * <ul>
+     *   <li>Returns <code>Double.NaN</code> if <code>length = 0</code>
+     *   <li>Returns (for any value of <code>p</code>) <code>values[begin]</code> if <code>
+     *       length = 1 </code>
+     *   <li>Throws <code>MathIllegalArgumentException</code> if <code>values</code> is null ,
+     *       <code>begin</code> or <code>length</code> is invalid, or <code>p</code> is not a valid
+     *       quantile value (p must be greater than 0 and less than or equal to 100)
+     * </ul>
+     *
+     * <p>See {@link org.apache.commons.math3.stat.descriptive.rank.Percentile} for a description of
+     * the percentile estimation algorithm used.
+     *
+     * @param values array of input values
+     * @param p the percentile to compute
+     * @param begin the first (0-based) element to include in the computation
+     * @param length the number of array elements to include
+     * @return the percentile value
+     * @throws MathIllegalArgumentException if the parameters are not valid or the input array is
+     *     null
+     */
+    public static double percentile(
+            final double[] values, final int begin, final int length, final double p)
+            throws MathIllegalArgumentException {
+        return PERCENTILE.evaluate(values, begin, length, p);
+    }
+
+    /**
+     * Returns the sum of the (signed) differences between corresponding elements of the input
+     * arrays -- i.e., sum(sample1[i] - sample2[i]).
+     *
+     * @param sample1 the first array
+     * @param sample2 the second array
+     * @return sum of paired differences
+     * @throws DimensionMismatchException if the arrays do not have the same (positive) length.
+     * @throws NoDataException if the sample arrays are empty.
+     */
+    public static double sumDifference(final double[] sample1, final double[] sample2)
+            throws DimensionMismatchException, NoDataException {
+        int n = sample1.length;
+        if (n != sample2.length) {
+            throw new DimensionMismatchException(n, sample2.length);
+        }
+        if (n <= 0) {
+            throw new NoDataException(LocalizedFormats.INSUFFICIENT_DIMENSION);
+        }
+        double result = 0;
+        for (int i = 0; i < n; i++) {
+            result += sample1[i] - sample2[i];
+        }
+        return result;
+    }
+
+    /**
+     * Returns the mean of the (signed) differences between corresponding elements of the input
+     * arrays -- i.e., sum(sample1[i] - sample2[i]) / sample1.length.
+     *
+     * @param sample1 the first array
+     * @param sample2 the second array
+     * @return mean of paired differences
+     * @throws DimensionMismatchException if the arrays do not have the same (positive) length.
+     * @throws NoDataException if the sample arrays are empty.
+     */
+    public static double meanDifference(final double[] sample1, final double[] sample2)
+            throws DimensionMismatchException, NoDataException {
+        return sumDifference(sample1, sample2) / sample1.length;
+    }
+
+    /**
+     * Returns the variance of the (signed) differences between corresponding elements of the input
+     * arrays -- i.e., var(sample1[i] - sample2[i]).
+     *
+     * @param sample1 the first array
+     * @param sample2 the second array
+     * @param meanDifference the mean difference between corresponding entries
+     * @see #meanDifference(double[],double[])
+     * @return variance of paired differences
+     * @throws DimensionMismatchException if the arrays do not have the same length.
+     * @throws NumberIsTooSmallException if the arrays length is less than 2.
+     */
+    public static double varianceDifference(
+            final double[] sample1, final double[] sample2, double meanDifference)
+            throws DimensionMismatchException, NumberIsTooSmallException {
+        double sum1 = 0d;
+        double sum2 = 0d;
+        double diff = 0d;
+        int n = sample1.length;
+        if (n != sample2.length) {
+            throw new DimensionMismatchException(n, sample2.length);
+        }
+        if (n < 2) {
+            throw new NumberIsTooSmallException(n, 2, true);
+        }
+        for (int i = 0; i < n; i++) {
+            diff = sample1[i] - sample2[i];
+            sum1 += (diff - meanDifference) * (diff - meanDifference);
+            sum2 += diff - meanDifference;
+        }
+        return (sum1 - (sum2 * sum2 / n)) / (n - 1);
+    }
+
+    /**
+     * Normalize (standardize) the sample, so it is has a mean of 0 and a standard deviation of 1.
+     *
+     * @param sample Sample to normalize.
+     * @return normalized (standardized) sample.
+     * @since 2.2
+     */
+    public static double[] normalize(final double[] sample) {
+        DescriptiveStatistics stats = new DescriptiveStatistics();
+
+        // Add the data from the series to stats
+        for (int i = 0; i < sample.length; i++) {
+            stats.addValue(sample[i]);
+        }
+
+        // Compute mean and standard deviation
+        double mean = stats.getMean();
+        double standardDeviation = stats.getStandardDeviation();
+
+        // initialize the standardizedSample, which has the same length as the sample
+        double[] standardizedSample = new double[sample.length];
+
+        for (int i = 0; i < sample.length; i++) {
+            // z = (x- mean)/standardDeviation
+            standardizedSample[i] = (sample[i] - mean) / standardDeviation;
+        }
+        return standardizedSample;
+    }
+
+    /**
+     * Returns the sample mode(s). The mode is the most frequently occurring value in the sample. If
+     * there is a unique value with maximum frequency, this value is returned as the only element of
+     * the output array. Otherwise, the returned array contains the maximum frequency elements in
+     * increasing order. For example, if {@code sample} is {0, 12, 5, 6, 0, 13, 5, 17}, the returned
+     * array will have length two, with 0 in the first element and 5 in the second.
+     *
+     * <p>NaN values are ignored when computing the mode - i.e., NaNs will never appear in the
+     * output array. If the sample includes only NaNs or has length 0, an empty array is returned.
+     *
+     * @param sample input data
+     * @return array of array of the most frequently occurring element(s) sorted in ascending order.
+     * @throws MathIllegalArgumentException if the indices are invalid or the array is null
+     * @since 3.3
+     */
+    public static double[] mode(double[] sample) throws MathIllegalArgumentException {
+        if (sample == null) {
+            throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY);
+        }
+        return getMode(sample, 0, sample.length);
+    }
+
+    /**
+     * Returns the sample mode(s). The mode is the most frequently occurring value in the sample. If
+     * there is a unique value with maximum frequency, this value is returned as the only element of
+     * the output array. Otherwise, the returned array contains the maximum frequency elements in
+     * increasing order. For example, if {@code sample} is {0, 12, 5, 6, 0, 13, 5, 17}, the returned
+     * array will have length two, with 0 in the first element and 5 in the second.
+     *
+     * <p>NaN values are ignored when computing the mode - i.e., NaNs will never appear in the
+     * output array. If the sample includes only NaNs or has length 0, an empty array is returned.
+     *
+     * @param sample input data
+     * @param begin index (0-based) of the first array element to include
+     * @param length the number of elements to include
+     * @return array of array of the most frequently occurring element(s) sorted in ascending order.
+     * @throws MathIllegalArgumentException if the indices are invalid or the array is null
+     * @since 3.3
+     */
+    public static double[] mode(double[] sample, final int begin, final int length) {
+        if (sample == null) {
+            throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY);
+        }
+
+        if (begin < 0) {
+            throw new NotPositiveException(LocalizedFormats.START_POSITION, Integer.valueOf(begin));
+        }
+
+        if (length < 0) {
+            throw new NotPositiveException(LocalizedFormats.LENGTH, Integer.valueOf(length));
+        }
+
+        return getMode(sample, begin, length);
+    }
+
+    /**
+     * Private helper method. Assumes parameters have been validated.
+     *
+     * @param values input data
+     * @param begin index (0-based) of the first array element to include
+     * @param length the number of elements to include
+     * @return array of array of the most frequently occurring element(s) sorted in ascending order.
+     */
+    private static double[] getMode(double[] values, final int begin, final int length) {
+        // Add the values to the frequency table
+        Frequency freq = new Frequency();
+        for (int i = begin; i < begin + length; i++) {
+            final double value = values[i];
+            if (!Double.isNaN(value)) {
+                freq.addValue(Double.valueOf(value));
+            }
+        }
+        List<Comparable<?>> list = freq.getMode();
+        // Convert the list to an array of primitive double
+        double[] modes = new double[list.size()];
+        int i = 0;
+        for (Comparable<?> c : list) {
+            modes[i++] = ((Double) c).doubleValue();
+        }
+        return modes;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/clustering/Cluster.java b/src/main/java/org/apache/commons/math3/stat/clustering/Cluster.java
new file mode 100644
index 0000000..8d9483e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/clustering/Cluster.java
@@ -0,0 +1,76 @@
+/*
+ * 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.math3.stat.clustering;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Cluster holding a set of {@link Clusterable} points.
+ * @param <T> the type of points that can be clustered
+ * @since 2.0
+ * @deprecated As of 3.2 (to be removed in 4.0),
+ * use {@link org.apache.commons.math3.ml.clustering.Cluster} instead
+ */
+@Deprecated
+public class Cluster<T extends Clusterable<T>> implements Serializable {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -3442297081515880464L;
+
+    /** The points contained in this cluster. */
+    private final List<T> points;
+
+    /** Center of the cluster. */
+    private final T center;
+
+    /**
+     * Build a cluster centered at a specified point.
+     * @param center the point which is to be the center of this cluster
+     */
+    public Cluster(final T center) {
+        this.center = center;
+        points = new ArrayList<T>();
+    }
+
+    /**
+     * Add a point to this cluster.
+     * @param point point to add
+     */
+    public void addPoint(final T point) {
+        points.add(point);
+    }
+
+    /**
+     * Get the points contained in the cluster.
+     * @return points contained in the cluster
+     */
+    public List<T> getPoints() {
+        return points;
+    }
+
+    /**
+     * Get the point chosen to be the center of this cluster.
+     * @return chosen cluster center
+     */
+    public T getCenter() {
+        return center;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/clustering/Clusterable.java b/src/main/java/org/apache/commons/math3/stat/clustering/Clusterable.java
new file mode 100644
index 0000000..f9818f3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/clustering/Clusterable.java
@@ -0,0 +1,48 @@
+/*
+ * 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.math3.stat.clustering;
+
+import java.util.Collection;
+
+/**
+ * Interface for points that can be clustered together.
+ * @param <T> the type of point that can be clustered
+ * @since 2.0
+ * @deprecated As of 3.2 (to be removed in 4.0),
+ * use {@link org.apache.commons.math3.ml.clustering.Clusterable} instead
+ */
+@Deprecated
+public interface Clusterable<T> {
+
+    /**
+     * Returns the distance from the given point.
+     *
+     * @param p the point to compute the distance from
+     * @return the distance from the given point
+     */
+    double distanceFrom(T p);
+
+    /**
+     * Returns the centroid of the given Collection of points.
+     *
+     * @param p the Collection of points to compute the centroid of
+     * @return the centroid of the given Collection of Points
+     */
+    T centroidOf(Collection<T> p);
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/clustering/DBSCANClusterer.java b/src/main/java/org/apache/commons/math3/stat/clustering/DBSCANClusterer.java
new file mode 100644
index 0000000..13247eb
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/clustering/DBSCANClusterer.java
@@ -0,0 +1,226 @@
+/*
+ * 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.math3.stat.clustering;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * DBSCAN (density-based spatial clustering of applications with noise) algorithm.
+ * <p>
+ * The DBSCAN algorithm forms clusters based on the idea of density connectivity, i.e.
+ * a point p is density connected to another point q, if there exists a chain of
+ * points p<sub>i</sub>, with i = 1 .. n and p<sub>1</sub> = p and p<sub>n</sub> = q,
+ * such that each pair &lt;p<sub>i</sub>, p<sub>i+1</sub>&gt; is directly density-reachable.
+ * A point q is directly density-reachable from point p if it is in the &epsilon;-neighborhood
+ * of this point.
+ * <p>
+ * Any point that is not density-reachable from a formed cluster is treated as noise, and
+ * will thus not be present in the result.
+ * <p>
+ * The algorithm requires two parameters:
+ * <ul>
+ *   <li>eps: the distance that defines the &epsilon;-neighborhood of a point
+ *   <li>minPoints: the minimum number of density-connected points required to form a cluster
+ * </ul>
+ * <p>
+ * <b>Note:</b> as DBSCAN is not a centroid-based clustering algorithm, the resulting
+ * {@link Cluster} objects will have no defined center, i.e. {@link Cluster#getCenter()} will
+ * return {@code null}.
+ *
+ * @param <T> type of the points to cluster
+ * @see <a href="http://en.wikipedia.org/wiki/DBSCAN">DBSCAN (wikipedia)</a>
+ * @see <a href="http://www.dbs.ifi.lmu.de/Publikationen/Papers/KDD-96.final.frame.pdf">
+ * A Density-Based Algorithm for Discovering Clusters in Large Spatial Databases with Noise</a>
+ * @since 3.1
+ * @deprecated As of 3.2 (to be removed in 4.0),
+ * use {@link org.apache.commons.math3.ml.clustering.DBSCANClusterer} instead
+ */
+@Deprecated
+public class DBSCANClusterer<T extends Clusterable<T>> {
+
+    /** Maximum radius of the neighborhood to be considered. */
+    private final double              eps;
+
+    /** Minimum number of points needed for a cluster. */
+    private final int                 minPts;
+
+    /** Status of a point during the clustering process. */
+    private enum PointStatus {
+        /** The point has is considered to be noise. */
+        NOISE,
+        /** The point is already part of a cluster. */
+        PART_OF_CLUSTER
+    }
+
+    /**
+     * Creates a new instance of a DBSCANClusterer.
+     *
+     * @param eps maximum radius of the neighborhood to be considered
+     * @param minPts minimum number of points needed for a cluster
+     * @throws NotPositiveException if {@code eps < 0.0} or {@code minPts < 0}
+     */
+    public DBSCANClusterer(final double eps, final int minPts)
+        throws NotPositiveException {
+        if (eps < 0.0d) {
+            throw new NotPositiveException(eps);
+        }
+        if (minPts < 0) {
+            throw new NotPositiveException(minPts);
+        }
+        this.eps = eps;
+        this.minPts = minPts;
+    }
+
+    /**
+     * Returns the maximum radius of the neighborhood to be considered.
+     *
+     * @return maximum radius of the neighborhood
+     */
+    public double getEps() {
+        return eps;
+    }
+
+    /**
+     * Returns the minimum number of points needed for a cluster.
+     *
+     * @return minimum number of points needed for a cluster
+     */
+    public int getMinPts() {
+        return minPts;
+    }
+
+    /**
+     * Performs DBSCAN cluster analysis.
+     * <p>
+     * <b>Note:</b> as DBSCAN is not a centroid-based clustering algorithm, the resulting
+     * {@link Cluster} objects will have no defined center, i.e. {@link Cluster#getCenter()} will
+     * return {@code null}.
+     *
+     * @param points the points to cluster
+     * @return the list of clusters
+     * @throws NullArgumentException if the data points are null
+     */
+    public List<Cluster<T>> cluster(final Collection<T> points) throws NullArgumentException {
+
+        // sanity checks
+        MathUtils.checkNotNull(points);
+
+        final List<Cluster<T>> clusters = new ArrayList<Cluster<T>>();
+        final Map<Clusterable<T>, PointStatus> visited = new HashMap<Clusterable<T>, PointStatus>();
+
+        for (final T point : points) {
+            if (visited.get(point) != null) {
+                continue;
+            }
+            final List<T> neighbors = getNeighbors(point, points);
+            if (neighbors.size() >= minPts) {
+                // DBSCAN does not care about center points
+                final Cluster<T> cluster = new Cluster<T>(null);
+                clusters.add(expandCluster(cluster, point, neighbors, points, visited));
+            } else {
+                visited.put(point, PointStatus.NOISE);
+            }
+        }
+
+        return clusters;
+    }
+
+    /**
+     * Expands the cluster to include density-reachable items.
+     *
+     * @param cluster Cluster to expand
+     * @param point Point to add to cluster
+     * @param neighbors List of neighbors
+     * @param points the data set
+     * @param visited the set of already visited points
+     * @return the expanded cluster
+     */
+    private Cluster<T> expandCluster(final Cluster<T> cluster,
+                                     final T point,
+                                     final List<T> neighbors,
+                                     final Collection<T> points,
+                                     final Map<Clusterable<T>, PointStatus> visited) {
+        cluster.addPoint(point);
+        visited.put(point, PointStatus.PART_OF_CLUSTER);
+
+        List<T> seeds = new ArrayList<T>(neighbors);
+        int index = 0;
+        while (index < seeds.size()) {
+            final T current = seeds.get(index);
+            PointStatus pStatus = visited.get(current);
+            // only check non-visited points
+            if (pStatus == null) {
+                final List<T> currentNeighbors = getNeighbors(current, points);
+                if (currentNeighbors.size() >= minPts) {
+                    seeds = merge(seeds, currentNeighbors);
+                }
+            }
+
+            if (pStatus != PointStatus.PART_OF_CLUSTER) {
+                visited.put(current, PointStatus.PART_OF_CLUSTER);
+                cluster.addPoint(current);
+            }
+
+            index++;
+        }
+        return cluster;
+    }
+
+    /**
+     * Returns a list of density-reachable neighbors of a {@code point}.
+     *
+     * @param point the point to look for
+     * @param points possible neighbors
+     * @return the List of neighbors
+     */
+    private List<T> getNeighbors(final T point, final Collection<T> points) {
+        final List<T> neighbors = new ArrayList<T>();
+        for (final T neighbor : points) {
+            if (point != neighbor && neighbor.distanceFrom(point) <= eps) {
+                neighbors.add(neighbor);
+            }
+        }
+        return neighbors;
+    }
+
+    /**
+     * Merges two lists together.
+     *
+     * @param one first list
+     * @param two second list
+     * @return merged lists
+     */
+    private List<T> merge(final List<T> one, final List<T> two) {
+        final Set<T> oneSet = new HashSet<T>(one);
+        for (T item : two) {
+            if (!oneSet.contains(item)) {
+                one.add(item);
+            }
+        }
+        return one;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/clustering/EuclideanDoublePoint.java b/src/main/java/org/apache/commons/math3/stat/clustering/EuclideanDoublePoint.java
new file mode 100644
index 0000000..32c236c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/clustering/EuclideanDoublePoint.java
@@ -0,0 +1,100 @@
+/*
+ * 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.math3.stat.clustering;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Arrays;
+
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * A simple implementation of {@link Clusterable} for points with double coordinates.
+ * @since 3.1
+ * @deprecated As of 3.2 (to be removed in 4.0),
+ * use {@link org.apache.commons.math3.ml.clustering.DoublePoint} instead
+ */
+@Deprecated
+public class EuclideanDoublePoint implements Clusterable<EuclideanDoublePoint>, Serializable {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 8026472786091227632L;
+
+    /** Point coordinates. */
+    private final double[] point;
+
+    /**
+     * Build an instance wrapping an integer array.
+     * <p>
+     * The wrapped array is referenced, it is <em>not</em> copied.
+     *
+     * @param point the n-dimensional point in integer space
+     */
+    public EuclideanDoublePoint(final double[] point) {
+        this.point = point;
+    }
+
+    /** {@inheritDoc} */
+    public EuclideanDoublePoint centroidOf(final Collection<EuclideanDoublePoint> points) {
+        final double[] centroid = new double[getPoint().length];
+        for (final EuclideanDoublePoint p : points) {
+            for (int i = 0; i < centroid.length; i++) {
+                centroid[i] += p.getPoint()[i];
+            }
+        }
+        for (int i = 0; i < centroid.length; i++) {
+            centroid[i] /= points.size();
+        }
+        return new EuclideanDoublePoint(centroid);
+    }
+
+    /** {@inheritDoc} */
+    public double distanceFrom(final EuclideanDoublePoint p) {
+        return MathArrays.distance(point, p.getPoint());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(final Object other) {
+        if (!(other instanceof EuclideanDoublePoint)) {
+            return false;
+        }
+        return Arrays.equals(point, ((EuclideanDoublePoint) other).point);
+    }
+
+    /**
+     * Get the n-dimensional point in integer space.
+     *
+     * @return a reference (not a copy!) to the wrapped array
+     */
+    public double[] getPoint() {
+        return point;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(point);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return Arrays.toString(point);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/clustering/EuclideanIntegerPoint.java b/src/main/java/org/apache/commons/math3/stat/clustering/EuclideanIntegerPoint.java
new file mode 100644
index 0000000..508b0fa
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/clustering/EuclideanIntegerPoint.java
@@ -0,0 +1,101 @@
+/*
+ * 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.math3.stat.clustering;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * A simple implementation of {@link Clusterable} for points with integer coordinates.
+ * @since 2.0
+ * @deprecated As of 3.2 (to be removed in 4.0),
+ * use {@link org.apache.commons.math3.ml.clustering.DoublePoint} instead
+ */
+@Deprecated
+public class EuclideanIntegerPoint implements Clusterable<EuclideanIntegerPoint>, Serializable {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 3946024775784901369L;
+
+    /** Point coordinates. */
+    private final int[] point;
+
+    /**
+     * Build an instance wrapping an integer array.
+     * <p>The wrapped array is referenced, it is <em>not</em> copied.</p>
+     * @param point the n-dimensional point in integer space
+     */
+    public EuclideanIntegerPoint(final int[] point) {
+        this.point = point;
+    }
+
+    /**
+     * Get the n-dimensional point in integer space.
+     * @return a reference (not a copy!) to the wrapped array
+     */
+    public int[] getPoint() {
+        return point;
+    }
+
+    /** {@inheritDoc} */
+    public double distanceFrom(final EuclideanIntegerPoint p) {
+        return MathArrays.distance(point, p.getPoint());
+    }
+
+    /** {@inheritDoc} */
+    public EuclideanIntegerPoint centroidOf(final Collection<EuclideanIntegerPoint> points) {
+        int[] centroid = new int[getPoint().length];
+        for (EuclideanIntegerPoint p : points) {
+            for (int i = 0; i < centroid.length; i++) {
+                centroid[i] += p.getPoint()[i];
+            }
+        }
+        for (int i = 0; i < centroid.length; i++) {
+            centroid[i] /= points.size();
+        }
+        return new EuclideanIntegerPoint(centroid);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(final Object other) {
+        if (!(other instanceof EuclideanIntegerPoint)) {
+            return false;
+        }
+        return Arrays.equals(point, ((EuclideanIntegerPoint) other).point);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(point);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @since 2.1
+     */
+    @Override
+    public String toString() {
+        return Arrays.toString(point);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/clustering/KMeansPlusPlusClusterer.java b/src/main/java/org/apache/commons/math3/stat/clustering/KMeansPlusPlusClusterer.java
new file mode 100644
index 0000000..07cec09
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/clustering/KMeansPlusPlusClusterer.java
@@ -0,0 +1,514 @@
+/*
+ * 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.math3.stat.clustering;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+
+import org.apache.commons.math3.exception.ConvergenceException;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.stat.descriptive.moment.Variance;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * Clustering algorithm based on David Arthur and Sergei Vassilvitski k-means++ algorithm.
+ * @param <T> type of the points to cluster
+ * @see <a href="http://en.wikipedia.org/wiki/K-means%2B%2B">K-means++ (wikipedia)</a>
+ * @since 2.0
+ * @deprecated As of 3.2 (to be removed in 4.0),
+ * use {@link org.apache.commons.math3.ml.clustering.KMeansPlusPlusClusterer} instead
+ */
+@Deprecated
+public class KMeansPlusPlusClusterer<T extends Clusterable<T>> {
+
+    /** Strategies to use for replacing an empty cluster. */
+    public enum EmptyClusterStrategy {
+
+        /** Split the cluster with largest distance variance. */
+        LARGEST_VARIANCE,
+
+        /** Split the cluster with largest number of points. */
+        LARGEST_POINTS_NUMBER,
+
+        /** Create a cluster around the point farthest from its centroid. */
+        FARTHEST_POINT,
+
+        /** Generate an error. */
+        ERROR
+
+    }
+
+    /** Random generator for choosing initial centers. */
+    private final Random random;
+
+    /** Selected strategy for empty clusters. */
+    private final EmptyClusterStrategy emptyStrategy;
+
+    /** Build a clusterer.
+     * <p>
+     * The default strategy for handling empty clusters that may appear during
+     * algorithm iterations is to split the cluster with largest distance variance.
+     * </p>
+     * @param random random generator to use for choosing initial centers
+     */
+    public KMeansPlusPlusClusterer(final Random random) {
+        this(random, EmptyClusterStrategy.LARGEST_VARIANCE);
+    }
+
+    /** Build a clusterer.
+     * @param random random generator to use for choosing initial centers
+     * @param emptyStrategy strategy to use for handling empty clusters that
+     * may appear during algorithm iterations
+     * @since 2.2
+     */
+    public KMeansPlusPlusClusterer(final Random random, final EmptyClusterStrategy emptyStrategy) {
+        this.random        = random;
+        this.emptyStrategy = emptyStrategy;
+    }
+
+    /**
+     * Runs the K-means++ clustering algorithm.
+     *
+     * @param points the points to cluster
+     * @param k the number of clusters to split the data into
+     * @param numTrials number of trial runs
+     * @param maxIterationsPerTrial the maximum number of iterations to run the algorithm
+     *     for at each trial run.  If negative, no maximum will be used
+     * @return a list of clusters containing the points
+     * @throws MathIllegalArgumentException if the data points are null or the number
+     *     of clusters is larger than the number of data points
+     * @throws ConvergenceException if an empty cluster is encountered and the
+     * {@link #emptyStrategy} is set to {@code ERROR}
+     */
+    public List<Cluster<T>> cluster(final Collection<T> points, final int k,
+                                    int numTrials, int maxIterationsPerTrial)
+        throws MathIllegalArgumentException, ConvergenceException {
+
+        // at first, we have not found any clusters list yet
+        List<Cluster<T>> best = null;
+        double bestVarianceSum = Double.POSITIVE_INFINITY;
+
+        // do several clustering trials
+        for (int i = 0; i < numTrials; ++i) {
+
+            // compute a clusters list
+            List<Cluster<T>> clusters = cluster(points, k, maxIterationsPerTrial);
+
+            // compute the variance of the current list
+            double varianceSum = 0.0;
+            for (final Cluster<T> cluster : clusters) {
+                if (!cluster.getPoints().isEmpty()) {
+
+                    // compute the distance variance of the current cluster
+                    final T center = cluster.getCenter();
+                    final Variance stat = new Variance();
+                    for (final T point : cluster.getPoints()) {
+                        stat.increment(point.distanceFrom(center));
+                    }
+                    varianceSum += stat.getResult();
+
+                }
+            }
+
+            if (varianceSum <= bestVarianceSum) {
+                // this one is the best we have found so far, remember it
+                best            = clusters;
+                bestVarianceSum = varianceSum;
+            }
+
+        }
+
+        // return the best clusters list found
+        return best;
+
+    }
+
+    /**
+     * Runs the K-means++ clustering algorithm.
+     *
+     * @param points the points to cluster
+     * @param k the number of clusters to split the data into
+     * @param maxIterations the maximum number of iterations to run the algorithm
+     *     for.  If negative, no maximum will be used
+     * @return a list of clusters containing the points
+     * @throws MathIllegalArgumentException if the data points are null or the number
+     *     of clusters is larger than the number of data points
+     * @throws ConvergenceException if an empty cluster is encountered and the
+     * {@link #emptyStrategy} is set to {@code ERROR}
+     */
+    public List<Cluster<T>> cluster(final Collection<T> points, final int k,
+                                    final int maxIterations)
+        throws MathIllegalArgumentException, ConvergenceException {
+
+        // sanity checks
+        MathUtils.checkNotNull(points);
+
+        // number of clusters has to be smaller or equal the number of data points
+        if (points.size() < k) {
+            throw new NumberIsTooSmallException(points.size(), k, false);
+        }
+
+        // create the initial clusters
+        List<Cluster<T>> clusters = chooseInitialCenters(points, k, random);
+
+        // create an array containing the latest assignment of a point to a cluster
+        // no need to initialize the array, as it will be filled with the first assignment
+        int[] assignments = new int[points.size()];
+        assignPointsToClusters(clusters, points, assignments);
+
+        // iterate through updating the centers until we're done
+        final int max = (maxIterations < 0) ? Integer.MAX_VALUE : maxIterations;
+        for (int count = 0; count < max; count++) {
+            boolean emptyCluster = false;
+            List<Cluster<T>> newClusters = new ArrayList<Cluster<T>>();
+            for (final Cluster<T> cluster : clusters) {
+                final T newCenter;
+                if (cluster.getPoints().isEmpty()) {
+                    switch (emptyStrategy) {
+                        case LARGEST_VARIANCE :
+                            newCenter = getPointFromLargestVarianceCluster(clusters);
+                            break;
+                        case LARGEST_POINTS_NUMBER :
+                            newCenter = getPointFromLargestNumberCluster(clusters);
+                            break;
+                        case FARTHEST_POINT :
+                            newCenter = getFarthestPoint(clusters);
+                            break;
+                        default :
+                            throw new ConvergenceException(LocalizedFormats.EMPTY_CLUSTER_IN_K_MEANS);
+                    }
+                    emptyCluster = true;
+                } else {
+                    newCenter = cluster.getCenter().centroidOf(cluster.getPoints());
+                }
+                newClusters.add(new Cluster<T>(newCenter));
+            }
+            int changes = assignPointsToClusters(newClusters, points, assignments);
+            clusters = newClusters;
+
+            // if there were no more changes in the point-to-cluster assignment
+            // and there are no empty clusters left, return the current clusters
+            if (changes == 0 && !emptyCluster) {
+                return clusters;
+            }
+        }
+        return clusters;
+    }
+
+    /**
+     * Adds the given points to the closest {@link Cluster}.
+     *
+     * @param <T> type of the points to cluster
+     * @param clusters the {@link Cluster}s to add the points to
+     * @param points the points to add to the given {@link Cluster}s
+     * @param assignments points assignments to clusters
+     * @return the number of points assigned to different clusters as the iteration before
+     */
+    private static <T extends Clusterable<T>> int
+        assignPointsToClusters(final List<Cluster<T>> clusters, final Collection<T> points,
+                               final int[] assignments) {
+        int assignedDifferently = 0;
+        int pointIndex = 0;
+        for (final T p : points) {
+            int clusterIndex = getNearestCluster(clusters, p);
+            if (clusterIndex != assignments[pointIndex]) {
+                assignedDifferently++;
+            }
+
+            Cluster<T> cluster = clusters.get(clusterIndex);
+            cluster.addPoint(p);
+            assignments[pointIndex++] = clusterIndex;
+        }
+
+        return assignedDifferently;
+    }
+
+    /**
+     * Use K-means++ to choose the initial centers.
+     *
+     * @param <T> type of the points to cluster
+     * @param points the points to choose the initial centers from
+     * @param k the number of centers to choose
+     * @param random random generator to use
+     * @return the initial centers
+     */
+    private static <T extends Clusterable<T>> List<Cluster<T>>
+        chooseInitialCenters(final Collection<T> points, final int k, final Random random) {
+
+        // Convert to list for indexed access. Make it unmodifiable, since removal of items
+        // would screw up the logic of this method.
+        final List<T> pointList = Collections.unmodifiableList(new ArrayList<T> (points));
+
+        // The number of points in the list.
+        final int numPoints = pointList.size();
+
+        // Set the corresponding element in this array to indicate when
+        // elements of pointList are no longer available.
+        final boolean[] taken = new boolean[numPoints];
+
+        // The resulting list of initial centers.
+        final List<Cluster<T>> resultSet = new ArrayList<Cluster<T>>();
+
+        // Choose one center uniformly at random from among the data points.
+        final int firstPointIndex = random.nextInt(numPoints);
+
+        final T firstPoint = pointList.get(firstPointIndex);
+
+        resultSet.add(new Cluster<T>(firstPoint));
+
+        // Must mark it as taken
+        taken[firstPointIndex] = true;
+
+        // To keep track of the minimum distance squared of elements of
+        // pointList to elements of resultSet.
+        final double[] minDistSquared = new double[numPoints];
+
+        // Initialize the elements.  Since the only point in resultSet is firstPoint,
+        // this is very easy.
+        for (int i = 0; i < numPoints; i++) {
+            if (i != firstPointIndex) { // That point isn't considered
+                double d = firstPoint.distanceFrom(pointList.get(i));
+                minDistSquared[i] = d*d;
+            }
+        }
+
+        while (resultSet.size() < k) {
+
+            // Sum up the squared distances for the points in pointList not
+            // already taken.
+            double distSqSum = 0.0;
+
+            for (int i = 0; i < numPoints; i++) {
+                if (!taken[i]) {
+                    distSqSum += minDistSquared[i];
+                }
+            }
+
+            // Add one new data point as a center. Each point x is chosen with
+            // probability proportional to D(x)2
+            final double r = random.nextDouble() * distSqSum;
+
+            // The index of the next point to be added to the resultSet.
+            int nextPointIndex = -1;
+
+            // Sum through the squared min distances again, stopping when
+            // sum >= r.
+            double sum = 0.0;
+            for (int i = 0; i < numPoints; i++) {
+                if (!taken[i]) {
+                    sum += minDistSquared[i];
+                    if (sum >= r) {
+                        nextPointIndex = i;
+                        break;
+                    }
+                }
+            }
+
+            // If it's not set to >= 0, the point wasn't found in the previous
+            // for loop, probably because distances are extremely small.  Just pick
+            // the last available point.
+            if (nextPointIndex == -1) {
+                for (int i = numPoints - 1; i >= 0; i--) {
+                    if (!taken[i]) {
+                        nextPointIndex = i;
+                        break;
+                    }
+                }
+            }
+
+            // We found one.
+            if (nextPointIndex >= 0) {
+
+                final T p = pointList.get(nextPointIndex);
+
+                resultSet.add(new Cluster<T> (p));
+
+                // Mark it as taken.
+                taken[nextPointIndex] = true;
+
+                if (resultSet.size() < k) {
+                    // Now update elements of minDistSquared.  We only have to compute
+                    // the distance to the new center to do this.
+                    for (int j = 0; j < numPoints; j++) {
+                        // Only have to worry about the points still not taken.
+                        if (!taken[j]) {
+                            double d = p.distanceFrom(pointList.get(j));
+                            double d2 = d * d;
+                            if (d2 < minDistSquared[j]) {
+                                minDistSquared[j] = d2;
+                            }
+                        }
+                    }
+                }
+
+            } else {
+                // None found --
+                // Break from the while loop to prevent
+                // an infinite loop.
+                break;
+            }
+        }
+
+        return resultSet;
+    }
+
+    /**
+     * Get a random point from the {@link Cluster} with the largest distance variance.
+     *
+     * @param clusters the {@link Cluster}s to search
+     * @return a random point from the selected cluster
+     * @throws ConvergenceException if clusters are all empty
+     */
+    private T getPointFromLargestVarianceCluster(final Collection<Cluster<T>> clusters)
+    throws ConvergenceException {
+
+        double maxVariance = Double.NEGATIVE_INFINITY;
+        Cluster<T> selected = null;
+        for (final Cluster<T> cluster : clusters) {
+            if (!cluster.getPoints().isEmpty()) {
+
+                // compute the distance variance of the current cluster
+                final T center = cluster.getCenter();
+                final Variance stat = new Variance();
+                for (final T point : cluster.getPoints()) {
+                    stat.increment(point.distanceFrom(center));
+                }
+                final double variance = stat.getResult();
+
+                // select the cluster with the largest variance
+                if (variance > maxVariance) {
+                    maxVariance = variance;
+                    selected = cluster;
+                }
+
+            }
+        }
+
+        // did we find at least one non-empty cluster ?
+        if (selected == null) {
+            throw new ConvergenceException(LocalizedFormats.EMPTY_CLUSTER_IN_K_MEANS);
+        }
+
+        // extract a random point from the cluster
+        final List<T> selectedPoints = selected.getPoints();
+        return selectedPoints.remove(random.nextInt(selectedPoints.size()));
+
+    }
+
+    /**
+     * Get a random point from the {@link Cluster} with the largest number of points
+     *
+     * @param clusters the {@link Cluster}s to search
+     * @return a random point from the selected cluster
+     * @throws ConvergenceException if clusters are all empty
+     */
+    private T getPointFromLargestNumberCluster(final Collection<Cluster<T>> clusters) throws ConvergenceException {
+
+        int maxNumber = 0;
+        Cluster<T> selected = null;
+        for (final Cluster<T> cluster : clusters) {
+
+            // get the number of points of the current cluster
+            final int number = cluster.getPoints().size();
+
+            // select the cluster with the largest number of points
+            if (number > maxNumber) {
+                maxNumber = number;
+                selected = cluster;
+            }
+
+        }
+
+        // did we find at least one non-empty cluster ?
+        if (selected == null) {
+            throw new ConvergenceException(LocalizedFormats.EMPTY_CLUSTER_IN_K_MEANS);
+        }
+
+        // extract a random point from the cluster
+        final List<T> selectedPoints = selected.getPoints();
+        return selectedPoints.remove(random.nextInt(selectedPoints.size()));
+
+    }
+
+    /**
+     * Get the point farthest to its cluster center
+     *
+     * @param clusters the {@link Cluster}s to search
+     * @return point farthest to its cluster center
+     * @throws ConvergenceException if clusters are all empty
+     */
+    private T getFarthestPoint(final Collection<Cluster<T>> clusters) throws ConvergenceException {
+
+        double maxDistance = Double.NEGATIVE_INFINITY;
+        Cluster<T> selectedCluster = null;
+        int selectedPoint = -1;
+        for (final Cluster<T> cluster : clusters) {
+
+            // get the farthest point
+            final T center = cluster.getCenter();
+            final List<T> points = cluster.getPoints();
+            for (int i = 0; i < points.size(); ++i) {
+                final double distance = points.get(i).distanceFrom(center);
+                if (distance > maxDistance) {
+                    maxDistance     = distance;
+                    selectedCluster = cluster;
+                    selectedPoint   = i;
+                }
+            }
+
+        }
+
+        // did we find at least one non-empty cluster ?
+        if (selectedCluster == null) {
+            throw new ConvergenceException(LocalizedFormats.EMPTY_CLUSTER_IN_K_MEANS);
+        }
+
+        return selectedCluster.getPoints().remove(selectedPoint);
+
+    }
+
+    /**
+     * Returns the nearest {@link Cluster} to the given point
+     *
+     * @param <T> type of the points to cluster
+     * @param clusters the {@link Cluster}s to search
+     * @param point the point to find the nearest {@link Cluster} for
+     * @return the index of the nearest {@link Cluster} to the given point
+     */
+    private static <T extends Clusterable<T>> int
+        getNearestCluster(final Collection<Cluster<T>> clusters, final T point) {
+        double minDistance = Double.MAX_VALUE;
+        int clusterIndex = 0;
+        int minCluster = 0;
+        for (final Cluster<T> c : clusters) {
+            final double distance = point.distanceFrom(c.getCenter());
+            if (distance < minDistance) {
+                minDistance = distance;
+                minCluster = clusterIndex;
+            }
+            clusterIndex++;
+        }
+        return minCluster;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/clustering/package-info.java b/src/main/java/org/apache/commons/math3/stat/clustering/package-info.java
new file mode 100644
index 0000000..f6b8d3e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/clustering/package-info.java
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+/**
+ * <h2>All classes and sub-packages of this package are deprecated.</h2>
+ * <h3>Please use their replacements, to be found under
+ *  <ul>
+ *   <li>{@link org.apache.commons.math3.ml.clustering}</li>
+ *  </ul>
+ * </h3>
+ *
+ * <p>
+ * Clustering algorithms.
+ * </p>
+ */
+package org.apache.commons.math3.stat.clustering;
diff --git a/src/main/java/org/apache/commons/math3/stat/correlation/Covariance.java b/src/main/java/org/apache/commons/math3/stat/correlation/Covariance.java
new file mode 100644
index 0000000..c462401
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/correlation/Covariance.java
@@ -0,0 +1,295 @@
+/*
+ * 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.math3.stat.correlation;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.linear.BlockRealMatrix;
+import org.apache.commons.math3.stat.descriptive.moment.Mean;
+import org.apache.commons.math3.stat.descriptive.moment.Variance;
+
+/**
+ * Computes covariances for pairs of arrays or columns of a matrix.
+ *
+ * <p>The constructors that take <code>RealMatrix</code> or
+ * <code>double[][]</code> arguments generate covariance matrices.  The
+ * columns of the input matrices are assumed to represent variable values.</p>
+ *
+ * <p>The constructor argument <code>biasCorrected</code> determines whether or
+ * not computed covariances are bias-corrected.</p>
+ *
+ * <p>Unbiased covariances are given by the formula</p>
+ * <code>cov(X, Y) = &Sigma;[(x<sub>i</sub> - E(X))(y<sub>i</sub> - E(Y))] / (n - 1)</code>
+ * where <code>E(X)</code> is the mean of <code>X</code> and <code>E(Y)</code>
+ * is the mean of the <code>Y</code> values.
+ *
+ * <p>Non-bias-corrected estimates use <code>n</code> in place of <code>n - 1</code>
+ *
+ * @since 2.0
+ */
+public class Covariance {
+
+    /** covariance matrix */
+    private final RealMatrix covarianceMatrix;
+
+    /**
+     * Create an empty covariance matrix.
+     */
+    /** Number of observations (length of covariate vectors) */
+    private final int n;
+
+    /**
+     * Create a Covariance with no data
+     */
+    public Covariance() {
+        super();
+        covarianceMatrix = null;
+        n = 0;
+    }
+
+    /**
+     * Create a Covariance matrix from a rectangular array
+     * whose columns represent covariates.
+     *
+     * <p>The <code>biasCorrected</code> parameter determines whether or not
+     * covariance estimates are bias-corrected.</p>
+     *
+     * <p>The input array must be rectangular with at least one column
+     * and two rows.</p>
+     *
+     * @param data rectangular array with columns representing covariates
+     * @param biasCorrected true means covariances are bias-corrected
+     * @throws MathIllegalArgumentException if the input data array is not
+     * rectangular with at least two rows and one column.
+     * @throws NotStrictlyPositiveException if the input data array is not
+     * rectangular with at least one row and one column.
+     */
+    public Covariance(double[][] data, boolean biasCorrected)
+    throws MathIllegalArgumentException, NotStrictlyPositiveException {
+        this(new BlockRealMatrix(data), biasCorrected);
+    }
+
+    /**
+     * Create a Covariance matrix from a rectangular array
+     * whose columns represent covariates.
+     *
+     * <p>The input array must be rectangular with at least one column
+     * and two rows</p>
+     *
+     * @param data rectangular array with columns representing covariates
+     * @throws MathIllegalArgumentException if the input data array is not
+     * rectangular with at least two rows and one column.
+     * @throws NotStrictlyPositiveException if the input data array is not
+     * rectangular with at least one row and one column.
+     */
+    public Covariance(double[][] data)
+    throws MathIllegalArgumentException, NotStrictlyPositiveException {
+        this(data, true);
+    }
+
+    /**
+     * Create a covariance matrix from a matrix whose columns
+     * represent covariates.
+     *
+     * <p>The <code>biasCorrected</code> parameter determines whether or not
+     * covariance estimates are bias-corrected.</p>
+     *
+     * <p>The matrix must have at least one column and two rows</p>
+     *
+     * @param matrix matrix with columns representing covariates
+     * @param biasCorrected true means covariances are bias-corrected
+     * @throws MathIllegalArgumentException if the input matrix does not have
+     * at least two rows and one column
+     */
+    public Covariance(RealMatrix matrix, boolean biasCorrected)
+    throws MathIllegalArgumentException {
+       checkSufficientData(matrix);
+       n = matrix.getRowDimension();
+       covarianceMatrix = computeCovarianceMatrix(matrix, biasCorrected);
+    }
+
+    /**
+     * Create a covariance matrix from a matrix whose columns
+     * represent covariates.
+     *
+     * <p>The matrix must have at least one column and two rows</p>
+     *
+     * @param matrix matrix with columns representing covariates
+     * @throws MathIllegalArgumentException if the input matrix does not have
+     * at least two rows and one column
+     */
+    public Covariance(RealMatrix matrix) throws MathIllegalArgumentException {
+        this(matrix, true);
+    }
+
+    /**
+     * Returns the covariance matrix
+     *
+     * @return covariance matrix
+     */
+    public RealMatrix getCovarianceMatrix() {
+        return covarianceMatrix;
+    }
+
+    /**
+     * Returns the number of observations (length of covariate vectors)
+     *
+     * @return number of observations
+     */
+    public int getN() {
+        return n;
+    }
+
+    /**
+     * Compute a covariance matrix from a matrix whose columns represent
+     * covariates.
+     * @param matrix input matrix (must have at least one column and two rows)
+     * @param biasCorrected determines whether or not covariance estimates are bias-corrected
+     * @return covariance matrix
+     * @throws MathIllegalArgumentException if the matrix does not contain sufficient data
+     */
+    protected RealMatrix computeCovarianceMatrix(RealMatrix matrix, boolean biasCorrected)
+    throws MathIllegalArgumentException {
+        int dimension = matrix.getColumnDimension();
+        Variance variance = new Variance(biasCorrected);
+        RealMatrix outMatrix = new BlockRealMatrix(dimension, dimension);
+        for (int i = 0; i < dimension; i++) {
+            for (int j = 0; j < i; j++) {
+              double cov = covariance(matrix.getColumn(i), matrix.getColumn(j), biasCorrected);
+              outMatrix.setEntry(i, j, cov);
+              outMatrix.setEntry(j, i, cov);
+            }
+            outMatrix.setEntry(i, i, variance.evaluate(matrix.getColumn(i)));
+        }
+        return outMatrix;
+    }
+
+    /**
+     * Create a covariance matrix from a matrix whose columns represent
+     * covariates. Covariances are computed using the bias-corrected formula.
+     * @param matrix input matrix (must have at least one column and two rows)
+     * @return covariance matrix
+     * @throws MathIllegalArgumentException if matrix does not contain sufficient data
+     * @see #Covariance
+     */
+    protected RealMatrix computeCovarianceMatrix(RealMatrix matrix)
+    throws MathIllegalArgumentException {
+        return computeCovarianceMatrix(matrix, true);
+    }
+
+    /**
+     * Compute a covariance matrix from a rectangular array whose columns represent
+     * covariates.
+     * @param data input array (must have at least one column and two rows)
+     * @param biasCorrected determines whether or not covariance estimates are bias-corrected
+     * @return covariance matrix
+     * @throws MathIllegalArgumentException if the data array does not contain sufficient
+     * data
+     * @throws NotStrictlyPositiveException if the input data array is not
+     * rectangular with at least one row and one column.
+     */
+    protected RealMatrix computeCovarianceMatrix(double[][] data, boolean biasCorrected)
+    throws MathIllegalArgumentException, NotStrictlyPositiveException {
+        return computeCovarianceMatrix(new BlockRealMatrix(data), biasCorrected);
+    }
+
+    /**
+     * Create a covariance matrix from a rectangular array whose columns represent
+     * covariates. Covariances are computed using the bias-corrected formula.
+     * @param data input array (must have at least one column and two rows)
+     * @return covariance matrix
+     * @throws MathIllegalArgumentException if the data array does not contain sufficient data
+     * @throws NotStrictlyPositiveException if the input data array is not
+     * rectangular with at least one row and one column.
+     * @see #Covariance
+     */
+    protected RealMatrix computeCovarianceMatrix(double[][] data)
+    throws MathIllegalArgumentException, NotStrictlyPositiveException {
+        return computeCovarianceMatrix(data, true);
+    }
+
+    /**
+     * Computes the covariance between the two arrays.
+     *
+     * <p>Array lengths must match and the common length must be at least 2.</p>
+     *
+     * @param xArray first data array
+     * @param yArray second data array
+     * @param biasCorrected if true, returned value will be bias-corrected
+     * @return returns the covariance for the two arrays
+     * @throws  MathIllegalArgumentException if the arrays lengths do not match or
+     * there is insufficient data
+     */
+    public double covariance(final double[] xArray, final double[] yArray, boolean biasCorrected)
+        throws MathIllegalArgumentException {
+        Mean mean = new Mean();
+        double result = 0d;
+        int length = xArray.length;
+        if (length != yArray.length) {
+            throw new MathIllegalArgumentException(
+                  LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, length, yArray.length);
+        } else if (length < 2) {
+            throw new MathIllegalArgumentException(
+                  LocalizedFormats.INSUFFICIENT_OBSERVED_POINTS_IN_SAMPLE, length, 2);
+        } else {
+            double xMean = mean.evaluate(xArray);
+            double yMean = mean.evaluate(yArray);
+            for (int i = 0; i < length; i++) {
+                double xDev = xArray[i] - xMean;
+                double yDev = yArray[i] - yMean;
+                result += (xDev * yDev - result) / (i + 1);
+            }
+        }
+        return biasCorrected ? result * ((double) length / (double)(length - 1)) : result;
+    }
+
+    /**
+     * Computes the covariance between the two arrays, using the bias-corrected
+     * formula.
+     *
+     * <p>Array lengths must match and the common length must be at least 2.</p>
+     *
+     * @param xArray first data array
+     * @param yArray second data array
+     * @return returns the covariance for the two arrays
+     * @throws  MathIllegalArgumentException if the arrays lengths do not match or
+     * there is insufficient data
+     */
+    public double covariance(final double[] xArray, final double[] yArray)
+        throws MathIllegalArgumentException {
+        return covariance(xArray, yArray, true);
+    }
+
+    /**
+     * Throws MathIllegalArgumentException if the matrix does not have at least
+     * one column and two rows.
+     * @param matrix matrix to check
+     * @throws MathIllegalArgumentException if the matrix does not contain sufficient data
+     * to compute covariance
+     */
+    private void checkSufficientData(final RealMatrix matrix) throws MathIllegalArgumentException {
+        int nRows = matrix.getRowDimension();
+        int nCols = matrix.getColumnDimension();
+        if (nRows < 2 || nCols < 1) {
+            throw new MathIllegalArgumentException(
+                    LocalizedFormats.INSUFFICIENT_ROWS_AND_COLUMNS,
+                    nRows, nCols);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/correlation/KendallsCorrelation.java b/src/main/java/org/apache/commons/math3/stat/correlation/KendallsCorrelation.java
new file mode 100644
index 0000000..d38cf71
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/correlation/KendallsCorrelation.java
@@ -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.math3.stat.correlation;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.linear.BlockRealMatrix;
+import org.apache.commons.math3.linear.MatrixUtils;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.Pair;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * Implementation of Kendall's Tau-b rank correlation</a>.
+ * <p>
+ * A pair of observations (x<sub>1</sub>, y<sub>1</sub>) and
+ * (x<sub>2</sub>, y<sub>2</sub>) are considered <i>concordant</i> if
+ * x<sub>1</sub> &lt; x<sub>2</sub> and y<sub>1</sub> &lt; y<sub>2</sub>
+ * or x<sub>2</sub> &lt; x<sub>1</sub> and y<sub>2</sub> &lt; y<sub>1</sub>.
+ * The pair is <i>discordant</i> if x<sub>1</sub> &lt; x<sub>2</sub> and
+ * y<sub>2</sub> &lt; y<sub>1</sub> or x<sub>2</sub> &lt; x<sub>1</sub> and
+ * y<sub>1</sub> &lt; y<sub>2</sub>.  If either x<sub>1</sub> = x<sub>2</sub>
+ * or y<sub>1</sub> = y<sub>2</sub>, the pair is neither concordant nor
+ * discordant.
+ * <p>
+ * Kendall's Tau-b is defined as:
+ * <pre>
+ * tau<sub>b</sub> = (n<sub>c</sub> - n<sub>d</sub>) / sqrt((n<sub>0</sub> - n<sub>1</sub>) * (n<sub>0</sub> - n<sub>2</sub>))
+ * </pre>
+ * <p>
+ * where:
+ * <ul>
+ *     <li>n<sub>0</sub> = n * (n - 1) / 2</li>
+ *     <li>n<sub>c</sub> = Number of concordant pairs</li>
+ *     <li>n<sub>d</sub> = Number of discordant pairs</li>
+ *     <li>n<sub>1</sub> = sum of t<sub>i</sub> * (t<sub>i</sub> - 1) / 2 for all i</li>
+ *     <li>n<sub>2</sub> = sum of u<sub>j</sub> * (u<sub>j</sub> - 1) / 2 for all j</li>
+ *     <li>t<sub>i</sub> = Number of tied values in the i<sup>th</sup> group of ties in x</li>
+ *     <li>u<sub>j</sub> = Number of tied values in the j<sup>th</sup> group of ties in y</li>
+ * </ul>
+ * <p>
+ * This implementation uses the O(n log n) algorithm described in
+ * William R. Knight's 1966 paper "A Computer Method for Calculating
+ * Kendall's Tau with Ungrouped Data" in the Journal of the American
+ * Statistical Association.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Kendall_tau_rank_correlation_coefficient">
+ * Kendall tau rank correlation coefficient (Wikipedia)</a>
+ * @see <a href="http://www.jstor.org/stable/2282833">A Computer
+ * Method for Calculating Kendall's Tau with Ungrouped Data</a>
+ *
+ * @since 3.3
+ */
+public class KendallsCorrelation {
+
+    /** correlation matrix */
+    private final RealMatrix correlationMatrix;
+
+    /**
+     * Create a KendallsCorrelation instance without data.
+     */
+    public KendallsCorrelation() {
+        correlationMatrix = null;
+    }
+
+    /**
+     * Create a KendallsCorrelation from a rectangular array
+     * whose columns represent values of variables to be correlated.
+     *
+     * @param data rectangular array with columns representing variables
+     * @throws IllegalArgumentException if the input data array is not
+     * rectangular with at least two rows and two columns.
+     */
+    public KendallsCorrelation(double[][] data) {
+        this(MatrixUtils.createRealMatrix(data));
+    }
+
+    /**
+     * Create a KendallsCorrelation from a RealMatrix whose columns
+     * represent variables to be correlated.
+     *
+     * @param matrix matrix with columns representing variables to correlate
+     */
+    public KendallsCorrelation(RealMatrix matrix) {
+        correlationMatrix = computeCorrelationMatrix(matrix);
+    }
+
+    /**
+     * Returns the correlation matrix.
+     *
+     * @return correlation matrix
+     */
+    public RealMatrix getCorrelationMatrix() {
+        return correlationMatrix;
+    }
+
+    /**
+     * Computes the Kendall's Tau rank correlation matrix for the columns of
+     * the input matrix.
+     *
+     * @param matrix matrix with columns representing variables to correlate
+     * @return correlation matrix
+     */
+    public RealMatrix computeCorrelationMatrix(final RealMatrix matrix) {
+        int nVars = matrix.getColumnDimension();
+        RealMatrix outMatrix = new BlockRealMatrix(nVars, nVars);
+        for (int i = 0; i < nVars; i++) {
+            for (int j = 0; j < i; j++) {
+                double corr = correlation(matrix.getColumn(i), matrix.getColumn(j));
+                outMatrix.setEntry(i, j, corr);
+                outMatrix.setEntry(j, i, corr);
+            }
+            outMatrix.setEntry(i, i, 1d);
+        }
+        return outMatrix;
+    }
+
+    /**
+     * Computes the Kendall's Tau rank correlation matrix for the columns of
+     * the input rectangular array.  The columns of the array represent values
+     * of variables to be correlated.
+     *
+     * @param matrix matrix with columns representing variables to correlate
+     * @return correlation matrix
+     */
+    public RealMatrix computeCorrelationMatrix(final double[][] matrix) {
+       return computeCorrelationMatrix(new BlockRealMatrix(matrix));
+    }
+
+    /**
+     * Computes the Kendall's Tau rank correlation coefficient between the two arrays.
+     *
+     * @param xArray first data array
+     * @param yArray second data array
+     * @return Returns Kendall's Tau rank correlation coefficient for the two arrays
+     * @throws DimensionMismatchException if the arrays lengths do not match
+     */
+    public double correlation(final double[] xArray, final double[] yArray)
+            throws DimensionMismatchException {
+
+        if (xArray.length != yArray.length) {
+            throw new DimensionMismatchException(xArray.length, yArray.length);
+        }
+
+        final int n = xArray.length;
+        final long numPairs = sum(n - 1);
+
+        @SuppressWarnings("unchecked")
+        Pair<Double, Double>[] pairs = new Pair[n];
+        for (int i = 0; i < n; i++) {
+            pairs[i] = new Pair<Double, Double>(xArray[i], yArray[i]);
+        }
+
+        Arrays.sort(pairs, new Comparator<Pair<Double, Double>>() {
+            /** {@inheritDoc} */
+            public int compare(Pair<Double, Double> pair1, Pair<Double, Double> pair2) {
+                int compareFirst = pair1.getFirst().compareTo(pair2.getFirst());
+                return compareFirst != 0 ? compareFirst : pair1.getSecond().compareTo(pair2.getSecond());
+            }
+        });
+
+        long tiedXPairs = 0;
+        long tiedXYPairs = 0;
+        long consecutiveXTies = 1;
+        long consecutiveXYTies = 1;
+        Pair<Double, Double> prev = pairs[0];
+        for (int i = 1; i < n; i++) {
+            final Pair<Double, Double> curr = pairs[i];
+            if (curr.getFirst().equals(prev.getFirst())) {
+                consecutiveXTies++;
+                if (curr.getSecond().equals(prev.getSecond())) {
+                    consecutiveXYTies++;
+                } else {
+                    tiedXYPairs += sum(consecutiveXYTies - 1);
+                    consecutiveXYTies = 1;
+                }
+            } else {
+                tiedXPairs += sum(consecutiveXTies - 1);
+                consecutiveXTies = 1;
+                tiedXYPairs += sum(consecutiveXYTies - 1);
+                consecutiveXYTies = 1;
+            }
+            prev = curr;
+        }
+        tiedXPairs += sum(consecutiveXTies - 1);
+        tiedXYPairs += sum(consecutiveXYTies - 1);
+
+        long swaps = 0;
+        @SuppressWarnings("unchecked")
+        Pair<Double, Double>[] pairsDestination = new Pair[n];
+        for (int segmentSize = 1; segmentSize < n; segmentSize <<= 1) {
+            for (int offset = 0; offset < n; offset += 2 * segmentSize) {
+                int i = offset;
+                final int iEnd = FastMath.min(i + segmentSize, n);
+                int j = iEnd;
+                final int jEnd = FastMath.min(j + segmentSize, n);
+
+                int copyLocation = offset;
+                while (i < iEnd || j < jEnd) {
+                    if (i < iEnd) {
+                        if (j < jEnd) {
+                            if (pairs[i].getSecond().compareTo(pairs[j].getSecond()) <= 0) {
+                                pairsDestination[copyLocation] = pairs[i];
+                                i++;
+                            } else {
+                                pairsDestination[copyLocation] = pairs[j];
+                                j++;
+                                swaps += iEnd - i;
+                            }
+                        } else {
+                            pairsDestination[copyLocation] = pairs[i];
+                            i++;
+                        }
+                    } else {
+                        pairsDestination[copyLocation] = pairs[j];
+                        j++;
+                    }
+                    copyLocation++;
+                }
+            }
+            final Pair<Double, Double>[] pairsTemp = pairs;
+            pairs = pairsDestination;
+            pairsDestination = pairsTemp;
+        }
+
+        long tiedYPairs = 0;
+        long consecutiveYTies = 1;
+        prev = pairs[0];
+        for (int i = 1; i < n; i++) {
+            final Pair<Double, Double> curr = pairs[i];
+            if (curr.getSecond().equals(prev.getSecond())) {
+                consecutiveYTies++;
+            } else {
+                tiedYPairs += sum(consecutiveYTies - 1);
+                consecutiveYTies = 1;
+            }
+            prev = curr;
+        }
+        tiedYPairs += sum(consecutiveYTies - 1);
+
+        final long concordantMinusDiscordant = numPairs - tiedXPairs - tiedYPairs + tiedXYPairs - 2 * swaps;
+        final double nonTiedPairsMultiplied = (numPairs - tiedXPairs) * (double) (numPairs - tiedYPairs);
+        return concordantMinusDiscordant / FastMath.sqrt(nonTiedPairsMultiplied);
+    }
+
+    /**
+     * Returns the sum of the number from 1 .. n according to Gauss' summation formula:
+     * \[ \sum\limits_{k=1}^n k = \frac{n(n + 1)}{2} \]
+     *
+     * @param n the summation end
+     * @return the sum of the number from 1 to n
+     */
+    private static long sum(long n) {
+        return n * (n + 1) / 2l;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/correlation/PearsonsCorrelation.java b/src/main/java/org/apache/commons/math3/stat/correlation/PearsonsCorrelation.java
new file mode 100644
index 0000000..53d17ab
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/correlation/PearsonsCorrelation.java
@@ -0,0 +1,330 @@
+/*
+ * 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.math3.stat.correlation;
+
+import org.apache.commons.math3.distribution.TDistribution;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.linear.BlockRealMatrix;
+import org.apache.commons.math3.stat.regression.SimpleRegression;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Computes Pearson's product-moment correlation coefficients for pairs of arrays
+ * or columns of a matrix.
+ *
+ * <p>The constructors that take <code>RealMatrix</code> or
+ * <code>double[][]</code> arguments generate correlation matrices.  The
+ * columns of the input matrices are assumed to represent variable values.
+ * Correlations are given by the formula</p>
+ *
+ * <p><code>cor(X, Y) = &Sigma;[(x<sub>i</sub> - E(X))(y<sub>i</sub> - E(Y))] / [(n - 1)s(X)s(Y)]</code>
+ * where <code>E(X)</code> is the mean of <code>X</code>, <code>E(Y)</code>
+ * is the mean of the <code>Y</code> values and s(X), s(Y) are standard deviations.</p>
+ *
+ * <p>To compute the correlation coefficient for a single pair of arrays, use {@link #PearsonsCorrelation()}
+ * to construct an instance with no data and then {@link #correlation(double[], double[])}.
+ * Correlation matrices can also be computed directly from an instance with no data using
+ * {@link #computeCorrelationMatrix(double[][])}. In order to use {@link #getCorrelationMatrix()},
+ * {@link #getCorrelationPValues()},  or {@link #getCorrelationStandardErrors()}; however, one of the
+ * constructors supplying data or a covariance matrix must be used to create the instance.</p>
+ *
+ * @since 2.0
+ */
+public class PearsonsCorrelation {
+
+    /** correlation matrix */
+    private final RealMatrix correlationMatrix;
+
+    /** number of observations */
+    private final int nObs;
+
+    /**
+     * Create a PearsonsCorrelation instance without data.
+     */
+    public PearsonsCorrelation() {
+        super();
+        correlationMatrix = null;
+        nObs = 0;
+    }
+
+    /**
+     * Create a PearsonsCorrelation from a rectangular array
+     * whose columns represent values of variables to be correlated.
+     *
+     * Throws MathIllegalArgumentException if the input array does not have at least
+     * two columns and two rows.  Pairwise correlations are set to NaN if one
+     * of the correlates has zero variance.
+     *
+     * @param data rectangular array with columns representing variables
+     * @throws MathIllegalArgumentException if the input data array is not
+     * rectangular with at least two rows and two columns.
+     * @see #correlation(double[], double[])
+     */
+    public PearsonsCorrelation(double[][] data) {
+        this(new BlockRealMatrix(data));
+    }
+
+    /**
+     * Create a PearsonsCorrelation from a RealMatrix whose columns
+     * represent variables to be correlated.
+     *
+     * Throws MathIllegalArgumentException if the matrix does not have at least
+     * two columns and two rows.  Pairwise correlations are set to NaN if one
+     * of the correlates has zero variance.
+     *
+     * @param matrix matrix with columns representing variables to correlate
+     * @throws MathIllegalArgumentException if the matrix does not contain sufficient data
+     * @see #correlation(double[], double[])
+     */
+    public PearsonsCorrelation(RealMatrix matrix) {
+        nObs = matrix.getRowDimension();
+        correlationMatrix = computeCorrelationMatrix(matrix);
+    }
+
+    /**
+     * Create a PearsonsCorrelation from a {@link Covariance}.  The correlation
+     * matrix is computed by scaling the Covariance's covariance matrix.
+     * The Covariance instance must have been created from a data matrix with
+     * columns representing variable values.
+     *
+     * @param covariance Covariance instance
+     */
+    public PearsonsCorrelation(Covariance covariance) {
+        RealMatrix covarianceMatrix = covariance.getCovarianceMatrix();
+        if (covarianceMatrix == null) {
+            throw new NullArgumentException(LocalizedFormats.COVARIANCE_MATRIX);
+        }
+        nObs = covariance.getN();
+        correlationMatrix = covarianceToCorrelation(covarianceMatrix);
+    }
+
+    /**
+     * Create a PearsonsCorrelation from a covariance matrix. The correlation
+     * matrix is computed by scaling the covariance matrix.
+     *
+     * @param covarianceMatrix covariance matrix
+     * @param numberOfObservations the number of observations in the dataset used to compute
+     * the covariance matrix
+     */
+    public PearsonsCorrelation(RealMatrix covarianceMatrix, int numberOfObservations) {
+        nObs = numberOfObservations;
+        correlationMatrix = covarianceToCorrelation(covarianceMatrix);
+    }
+
+    /**
+     * Returns the correlation matrix.
+     *
+     * <p>This method will return null if the argumentless constructor was used
+     * to create this instance, even if {@link #computeCorrelationMatrix(double[][])}
+     * has been called before it is activated.</p>
+     *
+     * @return correlation matrix
+     */
+    public RealMatrix getCorrelationMatrix() {
+        return correlationMatrix;
+    }
+
+    /**
+     * Returns a matrix of standard errors associated with the estimates
+     * in the correlation matrix.<br/>
+     * <code>getCorrelationStandardErrors().getEntry(i,j)</code> is the standard
+     * error associated with <code>getCorrelationMatrix.getEntry(i,j)</code>
+     *
+     * <p>The formula used to compute the standard error is <br/>
+     * <code>SE<sub>r</sub> = ((1 - r<sup>2</sup>) / (n - 2))<sup>1/2</sup></code>
+     * where <code>r</code> is the estimated correlation coefficient and
+     * <code>n</code> is the number of observations in the source dataset.</p>
+     *
+     * <p>To use this method, one of the constructors that supply an input
+     * matrix must have been used to create this instance.</p>
+     *
+     * @return matrix of correlation standard errors
+     * @throws NullPointerException if this instance was created with no data
+     */
+    public RealMatrix getCorrelationStandardErrors() {
+        int nVars = correlationMatrix.getColumnDimension();
+        double[][] out = new double[nVars][nVars];
+        for (int i = 0; i < nVars; i++) {
+            for (int j = 0; j < nVars; j++) {
+                double r = correlationMatrix.getEntry(i, j);
+                out[i][j] = FastMath.sqrt((1 - r * r) /(nObs - 2));
+            }
+        }
+        return new BlockRealMatrix(out);
+    }
+
+    /**
+     * Returns a matrix of p-values associated with the (two-sided) null
+     * hypothesis that the corresponding correlation coefficient is zero.
+     *
+     * <p><code>getCorrelationPValues().getEntry(i,j)</code> is the probability
+     * that a random variable distributed as <code>t<sub>n-2</sub></code> takes
+     * a value with absolute value greater than or equal to <br>
+     * <code>|r|((n - 2) / (1 - r<sup>2</sup>))<sup>1/2</sup></code></p>
+     *
+     * <p>The values in the matrix are sometimes referred to as the
+     * <i>significance</i> of the corresponding correlation coefficients.</p>
+     *
+     * <p>To use this method, one of the constructors that supply an input
+     * matrix must have been used to create this instance.</p>
+     *
+     * @return matrix of p-values
+     * @throws org.apache.commons.math3.exception.MaxCountExceededException
+     * if an error occurs estimating probabilities
+     * @throws NullPointerException if this instance was created with no data
+     */
+    public RealMatrix getCorrelationPValues() {
+        TDistribution tDistribution = new TDistribution(nObs - 2);
+        int nVars = correlationMatrix.getColumnDimension();
+        double[][] out = new double[nVars][nVars];
+        for (int i = 0; i < nVars; i++) {
+            for (int j = 0; j < nVars; j++) {
+                if (i == j) {
+                    out[i][j] = 0d;
+                } else {
+                    double r = correlationMatrix.getEntry(i, j);
+                    double t = FastMath.abs(r * FastMath.sqrt((nObs - 2)/(1 - r * r)));
+                    out[i][j] = 2 * tDistribution.cumulativeProbability(-t);
+                }
+            }
+        }
+        return new BlockRealMatrix(out);
+    }
+
+
+    /**
+     * Computes the correlation matrix for the columns of the
+     * input matrix, using {@link #correlation(double[], double[])}.
+     *
+     * Throws MathIllegalArgumentException if the matrix does not have at least
+     * two columns and two rows.  Pairwise correlations are set to NaN if one
+     * of the correlates has zero variance.
+     *
+     * @param matrix matrix with columns representing variables to correlate
+     * @return correlation matrix
+     * @throws MathIllegalArgumentException if the matrix does not contain sufficient data
+     * @see #correlation(double[], double[])
+     */
+    public RealMatrix computeCorrelationMatrix(RealMatrix matrix) {
+        checkSufficientData(matrix);
+        int nVars = matrix.getColumnDimension();
+        RealMatrix outMatrix = new BlockRealMatrix(nVars, nVars);
+        for (int i = 0; i < nVars; i++) {
+            for (int j = 0; j < i; j++) {
+              double corr = correlation(matrix.getColumn(i), matrix.getColumn(j));
+              outMatrix.setEntry(i, j, corr);
+              outMatrix.setEntry(j, i, corr);
+            }
+            outMatrix.setEntry(i, i, 1d);
+        }
+        return outMatrix;
+    }
+
+    /**
+     * Computes the correlation matrix for the columns of the
+     * input rectangular array.  The columns of the array represent values
+     * of variables to be correlated.
+     *
+     * Throws MathIllegalArgumentException if the matrix does not have at least
+     * two columns and two rows or if the array is not rectangular. Pairwise
+     * correlations are set to NaN if one of the correlates has zero variance.
+     *
+     * @param data matrix with columns representing variables to correlate
+     * @return correlation matrix
+     * @throws MathIllegalArgumentException if the array does not contain sufficient data
+     * @see #correlation(double[], double[])
+     */
+    public RealMatrix computeCorrelationMatrix(double[][] data) {
+       return computeCorrelationMatrix(new BlockRealMatrix(data));
+    }
+
+    /**
+     * Computes the Pearson's product-moment correlation coefficient between two arrays.
+     *
+     * <p>Throws MathIllegalArgumentException if the arrays do not have the same length
+     * or their common length is less than 2.  Returns {@code NaN} if either of the arrays
+     * has zero variance (i.e., if one of the arrays does not contain at least two distinct
+     * values).</p>
+     *
+     * @param xArray first data array
+     * @param yArray second data array
+     * @return Returns Pearson's correlation coefficient for the two arrays
+     * @throws DimensionMismatchException if the arrays lengths do not match
+     * @throws MathIllegalArgumentException if there is insufficient data
+     */
+    public double correlation(final double[] xArray, final double[] yArray) {
+        SimpleRegression regression = new SimpleRegression();
+        if (xArray.length != yArray.length) {
+            throw new DimensionMismatchException(xArray.length, yArray.length);
+        } else if (xArray.length < 2) {
+            throw new MathIllegalArgumentException(LocalizedFormats.INSUFFICIENT_DIMENSION,
+                                                   xArray.length, 2);
+        } else {
+            for(int i=0; i<xArray.length; i++) {
+                regression.addData(xArray[i], yArray[i]);
+            }
+            return regression.getR();
+        }
+    }
+
+    /**
+     * Derives a correlation matrix from a covariance matrix.
+     *
+     * <p>Uses the formula <br/>
+     * <code>r(X,Y) = cov(X,Y)/s(X)s(Y)</code> where
+     * <code>r(&middot,&middot;)</code> is the correlation coefficient and
+     * <code>s(&middot;)</code> means standard deviation.</p>
+     *
+     * @param covarianceMatrix the covariance matrix
+     * @return correlation matrix
+     */
+    public RealMatrix covarianceToCorrelation(RealMatrix covarianceMatrix) {
+        int nVars = covarianceMatrix.getColumnDimension();
+        RealMatrix outMatrix = new BlockRealMatrix(nVars, nVars);
+        for (int i = 0; i < nVars; i++) {
+            double sigma = FastMath.sqrt(covarianceMatrix.getEntry(i, i));
+            outMatrix.setEntry(i, i, 1d);
+            for (int j = 0; j < i; j++) {
+                double entry = covarianceMatrix.getEntry(i, j) /
+                       (sigma * FastMath.sqrt(covarianceMatrix.getEntry(j, j)));
+                outMatrix.setEntry(i, j, entry);
+                outMatrix.setEntry(j, i, entry);
+            }
+        }
+        return outMatrix;
+    }
+
+    /**
+     * Throws MathIllegalArgumentException if the matrix does not have at least
+     * two columns and two rows.
+     *
+     * @param matrix matrix to check for sufficiency
+     * @throws MathIllegalArgumentException if there is insufficient data
+     */
+    private void checkSufficientData(final RealMatrix matrix) {
+        int nRows = matrix.getRowDimension();
+        int nCols = matrix.getColumnDimension();
+        if (nRows < 2 || nCols < 2) {
+            throw new MathIllegalArgumentException(LocalizedFormats.INSUFFICIENT_ROWS_AND_COLUMNS,
+                                                   nRows, nCols);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/correlation/SpearmansCorrelation.java b/src/main/java/org/apache/commons/math3/stat/correlation/SpearmansCorrelation.java
new file mode 100644
index 0000000..80c0a54
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/correlation/SpearmansCorrelation.java
@@ -0,0 +1,262 @@
+/*
+ * 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.math3.stat.correlation;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.linear.BlockRealMatrix;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.stat.ranking.NaNStrategy;
+import org.apache.commons.math3.stat.ranking.NaturalRanking;
+import org.apache.commons.math3.stat.ranking.RankingAlgorithm;
+
+/**
+ * Spearman's rank correlation. This implementation performs a rank
+ * transformation on the input data and then computes {@link PearsonsCorrelation}
+ * on the ranked data.
+ * <p>
+ * By default, ranks are computed using {@link NaturalRanking} with default
+ * strategies for handling NaNs and ties in the data (NaNs maximal, ties averaged).
+ * The ranking algorithm can be set using a constructor argument.
+ *
+ * @since 2.0
+ */
+public class SpearmansCorrelation {
+
+    /** Input data */
+    private final RealMatrix data;
+
+    /** Ranking algorithm  */
+    private final RankingAlgorithm rankingAlgorithm;
+
+    /** Rank correlation */
+    private final PearsonsCorrelation rankCorrelation;
+
+    /**
+     * Create a SpearmansCorrelation without data.
+     */
+    public SpearmansCorrelation() {
+        this(new NaturalRanking());
+    }
+
+    /**
+     * Create a SpearmansCorrelation with the given ranking algorithm.
+     * <p>
+     * From version 4.0 onwards this constructor will throw an exception
+     * if the provided {@link NaturalRanking} uses a {@link NaNStrategy#REMOVED} strategy.
+     *
+     * @param rankingAlgorithm ranking algorithm
+     * @since 3.1
+     */
+    public SpearmansCorrelation(final RankingAlgorithm rankingAlgorithm) {
+        data = null;
+        this.rankingAlgorithm = rankingAlgorithm;
+        rankCorrelation = null;
+    }
+
+    /**
+     * Create a SpearmansCorrelation from the given data matrix.
+     *
+     * @param dataMatrix matrix of data with columns representing
+     * variables to correlate
+     */
+    public SpearmansCorrelation(final RealMatrix dataMatrix) {
+        this(dataMatrix, new NaturalRanking());
+    }
+
+    /**
+     * Create a SpearmansCorrelation with the given input data matrix
+     * and ranking algorithm.
+     * <p>
+     * From version 4.0 onwards this constructor will throw an exception
+     * if the provided {@link NaturalRanking} uses a {@link NaNStrategy#REMOVED} strategy.
+     *
+     * @param dataMatrix matrix of data with columns representing
+     * variables to correlate
+     * @param rankingAlgorithm ranking algorithm
+     */
+    public SpearmansCorrelation(final RealMatrix dataMatrix, final RankingAlgorithm rankingAlgorithm) {
+        this.rankingAlgorithm = rankingAlgorithm;
+        this.data = rankTransform(dataMatrix);
+        rankCorrelation = new PearsonsCorrelation(data);
+    }
+
+    /**
+     * Calculate the Spearman Rank Correlation Matrix.
+     *
+     * @return Spearman Rank Correlation Matrix
+     * @throws NullPointerException if this instance was created with no data
+     */
+    public RealMatrix getCorrelationMatrix() {
+        return rankCorrelation.getCorrelationMatrix();
+    }
+
+    /**
+     * Returns a {@link PearsonsCorrelation} instance constructed from the
+     * ranked input data. That is,
+     * <code>new SpearmansCorrelation(matrix).getRankCorrelation()</code>
+     * is equivalent to
+     * <code>new PearsonsCorrelation(rankTransform(matrix))</code> where
+     * <code>rankTransform(matrix)</code> is the result of applying the
+     * configured <code>RankingAlgorithm</code> to each of the columns of
+     * <code>matrix.</code>
+     *
+     * <p>Returns null if this instance was created with no data.</p>
+     *
+     * @return PearsonsCorrelation among ranked column data
+     */
+    public PearsonsCorrelation getRankCorrelation() {
+        return rankCorrelation;
+    }
+
+    /**
+     * Computes the Spearman's rank correlation matrix for the columns of the
+     * input matrix.
+     *
+     * @param matrix matrix with columns representing variables to correlate
+     * @return correlation matrix
+     */
+    public RealMatrix computeCorrelationMatrix(final RealMatrix matrix) {
+        final RealMatrix matrixCopy = rankTransform(matrix);
+        return new PearsonsCorrelation().computeCorrelationMatrix(matrixCopy);
+    }
+
+    /**
+     * Computes the Spearman's rank correlation matrix for the columns of the
+     * input rectangular array.  The columns of the array represent values
+     * of variables to be correlated.
+     *
+     * @param matrix matrix with columns representing variables to correlate
+     * @return correlation matrix
+     */
+    public RealMatrix computeCorrelationMatrix(final double[][] matrix) {
+       return computeCorrelationMatrix(new BlockRealMatrix(matrix));
+    }
+
+    /**
+     * Computes the Spearman's rank correlation coefficient between the two arrays.
+     *
+     * @param xArray first data array
+     * @param yArray second data array
+     * @return Returns Spearman's rank correlation coefficient for the two arrays
+     * @throws DimensionMismatchException if the arrays lengths do not match
+     * @throws MathIllegalArgumentException if the array length is less than 2
+     */
+    public double correlation(final double[] xArray, final double[] yArray) {
+        if (xArray.length != yArray.length) {
+            throw new DimensionMismatchException(xArray.length, yArray.length);
+        } else if (xArray.length < 2) {
+            throw new MathIllegalArgumentException(LocalizedFormats.INSUFFICIENT_DIMENSION,
+                                                   xArray.length, 2);
+        } else {
+            double[] x = xArray;
+            double[] y = yArray;
+            if (rankingAlgorithm instanceof NaturalRanking &&
+                NaNStrategy.REMOVED == ((NaturalRanking) rankingAlgorithm).getNanStrategy()) {
+                final Set<Integer> nanPositions = new HashSet<Integer>();
+
+                nanPositions.addAll(getNaNPositions(xArray));
+                nanPositions.addAll(getNaNPositions(yArray));
+
+                x = removeValues(xArray, nanPositions);
+                y = removeValues(yArray, nanPositions);
+            }
+            return new PearsonsCorrelation().correlation(rankingAlgorithm.rank(x), rankingAlgorithm.rank(y));
+        }
+    }
+
+    /**
+     * Applies rank transform to each of the columns of <code>matrix</code>
+     * using the current <code>rankingAlgorithm</code>.
+     *
+     * @param matrix matrix to transform
+     * @return a rank-transformed matrix
+     */
+    private RealMatrix rankTransform(final RealMatrix matrix) {
+        RealMatrix transformed = null;
+
+        if (rankingAlgorithm instanceof NaturalRanking &&
+                ((NaturalRanking) rankingAlgorithm).getNanStrategy() == NaNStrategy.REMOVED) {
+            final Set<Integer> nanPositions = new HashSet<Integer>();
+            for (int i = 0; i < matrix.getColumnDimension(); i++) {
+                nanPositions.addAll(getNaNPositions(matrix.getColumn(i)));
+            }
+
+            // if we have found NaN values, we have to update the matrix size
+            if (!nanPositions.isEmpty()) {
+                transformed = new BlockRealMatrix(matrix.getRowDimension() - nanPositions.size(),
+                                                  matrix.getColumnDimension());
+                for (int i = 0; i < transformed.getColumnDimension(); i++) {
+                    transformed.setColumn(i, removeValues(matrix.getColumn(i), nanPositions));
+                }
+            }
+        }
+
+        if (transformed == null) {
+            transformed = matrix.copy();
+        }
+
+        for (int i = 0; i < transformed.getColumnDimension(); i++) {
+            transformed.setColumn(i, rankingAlgorithm.rank(transformed.getColumn(i)));
+        }
+
+        return transformed;
+    }
+
+    /**
+     * Returns a list containing the indices of NaN values in the input array.
+     *
+     * @param input the input array
+     * @return a list of NaN positions in the input array
+     */
+    private List<Integer> getNaNPositions(final double[] input) {
+        final List<Integer> positions = new ArrayList<Integer>();
+        for (int i = 0; i < input.length; i++) {
+            if (Double.isNaN(input[i])) {
+                positions.add(i);
+            }
+        }
+        return positions;
+    }
+
+    /**
+     * Removes all values from the input array at the specified indices.
+     *
+     * @param input the input array
+     * @param indices a set containing the indices to be removed
+     * @return the input array without the values at the specified indices
+     */
+    private double[] removeValues(final double[] input, final Set<Integer> indices) {
+        if (indices.isEmpty()) {
+            return input;
+        }
+        final double[] result = new double[input.length - indices.size()];
+        for (int i = 0, j = 0; i < input.length; i++) {
+            if (!indices.contains(i)) {
+                result[j++] = input[i];
+            }
+        }
+        return result;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/correlation/StorelessBivariateCovariance.java b/src/main/java/org/apache/commons/math3/stat/correlation/StorelessBivariateCovariance.java
new file mode 100644
index 0000000..1a798d2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/correlation/StorelessBivariateCovariance.java
@@ -0,0 +1,138 @@
+/*
+ * 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.math3.stat.correlation;
+
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Bivariate Covariance implementation that does not require input data to be
+ * stored in memory.
+ *
+ * <p>This class is based on a paper written by Philippe P&eacute;bay:
+ * <a href="http://prod.sandia.gov/techlib/access-control.cgi/2008/086212.pdf">
+ * Formulas for Robust, One-Pass Parallel Computation of Covariances and
+ * Arbitrary-Order Statistical Moments</a>, 2008, Technical Report SAND2008-6212,
+ * Sandia National Laboratories. It computes the covariance for a pair of variables.
+ * Use {@link StorelessCovariance} to estimate an entire covariance matrix.</p>
+ *
+ * <p>Note: This class is package private as it is only used internally in
+ * the {@link StorelessCovariance} class.</p>
+ *
+ * @since 3.0
+ */
+class StorelessBivariateCovariance {
+
+    /** the mean of variable x */
+    private double meanX;
+
+    /** the mean of variable y */
+    private double meanY;
+
+    /** number of observations */
+    private double n;
+
+    /** the running covariance estimate */
+    private double covarianceNumerator;
+
+    /** flag for bias correction */
+    private boolean biasCorrected;
+
+    /**
+     * Create an empty {@link StorelessBivariateCovariance} instance with
+     * bias correction.
+     */
+    StorelessBivariateCovariance() {
+        this(true);
+    }
+
+    /**
+     * Create an empty {@link StorelessBivariateCovariance} instance.
+     *
+     * @param biasCorrection if <code>true</code> the covariance estimate is corrected
+     * for bias, i.e. n-1 in the denominator, otherwise there is no bias correction,
+     * i.e. n in the denominator.
+     */
+    StorelessBivariateCovariance(final boolean biasCorrection) {
+        meanX = meanY = 0.0;
+        n = 0;
+        covarianceNumerator = 0.0;
+        biasCorrected = biasCorrection;
+    }
+
+    /**
+     * Update the covariance estimation with a pair of variables (x, y).
+     *
+     * @param x the x value
+     * @param y the y value
+     */
+    public void increment(final double x, final double y) {
+        n++;
+        final double deltaX = x - meanX;
+        final double deltaY = y - meanY;
+        meanX += deltaX / n;
+        meanY += deltaY / n;
+        covarianceNumerator += ((n - 1.0) / n) * deltaX * deltaY;
+    }
+
+    /**
+     * Appends another bivariate covariance calculation to this.
+     * After this operation, statistics returned should be close to what would
+     * have been obtained by by performing all of the {@link #increment(double, double)}
+     * operations in {@code cov} directly on this.
+     *
+     * @param cov StorelessBivariateCovariance instance to append.
+     */
+    public void append(StorelessBivariateCovariance cov) {
+        double oldN = n;
+        n += cov.n;
+        final double deltaX = cov.meanX - meanX;
+        final double deltaY = cov.meanY - meanY;
+        meanX += deltaX * cov.n / n;
+        meanY += deltaY * cov.n / n;
+        covarianceNumerator += cov.covarianceNumerator + oldN * cov.n / n * deltaX * deltaY;
+    }
+
+    /**
+     * Returns the number of observations.
+     *
+     * @return number of observations
+     */
+    public double getN() {
+        return n;
+    }
+
+    /**
+     * Return the current covariance estimate.
+     *
+     * @return the current covariance
+     * @throws NumberIsTooSmallException if the number of observations
+     * is &lt; 2
+     */
+    public double getResult() throws NumberIsTooSmallException {
+        if (n < 2) {
+            throw new NumberIsTooSmallException(LocalizedFormats.INSUFFICIENT_DIMENSION,
+                                                n, 2, true);
+        }
+        if (biasCorrected) {
+            return covarianceNumerator / (n - 1d);
+        } else {
+            return covarianceNumerator / n;
+        }
+    }
+}
+
diff --git a/src/main/java/org/apache/commons/math3/stat/correlation/StorelessCovariance.java b/src/main/java/org/apache/commons/math3/stat/correlation/StorelessCovariance.java
new file mode 100644
index 0000000..7e927ca
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/correlation/StorelessCovariance.java
@@ -0,0 +1,229 @@
+/*
+ * 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.math3.stat.correlation;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathUnsupportedOperationException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.linear.MatrixUtils;
+import org.apache.commons.math3.linear.RealMatrix;
+
+/**
+ * Covariance implementation that does not require input data to be
+ * stored in memory. The size of the covariance matrix is specified in the
+ * constructor. Specific elements of the matrix are incrementally updated with
+ * calls to incrementRow() or increment Covariance().
+ *
+ * <p>This class is based on a paper written by Philippe P&eacute;bay:
+ * <a href="http://prod.sandia.gov/techlib/access-control.cgi/2008/086212.pdf">
+ * Formulas for Robust, One-Pass Parallel Computation of Covariances and
+ * Arbitrary-Order Statistical Moments</a>, 2008, Technical Report SAND2008-6212,
+ * Sandia National Laboratories.</p>
+ *
+ * <p>Note: the underlying covariance matrix is symmetric, thus only the
+ * upper triangular part of the matrix is stored and updated each increment.</p>
+ *
+ * @since 3.0
+ */
+public class StorelessCovariance extends Covariance {
+
+    /** the square covariance matrix (upper triangular part) */
+    private StorelessBivariateCovariance[] covMatrix;
+
+    /** dimension of the square covariance matrix */
+    private int dimension;
+
+    /**
+     * Create a bias corrected covariance matrix with a given dimension.
+     *
+     * @param dim the dimension of the square covariance matrix
+     */
+    public StorelessCovariance(final int dim) {
+        this(dim, true);
+    }
+
+    /**
+     * Create a covariance matrix with a given number of rows and columns and the
+     * indicated bias correction.
+     *
+     * @param dim the dimension of the covariance matrix
+     * @param biasCorrected if <code>true</code> the covariance estimate is corrected
+     * for bias, i.e. n-1 in the denominator, otherwise there is no bias correction,
+     * i.e. n in the denominator.
+     */
+    public StorelessCovariance(final int dim, final boolean biasCorrected) {
+        dimension = dim;
+        covMatrix = new StorelessBivariateCovariance[dimension * (dimension + 1) / 2];
+        initializeMatrix(biasCorrected);
+    }
+
+    /**
+     * Initialize the internal two-dimensional array of
+     * {@link StorelessBivariateCovariance} instances.
+     *
+     * @param biasCorrected if the covariance estimate shall be corrected for bias
+     */
+    private void initializeMatrix(final boolean biasCorrected) {
+        for(int i = 0; i < dimension; i++){
+            for(int j = 0; j < dimension; j++){
+                setElement(i, j, new StorelessBivariateCovariance(biasCorrected));
+            }
+        }
+    }
+
+    /**
+     * Returns the index (i, j) translated into the one-dimensional
+     * array used to store the upper triangular part of the symmetric
+     * covariance matrix.
+     *
+     * @param i the row index
+     * @param j the column index
+     * @return the corresponding index in the matrix array
+     */
+    private int indexOf(final int i, final int j) {
+        return j < i ? i * (i + 1) / 2 + j : j * (j + 1) / 2 + i;
+    }
+
+    /**
+     * Gets the element at index (i, j) from the covariance matrix
+     * @param i the row index
+     * @param j the column index
+     * @return the {@link StorelessBivariateCovariance} element at the given index
+     */
+    private StorelessBivariateCovariance getElement(final int i, final int j) {
+        return covMatrix[indexOf(i, j)];
+    }
+
+    /**
+     * Sets the covariance element at index (i, j) in the covariance matrix
+     * @param i the row index
+     * @param j the column index
+     * @param cov the {@link StorelessBivariateCovariance} element to be set
+     */
+    private void setElement(final int i, final int j,
+                            final StorelessBivariateCovariance cov) {
+        covMatrix[indexOf(i, j)] = cov;
+    }
+
+    /**
+     * Get the covariance for an individual element of the covariance matrix.
+     *
+     * @param xIndex row index in the covariance matrix
+     * @param yIndex column index in the covariance matrix
+     * @return the covariance of the given element
+     * @throws NumberIsTooSmallException if the number of observations
+     * in the cell is &lt; 2
+     */
+    public double getCovariance(final int xIndex,
+                                final int yIndex)
+        throws NumberIsTooSmallException {
+
+        return getElement(xIndex, yIndex).getResult();
+
+    }
+
+    /**
+     * Increment the covariance matrix with one row of data.
+     *
+     * @param data array representing one row of data.
+     * @throws DimensionMismatchException if the length of <code>rowData</code>
+     * does not match with the covariance matrix
+     */
+    public void increment(final double[] data)
+        throws DimensionMismatchException {
+
+        int length = data.length;
+        if (length != dimension) {
+            throw new DimensionMismatchException(length, dimension);
+        }
+
+        // only update the upper triangular part of the covariance matrix
+        // as only these parts are actually stored
+        for (int i = 0; i < length; i++){
+            for (int j = i; j < length; j++){
+                getElement(i, j).increment(data[i], data[j]);
+            }
+        }
+
+    }
+
+    /**
+     * Appends {@code sc} to this, effectively aggregating the computations in {@code sc}
+     * with this.  After invoking this method, covariances returned should be close
+     * to what would have been obtained by performing all of the {@link #increment(double[])}
+     * operations in {@code sc} directly on this.
+     *
+     * @param sc externally computed StorelessCovariance to add to this
+     * @throws DimensionMismatchException if the dimension of sc does not match this
+     * @since 3.3
+     */
+    public void append(StorelessCovariance sc) throws DimensionMismatchException {
+        if (sc.dimension != dimension) {
+            throw new DimensionMismatchException(sc.dimension, dimension);
+        }
+
+        // only update the upper triangular part of the covariance matrix
+        // as only these parts are actually stored
+        for (int i = 0; i < dimension; i++) {
+            for (int j = i; j < dimension; j++) {
+                getElement(i, j).append(sc.getElement(i, j));
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NumberIsTooSmallException if the number of observations
+     * in a cell is &lt; 2
+     */
+    @Override
+    public RealMatrix getCovarianceMatrix() throws NumberIsTooSmallException {
+        return MatrixUtils.createRealMatrix(getData());
+    }
+
+    /**
+     * Return the covariance matrix as two-dimensional array.
+     *
+     * @return a two-dimensional double array of covariance values
+     * @throws NumberIsTooSmallException if the number of observations
+     * for a cell is &lt; 2
+     */
+    public double[][] getData() throws NumberIsTooSmallException {
+        final double[][] data = new double[dimension][dimension];
+        for (int i = 0; i < dimension; i++) {
+            for (int j = 0; j < dimension; j++) {
+                data[i][j] = getElement(i, j).getResult();
+            }
+        }
+        return data;
+    }
+
+    /**
+     * This {@link Covariance} method is not supported by a {@link StorelessCovariance},
+     * since the number of bivariate observations does not have to be the same for different
+     * pairs of covariates - i.e., N as defined in {@link Covariance#getN()} is undefined.
+     *
+     * @return nothing as this implementation always throws a
+     * {@link MathUnsupportedOperationException}
+     * @throws MathUnsupportedOperationException in all cases
+     */
+    @Override
+    public int getN()
+        throws MathUnsupportedOperationException {
+        throw new MathUnsupportedOperationException();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/correlation/package-info.java b/src/main/java/org/apache/commons/math3/stat/correlation/package-info.java
new file mode 100644
index 0000000..adf285e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/correlation/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+/**
+ *
+ *        Correlations/Covariance computations.
+ *
+ */
+package org.apache.commons.math3.stat.correlation;
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/AbstractStorelessUnivariateStatistic.java b/src/main/java/org/apache/commons/math3/stat/descriptive/AbstractStorelessUnivariateStatistic.java
new file mode 100644
index 0000000..4249994
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/AbstractStorelessUnivariateStatistic.java
@@ -0,0 +1,187 @@
+/*
+ * 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.math3.stat.descriptive;
+
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.util.MathUtils;
+import org.apache.commons.math3.util.Precision;
+
+/**
+ *
+ * Abstract implementation of the {@link StorelessUnivariateStatistic} interface.
+ * <p>
+ * Provides default <code>evaluate()</code> and <code>incrementAll(double[])</code>
+ * implementations.</p>
+ * <p>
+ * <strong>Note that these implementations are not synchronized.</strong></p>
+ *
+ */
+public abstract class AbstractStorelessUnivariateStatistic
+    extends AbstractUnivariateStatistic
+    implements StorelessUnivariateStatistic {
+
+    /**
+     * This default implementation calls {@link #clear}, then invokes
+     * {@link #increment} in a loop over the the input array, and then uses
+     * {@link #getResult} to compute the return value.
+     * <p>
+     * Note that this implementation changes the internal state of the
+     * statistic.  Its side effects are the same as invoking {@link #clear} and
+     * then {@link #incrementAll(double[])}.</p>
+     * <p>
+     * Implementations may override this method with a more efficient and
+     * possibly more accurate implementation that works directly with the
+     * input array.</p>
+     * <p>
+     * If the array is null, a MathIllegalArgumentException is thrown.</p>
+     * @param values input array
+     * @return the value of the statistic applied to the input array
+     * @throws MathIllegalArgumentException if values is null
+     * @see org.apache.commons.math3.stat.descriptive.UnivariateStatistic#evaluate(double[])
+     */
+    @Override
+    public double evaluate(final double[] values) throws MathIllegalArgumentException {
+        if (values == null) {
+            throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY);
+        }
+        return evaluate(values, 0, values.length);
+    }
+
+    /**
+     * This default implementation calls {@link #clear}, then invokes
+     * {@link #increment} in a loop over the specified portion of the input
+     * array, and then uses {@link #getResult} to compute the return value.
+     * <p>
+     * Note that this implementation changes the internal state of the
+     * statistic.  Its side effects are the same as invoking {@link #clear} and
+     * then {@link #incrementAll(double[], int, int)}.</p>
+     * <p>
+     * Implementations may override this method with a more efficient and
+     * possibly more accurate implementation that works directly with the
+     * input array.</p>
+     * <p>
+     * If the array is null or the index parameters are not valid, an
+     * MathIllegalArgumentException is thrown.</p>
+     * @param values the input array
+     * @param begin the index of the first element to include
+     * @param length the number of elements to include
+     * @return the value of the statistic applied to the included array entries
+     * @throws MathIllegalArgumentException if the array is null or the indices are not valid
+     * @see org.apache.commons.math3.stat.descriptive.UnivariateStatistic#evaluate(double[], int, int)
+     */
+    @Override
+    public double evaluate(final double[] values, final int begin,
+            final int length) throws MathIllegalArgumentException {
+        if (test(values, begin, length)) {
+            clear();
+            incrementAll(values, begin, length);
+        }
+        return getResult();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public abstract StorelessUnivariateStatistic copy();
+
+    /**
+     * {@inheritDoc}
+     */
+    public abstract void clear();
+
+    /**
+     * {@inheritDoc}
+     */
+    public abstract double getResult();
+
+    /**
+     * {@inheritDoc}
+     */
+    public abstract void increment(final double d);
+
+    /**
+     * This default implementation just calls {@link #increment} in a loop over
+     * the input array.
+     * <p>
+     * Throws IllegalArgumentException if the input values array is null.</p>
+     *
+     * @param values values to add
+     * @throws MathIllegalArgumentException if values is null
+     * @see org.apache.commons.math3.stat.descriptive.StorelessUnivariateStatistic#incrementAll(double[])
+     */
+    public void incrementAll(double[] values) throws MathIllegalArgumentException {
+        if (values == null) {
+            throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY);
+        }
+        incrementAll(values, 0, values.length);
+    }
+
+    /**
+     * This default implementation just calls {@link #increment} in a loop over
+     * the specified portion of the input array.
+     * <p>
+     * Throws IllegalArgumentException if the input values array is null.</p>
+     *
+     * @param values  array holding values to add
+     * @param begin   index of the first array element to add
+     * @param length  number of array elements to add
+     * @throws MathIllegalArgumentException if values is null
+     * @see org.apache.commons.math3.stat.descriptive.StorelessUnivariateStatistic#incrementAll(double[], int, int)
+     */
+    public void incrementAll(double[] values, int begin, int length) throws MathIllegalArgumentException {
+        if (test(values, begin, length)) {
+            int k = begin + length;
+            for (int i = begin; i < k; i++) {
+                increment(values[i]);
+            }
+        }
+    }
+
+    /**
+     * Returns true iff <code>object</code> is an
+     * <code>AbstractStorelessUnivariateStatistic</code> returning the same
+     * values as this for <code>getResult()</code> and <code>getN()</code>
+     * @param object object to test equality against.
+     * @return true if object returns the same value as this
+     */
+    @Override
+    public boolean equals(Object object) {
+        if (object == this ) {
+            return true;
+        }
+       if (object instanceof AbstractStorelessUnivariateStatistic == false) {
+            return false;
+        }
+        AbstractStorelessUnivariateStatistic stat = (AbstractStorelessUnivariateStatistic) object;
+        return Precision.equalsIncludingNaN(stat.getResult(), this.getResult()) &&
+               Precision.equalsIncludingNaN(stat.getN(), this.getN());
+    }
+
+    /**
+     * Returns hash code based on getResult() and getN()
+     *
+     * @return hash code
+     */
+    @Override
+    public int hashCode() {
+        return 31* (31 + MathUtils.hash(getResult())) + MathUtils.hash(getN());
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/AbstractUnivariateStatistic.java b/src/main/java/org/apache/commons/math3/stat/descriptive/AbstractUnivariateStatistic.java
new file mode 100644
index 0000000..9abe45a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/AbstractUnivariateStatistic.java
@@ -0,0 +1,263 @@
+/*
+ * 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.math3.stat.descriptive;
+
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * Abstract base class for all implementations of the
+ * {@link UnivariateStatistic} interface.
+ * <p>
+ * Provides a default implementation of <code>evaluate(double[]),</code>
+ * delegating to <code>evaluate(double[], int, int)</code> in the natural way.
+ * </p>
+ * <p>
+ * Also includes a <code>test</code> method that performs generic parameter
+ * validation for the <code>evaluate</code> methods.</p>
+ *
+ */
+public abstract class AbstractUnivariateStatistic
+    implements UnivariateStatistic {
+
+    /** Stored data. */
+    private double[] storedData;
+
+    /**
+     * Set the data array.
+     * <p>
+     * The stored value is a copy of the parameter array, not the array itself.
+     * </p>
+     * @param values data array to store (may be null to remove stored data)
+     * @see #evaluate()
+     */
+    public void setData(final double[] values) {
+        storedData = (values == null) ? null : values.clone();
+    }
+
+    /**
+     * Get a copy of the stored data array.
+     * @return copy of the stored data array (may be null)
+     */
+    public double[] getData() {
+        return (storedData == null) ? null : storedData.clone();
+    }
+
+    /**
+     * Get a reference to the stored data array.
+     * @return reference to the stored data array (may be null)
+     */
+    protected double[] getDataRef() {
+        return storedData;
+    }
+
+    /**
+     * Set the data array.  The input array is copied, not referenced.
+     *
+     * @param values data array to store
+     * @param begin the index of the first element to include
+     * @param length the number of elements to include
+     * @throws MathIllegalArgumentException if values is null or the indices
+     * are not valid
+     * @see #evaluate()
+     */
+    public void setData(final double[] values, final int begin, final int length)
+    throws MathIllegalArgumentException {
+        if (values == null) {
+            throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY);
+        }
+
+        if (begin < 0) {
+            throw new NotPositiveException(LocalizedFormats.START_POSITION, begin);
+        }
+
+        if (length < 0) {
+            throw new NotPositiveException(LocalizedFormats.LENGTH, length);
+        }
+
+        if (begin + length > values.length) {
+            throw new NumberIsTooLargeException(LocalizedFormats.SUBARRAY_ENDS_AFTER_ARRAY_END,
+                                                begin + length, values.length, true);
+        }
+        storedData = new double[length];
+        System.arraycopy(values, begin, storedData, 0, length);
+    }
+
+    /**
+     * Returns the result of evaluating the statistic over the stored data.
+     * <p>
+     * The stored array is the one which was set by previous calls to {@link #setData(double[])}.
+     * </p>
+     * @return the value of the statistic applied to the stored data
+     * @throws MathIllegalArgumentException if the stored data array is null
+     */
+    public double evaluate() throws MathIllegalArgumentException {
+        return evaluate(storedData);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public double evaluate(final double[] values) throws MathIllegalArgumentException {
+        test(values, 0, 0);
+        return evaluate(values, 0, values.length);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public abstract double evaluate(final double[] values, final int begin, final int length)
+    throws MathIllegalArgumentException;
+
+    /**
+     * {@inheritDoc}
+     */
+    public abstract UnivariateStatistic copy();
+
+    /**
+     * This method is used by <code>evaluate(double[], int, int)</code> methods
+     * to verify that the input parameters designate a subarray of positive length.
+     * <p>
+     * <ul>
+     * <li>returns <code>true</code> iff the parameters designate a subarray of
+     * positive length</li>
+     * <li>throws <code>MathIllegalArgumentException</code> if the array is null or
+     * or the indices are invalid</li>
+     * <li>returns <code>false</li> if the array is non-null, but
+     * <code>length</code> is 0.
+     * </ul></p>
+     *
+     * @param values the input array
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @return true if the parameters are valid and designate a subarray of positive length
+     * @throws MathIllegalArgumentException if the indices are invalid or the array is null
+     */
+    protected boolean test(
+        final double[] values,
+        final int begin,
+        final int length) throws MathIllegalArgumentException {
+        return MathArrays.verifyValues(values, begin, length, false);
+    }
+
+    /**
+     * This method is used by <code>evaluate(double[], int, int)</code> methods
+     * to verify that the input parameters designate a subarray of positive length.
+     * <p>
+     * <ul>
+     * <li>returns <code>true</code> iff the parameters designate a subarray of
+     * non-negative length</li>
+     * <li>throws <code>IllegalArgumentException</code> if the array is null or
+     * or the indices are invalid</li>
+     * <li>returns <code>false</li> if the array is non-null, but
+     * <code>length</code> is 0 unless <code>allowEmpty</code> is <code>true</code>
+     * </ul></p>
+     *
+     * @param values the input array
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @param allowEmpty if <code>true</code> then zero length arrays are allowed
+     * @return true if the parameters are valid
+     * @throws MathIllegalArgumentException if the indices are invalid or the array is null
+     * @since 3.0
+     */
+    protected boolean test(final double[] values, final int begin,
+            final int length, final boolean allowEmpty) throws MathIllegalArgumentException {
+        return MathArrays.verifyValues(values, begin, length, allowEmpty);
+    }
+
+    /**
+     * This method is used by <code>evaluate(double[], double[], int, int)</code> methods
+     * to verify that the begin and length parameters designate a subarray of positive length
+     * and the weights are all non-negative, non-NaN, finite, and not all zero.
+     * <p>
+     * <ul>
+     * <li>returns <code>true</code> iff the parameters designate a subarray of
+     * positive length and the weights array contains legitimate values.</li>
+     * <li>throws <code>IllegalArgumentException</code> if any of the following are true:
+     * <ul><li>the values array is null</li>
+     *     <li>the weights array is null</li>
+     *     <li>the weights array does not have the same length as the values array</li>
+     *     <li>the weights array contains one or more infinite values</li>
+     *     <li>the weights array contains one or more NaN values</li>
+     *     <li>the weights array contains negative values</li>
+     *     <li>the start and length arguments do not determine a valid array</li></ul>
+     * </li>
+     * <li>returns <code>false</li> if the array is non-null, but
+     * <code>length</code> is 0.
+     * </ul></p>
+     *
+     * @param values the input array
+     * @param weights the weights array
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @return true if the parameters are valid and designate a subarray of positive length
+     * @throws MathIllegalArgumentException if the indices are invalid or the array is null
+     * @since 2.1
+     */
+    protected boolean test(
+        final double[] values,
+        final double[] weights,
+        final int begin,
+        final int length) throws MathIllegalArgumentException {
+        return MathArrays.verifyValues(values, weights, begin, length, false);
+    }
+
+    /**
+     * This method is used by <code>evaluate(double[], double[], int, int)</code> methods
+     * to verify that the begin and length parameters designate a subarray of positive length
+     * and the weights are all non-negative, non-NaN, finite, and not all zero.
+     * <p>
+     * <ul>
+     * <li>returns <code>true</code> iff the parameters designate a subarray of
+     * non-negative length and the weights array contains legitimate values.</li>
+     * <li>throws <code>MathIllegalArgumentException</code> if any of the following are true:
+     * <ul><li>the values array is null</li>
+     *     <li>the weights array is null</li>
+     *     <li>the weights array does not have the same length as the values array</li>
+     *     <li>the weights array contains one or more infinite values</li>
+     *     <li>the weights array contains one or more NaN values</li>
+     *     <li>the weights array contains negative values</li>
+     *     <li>the start and length arguments do not determine a valid array</li></ul>
+     * </li>
+     * <li>returns <code>false</li> if the array is non-null, but
+     * <code>length</code> is 0 unless <code>allowEmpty</code> is <code>true</code>.
+     * </ul></p>
+     *
+     * @param values the input array.
+     * @param weights the weights array.
+     * @param begin index of the first array element to include.
+     * @param length the number of elements to include.
+     * @param allowEmpty if {@code true} than allow zero length arrays to pass.
+     * @return {@code true} if the parameters are valid.
+     * @throws NullArgumentException if either of the arrays are null
+     * @throws MathIllegalArgumentException if the array indices are not valid,
+     * the weights array contains NaN, infinite or negative elements, or there
+     * are no positive weights.
+     * @since 3.0
+     */
+    protected boolean test(final double[] values, final double[] weights,
+            final int begin, final int length, final boolean allowEmpty) throws MathIllegalArgumentException {
+
+        return MathArrays.verifyValues(values, weights, begin, length, allowEmpty);
+    }
+}
+
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/AggregateSummaryStatistics.java b/src/main/java/org/apache/commons/math3/stat/descriptive/AggregateSummaryStatistics.java
new file mode 100644
index 0000000..6ab3c33
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/AggregateSummaryStatistics.java
@@ -0,0 +1,422 @@
+/*
+ * 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.math3.stat.descriptive;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.apache.commons.math3.exception.NullArgumentException;
+
+/**
+ * <p>
+ * An aggregator for {@code SummaryStatistics} from several data sets or
+ * data set partitions.  In its simplest usage mode, the client creates an
+ * instance via the zero-argument constructor, then uses
+ * {@link #createContributingStatistics()} to obtain a {@code SummaryStatistics}
+ * for each individual data set / partition.  The per-set statistics objects
+ * are used as normal, and at any time the aggregate statistics for all the
+ * contributors can be obtained from this object.
+ * </p><p>
+ * Clients with specialized requirements can use alternative constructors to
+ * control the statistics implementations and initial values used by the
+ * contributing and the internal aggregate {@code SummaryStatistics} objects.
+ * </p><p>
+ * A static {@link #aggregate(Collection)} method is also included that computes
+ * aggregate statistics directly from a Collection of SummaryStatistics instances.
+ * </p><p>
+ * When {@link #createContributingStatistics()} is used to create SummaryStatistics
+ * instances to be aggregated concurrently, the created instances'
+ * {@link SummaryStatistics#addValue(double)} methods must synchronize on the aggregating
+ * instance maintained by this class.  In multithreaded environments, if the functionality
+ * provided by {@link #aggregate(Collection)} is adequate, that method should be used
+ * to avoid unnecessary computation and synchronization delays.</p>
+ *
+ * @since 2.0
+ *
+ */
+public class AggregateSummaryStatistics implements StatisticalSummary,
+        Serializable {
+
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -8207112444016386906L;
+
+    /**
+     * A SummaryStatistics serving as a prototype for creating SummaryStatistics
+     * contributing to this aggregate
+     */
+    private final SummaryStatistics statisticsPrototype;
+
+    /**
+     * The SummaryStatistics in which aggregate statistics are accumulated.
+     */
+    private final SummaryStatistics statistics;
+
+    /**
+     * Initializes a new AggregateSummaryStatistics with default statistics
+     * implementations.
+     *
+     */
+    public AggregateSummaryStatistics() {
+        // No try-catch or throws NAE because arg is guaranteed non-null
+        this(new SummaryStatistics());
+    }
+
+    /**
+     * Initializes a new AggregateSummaryStatistics with the specified statistics
+     * object as a prototype for contributing statistics and for the internal
+     * aggregate statistics.  This provides for customized statistics implementations
+     * to be used by contributing and aggregate statistics.
+     *
+     * @param prototypeStatistics a {@code SummaryStatistics} serving as a
+     *      prototype both for the internal aggregate statistics and for
+     *      contributing statistics obtained via the
+     *      {@code createContributingStatistics()} method.  Being a prototype
+     *      means that other objects are initialized by copying this object's state.
+     *      If {@code null}, a new, default statistics object is used.  Any statistic
+     *      values in the prototype are propagated to contributing statistics
+     *      objects and (once) into these aggregate statistics.
+     * @throws NullArgumentException if prototypeStatistics is null
+     * @see #createContributingStatistics()
+     */
+    public AggregateSummaryStatistics(SummaryStatistics prototypeStatistics) throws NullArgumentException {
+        this(prototypeStatistics,
+             prototypeStatistics == null ? null : new SummaryStatistics(prototypeStatistics));
+    }
+
+    /**
+     * Initializes a new AggregateSummaryStatistics with the specified statistics
+     * object as a prototype for contributing statistics and for the internal
+     * aggregate statistics.  This provides for different statistics implementations
+     * to be used by contributing and aggregate statistics and for an initial
+     * state to be supplied for the aggregate statistics.
+     *
+     * @param prototypeStatistics a {@code SummaryStatistics} serving as a
+     *      prototype both for the internal aggregate statistics and for
+     *      contributing statistics obtained via the
+     *      {@code createContributingStatistics()} method.  Being a prototype
+     *      means that other objects are initialized by copying this object's state.
+     *      If {@code null}, a new, default statistics object is used.  Any statistic
+     *      values in the prototype are propagated to contributing statistics
+     *      objects, but not into these aggregate statistics.
+     * @param initialStatistics a {@code SummaryStatistics} to serve as the
+     *      internal aggregate statistics object.  If {@code null}, a new, default
+     *      statistics object is used.
+     * @see #createContributingStatistics()
+     */
+    public AggregateSummaryStatistics(SummaryStatistics prototypeStatistics,
+                                      SummaryStatistics initialStatistics) {
+        this.statisticsPrototype =
+            (prototypeStatistics == null) ? new SummaryStatistics() : prototypeStatistics;
+        this.statistics =
+            (initialStatistics == null) ? new SummaryStatistics() : initialStatistics;
+    }
+
+    /**
+     * {@inheritDoc}.  This version returns the maximum over all the aggregated
+     * data.
+     *
+     * @see StatisticalSummary#getMax()
+     */
+    public double getMax() {
+        synchronized (statistics) {
+            return statistics.getMax();
+        }
+    }
+
+    /**
+     * {@inheritDoc}.  This version returns the mean of all the aggregated data.
+     *
+     * @see StatisticalSummary#getMean()
+     */
+    public double getMean() {
+        synchronized (statistics) {
+            return statistics.getMean();
+        }
+    }
+
+    /**
+     * {@inheritDoc}.  This version returns the minimum over all the aggregated
+     * data.
+     *
+     * @see StatisticalSummary#getMin()
+     */
+    public double getMin() {
+        synchronized (statistics) {
+            return statistics.getMin();
+        }
+    }
+
+    /**
+     * {@inheritDoc}.  This version returns a count of all the aggregated data.
+     *
+     * @see StatisticalSummary#getN()
+     */
+    public long getN() {
+        synchronized (statistics) {
+            return statistics.getN();
+        }
+    }
+
+    /**
+     * {@inheritDoc}.  This version returns the standard deviation of all the
+     * aggregated data.
+     *
+     * @see StatisticalSummary#getStandardDeviation()
+     */
+    public double getStandardDeviation() {
+        synchronized (statistics) {
+            return statistics.getStandardDeviation();
+        }
+    }
+
+    /**
+     * {@inheritDoc}.  This version returns a sum of all the aggregated data.
+     *
+     * @see StatisticalSummary#getSum()
+     */
+    public double getSum() {
+        synchronized (statistics) {
+            return statistics.getSum();
+        }
+    }
+
+    /**
+     * {@inheritDoc}.  This version returns the variance of all the aggregated
+     * data.
+     *
+     * @see StatisticalSummary#getVariance()
+     */
+    public double getVariance() {
+        synchronized (statistics) {
+            return statistics.getVariance();
+        }
+    }
+
+    /**
+     * Returns the sum of the logs of all the aggregated data.
+     *
+     * @return the sum of logs
+     * @see SummaryStatistics#getSumOfLogs()
+     */
+    public double getSumOfLogs() {
+        synchronized (statistics) {
+            return statistics.getSumOfLogs();
+        }
+    }
+
+    /**
+     * Returns the geometric mean of all the aggregated data.
+     *
+     * @return the geometric mean
+     * @see SummaryStatistics#getGeometricMean()
+     */
+    public double getGeometricMean() {
+        synchronized (statistics) {
+            return statistics.getGeometricMean();
+        }
+    }
+
+    /**
+     * Returns the sum of the squares of all the aggregated data.
+     *
+     * @return The sum of squares
+     * @see SummaryStatistics#getSumsq()
+     */
+    public double getSumsq() {
+        synchronized (statistics) {
+            return statistics.getSumsq();
+        }
+    }
+
+    /**
+     * Returns a statistic related to the Second Central Moment.  Specifically,
+     * what is returned is the sum of squared deviations from the sample mean
+     * among the all of the aggregated data.
+     *
+     * @return second central moment statistic
+     * @see SummaryStatistics#getSecondMoment()
+     */
+    public double getSecondMoment() {
+        synchronized (statistics) {
+            return statistics.getSecondMoment();
+        }
+    }
+
+    /**
+     * Return a {@link StatisticalSummaryValues} instance reporting current
+     * aggregate statistics.
+     *
+     * @return Current values of aggregate statistics
+     */
+    public StatisticalSummary getSummary() {
+        synchronized (statistics) {
+            return new StatisticalSummaryValues(getMean(), getVariance(), getN(),
+                    getMax(), getMin(), getSum());
+        }
+    }
+
+    /**
+     * Creates and returns a {@code SummaryStatistics} whose data will be
+     * aggregated with those of this {@code AggregateSummaryStatistics}.
+     *
+     * @return a {@code SummaryStatistics} whose data will be aggregated with
+     *      those of this {@code AggregateSummaryStatistics}.  The initial state
+     *      is a copy of the configured prototype statistics.
+     */
+    public SummaryStatistics createContributingStatistics() {
+        SummaryStatistics contributingStatistics
+                = new AggregatingSummaryStatistics(statistics);
+
+        // No try - catch or advertising NAE because neither argument will ever be null
+        SummaryStatistics.copy(statisticsPrototype, contributingStatistics);
+
+        return contributingStatistics;
+    }
+
+    /**
+     * Computes aggregate summary statistics. This method can be used to combine statistics
+     * computed over partitions or subsamples - i.e., the StatisticalSummaryValues returned
+     * should contain the same values that would have been obtained by computing a single
+     * StatisticalSummary over the combined dataset.
+     * <p>
+     * Returns null if the collection is empty or null.
+     * </p>
+     *
+     * @param statistics collection of SummaryStatistics to aggregate
+     * @return summary statistics for the combined dataset
+     */
+    public static StatisticalSummaryValues aggregate(Collection<? extends StatisticalSummary> statistics) {
+        if (statistics == null) {
+            return null;
+        }
+        Iterator<? extends StatisticalSummary> iterator = statistics.iterator();
+        if (!iterator.hasNext()) {
+            return null;
+        }
+        StatisticalSummary current = iterator.next();
+        long n = current.getN();
+        double min = current.getMin();
+        double sum = current.getSum();
+        double max = current.getMax();
+        double var = current.getVariance();
+        double m2 = var * (n - 1d);
+        double mean = current.getMean();
+        while (iterator.hasNext()) {
+            current = iterator.next();
+            if (current.getMin() < min || Double.isNaN(min)) {
+                min = current.getMin();
+            }
+            if (current.getMax() > max || Double.isNaN(max)) {
+                max = current.getMax();
+            }
+            sum += current.getSum();
+            final double oldN = n;
+            final double curN = current.getN();
+            n += curN;
+            final double meanDiff = current.getMean() - mean;
+            mean = sum / n;
+            final double curM2 = current.getVariance() * (curN - 1d);
+            m2 = m2 + curM2 + meanDiff * meanDiff * oldN * curN / n;
+        }
+        final double variance;
+        if (n == 0) {
+            variance = Double.NaN;
+        } else if (n == 1) {
+            variance = 0d;
+        } else {
+            variance = m2 / (n - 1);
+        }
+        return new StatisticalSummaryValues(mean, variance, n, max, min, sum);
+    }
+
+    /**
+     * A SummaryStatistics that also forwards all values added to it to a second
+     * {@code SummaryStatistics} for aggregation.
+     *
+     * @since 2.0
+     */
+    private static class AggregatingSummaryStatistics extends SummaryStatistics {
+
+        /**
+         * The serialization version of this class
+         */
+        private static final long serialVersionUID = 1L;
+
+        /**
+         * An additional SummaryStatistics into which values added to these
+         * statistics (and possibly others) are aggregated
+         */
+        private final SummaryStatistics aggregateStatistics;
+
+        /**
+         * Initializes a new AggregatingSummaryStatistics with the specified
+         * aggregate statistics object
+         *
+         * @param aggregateStatistics a {@code SummaryStatistics} into which
+         *      values added to this statistics object should be aggregated
+         */
+        AggregatingSummaryStatistics(SummaryStatistics aggregateStatistics) {
+            this.aggregateStatistics = aggregateStatistics;
+        }
+
+        /**
+         * {@inheritDoc}.  This version adds the provided value to the configured
+         * aggregate after adding it to these statistics.
+         *
+         * @see SummaryStatistics#addValue(double)
+         */
+        @Override
+        public void addValue(double value) {
+            super.addValue(value);
+            synchronized (aggregateStatistics) {
+                aggregateStatistics.addValue(value);
+            }
+        }
+
+        /**
+         * Returns true iff <code>object</code> is a
+         * <code>SummaryStatistics</code> instance and all statistics have the
+         * same values as this.
+         * @param object the object to test equality against.
+         * @return true if object equals this
+         */
+        @Override
+        public boolean equals(Object object) {
+            if (object == this) {
+                return true;
+            }
+            if (object instanceof AggregatingSummaryStatistics == false) {
+                return false;
+            }
+            AggregatingSummaryStatistics stat = (AggregatingSummaryStatistics)object;
+            return super.equals(stat) &&
+                   aggregateStatistics.equals(stat.aggregateStatistics);
+        }
+
+        /**
+         * Returns hash code based on values of statistics
+         * @return hash code
+         */
+        @Override
+        public int hashCode() {
+            return 123 + super.hashCode() + aggregateStatistics.hashCode();
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/DescriptiveStatistics.java b/src/main/java/org/apache/commons/math3/stat/descriptive/DescriptiveStatistics.java
new file mode 100644
index 0000000..b215bc8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/DescriptiveStatistics.java
@@ -0,0 +1,777 @@
+/*
+ * 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.math3.stat.descriptive;
+
+import java.io.Serializable;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.stat.descriptive.moment.GeometricMean;
+import org.apache.commons.math3.stat.descriptive.moment.Kurtosis;
+import org.apache.commons.math3.stat.descriptive.moment.Mean;
+import org.apache.commons.math3.stat.descriptive.moment.Skewness;
+import org.apache.commons.math3.stat.descriptive.moment.Variance;
+import org.apache.commons.math3.stat.descriptive.rank.Max;
+import org.apache.commons.math3.stat.descriptive.rank.Min;
+import org.apache.commons.math3.stat.descriptive.rank.Percentile;
+import org.apache.commons.math3.stat.descriptive.summary.Sum;
+import org.apache.commons.math3.stat.descriptive.summary.SumOfSquares;
+import org.apache.commons.math3.util.MathUtils;
+import org.apache.commons.math3.util.ResizableDoubleArray;
+import org.apache.commons.math3.util.FastMath;
+
+
+/**
+ * Maintains a dataset of values of a single variable and computes descriptive
+ * statistics based on stored data. The {@link #getWindowSize() windowSize}
+ * property sets a limit on the number of values that can be stored in the
+ * dataset.  The default value, INFINITE_WINDOW, puts no limit on the size of
+ * the dataset.  This value should be used with caution, as the backing store
+ * will grow without bound in this case.  For very large datasets,
+ * {@link SummaryStatistics}, which does not store the dataset, should be used
+ * instead of this class. If <code>windowSize</code> is not INFINITE_WINDOW and
+ * more values are added than can be stored in the dataset, new values are
+ * added in a "rolling" manner, with new values replacing the "oldest" values
+ * in the dataset.
+ *
+ * <p>Note: this class is not threadsafe.  Use
+ * {@link SynchronizedDescriptiveStatistics} if concurrent access from multiple
+ * threads is required.</p>
+ *
+ */
+public class DescriptiveStatistics implements StatisticalSummary, Serializable {
+
+    /**
+     * Represents an infinite window size.  When the {@link #getWindowSize()}
+     * returns this value, there is no limit to the number of data values
+     * that can be stored in the dataset.
+     */
+    public static final int INFINITE_WINDOW = -1;
+
+    /** Serialization UID */
+    private static final long serialVersionUID = 4133067267405273064L;
+
+    /** Name of the setQuantile method. */
+    private static final String SET_QUANTILE_METHOD_NAME = "setQuantile";
+
+    /** hold the window size **/
+    protected int windowSize = INFINITE_WINDOW;
+
+    /**
+     *  Stored data values
+     */
+    private ResizableDoubleArray eDA = new ResizableDoubleArray();
+
+    /** Mean statistic implementation - can be reset by setter. */
+    private UnivariateStatistic meanImpl = new Mean();
+
+    /** Geometric mean statistic implementation - can be reset by setter. */
+    private UnivariateStatistic geometricMeanImpl = new GeometricMean();
+
+    /** Kurtosis statistic implementation - can be reset by setter. */
+    private UnivariateStatistic kurtosisImpl = new Kurtosis();
+
+    /** Maximum statistic implementation - can be reset by setter. */
+    private UnivariateStatistic maxImpl = new Max();
+
+    /** Minimum statistic implementation - can be reset by setter. */
+    private UnivariateStatistic minImpl = new Min();
+
+    /** Percentile statistic implementation - can be reset by setter. */
+    private UnivariateStatistic percentileImpl = new Percentile();
+
+    /** Skewness statistic implementation - can be reset by setter. */
+    private UnivariateStatistic skewnessImpl = new Skewness();
+
+    /** Variance statistic implementation - can be reset by setter. */
+    private UnivariateStatistic varianceImpl = new Variance();
+
+    /** Sum of squares statistic implementation - can be reset by setter. */
+    private UnivariateStatistic sumsqImpl = new SumOfSquares();
+
+    /** Sum statistic implementation - can be reset by setter. */
+    private UnivariateStatistic sumImpl = new Sum();
+
+    /**
+     * Construct a DescriptiveStatistics instance with an infinite window
+     */
+    public DescriptiveStatistics() {
+    }
+
+    /**
+     * Construct a DescriptiveStatistics instance with the specified window
+     *
+     * @param window the window size.
+     * @throws MathIllegalArgumentException if window size is less than 1 but
+     * not equal to {@link #INFINITE_WINDOW}
+     */
+    public DescriptiveStatistics(int window) throws MathIllegalArgumentException {
+        setWindowSize(window);
+    }
+
+    /**
+     * Construct a DescriptiveStatistics instance with an infinite window
+     * and the initial data values in double[] initialDoubleArray.
+     * If initialDoubleArray is null, then this constructor corresponds to
+     * DescriptiveStatistics()
+     *
+     * @param initialDoubleArray the initial double[].
+     */
+    public DescriptiveStatistics(double[] initialDoubleArray) {
+        if (initialDoubleArray != null) {
+            eDA = new ResizableDoubleArray(initialDoubleArray);
+        }
+    }
+
+    /**
+     * Copy constructor.  Construct a new DescriptiveStatistics instance that
+     * is a copy of original.
+     *
+     * @param original DescriptiveStatistics instance to copy
+     * @throws NullArgumentException if original is null
+     */
+    public DescriptiveStatistics(DescriptiveStatistics original) throws NullArgumentException {
+        copy(original, this);
+    }
+
+    /**
+     * Adds the value to the dataset. If the dataset is at the maximum size
+     * (i.e., the number of stored elements equals the currently configured
+     * windowSize), the first (oldest) element in the dataset is discarded
+     * to make room for the new value.
+     *
+     * @param v the value to be added
+     */
+    public void addValue(double v) {
+        if (windowSize != INFINITE_WINDOW) {
+            if (getN() == windowSize) {
+                eDA.addElementRolling(v);
+            } else if (getN() < windowSize) {
+                eDA.addElement(v);
+            }
+        } else {
+            eDA.addElement(v);
+        }
+    }
+
+    /**
+     * Removes the most recent value from the dataset.
+     *
+     * @throws MathIllegalStateException if there are no elements stored
+     */
+    public void removeMostRecentValue() throws MathIllegalStateException {
+        try {
+            eDA.discardMostRecentElements(1);
+        } catch (MathIllegalArgumentException ex) {
+            throw new MathIllegalStateException(LocalizedFormats.NO_DATA);
+        }
+    }
+
+    /**
+     * Replaces the most recently stored value with the given value.
+     * There must be at least one element stored to call this method.
+     *
+     * @param v the value to replace the most recent stored value
+     * @return replaced value
+     * @throws MathIllegalStateException if there are no elements stored
+     */
+    public double replaceMostRecentValue(double v) throws MathIllegalStateException {
+        return eDA.substituteMostRecentElement(v);
+    }
+
+    /**
+     * Returns the <a href="http://www.xycoon.com/arithmetic_mean.htm">
+     * arithmetic mean </a> of the available values
+     * @return The mean or Double.NaN if no values have been added.
+     */
+    public double getMean() {
+        return apply(meanImpl);
+    }
+
+    /**
+     * Returns the <a href="http://www.xycoon.com/geometric_mean.htm">
+     * geometric mean </a> of the available values.
+     * <p>
+     * See {@link GeometricMean} for details on the computing algorithm.</p>
+     *
+     * @return The geometricMean, Double.NaN if no values have been added,
+     * or if any negative values have been added.
+     */
+    public double getGeometricMean() {
+        return apply(geometricMeanImpl);
+    }
+
+    /**
+     * Returns the (sample) variance of the available values.
+     *
+     * <p>This method returns the bias-corrected sample variance (using {@code n - 1} in
+     * the denominator).  Use {@link #getPopulationVariance()} for the non-bias-corrected
+     * population variance.</p>
+     *
+     * @return The variance, Double.NaN if no values have been added
+     * or 0.0 for a single value set.
+     */
+    public double getVariance() {
+        return apply(varianceImpl);
+    }
+
+    /**
+     * Returns the <a href="http://en.wikibooks.org/wiki/Statistics/Summary/Variance">
+     * population variance</a> of the available values.
+     *
+     * @return The population variance, Double.NaN if no values have been added,
+     * or 0.0 for a single value set.
+     */
+    public double getPopulationVariance() {
+        return apply(new Variance(false));
+    }
+
+    /**
+     * Returns the standard deviation of the available values.
+     * @return The standard deviation, Double.NaN if no values have been added
+     * or 0.0 for a single value set.
+     */
+    public double getStandardDeviation() {
+        double stdDev = Double.NaN;
+        if (getN() > 0) {
+            if (getN() > 1) {
+                stdDev = FastMath.sqrt(getVariance());
+            } else {
+                stdDev = 0.0;
+            }
+        }
+        return stdDev;
+    }
+
+    /**
+     * Returns the quadratic mean, a.k.a.
+     * <a href="http://mathworld.wolfram.com/Root-Mean-Square.html">
+     * root-mean-square</a> of the available values
+     * @return The quadratic mean or {@code Double.NaN} if no values
+     * have been added.
+     */
+    public double getQuadraticMean() {
+        final long n = getN();
+        return n > 0 ? FastMath.sqrt(getSumsq() / n) : Double.NaN;
+    }
+
+    /**
+     * Returns the skewness of the available values. Skewness is a
+     * measure of the asymmetry of a given distribution.
+     *
+     * @return The skewness, Double.NaN if less than 3 values have been added.
+     */
+    public double getSkewness() {
+        return apply(skewnessImpl);
+    }
+
+    /**
+     * Returns the Kurtosis of the available values. Kurtosis is a
+     * measure of the "peakedness" of a distribution.
+     *
+     * @return The kurtosis, Double.NaN if less than 4 values have been added.
+     */
+    public double getKurtosis() {
+        return apply(kurtosisImpl);
+    }
+
+    /**
+     * Returns the maximum of the available values
+     * @return The max or Double.NaN if no values have been added.
+     */
+    public double getMax() {
+        return apply(maxImpl);
+    }
+
+    /**
+    * Returns the minimum of the available values
+    * @return The min or Double.NaN if no values have been added.
+    */
+    public double getMin() {
+        return apply(minImpl);
+    }
+
+    /**
+     * Returns the number of available values
+     * @return The number of available values
+     */
+    public long getN() {
+        return eDA.getNumElements();
+    }
+
+    /**
+     * Returns the sum of the values that have been added to Univariate.
+     * @return The sum or Double.NaN if no values have been added
+     */
+    public double getSum() {
+        return apply(sumImpl);
+    }
+
+    /**
+     * Returns the sum of the squares of the available values.
+     * @return The sum of the squares or Double.NaN if no
+     * values have been added.
+     */
+    public double getSumsq() {
+        return apply(sumsqImpl);
+    }
+
+    /**
+     * Resets all statistics and storage
+     */
+    public void clear() {
+        eDA.clear();
+    }
+
+
+    /**
+     * Returns the maximum number of values that can be stored in the
+     * dataset, or INFINITE_WINDOW (-1) if there is no limit.
+     *
+     * @return The current window size or -1 if its Infinite.
+     */
+    public int getWindowSize() {
+        return windowSize;
+    }
+
+    /**
+     * WindowSize controls the number of values that contribute to the
+     * reported statistics.  For example, if windowSize is set to 3 and the
+     * values {1,2,3,4,5} have been added <strong> in that order</strong> then
+     * the <i>available values</i> are {3,4,5} and all reported statistics will
+     * be based on these values. If {@code windowSize} is decreased as a result
+     * of this call and there are more than the new value of elements in the
+     * current dataset, values from the front of the array are discarded to
+     * reduce the dataset to {@code windowSize} elements.
+     *
+     * @param windowSize sets the size of the window.
+     * @throws MathIllegalArgumentException if window size is less than 1 but
+     * not equal to {@link #INFINITE_WINDOW}
+     */
+    public void setWindowSize(int windowSize) throws MathIllegalArgumentException {
+        if (windowSize < 1 && windowSize != INFINITE_WINDOW) {
+            throw new MathIllegalArgumentException(
+                    LocalizedFormats.NOT_POSITIVE_WINDOW_SIZE, windowSize);
+        }
+
+        this.windowSize = windowSize;
+
+        // We need to check to see if we need to discard elements
+        // from the front of the array.  If the windowSize is less than
+        // the current number of elements.
+        if (windowSize != INFINITE_WINDOW && windowSize < eDA.getNumElements()) {
+            eDA.discardFrontElements(eDA.getNumElements() - windowSize);
+        }
+    }
+
+    /**
+     * Returns the current set of values in an array of double primitives.
+     * The order of addition is preserved.  The returned array is a fresh
+     * copy of the underlying data -- i.e., it is not a reference to the
+     * stored data.
+     *
+     * @return returns the current set of numbers in the order in which they
+     *         were added to this set
+     */
+    public double[] getValues() {
+        return eDA.getElements();
+    }
+
+    /**
+     * Returns the current set of values in an array of double primitives,
+     * sorted in ascending order.  The returned array is a fresh
+     * copy of the underlying data -- i.e., it is not a reference to the
+     * stored data.
+     * @return returns the current set of
+     * numbers sorted in ascending order
+     */
+    public double[] getSortedValues() {
+        double[] sort = getValues();
+        Arrays.sort(sort);
+        return sort;
+    }
+
+    /**
+     * Returns the element at the specified index
+     * @param index The Index of the element
+     * @return return the element at the specified index
+     */
+    public double getElement(int index) {
+        return eDA.getElement(index);
+    }
+
+    /**
+     * Returns an estimate for the pth percentile of the stored values.
+     * <p>
+     * The implementation provided here follows the first estimation procedure presented
+     * <a href="http://www.itl.nist.gov/div898/handbook/prc/section2/prc252.htm">here.</a>
+     * </p><p>
+     * <strong>Preconditions</strong>:<ul>
+     * <li><code>0 &lt; p &le; 100</code> (otherwise an
+     * <code>MathIllegalArgumentException</code> is thrown)</li>
+     * <li>at least one value must be stored (returns <code>Double.NaN
+     *     </code> otherwise)</li>
+     * </ul></p>
+     *
+     * @param p the requested percentile (scaled from 0 - 100)
+     * @return An estimate for the pth percentile of the stored data
+     * @throws MathIllegalStateException if percentile implementation has been
+     *  overridden and the supplied implementation does not support setQuantile
+     * @throws MathIllegalArgumentException if p is not a valid quantile
+     */
+    public double getPercentile(double p) throws MathIllegalStateException, MathIllegalArgumentException {
+        if (percentileImpl instanceof Percentile) {
+            ((Percentile) percentileImpl).setQuantile(p);
+        } else {
+            try {
+                percentileImpl.getClass().getMethod(SET_QUANTILE_METHOD_NAME,
+                        new Class[] {Double.TYPE}).invoke(percentileImpl,
+                                new Object[] {Double.valueOf(p)});
+            } catch (NoSuchMethodException e1) { // Setter guard should prevent
+                throw new MathIllegalStateException(
+                      LocalizedFormats.PERCENTILE_IMPLEMENTATION_UNSUPPORTED_METHOD,
+                      percentileImpl.getClass().getName(), SET_QUANTILE_METHOD_NAME);
+            } catch (IllegalAccessException e2) {
+                throw new MathIllegalStateException(
+                      LocalizedFormats.PERCENTILE_IMPLEMENTATION_CANNOT_ACCESS_METHOD,
+                      SET_QUANTILE_METHOD_NAME, percentileImpl.getClass().getName());
+            } catch (InvocationTargetException e3) {
+                throw new IllegalStateException(e3.getCause());
+            }
+        }
+        return apply(percentileImpl);
+    }
+
+    /**
+     * Generates a text report displaying univariate statistics from values
+     * that have been added.  Each statistic is displayed on a separate
+     * line.
+     *
+     * @return String with line feeds displaying statistics
+     */
+    @Override
+    public String toString() {
+        StringBuilder outBuffer = new StringBuilder();
+        String endl = "\n";
+        outBuffer.append("DescriptiveStatistics:").append(endl);
+        outBuffer.append("n: ").append(getN()).append(endl);
+        outBuffer.append("min: ").append(getMin()).append(endl);
+        outBuffer.append("max: ").append(getMax()).append(endl);
+        outBuffer.append("mean: ").append(getMean()).append(endl);
+        outBuffer.append("std dev: ").append(getStandardDeviation())
+            .append(endl);
+        try {
+            // No catch for MIAE because actual parameter is valid below
+            outBuffer.append("median: ").append(getPercentile(50)).append(endl);
+        } catch (MathIllegalStateException ex) {
+            outBuffer.append("median: unavailable").append(endl);
+        }
+        outBuffer.append("skewness: ").append(getSkewness()).append(endl);
+        outBuffer.append("kurtosis: ").append(getKurtosis()).append(endl);
+        return outBuffer.toString();
+    }
+
+    /**
+     * Apply the given statistic to the data associated with this set of statistics.
+     * @param stat the statistic to apply
+     * @return the computed value of the statistic.
+     */
+    public double apply(UnivariateStatistic stat) {
+        // No try-catch or advertised exception here because arguments are guaranteed valid
+        return eDA.compute(stat);
+    }
+
+    // Implementation getters and setter
+
+    /**
+     * Returns the currently configured mean implementation.
+     *
+     * @return the UnivariateStatistic implementing the mean
+     * @since 1.2
+     */
+    public synchronized UnivariateStatistic getMeanImpl() {
+        return meanImpl;
+    }
+
+    /**
+     * <p>Sets the implementation for the mean.</p>
+     *
+     * @param meanImpl the UnivariateStatistic instance to use
+     * for computing the mean
+     * @since 1.2
+     */
+    public synchronized void setMeanImpl(UnivariateStatistic meanImpl) {
+        this.meanImpl = meanImpl;
+    }
+
+    /**
+     * Returns the currently configured geometric mean implementation.
+     *
+     * @return the UnivariateStatistic implementing the geometric mean
+     * @since 1.2
+     */
+    public synchronized UnivariateStatistic getGeometricMeanImpl() {
+        return geometricMeanImpl;
+    }
+
+    /**
+     * <p>Sets the implementation for the gemoetric mean.</p>
+     *
+     * @param geometricMeanImpl the UnivariateStatistic instance to use
+     * for computing the geometric mean
+     * @since 1.2
+     */
+    public synchronized void setGeometricMeanImpl(
+            UnivariateStatistic geometricMeanImpl) {
+        this.geometricMeanImpl = geometricMeanImpl;
+    }
+
+    /**
+     * Returns the currently configured kurtosis implementation.
+     *
+     * @return the UnivariateStatistic implementing the kurtosis
+     * @since 1.2
+     */
+    public synchronized UnivariateStatistic getKurtosisImpl() {
+        return kurtosisImpl;
+    }
+
+    /**
+     * <p>Sets the implementation for the kurtosis.</p>
+     *
+     * @param kurtosisImpl the UnivariateStatistic instance to use
+     * for computing the kurtosis
+     * @since 1.2
+     */
+    public synchronized void setKurtosisImpl(UnivariateStatistic kurtosisImpl) {
+        this.kurtosisImpl = kurtosisImpl;
+    }
+
+    /**
+     * Returns the currently configured maximum implementation.
+     *
+     * @return the UnivariateStatistic implementing the maximum
+     * @since 1.2
+     */
+    public synchronized UnivariateStatistic getMaxImpl() {
+        return maxImpl;
+    }
+
+    /**
+     * <p>Sets the implementation for the maximum.</p>
+     *
+     * @param maxImpl the UnivariateStatistic instance to use
+     * for computing the maximum
+     * @since 1.2
+     */
+    public synchronized void setMaxImpl(UnivariateStatistic maxImpl) {
+        this.maxImpl = maxImpl;
+    }
+
+    /**
+     * Returns the currently configured minimum implementation.
+     *
+     * @return the UnivariateStatistic implementing the minimum
+     * @since 1.2
+     */
+    public synchronized UnivariateStatistic getMinImpl() {
+        return minImpl;
+    }
+
+    /**
+     * <p>Sets the implementation for the minimum.</p>
+     *
+     * @param minImpl the UnivariateStatistic instance to use
+     * for computing the minimum
+     * @since 1.2
+     */
+    public synchronized void setMinImpl(UnivariateStatistic minImpl) {
+        this.minImpl = minImpl;
+    }
+
+    /**
+     * Returns the currently configured percentile implementation.
+     *
+     * @return the UnivariateStatistic implementing the percentile
+     * @since 1.2
+     */
+    public synchronized UnivariateStatistic getPercentileImpl() {
+        return percentileImpl;
+    }
+
+    /**
+     * Sets the implementation to be used by {@link #getPercentile(double)}.
+     * The supplied <code>UnivariateStatistic</code> must provide a
+     * <code>setQuantile(double)</code> method; otherwise
+     * <code>IllegalArgumentException</code> is thrown.
+     *
+     * @param percentileImpl the percentileImpl to set
+     * @throws MathIllegalArgumentException if the supplied implementation does not
+     *  provide a <code>setQuantile</code> method
+     * @since 1.2
+     */
+    public synchronized void setPercentileImpl(UnivariateStatistic percentileImpl)
+    throws MathIllegalArgumentException {
+        try {
+            percentileImpl.getClass().getMethod(SET_QUANTILE_METHOD_NAME,
+                    new Class[] {Double.TYPE}).invoke(percentileImpl,
+                            new Object[] {Double.valueOf(50.0d)});
+        } catch (NoSuchMethodException e1) {
+            throw new MathIllegalArgumentException(
+                  LocalizedFormats.PERCENTILE_IMPLEMENTATION_UNSUPPORTED_METHOD,
+                  percentileImpl.getClass().getName(), SET_QUANTILE_METHOD_NAME);
+        } catch (IllegalAccessException e2) {
+            throw new MathIllegalArgumentException(
+                  LocalizedFormats.PERCENTILE_IMPLEMENTATION_CANNOT_ACCESS_METHOD,
+                  SET_QUANTILE_METHOD_NAME, percentileImpl.getClass().getName());
+        } catch (InvocationTargetException e3) {
+            throw new IllegalArgumentException(e3.getCause());
+        }
+        this.percentileImpl = percentileImpl;
+    }
+
+    /**
+     * Returns the currently configured skewness implementation.
+     *
+     * @return the UnivariateStatistic implementing the skewness
+     * @since 1.2
+     */
+    public synchronized UnivariateStatistic getSkewnessImpl() {
+        return skewnessImpl;
+    }
+
+    /**
+     * <p>Sets the implementation for the skewness.</p>
+     *
+     * @param skewnessImpl the UnivariateStatistic instance to use
+     * for computing the skewness
+     * @since 1.2
+     */
+    public synchronized void setSkewnessImpl(
+            UnivariateStatistic skewnessImpl) {
+        this.skewnessImpl = skewnessImpl;
+    }
+
+    /**
+     * Returns the currently configured variance implementation.
+     *
+     * @return the UnivariateStatistic implementing the variance
+     * @since 1.2
+     */
+    public synchronized UnivariateStatistic getVarianceImpl() {
+        return varianceImpl;
+    }
+
+    /**
+     * <p>Sets the implementation for the variance.</p>
+     *
+     * @param varianceImpl the UnivariateStatistic instance to use
+     * for computing the variance
+     * @since 1.2
+     */
+    public synchronized void setVarianceImpl(
+            UnivariateStatistic varianceImpl) {
+        this.varianceImpl = varianceImpl;
+    }
+
+    /**
+     * Returns the currently configured sum of squares implementation.
+     *
+     * @return the UnivariateStatistic implementing the sum of squares
+     * @since 1.2
+     */
+    public synchronized UnivariateStatistic getSumsqImpl() {
+        return sumsqImpl;
+    }
+
+    /**
+     * <p>Sets the implementation for the sum of squares.</p>
+     *
+     * @param sumsqImpl the UnivariateStatistic instance to use
+     * for computing the sum of squares
+     * @since 1.2
+     */
+    public synchronized void setSumsqImpl(UnivariateStatistic sumsqImpl) {
+        this.sumsqImpl = sumsqImpl;
+    }
+
+    /**
+     * Returns the currently configured sum implementation.
+     *
+     * @return the UnivariateStatistic implementing the sum
+     * @since 1.2
+     */
+    public synchronized UnivariateStatistic getSumImpl() {
+        return sumImpl;
+    }
+
+    /**
+     * <p>Sets the implementation for the sum.</p>
+     *
+     * @param sumImpl the UnivariateStatistic instance to use
+     * for computing the sum
+     * @since 1.2
+     */
+    public synchronized void setSumImpl(UnivariateStatistic sumImpl) {
+        this.sumImpl = sumImpl;
+    }
+
+    /**
+     * Returns a copy of this DescriptiveStatistics instance with the same internal state.
+     *
+     * @return a copy of this
+     */
+    public DescriptiveStatistics copy() {
+        DescriptiveStatistics result = new DescriptiveStatistics();
+        // No try-catch or advertised exception because parms are guaranteed valid
+        copy(this, result);
+        return result;
+    }
+
+    /**
+     * Copies source to dest.
+     * <p>Neither source nor dest can be null.</p>
+     *
+     * @param source DescriptiveStatistics to copy
+     * @param dest DescriptiveStatistics to copy to
+     * @throws NullArgumentException if either source or dest is null
+     */
+    public static void copy(DescriptiveStatistics source, DescriptiveStatistics dest)
+        throws NullArgumentException {
+        MathUtils.checkNotNull(source);
+        MathUtils.checkNotNull(dest);
+        // Copy data and window size
+        dest.eDA = source.eDA.copy();
+        dest.windowSize = source.windowSize;
+
+        // Copy implementations
+        dest.maxImpl = source.maxImpl.copy();
+        dest.meanImpl = source.meanImpl.copy();
+        dest.minImpl = source.minImpl.copy();
+        dest.sumImpl = source.sumImpl.copy();
+        dest.varianceImpl = source.varianceImpl.copy();
+        dest.sumsqImpl = source.sumsqImpl.copy();
+        dest.geometricMeanImpl = source.geometricMeanImpl.copy();
+        dest.kurtosisImpl = source.kurtosisImpl;
+        dest.skewnessImpl = source.skewnessImpl;
+        dest.percentileImpl = source.percentileImpl;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/MultivariateSummaryStatistics.java b/src/main/java/org/apache/commons/math3/stat/descriptive/MultivariateSummaryStatistics.java
new file mode 100644
index 0000000..3ede26e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/MultivariateSummaryStatistics.java
@@ -0,0 +1,635 @@
+/*
+ * 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.math3.stat.descriptive;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.stat.descriptive.moment.GeometricMean;
+import org.apache.commons.math3.stat.descriptive.moment.Mean;
+import org.apache.commons.math3.stat.descriptive.moment.VectorialCovariance;
+import org.apache.commons.math3.stat.descriptive.rank.Max;
+import org.apache.commons.math3.stat.descriptive.rank.Min;
+import org.apache.commons.math3.stat.descriptive.summary.Sum;
+import org.apache.commons.math3.stat.descriptive.summary.SumOfLogs;
+import org.apache.commons.math3.stat.descriptive.summary.SumOfSquares;
+import org.apache.commons.math3.util.MathUtils;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.util.Precision;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * <p>Computes summary statistics for a stream of n-tuples added using the
+ * {@link #addValue(double[]) addValue} method. The data values are not stored
+ * in memory, so this class can be used to compute statistics for very large
+ * n-tuple streams.</p>
+ *
+ * <p>The {@link StorelessUnivariateStatistic} instances used to maintain
+ * summary state and compute statistics are configurable via setters.
+ * For example, the default implementation for the mean can be overridden by
+ * calling {@link #setMeanImpl(StorelessUnivariateStatistic[])}. Actual
+ * parameters to these methods must implement the
+ * {@link StorelessUnivariateStatistic} interface and configuration must be
+ * completed before <code>addValue</code> is called. No configuration is
+ * necessary to use the default, commons-math provided implementations.</p>
+ *
+ * <p>To compute statistics for a stream of n-tuples, construct a
+ * MultivariateStatistics instance with dimension n and then use
+ * {@link #addValue(double[])} to add n-tuples. The <code>getXxx</code>
+ * methods where Xxx is a statistic return an array of <code>double</code>
+ * values, where for <code>i = 0,...,n-1</code> the i<sup>th</sup> array element is the
+ * value of the given statistic for data range consisting of the i<sup>th</sup> element of
+ * each of the input n-tuples.  For example, if <code>addValue</code> is called
+ * with actual parameters {0, 1, 2}, then {3, 4, 5} and finally {6, 7, 8},
+ * <code>getSum</code> will return a three-element array with values
+ * {0+3+6, 1+4+7, 2+5+8}</p>
+ *
+ * <p>Note: This class is not thread-safe. Use
+ * {@link SynchronizedMultivariateSummaryStatistics} if concurrent access from multiple
+ * threads is required.</p>
+ *
+ * @since 1.2
+ */
+public class MultivariateSummaryStatistics
+    implements StatisticalMultivariateSummary, Serializable {
+
+    /** Serialization UID */
+    private static final long serialVersionUID = 2271900808994826718L;
+
+    /** Dimension of the data. */
+    private int k;
+
+    /** Count of values that have been added */
+    private long n = 0;
+
+    /** Sum statistic implementation - can be reset by setter. */
+    private StorelessUnivariateStatistic[] sumImpl;
+
+    /** Sum of squares statistic implementation - can be reset by setter. */
+    private StorelessUnivariateStatistic[] sumSqImpl;
+
+    /** Minimum statistic implementation - can be reset by setter. */
+    private StorelessUnivariateStatistic[] minImpl;
+
+    /** Maximum statistic implementation - can be reset by setter. */
+    private StorelessUnivariateStatistic[] maxImpl;
+
+    /** Sum of log statistic implementation - can be reset by setter. */
+    private StorelessUnivariateStatistic[] sumLogImpl;
+
+    /** Geometric mean statistic implementation - can be reset by setter. */
+    private StorelessUnivariateStatistic[] geoMeanImpl;
+
+    /** Mean statistic implementation - can be reset by setter. */
+    private StorelessUnivariateStatistic[] meanImpl;
+
+    /** Covariance statistic implementation - cannot be reset. */
+    private VectorialCovariance covarianceImpl;
+
+    /**
+     * Construct a MultivariateSummaryStatistics instance
+     * @param k dimension of the data
+     * @param isCovarianceBiasCorrected if true, the unbiased sample
+     * covariance is computed, otherwise the biased population covariance
+     * is computed
+     */
+    public MultivariateSummaryStatistics(int k, boolean isCovarianceBiasCorrected) {
+        this.k = k;
+
+        sumImpl     = new StorelessUnivariateStatistic[k];
+        sumSqImpl   = new StorelessUnivariateStatistic[k];
+        minImpl     = new StorelessUnivariateStatistic[k];
+        maxImpl     = new StorelessUnivariateStatistic[k];
+        sumLogImpl  = new StorelessUnivariateStatistic[k];
+        geoMeanImpl = new StorelessUnivariateStatistic[k];
+        meanImpl    = new StorelessUnivariateStatistic[k];
+
+        for (int i = 0; i < k; ++i) {
+            sumImpl[i]     = new Sum();
+            sumSqImpl[i]   = new SumOfSquares();
+            minImpl[i]     = new Min();
+            maxImpl[i]     = new Max();
+            sumLogImpl[i]  = new SumOfLogs();
+            geoMeanImpl[i] = new GeometricMean();
+            meanImpl[i]    = new Mean();
+        }
+
+        covarianceImpl =
+            new VectorialCovariance(k, isCovarianceBiasCorrected);
+
+    }
+
+    /**
+     * Add an n-tuple to the data
+     *
+     * @param value  the n-tuple to add
+     * @throws DimensionMismatchException if the length of the array
+     * does not match the one used at construction
+     */
+    public void addValue(double[] value) throws DimensionMismatchException {
+        checkDimension(value.length);
+        for (int i = 0; i < k; ++i) {
+            double v = value[i];
+            sumImpl[i].increment(v);
+            sumSqImpl[i].increment(v);
+            minImpl[i].increment(v);
+            maxImpl[i].increment(v);
+            sumLogImpl[i].increment(v);
+            geoMeanImpl[i].increment(v);
+            meanImpl[i].increment(v);
+        }
+        covarianceImpl.increment(value);
+        n++;
+    }
+
+    /**
+     * Returns the dimension of the data
+     * @return The dimension of the data
+     */
+    public int getDimension() {
+        return k;
+    }
+
+    /**
+     * Returns the number of available values
+     * @return The number of available values
+     */
+    public long getN() {
+        return n;
+    }
+
+    /**
+     * Returns an array of the results of a statistic.
+     * @param stats univariate statistic array
+     * @return results array
+     */
+    private double[] getResults(StorelessUnivariateStatistic[] stats) {
+        double[] results = new double[stats.length];
+        for (int i = 0; i < results.length; ++i) {
+            results[i] = stats[i].getResult();
+        }
+        return results;
+    }
+
+    /**
+     * Returns an array whose i<sup>th</sup> entry is the sum of the
+     * i<sup>th</sup> entries of the arrays that have been added using
+     * {@link #addValue(double[])}
+     *
+     * @return the array of component sums
+     */
+    public double[] getSum() {
+        return getResults(sumImpl);
+    }
+
+    /**
+     * Returns an array whose i<sup>th</sup> entry is the sum of squares of the
+     * i<sup>th</sup> entries of the arrays that have been added using
+     * {@link #addValue(double[])}
+     *
+     * @return the array of component sums of squares
+     */
+    public double[] getSumSq() {
+        return getResults(sumSqImpl);
+    }
+
+    /**
+     * Returns an array whose i<sup>th</sup> entry is the sum of logs of the
+     * i<sup>th</sup> entries of the arrays that have been added using
+     * {@link #addValue(double[])}
+     *
+     * @return the array of component log sums
+     */
+    public double[] getSumLog() {
+        return getResults(sumLogImpl);
+    }
+
+    /**
+     * Returns an array whose i<sup>th</sup> entry is the mean of the
+     * i<sup>th</sup> entries of the arrays that have been added using
+     * {@link #addValue(double[])}
+     *
+     * @return the array of component means
+     */
+    public double[] getMean() {
+        return getResults(meanImpl);
+    }
+
+    /**
+     * Returns an array whose i<sup>th</sup> entry is the standard deviation of the
+     * i<sup>th</sup> entries of the arrays that have been added using
+     * {@link #addValue(double[])}
+     *
+     * @return the array of component standard deviations
+     */
+    public double[] getStandardDeviation() {
+        double[] stdDev = new double[k];
+        if (getN() < 1) {
+            Arrays.fill(stdDev, Double.NaN);
+        } else if (getN() < 2) {
+            Arrays.fill(stdDev, 0.0);
+        } else {
+            RealMatrix matrix = covarianceImpl.getResult();
+            for (int i = 0; i < k; ++i) {
+                stdDev[i] = FastMath.sqrt(matrix.getEntry(i, i));
+            }
+        }
+        return stdDev;
+    }
+
+    /**
+     * Returns the covariance matrix of the values that have been added.
+     *
+     * @return the covariance matrix
+     */
+    public RealMatrix getCovariance() {
+        return covarianceImpl.getResult();
+    }
+
+    /**
+     * Returns an array whose i<sup>th</sup> entry is the maximum of the
+     * i<sup>th</sup> entries of the arrays that have been added using
+     * {@link #addValue(double[])}
+     *
+     * @return the array of component maxima
+     */
+    public double[] getMax() {
+        return getResults(maxImpl);
+    }
+
+    /**
+     * Returns an array whose i<sup>th</sup> entry is the minimum of the
+     * i<sup>th</sup> entries of the arrays that have been added using
+     * {@link #addValue(double[])}
+     *
+     * @return the array of component minima
+     */
+    public double[] getMin() {
+        return getResults(minImpl);
+    }
+
+    /**
+     * Returns an array whose i<sup>th</sup> entry is the geometric mean of the
+     * i<sup>th</sup> entries of the arrays that have been added using
+     * {@link #addValue(double[])}
+     *
+     * @return the array of component geometric means
+     */
+    public double[] getGeometricMean() {
+        return getResults(geoMeanImpl);
+    }
+
+    /**
+     * Generates a text report displaying
+     * summary statistics from values that
+     * have been added.
+     * @return String with line feeds displaying statistics
+     */
+    @Override
+    public String toString() {
+        final String separator = ", ";
+        final String suffix = System.getProperty("line.separator");
+        StringBuilder outBuffer = new StringBuilder();
+        outBuffer.append("MultivariateSummaryStatistics:" + suffix);
+        outBuffer.append("n: " + getN() + suffix);
+        append(outBuffer, getMin(), "min: ", separator, suffix);
+        append(outBuffer, getMax(), "max: ", separator, suffix);
+        append(outBuffer, getMean(), "mean: ", separator, suffix);
+        append(outBuffer, getGeometricMean(), "geometric mean: ", separator, suffix);
+        append(outBuffer, getSumSq(), "sum of squares: ", separator, suffix);
+        append(outBuffer, getSumLog(), "sum of logarithms: ", separator, suffix);
+        append(outBuffer, getStandardDeviation(), "standard deviation: ", separator, suffix);
+        outBuffer.append("covariance: " + getCovariance().toString() + suffix);
+        return outBuffer.toString();
+    }
+
+    /**
+     * Append a text representation of an array to a buffer.
+     * @param buffer buffer to fill
+     * @param data data array
+     * @param prefix text prefix
+     * @param separator elements separator
+     * @param suffix text suffix
+     */
+    private void append(StringBuilder buffer, double[] data,
+                        String prefix, String separator, String suffix) {
+        buffer.append(prefix);
+        for (int i = 0; i < data.length; ++i) {
+            if (i > 0) {
+                buffer.append(separator);
+            }
+            buffer.append(data[i]);
+        }
+        buffer.append(suffix);
+    }
+
+    /**
+     * Resets all statistics and storage
+     */
+    public void clear() {
+        this.n = 0;
+        for (int i = 0; i < k; ++i) {
+            minImpl[i].clear();
+            maxImpl[i].clear();
+            sumImpl[i].clear();
+            sumLogImpl[i].clear();
+            sumSqImpl[i].clear();
+            geoMeanImpl[i].clear();
+            meanImpl[i].clear();
+        }
+        covarianceImpl.clear();
+    }
+
+    /**
+     * Returns true iff <code>object</code> is a <code>MultivariateSummaryStatistics</code>
+     * instance and all statistics have the same values as this.
+     * @param object the object to test equality against.
+     * @return true if object equals this
+     */
+    @Override
+    public boolean equals(Object object) {
+        if (object == this ) {
+            return true;
+        }
+        if (object instanceof MultivariateSummaryStatistics == false) {
+            return false;
+        }
+        MultivariateSummaryStatistics stat = (MultivariateSummaryStatistics) object;
+        return MathArrays.equalsIncludingNaN(stat.getGeometricMean(), getGeometricMean()) &&
+               MathArrays.equalsIncludingNaN(stat.getMax(),           getMax())           &&
+               MathArrays.equalsIncludingNaN(stat.getMean(),          getMean())          &&
+               MathArrays.equalsIncludingNaN(stat.getMin(),           getMin())           &&
+               Precision.equalsIncludingNaN(stat.getN(),             getN())             &&
+               MathArrays.equalsIncludingNaN(stat.getSum(),           getSum())           &&
+               MathArrays.equalsIncludingNaN(stat.getSumSq(),         getSumSq())         &&
+               MathArrays.equalsIncludingNaN(stat.getSumLog(),        getSumLog())        &&
+               stat.getCovariance().equals( getCovariance());
+    }
+
+    /**
+     * Returns hash code based on values of statistics
+     *
+     * @return hash code
+     */
+    @Override
+    public int hashCode() {
+        int result = 31 + MathUtils.hash(getGeometricMean());
+        result = result * 31 + MathUtils.hash(getGeometricMean());
+        result = result * 31 + MathUtils.hash(getMax());
+        result = result * 31 + MathUtils.hash(getMean());
+        result = result * 31 + MathUtils.hash(getMin());
+        result = result * 31 + MathUtils.hash(getN());
+        result = result * 31 + MathUtils.hash(getSum());
+        result = result * 31 + MathUtils.hash(getSumSq());
+        result = result * 31 + MathUtils.hash(getSumLog());
+        result = result * 31 + getCovariance().hashCode();
+        return result;
+    }
+
+    // Getters and setters for statistics implementations
+    /**
+     * Sets statistics implementations.
+     * @param newImpl new implementations for statistics
+     * @param oldImpl old implementations for statistics
+     * @throws DimensionMismatchException if the array dimension
+     * does not match the one used at construction
+     * @throws MathIllegalStateException if data has already been added
+     * (i.e. if n > 0)
+     */
+    private void setImpl(StorelessUnivariateStatistic[] newImpl,
+                         StorelessUnivariateStatistic[] oldImpl) throws MathIllegalStateException,
+                         DimensionMismatchException {
+        checkEmpty();
+        checkDimension(newImpl.length);
+        System.arraycopy(newImpl, 0, oldImpl, 0, newImpl.length);
+    }
+
+    /**
+     * Returns the currently configured Sum implementation
+     *
+     * @return the StorelessUnivariateStatistic implementing the sum
+     */
+    public StorelessUnivariateStatistic[] getSumImpl() {
+        return sumImpl.clone();
+    }
+
+    /**
+     * <p>Sets the implementation for the Sum.</p>
+     * <p>This method must be activated before any data has been added - i.e.,
+     * before {@link #addValue(double[]) addValue} has been used to add data;
+     * otherwise an IllegalStateException will be thrown.</p>
+     *
+     * @param sumImpl the StorelessUnivariateStatistic instance to use
+     * for computing the Sum
+     * @throws DimensionMismatchException if the array dimension
+     * does not match the one used at construction
+     * @throws MathIllegalStateException if data has already been added
+     *  (i.e if n > 0)
+     */
+    public void setSumImpl(StorelessUnivariateStatistic[] sumImpl)
+    throws MathIllegalStateException, DimensionMismatchException {
+        setImpl(sumImpl, this.sumImpl);
+    }
+
+    /**
+     * Returns the currently configured sum of squares implementation
+     *
+     * @return the StorelessUnivariateStatistic implementing the sum of squares
+     */
+    public StorelessUnivariateStatistic[] getSumsqImpl() {
+        return sumSqImpl.clone();
+    }
+
+    /**
+     * <p>Sets the implementation for the sum of squares.</p>
+     * <p>This method must be activated before any data has been added - i.e.,
+     * before {@link #addValue(double[]) addValue} has been used to add data;
+     * otherwise an IllegalStateException will be thrown.</p>
+     *
+     * @param sumsqImpl the StorelessUnivariateStatistic instance to use
+     * for computing the sum of squares
+     * @throws DimensionMismatchException if the array dimension
+     * does not match the one used at construction
+     * @throws MathIllegalStateException if data has already been added
+     *  (i.e if n > 0)
+     */
+    public void setSumsqImpl(StorelessUnivariateStatistic[] sumsqImpl)
+    throws MathIllegalStateException, DimensionMismatchException {
+        setImpl(sumsqImpl, this.sumSqImpl);
+    }
+
+    /**
+     * Returns the currently configured minimum implementation
+     *
+     * @return the StorelessUnivariateStatistic implementing the minimum
+     */
+    public StorelessUnivariateStatistic[] getMinImpl() {
+        return minImpl.clone();
+    }
+
+    /**
+     * <p>Sets the implementation for the minimum.</p>
+     * <p>This method must be activated before any data has been added - i.e.,
+     * before {@link #addValue(double[]) addValue} has been used to add data;
+     * otherwise an IllegalStateException will be thrown.</p>
+     *
+     * @param minImpl the StorelessUnivariateStatistic instance to use
+     * for computing the minimum
+     * @throws DimensionMismatchException if the array dimension
+     * does not match the one used at construction
+     * @throws MathIllegalStateException if data has already been added
+     *  (i.e if n > 0)
+     */
+    public void setMinImpl(StorelessUnivariateStatistic[] minImpl)
+    throws MathIllegalStateException, DimensionMismatchException {
+        setImpl(minImpl, this.minImpl);
+    }
+
+    /**
+     * Returns the currently configured maximum implementation
+     *
+     * @return the StorelessUnivariateStatistic implementing the maximum
+     */
+    public StorelessUnivariateStatistic[] getMaxImpl() {
+        return maxImpl.clone();
+    }
+
+    /**
+     * <p>Sets the implementation for the maximum.</p>
+     * <p>This method must be activated before any data has been added - i.e.,
+     * before {@link #addValue(double[]) addValue} has been used to add data;
+     * otherwise an IllegalStateException will be thrown.</p>
+     *
+     * @param maxImpl the StorelessUnivariateStatistic instance to use
+     * for computing the maximum
+     * @throws DimensionMismatchException if the array dimension
+     * does not match the one used at construction
+     * @throws MathIllegalStateException if data has already been added
+     *  (i.e if n > 0)
+     */
+    public void setMaxImpl(StorelessUnivariateStatistic[] maxImpl)
+    throws MathIllegalStateException, DimensionMismatchException{
+        setImpl(maxImpl, this.maxImpl);
+    }
+
+    /**
+     * Returns the currently configured sum of logs implementation
+     *
+     * @return the StorelessUnivariateStatistic implementing the log sum
+     */
+    public StorelessUnivariateStatistic[] getSumLogImpl() {
+        return sumLogImpl.clone();
+    }
+
+    /**
+     * <p>Sets the implementation for the sum of logs.</p>
+     * <p>This method must be activated before any data has been added - i.e.,
+     * before {@link #addValue(double[]) addValue} has been used to add data;
+     * otherwise an IllegalStateException will be thrown.</p>
+     *
+     * @param sumLogImpl the StorelessUnivariateStatistic instance to use
+     * for computing the log sum
+     * @throws DimensionMismatchException if the array dimension
+     * does not match the one used at construction
+     * @throws MathIllegalStateException if data has already been added
+     *  (i.e if n > 0)
+     */
+    public void setSumLogImpl(StorelessUnivariateStatistic[] sumLogImpl)
+    throws MathIllegalStateException, DimensionMismatchException{
+        setImpl(sumLogImpl, this.sumLogImpl);
+    }
+
+    /**
+     * Returns the currently configured geometric mean implementation
+     *
+     * @return the StorelessUnivariateStatistic implementing the geometric mean
+     */
+    public StorelessUnivariateStatistic[] getGeoMeanImpl() {
+        return geoMeanImpl.clone();
+    }
+
+    /**
+     * <p>Sets the implementation for the geometric mean.</p>
+     * <p>This method must be activated before any data has been added - i.e.,
+     * before {@link #addValue(double[]) addValue} has been used to add data;
+     * otherwise an IllegalStateException will be thrown.</p>
+     *
+     * @param geoMeanImpl the StorelessUnivariateStatistic instance to use
+     * for computing the geometric mean
+     * @throws DimensionMismatchException if the array dimension
+     * does not match the one used at construction
+     * @throws MathIllegalStateException if data has already been added
+     *  (i.e if n > 0)
+     */
+    public void setGeoMeanImpl(StorelessUnivariateStatistic[] geoMeanImpl)
+    throws MathIllegalStateException, DimensionMismatchException {
+        setImpl(geoMeanImpl, this.geoMeanImpl);
+    }
+
+    /**
+     * Returns the currently configured mean implementation
+     *
+     * @return the StorelessUnivariateStatistic implementing the mean
+     */
+    public StorelessUnivariateStatistic[] getMeanImpl() {
+        return meanImpl.clone();
+    }
+
+    /**
+     * <p>Sets the implementation for the mean.</p>
+     * <p>This method must be activated before any data has been added - i.e.,
+     * before {@link #addValue(double[]) addValue} has been used to add data;
+     * otherwise an IllegalStateException will be thrown.</p>
+     *
+     * @param meanImpl the StorelessUnivariateStatistic instance to use
+     * for computing the mean
+     * @throws DimensionMismatchException if the array dimension
+     * does not match the one used at construction
+     * @throws MathIllegalStateException if data has already been added
+     *  (i.e if n > 0)
+     */
+    public void setMeanImpl(StorelessUnivariateStatistic[] meanImpl)
+    throws MathIllegalStateException, DimensionMismatchException{
+        setImpl(meanImpl, this.meanImpl);
+    }
+
+    /**
+     * Throws MathIllegalStateException if the statistic is not empty.
+     * @throws MathIllegalStateException if n > 0.
+     */
+    private void checkEmpty() throws MathIllegalStateException {
+        if (n > 0) {
+            throw new MathIllegalStateException(
+                    LocalizedFormats.VALUES_ADDED_BEFORE_CONFIGURING_STATISTIC, n);
+        }
+    }
+
+    /**
+     * Throws DimensionMismatchException if dimension != k.
+     * @param dimension dimension to check
+     * @throws DimensionMismatchException if dimension != k
+     */
+    private void checkDimension(int dimension) throws DimensionMismatchException {
+        if (dimension != k) {
+            throw new DimensionMismatchException(dimension, k);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/StatisticalMultivariateSummary.java b/src/main/java/org/apache/commons/math3/stat/descriptive/StatisticalMultivariateSummary.java
new file mode 100644
index 0000000..bfe4deb
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/StatisticalMultivariateSummary.java
@@ -0,0 +1,119 @@
+/*
+ * 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.math3.stat.descriptive;
+
+import org.apache.commons.math3.linear.RealMatrix;
+
+/**
+ *  Reporting interface for basic multivariate statistics.
+ *
+ * @since 1.2
+ */
+public interface StatisticalMultivariateSummary {
+
+    /**
+     * Returns the dimension of the data
+     * @return The dimension of the data
+     */
+    int getDimension();
+
+    /**
+     * Returns an array whose i<sup>th</sup> entry is the
+     * mean of the i<sup>th</sup> entries of the arrays
+     * that correspond to each multivariate sample
+     *
+     * @return the array of component means
+     */
+    double[] getMean();
+
+    /**
+     * Returns the covariance of the available values.
+     * @return The covariance, null if no multivariate sample
+     * have been added or a zeroed matrix for a single value set.
+     */
+    RealMatrix getCovariance();
+
+    /**
+     * Returns an array whose i<sup>th</sup> entry is the
+     * standard deviation of the i<sup>th</sup> entries of the arrays
+     * that correspond to each multivariate sample
+     *
+     * @return the array of component standard deviations
+     */
+    double[] getStandardDeviation();
+
+    /**
+     * Returns an array whose i<sup>th</sup> entry is the
+     * maximum of the i<sup>th</sup> entries of the arrays
+     * that correspond to each multivariate sample
+     *
+     * @return the array of component maxima
+     */
+    double[] getMax();
+
+    /**
+     * Returns an array whose i<sup>th</sup> entry is the
+     * minimum of the i<sup>th</sup> entries of the arrays
+     * that correspond to each multivariate sample
+     *
+     * @return the array of component minima
+     */
+    double[] getMin();
+
+    /**
+     * Returns the number of available values
+     * @return The number of available values
+     */
+    long getN();
+
+    /**
+     * Returns an array whose i<sup>th</sup> entry is the
+     * geometric mean of the i<sup>th</sup> entries of the arrays
+     * that correspond to each multivariate sample
+     *
+     * @return the array of component geometric means
+     */
+    double[] getGeometricMean();
+
+    /**
+     * Returns an array whose i<sup>th</sup> entry is the
+     * sum of the i<sup>th</sup> entries of the arrays
+     * that correspond to each multivariate sample
+     *
+     * @return the array of component sums
+     */
+    double[] getSum();
+
+    /**
+     * Returns an array whose i<sup>th</sup> entry is the
+     * sum of squares of the i<sup>th</sup> entries of the arrays
+     * that correspond to each multivariate sample
+     *
+     * @return the array of component sums of squares
+     */
+    double[] getSumSq();
+
+    /**
+     * Returns an array whose i<sup>th</sup> entry is the
+     * sum of logs of the i<sup>th</sup> entries of the arrays
+     * that correspond to each multivariate sample
+     *
+     * @return the array of component log sums
+     */
+    double[] getSumLog();
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/StatisticalSummary.java b/src/main/java/org/apache/commons/math3/stat/descriptive/StatisticalSummary.java
new file mode 100644
index 0000000..2f310ac
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/StatisticalSummary.java
@@ -0,0 +1,64 @@
+/*
+ * 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.math3.stat.descriptive;
+
+/**
+ *  Reporting interface for basic univariate statistics.
+ *
+ */
+public interface StatisticalSummary {
+
+    /**
+     * Returns the <a href="http://www.xycoon.com/arithmetic_mean.htm">
+     * arithmetic mean </a> of the available values
+     * @return The mean or Double.NaN if no values have been added.
+     */
+    double getMean();
+    /**
+     * Returns the variance of the available values.
+     * @return The variance, Double.NaN if no values have been added
+     * or 0.0 for a single value set.
+     */
+    double getVariance();
+    /**
+     * Returns the standard deviation of the available values.
+     * @return The standard deviation, Double.NaN if no values have been added
+     * or 0.0 for a single value set.
+     */
+    double getStandardDeviation();
+    /**
+     * Returns the maximum of the available values
+     * @return The max or Double.NaN if no values have been added.
+     */
+    double getMax();
+    /**
+    * Returns the minimum of the available values
+    * @return The min or Double.NaN if no values have been added.
+    */
+    double getMin();
+    /**
+     * Returns the number of available values
+     * @return The number of available values
+     */
+    long getN();
+    /**
+     * Returns the sum of the values that have been added to Univariate.
+     * @return The sum or Double.NaN if no values have been added
+     */
+    double getSum();
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/StatisticalSummaryValues.java b/src/main/java/org/apache/commons/math3/stat/descriptive/StatisticalSummaryValues.java
new file mode 100644
index 0000000..e216e9b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/StatisticalSummaryValues.java
@@ -0,0 +1,186 @@
+/*
+ * 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.math3.stat.descriptive;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+import org.apache.commons.math3.util.Precision;
+
+/**
+ *  Value object representing the results of a univariate statistical summary.
+ *
+ */
+public class StatisticalSummaryValues implements Serializable,
+    StatisticalSummary {
+
+    /** Serialization id */
+    private static final long serialVersionUID = -5108854841843722536L;
+
+    /** The sample mean */
+    private final double mean;
+
+    /** The sample variance */
+    private final double variance;
+
+    /** The number of observations in the sample */
+    private final long n;
+
+    /** The maximum value */
+    private final double max;
+
+    /** The minimum value */
+    private final double min;
+
+    /** The sum of the sample values */
+    private final double sum;
+
+    /**
+      * Constructor
+      *
+      * @param mean  the sample mean
+      * @param variance  the sample variance
+      * @param n  the number of observations in the sample
+      * @param max  the maximum value
+      * @param min  the minimum value
+      * @param sum  the sum of the values
+     */
+    public StatisticalSummaryValues(double mean, double variance, long n,
+        double max, double min, double sum) {
+        super();
+        this.mean = mean;
+        this.variance = variance;
+        this.n = n;
+        this.max = max;
+        this.min = min;
+        this.sum = sum;
+    }
+
+    /**
+     * @return Returns the max.
+     */
+    public double getMax() {
+        return max;
+    }
+
+    /**
+     * @return Returns the mean.
+     */
+    public double getMean() {
+        return mean;
+    }
+
+    /**
+     * @return Returns the min.
+     */
+    public double getMin() {
+        return min;
+    }
+
+    /**
+     * @return Returns the number of values.
+     */
+    public long getN() {
+        return n;
+    }
+
+    /**
+     * @return Returns the sum.
+     */
+    public double getSum() {
+        return sum;
+    }
+
+    /**
+     * @return Returns the standard deviation
+     */
+    public double getStandardDeviation() {
+        return FastMath.sqrt(variance);
+    }
+
+    /**
+     * @return Returns the variance.
+     */
+    public double getVariance() {
+        return variance;
+    }
+
+    /**
+     * Returns true iff <code>object</code> is a
+     * <code>StatisticalSummaryValues</code> instance and all statistics have
+     *  the same values as this.
+     *
+     * @param object the object to test equality against.
+     * @return true if object equals this
+     */
+    @Override
+    public boolean equals(Object object) {
+        if (object == this ) {
+            return true;
+        }
+        if (object instanceof StatisticalSummaryValues == false) {
+            return false;
+        }
+        StatisticalSummaryValues stat = (StatisticalSummaryValues) object;
+        return Precision.equalsIncludingNaN(stat.getMax(),      getMax())  &&
+               Precision.equalsIncludingNaN(stat.getMean(),     getMean()) &&
+               Precision.equalsIncludingNaN(stat.getMin(),      getMin())  &&
+               Precision.equalsIncludingNaN(stat.getN(),        getN())    &&
+               Precision.equalsIncludingNaN(stat.getSum(),      getSum())  &&
+               Precision.equalsIncludingNaN(stat.getVariance(), getVariance());
+    }
+
+    /**
+     * Returns hash code based on values of statistics
+     *
+     * @return hash code
+     */
+    @Override
+    public int hashCode() {
+        int result = 31 + MathUtils.hash(getMax());
+        result = result * 31 + MathUtils.hash(getMean());
+        result = result * 31 + MathUtils.hash(getMin());
+        result = result * 31 + MathUtils.hash(getN());
+        result = result * 31 + MathUtils.hash(getSum());
+        result = result * 31 + MathUtils.hash(getVariance());
+        return result;
+    }
+
+    /**
+     * Generates a text report displaying values of statistics.
+     * Each statistic is displayed on a separate line.
+     *
+     * @return String with line feeds displaying statistics
+     */
+    @Override
+    public String toString() {
+        StringBuffer outBuffer = new StringBuffer();
+        String endl = "\n";
+        outBuffer.append("StatisticalSummaryValues:").append(endl);
+        outBuffer.append("n: ").append(getN()).append(endl);
+        outBuffer.append("min: ").append(getMin()).append(endl);
+        outBuffer.append("max: ").append(getMax()).append(endl);
+        outBuffer.append("mean: ").append(getMean()).append(endl);
+        outBuffer.append("std dev: ").append(getStandardDeviation())
+            .append(endl);
+        outBuffer.append("variance: ").append(getVariance()).append(endl);
+        outBuffer.append("sum: ").append(getSum()).append(endl);
+        return outBuffer.toString();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/StorelessUnivariateStatistic.java b/src/main/java/org/apache/commons/math3/stat/descriptive/StorelessUnivariateStatistic.java
new file mode 100644
index 0000000..e1c2464
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/StorelessUnivariateStatistic.java
@@ -0,0 +1,87 @@
+/*
+ * 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.math3.stat.descriptive;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+
+/**
+ * Extends the definition of {@link UnivariateStatistic} with
+ * {@link #increment} and {@link #incrementAll(double[])} methods for adding
+ * values and updating internal state.
+ * <p>
+ * This interface is designed to be used for calculating statistics that can be
+ * computed in one pass through the data without storing the full array of
+ * sample values.</p>
+ *
+ */
+public interface StorelessUnivariateStatistic extends UnivariateStatistic {
+
+    /**
+     * Updates the internal state of the statistic to reflect the addition of the new value.
+     * @param d  the new value.
+     */
+    void increment(double d);
+
+    /**
+     * Updates the internal state of the statistic to reflect addition of
+     * all values in the values array.  Does not clear the statistic first --
+     * i.e., the values are added <strong>incrementally</strong> to the dataset.
+     *
+     * @param values  array holding the new values to add
+     * @throws MathIllegalArgumentException if the array is null
+     */
+    void incrementAll(double[] values) throws MathIllegalArgumentException;
+
+    /**
+     * Updates the internal state of the statistic to reflect addition of
+     * the values in the designated portion of the values array.  Does not
+     * clear the statistic first -- i.e., the values are added
+     * <strong>incrementally</strong> to the dataset.
+     *
+     * @param values  array holding the new values to add
+     * @param start  the array index of the first value to add
+     * @param length  the number of elements to add
+     * @throws MathIllegalArgumentException if the array is null or the index
+     */
+    void incrementAll(double[] values, int start, int length) throws MathIllegalArgumentException;
+
+    /**
+     * Returns the current value of the Statistic.
+     * @return value of the statistic, <code>Double.NaN</code> if it
+     * has been cleared or just instantiated.
+     */
+    double getResult();
+
+    /**
+     * Returns the number of values that have been added.
+     * @return the number of values.
+     */
+    long getN();
+
+    /**
+     * Clears the internal state of the Statistic
+     */
+    void clear();
+
+    /**
+     * Returns a copy of the statistic with the same internal state.
+     *
+     * @return a copy of the statistic
+     */
+    StorelessUnivariateStatistic copy();
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/SummaryStatistics.java b/src/main/java/org/apache/commons/math3/stat/descriptive/SummaryStatistics.java
new file mode 100644
index 0000000..62fee80
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/SummaryStatistics.java
@@ -0,0 +1,765 @@
+/*
+ * 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.math3.stat.descriptive;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.stat.descriptive.moment.GeometricMean;
+import org.apache.commons.math3.stat.descriptive.moment.Mean;
+import org.apache.commons.math3.stat.descriptive.moment.SecondMoment;
+import org.apache.commons.math3.stat.descriptive.moment.Variance;
+import org.apache.commons.math3.stat.descriptive.rank.Max;
+import org.apache.commons.math3.stat.descriptive.rank.Min;
+import org.apache.commons.math3.stat.descriptive.summary.Sum;
+import org.apache.commons.math3.stat.descriptive.summary.SumOfLogs;
+import org.apache.commons.math3.stat.descriptive.summary.SumOfSquares;
+import org.apache.commons.math3.util.MathUtils;
+import org.apache.commons.math3.util.Precision;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * <p>
+ * Computes summary statistics for a stream of data values added using the
+ * {@link #addValue(double) addValue} method. The data values are not stored in
+ * memory, so this class can be used to compute statistics for very large data
+ * streams.
+ * </p>
+ * <p>
+ * The {@link StorelessUnivariateStatistic} instances used to maintain summary
+ * state and compute statistics are configurable via setters. For example, the
+ * default implementation for the variance can be overridden by calling
+ * {@link #setVarianceImpl(StorelessUnivariateStatistic)}. Actual parameters to
+ * these methods must implement the {@link StorelessUnivariateStatistic}
+ * interface and configuration must be completed before <code>addValue</code>
+ * is called. No configuration is necessary to use the default, commons-math
+ * provided implementations.
+ * </p>
+ * <p>
+ * Note: This class is not thread-safe. Use
+ * {@link SynchronizedSummaryStatistics} if concurrent access from multiple
+ * threads is required.
+ * </p>
+ */
+public class SummaryStatistics implements StatisticalSummary, Serializable {
+
+    /** Serialization UID */
+    private static final long serialVersionUID = -2021321786743555871L;
+
+    /** count of values that have been added */
+    private long n = 0;
+
+    /** SecondMoment is used to compute the mean and variance */
+    private SecondMoment secondMoment = new SecondMoment();
+
+    /** sum of values that have been added */
+    private Sum sum = new Sum();
+
+    /** sum of the square of each value that has been added */
+    private SumOfSquares sumsq = new SumOfSquares();
+
+    /** min of values that have been added */
+    private Min min = new Min();
+
+    /** max of values that have been added */
+    private Max max = new Max();
+
+    /** sumLog of values that have been added */
+    private SumOfLogs sumLog = new SumOfLogs();
+
+    /** geoMean of values that have been added */
+    private GeometricMean geoMean = new GeometricMean(sumLog);
+
+    /** mean of values that have been added */
+    private Mean mean = new Mean(secondMoment);
+
+    /** variance of values that have been added */
+    private Variance variance = new Variance(secondMoment);
+
+    /** Sum statistic implementation - can be reset by setter. */
+    private StorelessUnivariateStatistic sumImpl = sum;
+
+    /** Sum of squares statistic implementation - can be reset by setter. */
+    private StorelessUnivariateStatistic sumsqImpl = sumsq;
+
+    /** Minimum statistic implementation - can be reset by setter. */
+    private StorelessUnivariateStatistic minImpl = min;
+
+    /** Maximum statistic implementation - can be reset by setter. */
+    private StorelessUnivariateStatistic maxImpl = max;
+
+    /** Sum of log statistic implementation - can be reset by setter. */
+    private StorelessUnivariateStatistic sumLogImpl = sumLog;
+
+    /** Geometric mean statistic implementation - can be reset by setter. */
+    private StorelessUnivariateStatistic geoMeanImpl = geoMean;
+
+    /** Mean statistic implementation - can be reset by setter. */
+    private StorelessUnivariateStatistic meanImpl = mean;
+
+    /** Variance statistic implementation - can be reset by setter. */
+    private StorelessUnivariateStatistic varianceImpl = variance;
+
+    /**
+     * Construct a SummaryStatistics instance
+     */
+    public SummaryStatistics() {
+    }
+
+    /**
+     * A copy constructor. Creates a deep-copy of the {@code original}.
+     *
+     * @param original the {@code SummaryStatistics} instance to copy
+     * @throws NullArgumentException if original is null
+     */
+    public SummaryStatistics(SummaryStatistics original) throws NullArgumentException {
+        copy(original, this);
+    }
+
+    /**
+     * Return a {@link StatisticalSummaryValues} instance reporting current
+     * statistics.
+     * @return Current values of statistics
+     */
+    public StatisticalSummary getSummary() {
+        return new StatisticalSummaryValues(getMean(), getVariance(), getN(),
+                getMax(), getMin(), getSum());
+    }
+
+    /**
+     * Add a value to the data
+     * @param value the value to add
+     */
+    public void addValue(double value) {
+        sumImpl.increment(value);
+        sumsqImpl.increment(value);
+        minImpl.increment(value);
+        maxImpl.increment(value);
+        sumLogImpl.increment(value);
+        secondMoment.increment(value);
+        // If mean, variance or geomean have been overridden,
+        // need to increment these
+        if (meanImpl != mean) {
+            meanImpl.increment(value);
+        }
+        if (varianceImpl != variance) {
+            varianceImpl.increment(value);
+        }
+        if (geoMeanImpl != geoMean) {
+            geoMeanImpl.increment(value);
+        }
+        n++;
+    }
+
+    /**
+     * Returns the number of available values
+     * @return The number of available values
+     */
+    public long getN() {
+        return n;
+    }
+
+    /**
+     * Returns the sum of the values that have been added
+     * @return The sum or <code>Double.NaN</code> if no values have been added
+     */
+    public double getSum() {
+        return sumImpl.getResult();
+    }
+
+    /**
+     * Returns the sum of the squares of the values that have been added.
+     * <p>
+     * Double.NaN is returned if no values have been added.
+     * </p>
+     * @return The sum of squares
+     */
+    public double getSumsq() {
+        return sumsqImpl.getResult();
+    }
+
+    /**
+     * Returns the mean of the values that have been added.
+     * <p>
+     * Double.NaN is returned if no values have been added.
+     * </p>
+     * @return the mean
+     */
+    public double getMean() {
+        return meanImpl.getResult();
+    }
+
+    /**
+     * Returns the standard deviation of the values that have been added.
+     * <p>
+     * Double.NaN is returned if no values have been added.
+     * </p>
+     * @return the standard deviation
+     */
+    public double getStandardDeviation() {
+        double stdDev = Double.NaN;
+        if (getN() > 0) {
+            if (getN() > 1) {
+                stdDev = FastMath.sqrt(getVariance());
+            } else {
+                stdDev = 0.0;
+            }
+        }
+        return stdDev;
+    }
+
+    /**
+     * Returns the quadratic mean, a.k.a.
+     * <a href="http://mathworld.wolfram.com/Root-Mean-Square.html">
+     * root-mean-square</a> of the available values
+     * @return The quadratic mean or {@code Double.NaN} if no values
+     * have been added.
+     */
+    public double getQuadraticMean() {
+        final long size = getN();
+        return size > 0 ? FastMath.sqrt(getSumsq() / size) : Double.NaN;
+    }
+
+    /**
+     * Returns the (sample) variance of the available values.
+     *
+     * <p>This method returns the bias-corrected sample variance (using {@code n - 1} in
+     * the denominator).  Use {@link #getPopulationVariance()} for the non-bias-corrected
+     * population variance.</p>
+     *
+     * <p>Double.NaN is returned if no values have been added.</p>
+     *
+     * @return the variance
+     */
+    public double getVariance() {
+        return varianceImpl.getResult();
+    }
+
+    /**
+     * Returns the <a href="http://en.wikibooks.org/wiki/Statistics/Summary/Variance">
+     * population variance</a> of the values that have been added.
+     *
+     * <p>Double.NaN is returned if no values have been added.</p>
+     *
+     * @return the population variance
+     */
+    public double getPopulationVariance() {
+        Variance populationVariance = new Variance(secondMoment);
+        populationVariance.setBiasCorrected(false);
+        return populationVariance.getResult();
+    }
+
+    /**
+     * Returns the maximum of the values that have been added.
+     * <p>
+     * Double.NaN is returned if no values have been added.
+     * </p>
+     * @return the maximum
+     */
+    public double getMax() {
+        return maxImpl.getResult();
+    }
+
+    /**
+     * Returns the minimum of the values that have been added.
+     * <p>
+     * Double.NaN is returned if no values have been added.
+     * </p>
+     * @return the minimum
+     */
+    public double getMin() {
+        return minImpl.getResult();
+    }
+
+    /**
+     * Returns the geometric mean of the values that have been added.
+     * <p>
+     * Double.NaN is returned if no values have been added.
+     * </p>
+     * @return the geometric mean
+     */
+    public double getGeometricMean() {
+        return geoMeanImpl.getResult();
+    }
+
+    /**
+     * Returns the sum of the logs of the values that have been added.
+     * <p>
+     * Double.NaN is returned if no values have been added.
+     * </p>
+     * @return the sum of logs
+     * @since 1.2
+     */
+    public double getSumOfLogs() {
+        return sumLogImpl.getResult();
+    }
+
+    /**
+     * Returns a statistic related to the Second Central Moment.  Specifically,
+     * what is returned is the sum of squared deviations from the sample mean
+     * among the values that have been added.
+     * <p>
+     * Returns <code>Double.NaN</code> if no data values have been added and
+     * returns <code>0</code> if there is just one value in the data set.</p>
+     * <p>
+     * @return second central moment statistic
+     * @since 2.0
+     */
+    public double getSecondMoment() {
+        return secondMoment.getResult();
+    }
+
+    /**
+     * Generates a text report displaying summary statistics from values that
+     * have been added.
+     * @return String with line feeds displaying statistics
+     * @since 1.2
+     */
+    @Override
+    public String toString() {
+        StringBuilder outBuffer = new StringBuilder();
+        String endl = "\n";
+        outBuffer.append("SummaryStatistics:").append(endl);
+        outBuffer.append("n: ").append(getN()).append(endl);
+        outBuffer.append("min: ").append(getMin()).append(endl);
+        outBuffer.append("max: ").append(getMax()).append(endl);
+        outBuffer.append("sum: ").append(getSum()).append(endl);
+        outBuffer.append("mean: ").append(getMean()).append(endl);
+        outBuffer.append("geometric mean: ").append(getGeometricMean())
+            .append(endl);
+        outBuffer.append("variance: ").append(getVariance()).append(endl);
+        outBuffer.append("population variance: ").append(getPopulationVariance()).append(endl);
+        outBuffer.append("second moment: ").append(getSecondMoment()).append(endl);
+        outBuffer.append("sum of squares: ").append(getSumsq()).append(endl);
+        outBuffer.append("standard deviation: ").append(getStandardDeviation())
+            .append(endl);
+        outBuffer.append("sum of logs: ").append(getSumOfLogs()).append(endl);
+        return outBuffer.toString();
+    }
+
+    /**
+     * Resets all statistics and storage
+     */
+    public void clear() {
+        this.n = 0;
+        minImpl.clear();
+        maxImpl.clear();
+        sumImpl.clear();
+        sumLogImpl.clear();
+        sumsqImpl.clear();
+        geoMeanImpl.clear();
+        secondMoment.clear();
+        if (meanImpl != mean) {
+            meanImpl.clear();
+        }
+        if (varianceImpl != variance) {
+            varianceImpl.clear();
+        }
+    }
+
+    /**
+     * Returns true iff <code>object</code> is a
+     * <code>SummaryStatistics</code> instance and all statistics have the
+     * same values as this.
+     * @param object the object to test equality against.
+     * @return true if object equals this
+     */
+    @Override
+    public boolean equals(Object object) {
+        if (object == this) {
+            return true;
+        }
+        if (object instanceof SummaryStatistics == false) {
+            return false;
+        }
+        SummaryStatistics stat = (SummaryStatistics)object;
+        return Precision.equalsIncludingNaN(stat.getGeometricMean(), getGeometricMean()) &&
+               Precision.equalsIncludingNaN(stat.getMax(),           getMax())           &&
+               Precision.equalsIncludingNaN(stat.getMean(),          getMean())          &&
+               Precision.equalsIncludingNaN(stat.getMin(),           getMin())           &&
+               Precision.equalsIncludingNaN(stat.getN(),             getN())             &&
+               Precision.equalsIncludingNaN(stat.getSum(),           getSum())           &&
+               Precision.equalsIncludingNaN(stat.getSumsq(),         getSumsq())         &&
+               Precision.equalsIncludingNaN(stat.getVariance(),      getVariance());
+    }
+
+    /**
+     * Returns hash code based on values of statistics
+     * @return hash code
+     */
+    @Override
+    public int hashCode() {
+        int result = 31 + MathUtils.hash(getGeometricMean());
+        result = result * 31 + MathUtils.hash(getGeometricMean());
+        result = result * 31 + MathUtils.hash(getMax());
+        result = result * 31 + MathUtils.hash(getMean());
+        result = result * 31 + MathUtils.hash(getMin());
+        result = result * 31 + MathUtils.hash(getN());
+        result = result * 31 + MathUtils.hash(getSum());
+        result = result * 31 + MathUtils.hash(getSumsq());
+        result = result * 31 + MathUtils.hash(getVariance());
+        return result;
+    }
+
+    // Getters and setters for statistics implementations
+    /**
+     * Returns the currently configured Sum implementation
+     * @return the StorelessUnivariateStatistic implementing the sum
+     * @since 1.2
+     */
+    public StorelessUnivariateStatistic getSumImpl() {
+        return sumImpl;
+    }
+
+    /**
+     * <p>
+     * Sets the implementation for the Sum.
+     * </p>
+     * <p>
+     * This method cannot be activated after data has been added - i.e.,
+     * after {@link #addValue(double) addValue} has been used to add data.
+     * If it is activated after data has been added, an IllegalStateException
+     * will be thrown.
+     * </p>
+     * @param sumImpl the StorelessUnivariateStatistic instance to use for
+     *        computing the Sum
+     * @throws MathIllegalStateException if data has already been added (i.e if n >0)
+     * @since 1.2
+     */
+    public void setSumImpl(StorelessUnivariateStatistic sumImpl)
+    throws MathIllegalStateException {
+        checkEmpty();
+        this.sumImpl = sumImpl;
+    }
+
+    /**
+     * Returns the currently configured sum of squares implementation
+     * @return the StorelessUnivariateStatistic implementing the sum of squares
+     * @since 1.2
+     */
+    public StorelessUnivariateStatistic getSumsqImpl() {
+        return sumsqImpl;
+    }
+
+    /**
+     * <p>
+     * Sets the implementation for the sum of squares.
+     * </p>
+     * <p>
+     * This method cannot be activated after data has been added - i.e.,
+     * after {@link #addValue(double) addValue} has been used to add data.
+     * If it is activated after data has been added, an IllegalStateException
+     * will be thrown.
+     * </p>
+     * @param sumsqImpl the StorelessUnivariateStatistic instance to use for
+     *        computing the sum of squares
+     * @throws MathIllegalStateException if data has already been added (i.e if n > 0)
+     * @since 1.2
+     */
+    public void setSumsqImpl(StorelessUnivariateStatistic sumsqImpl)
+    throws MathIllegalStateException {
+        checkEmpty();
+        this.sumsqImpl = sumsqImpl;
+    }
+
+    /**
+     * Returns the currently configured minimum implementation
+     * @return the StorelessUnivariateStatistic implementing the minimum
+     * @since 1.2
+     */
+    public StorelessUnivariateStatistic getMinImpl() {
+        return minImpl;
+    }
+
+    /**
+     * <p>
+     * Sets the implementation for the minimum.
+     * </p>
+     * <p>
+     * This method cannot be activated after data has been added - i.e.,
+     * after {@link #addValue(double) addValue} has been used to add data.
+     * If it is activated after data has been added, an IllegalStateException
+     * will be thrown.
+     * </p>
+     * @param minImpl the StorelessUnivariateStatistic instance to use for
+     *        computing the minimum
+     * @throws MathIllegalStateException if data has already been added (i.e if n > 0)
+     * @since 1.2
+     */
+    public void setMinImpl(StorelessUnivariateStatistic minImpl)
+    throws MathIllegalStateException {
+        checkEmpty();
+        this.minImpl = minImpl;
+    }
+
+    /**
+     * Returns the currently configured maximum implementation
+     * @return the StorelessUnivariateStatistic implementing the maximum
+     * @since 1.2
+     */
+    public StorelessUnivariateStatistic getMaxImpl() {
+        return maxImpl;
+    }
+
+    /**
+     * <p>
+     * Sets the implementation for the maximum.
+     * </p>
+     * <p>
+     * This method cannot be activated after data has been added - i.e.,
+     * after {@link #addValue(double) addValue} has been used to add data.
+     * If it is activated after data has been added, an IllegalStateException
+     * will be thrown.
+     * </p>
+     * @param maxImpl the StorelessUnivariateStatistic instance to use for
+     *        computing the maximum
+     * @throws MathIllegalStateException if data has already been added (i.e if n > 0)
+     * @since 1.2
+     */
+    public void setMaxImpl(StorelessUnivariateStatistic maxImpl)
+    throws MathIllegalStateException {
+        checkEmpty();
+        this.maxImpl = maxImpl;
+    }
+
+    /**
+     * Returns the currently configured sum of logs implementation
+     * @return the StorelessUnivariateStatistic implementing the log sum
+     * @since 1.2
+     */
+    public StorelessUnivariateStatistic getSumLogImpl() {
+        return sumLogImpl;
+    }
+
+    /**
+     * <p>
+     * Sets the implementation for the sum of logs.
+     * </p>
+     * <p>
+     * This method cannot be activated after data has been added - i.e.,
+     * after {@link #addValue(double) addValue} has been used to add data.
+     * If it is activated after data has been added, an IllegalStateException
+     * will be thrown.
+     * </p>
+     * @param sumLogImpl the StorelessUnivariateStatistic instance to use for
+     *        computing the log sum
+     * @throws MathIllegalStateException if data has already been added (i.e if n > 0)
+     * @since 1.2
+     */
+    public void setSumLogImpl(StorelessUnivariateStatistic sumLogImpl)
+    throws MathIllegalStateException {
+        checkEmpty();
+        this.sumLogImpl = sumLogImpl;
+        geoMean.setSumLogImpl(sumLogImpl);
+    }
+
+    /**
+     * Returns the currently configured geometric mean implementation
+     * @return the StorelessUnivariateStatistic implementing the geometric mean
+     * @since 1.2
+     */
+    public StorelessUnivariateStatistic getGeoMeanImpl() {
+        return geoMeanImpl;
+    }
+
+    /**
+     * <p>
+     * Sets the implementation for the geometric mean.
+     * </p>
+     * <p>
+     * This method cannot be activated after data has been added - i.e.,
+     * after {@link #addValue(double) addValue} has been used to add data.
+     * If it is activated after data has been added, an IllegalStateException
+     * will be thrown.
+     * </p>
+     * @param geoMeanImpl the StorelessUnivariateStatistic instance to use for
+     *        computing the geometric mean
+     * @throws MathIllegalStateException if data has already been added (i.e if n > 0)
+     * @since 1.2
+     */
+    public void setGeoMeanImpl(StorelessUnivariateStatistic geoMeanImpl)
+    throws MathIllegalStateException {
+        checkEmpty();
+        this.geoMeanImpl = geoMeanImpl;
+    }
+
+    /**
+     * Returns the currently configured mean implementation
+     * @return the StorelessUnivariateStatistic implementing the mean
+     * @since 1.2
+     */
+    public StorelessUnivariateStatistic getMeanImpl() {
+        return meanImpl;
+    }
+
+    /**
+     * <p>
+     * Sets the implementation for the mean.
+     * </p>
+     * <p>
+     * This method cannot be activated after data has been added - i.e.,
+     * after {@link #addValue(double) addValue} has been used to add data.
+     * If it is activated after data has been added, an IllegalStateException
+     * will be thrown.
+     * </p>
+     * @param meanImpl the StorelessUnivariateStatistic instance to use for
+     *        computing the mean
+     * @throws MathIllegalStateException if data has already been added (i.e if n > 0)
+     * @since 1.2
+     */
+    public void setMeanImpl(StorelessUnivariateStatistic meanImpl)
+    throws MathIllegalStateException {
+        checkEmpty();
+        this.meanImpl = meanImpl;
+    }
+
+    /**
+     * Returns the currently configured variance implementation
+     * @return the StorelessUnivariateStatistic implementing the variance
+     * @since 1.2
+     */
+    public StorelessUnivariateStatistic getVarianceImpl() {
+        return varianceImpl;
+    }
+
+    /**
+     * <p>
+     * Sets the implementation for the variance.
+     * </p>
+     * <p>
+     * This method cannot be activated after data has been added - i.e.,
+     * after {@link #addValue(double) addValue} has been used to add data.
+     * If it is activated after data has been added, an IllegalStateException
+     * will be thrown.
+     * </p>
+     * @param varianceImpl the StorelessUnivariateStatistic instance to use for
+     *        computing the variance
+     * @throws MathIllegalStateException if data has already been added (i.e if n > 0)
+     * @since 1.2
+     */
+    public void setVarianceImpl(StorelessUnivariateStatistic varianceImpl)
+    throws MathIllegalStateException {
+        checkEmpty();
+        this.varianceImpl = varianceImpl;
+    }
+
+    /**
+     * Throws IllegalStateException if n > 0.
+     * @throws MathIllegalStateException if data has been added
+     */
+    private void checkEmpty() throws MathIllegalStateException {
+        if (n > 0) {
+            throw new MathIllegalStateException(
+                LocalizedFormats.VALUES_ADDED_BEFORE_CONFIGURING_STATISTIC, n);
+        }
+    }
+
+    /**
+     * Returns a copy of this SummaryStatistics instance with the same internal state.
+     *
+     * @return a copy of this
+     */
+    public SummaryStatistics copy() {
+        SummaryStatistics result = new SummaryStatistics();
+        // No try-catch or advertised exception because arguments are guaranteed non-null
+        copy(this, result);
+        return result;
+    }
+
+    /**
+     * Copies source to dest.
+     * <p>Neither source nor dest can be null.</p>
+     *
+     * @param source SummaryStatistics to copy
+     * @param dest SummaryStatistics to copy to
+     * @throws NullArgumentException if either source or dest is null
+     */
+    public static void copy(SummaryStatistics source, SummaryStatistics dest)
+        throws NullArgumentException {
+        MathUtils.checkNotNull(source);
+        MathUtils.checkNotNull(dest);
+        dest.maxImpl = source.maxImpl.copy();
+        dest.minImpl = source.minImpl.copy();
+        dest.sumImpl = source.sumImpl.copy();
+        dest.sumLogImpl = source.sumLogImpl.copy();
+        dest.sumsqImpl = source.sumsqImpl.copy();
+        dest.secondMoment = source.secondMoment.copy();
+        dest.n = source.n;
+
+        // Keep commons-math supplied statistics with embedded moments in synch
+        if (source.getVarianceImpl() instanceof Variance) {
+            dest.varianceImpl = new Variance(dest.secondMoment);
+        } else {
+            dest.varianceImpl = source.varianceImpl.copy();
+        }
+        if (source.meanImpl instanceof Mean) {
+            dest.meanImpl = new Mean(dest.secondMoment);
+        } else {
+            dest.meanImpl = source.meanImpl.copy();
+        }
+        if (source.getGeoMeanImpl() instanceof GeometricMean) {
+            dest.geoMeanImpl = new GeometricMean((SumOfLogs) dest.sumLogImpl);
+        } else {
+            dest.geoMeanImpl = source.geoMeanImpl.copy();
+        }
+
+        // Make sure that if stat == statImpl in source, same
+        // holds in dest; otherwise copy stat
+        if (source.geoMean == source.geoMeanImpl) {
+            dest.geoMean = (GeometricMean) dest.geoMeanImpl;
+        } else {
+            GeometricMean.copy(source.geoMean, dest.geoMean);
+        }
+        if (source.max == source.maxImpl) {
+            dest.max = (Max) dest.maxImpl;
+        } else {
+            Max.copy(source.max, dest.max);
+        }
+        if (source.mean == source.meanImpl) {
+            dest.mean = (Mean) dest.meanImpl;
+        } else {
+            Mean.copy(source.mean, dest.mean);
+        }
+        if (source.min == source.minImpl) {
+            dest.min = (Min) dest.minImpl;
+        } else {
+            Min.copy(source.min, dest.min);
+        }
+        if (source.sum == source.sumImpl) {
+            dest.sum = (Sum) dest.sumImpl;
+        } else {
+            Sum.copy(source.sum, dest.sum);
+        }
+        if (source.variance == source.varianceImpl) {
+            dest.variance = (Variance) dest.varianceImpl;
+        } else {
+            Variance.copy(source.variance, dest.variance);
+        }
+        if (source.sumLog == source.sumLogImpl) {
+            dest.sumLog = (SumOfLogs) dest.sumLogImpl;
+        } else {
+            SumOfLogs.copy(source.sumLog, dest.sumLog);
+        }
+        if (source.sumsq == source.sumsqImpl) {
+            dest.sumsq = (SumOfSquares) dest.sumsqImpl;
+        } else {
+            SumOfSquares.copy(source.sumsq, dest.sumsq);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/SynchronizedDescriptiveStatistics.java b/src/main/java/org/apache/commons/math3/stat/descriptive/SynchronizedDescriptiveStatistics.java
new file mode 100644
index 0000000..270e4aa
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/SynchronizedDescriptiveStatistics.java
@@ -0,0 +1,192 @@
+/*
+ * 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.math3.stat.descriptive;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * Implementation of
+ * {@link org.apache.commons.math3.stat.descriptive.DescriptiveStatistics} that
+ * is safe to use in a multithreaded environment.  Multiple threads can safely
+ * operate on a single instance without causing runtime exceptions due to race
+ * conditions.  In effect, this implementation makes modification and access
+ * methods atomic operations for a single instance.  That is to say, as one
+ * thread is computing a statistic from the instance, no other thread can modify
+ * the instance nor compute another statistic.
+ *
+ * @since 1.2
+ */
+public class SynchronizedDescriptiveStatistics extends DescriptiveStatistics {
+
+    /** Serialization UID */
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Construct an instance with infinite window
+     */
+    public SynchronizedDescriptiveStatistics() {
+        // no try-catch or advertized IAE because arg is valid
+        this(INFINITE_WINDOW);
+    }
+
+    /**
+     * Construct an instance with finite window
+     * @param window the finite window size.
+     * @throws MathIllegalArgumentException if window size is less than 1 but
+     * not equal to {@link #INFINITE_WINDOW}
+     */
+    public SynchronizedDescriptiveStatistics(int window) throws MathIllegalArgumentException {
+        super(window);
+    }
+
+    /**
+     * A copy constructor. Creates a deep-copy of the {@code original}.
+     *
+     * @param original the {@code SynchronizedDescriptiveStatistics} instance to copy
+     * @throws NullArgumentException if original is null
+     */
+    public SynchronizedDescriptiveStatistics(SynchronizedDescriptiveStatistics original)
+    throws NullArgumentException {
+        copy(original, this);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void addValue(double v) {
+        super.addValue(v);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized double apply(UnivariateStatistic stat) {
+        return super.apply(stat);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void clear() {
+        super.clear();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized double getElement(int index) {
+        return super.getElement(index);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized long getN() {
+        return super.getN();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized double getStandardDeviation() {
+        return super.getStandardDeviation();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized double getQuadraticMean() {
+        return super.getQuadraticMean();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized double[] getValues() {
+        return super.getValues();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized int getWindowSize() {
+        return super.getWindowSize();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void setWindowSize(int windowSize) throws MathIllegalArgumentException {
+        super.setWindowSize(windowSize);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized String toString() {
+        return super.toString();
+    }
+
+    /**
+     * Returns a copy of this SynchronizedDescriptiveStatistics instance with the
+     * same internal state.
+     *
+     * @return a copy of this
+     */
+    @Override
+    public synchronized SynchronizedDescriptiveStatistics copy() {
+        SynchronizedDescriptiveStatistics result =
+            new SynchronizedDescriptiveStatistics();
+        // No try-catch or advertised exception because arguments are guaranteed non-null
+        copy(this, result);
+        return result;
+    }
+
+    /**
+     * Copies source to dest.
+     * <p>Neither source nor dest can be null.</p>
+     * <p>Acquires synchronization lock on source, then dest before copying.</p>
+     *
+     * @param source SynchronizedDescriptiveStatistics to copy
+     * @param dest SynchronizedDescriptiveStatistics to copy to
+     * @throws NullArgumentException if either source or dest is null
+     */
+    public static void copy(SynchronizedDescriptiveStatistics source,
+                            SynchronizedDescriptiveStatistics dest)
+        throws NullArgumentException {
+        MathUtils.checkNotNull(source);
+        MathUtils.checkNotNull(dest);
+        synchronized (source) {
+            synchronized (dest) {
+                DescriptiveStatistics.copy(source, dest);
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/SynchronizedMultivariateSummaryStatistics.java b/src/main/java/org/apache/commons/math3/stat/descriptive/SynchronizedMultivariateSummaryStatistics.java
new file mode 100644
index 0000000..889eb3a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/SynchronizedMultivariateSummaryStatistics.java
@@ -0,0 +1,297 @@
+/*
+ * 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.math3.stat.descriptive;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.linear.RealMatrix;
+
+/**
+ * Implementation of
+ * {@link org.apache.commons.math3.stat.descriptive.MultivariateSummaryStatistics} that
+ * is safe to use in a multithreaded environment.  Multiple threads can safely
+ * operate on a single instance without causing runtime exceptions due to race
+ * conditions.  In effect, this implementation makes modification and access
+ * methods atomic operations for a single instance.  That is to say, as one
+ * thread is computing a statistic from the instance, no other thread can modify
+ * the instance nor compute another statistic.
+ * @since 1.2
+ */
+public class SynchronizedMultivariateSummaryStatistics
+    extends MultivariateSummaryStatistics {
+
+    /** Serialization UID */
+    private static final long serialVersionUID = 7099834153347155363L;
+
+    /**
+     * Construct a SynchronizedMultivariateSummaryStatistics instance
+     * @param k dimension of the data
+     * @param isCovarianceBiasCorrected if true, the unbiased sample
+     * covariance is computed, otherwise the biased population covariance
+     * is computed
+     */
+    public SynchronizedMultivariateSummaryStatistics(int k, boolean isCovarianceBiasCorrected) {
+        super(k, isCovarianceBiasCorrected);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void addValue(double[] value) throws DimensionMismatchException {
+      super.addValue(value);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized int getDimension() {
+        return super.getDimension();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized long getN() {
+        return super.getN();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized double[] getSum() {
+        return super.getSum();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized double[] getSumSq() {
+        return super.getSumSq();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized double[] getSumLog() {
+        return super.getSumLog();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized double[] getMean() {
+        return super.getMean();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized double[] getStandardDeviation() {
+        return super.getStandardDeviation();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized RealMatrix getCovariance() {
+        return super.getCovariance();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized double[] getMax() {
+        return super.getMax();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized double[] getMin() {
+        return super.getMin();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized double[] getGeometricMean() {
+        return super.getGeometricMean();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized String toString() {
+        return super.toString();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void clear() {
+        super.clear();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized boolean equals(Object object) {
+        return super.equals(object);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized int hashCode() {
+        return super.hashCode();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized StorelessUnivariateStatistic[] getSumImpl() {
+        return super.getSumImpl();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void setSumImpl(StorelessUnivariateStatistic[] sumImpl)
+    throws DimensionMismatchException, MathIllegalStateException {
+        super.setSumImpl(sumImpl);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized StorelessUnivariateStatistic[] getSumsqImpl() {
+        return super.getSumsqImpl();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void setSumsqImpl(StorelessUnivariateStatistic[] sumsqImpl)
+    throws DimensionMismatchException, MathIllegalStateException {
+        super.setSumsqImpl(sumsqImpl);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized StorelessUnivariateStatistic[] getMinImpl() {
+        return super.getMinImpl();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void setMinImpl(StorelessUnivariateStatistic[] minImpl)
+    throws DimensionMismatchException, MathIllegalStateException {
+        super.setMinImpl(minImpl);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized StorelessUnivariateStatistic[] getMaxImpl() {
+        return super.getMaxImpl();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void setMaxImpl(StorelessUnivariateStatistic[] maxImpl)
+    throws DimensionMismatchException, MathIllegalStateException{
+        super.setMaxImpl(maxImpl);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized StorelessUnivariateStatistic[] getSumLogImpl() {
+        return super.getSumLogImpl();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void setSumLogImpl(StorelessUnivariateStatistic[] sumLogImpl)
+    throws DimensionMismatchException, MathIllegalStateException {
+        super.setSumLogImpl(sumLogImpl);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized StorelessUnivariateStatistic[] getGeoMeanImpl() {
+        return super.getGeoMeanImpl();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void setGeoMeanImpl(StorelessUnivariateStatistic[] geoMeanImpl)
+    throws DimensionMismatchException, MathIllegalStateException {
+        super.setGeoMeanImpl(geoMeanImpl);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized StorelessUnivariateStatistic[] getMeanImpl() {
+        return super.getMeanImpl();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void setMeanImpl(StorelessUnivariateStatistic[] meanImpl)
+    throws DimensionMismatchException, MathIllegalStateException {
+        super.setMeanImpl(meanImpl);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/SynchronizedSummaryStatistics.java b/src/main/java/org/apache/commons/math3/stat/descriptive/SynchronizedSummaryStatistics.java
new file mode 100644
index 0000000..7eaf9ac
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/SynchronizedSummaryStatistics.java
@@ -0,0 +1,366 @@
+/*
+ * 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.math3.stat.descriptive;
+
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * Implementation of
+ * {@link org.apache.commons.math3.stat.descriptive.SummaryStatistics} that
+ * is safe to use in a multithreaded environment.  Multiple threads can safely
+ * operate on a single instance without causing runtime exceptions due to race
+ * conditions.  In effect, this implementation makes modification and access
+ * methods atomic operations for a single instance.  That is to say, as one
+ * thread is computing a statistic from the instance, no other thread can modify
+ * the instance nor compute another statistic.
+ *
+ * @since 1.2
+ */
+public class SynchronizedSummaryStatistics extends SummaryStatistics {
+
+    /** Serialization UID */
+    private static final long serialVersionUID = 1909861009042253704L;
+
+    /**
+     * Construct a SynchronizedSummaryStatistics instance
+     */
+    public SynchronizedSummaryStatistics() {
+        super();
+    }
+
+    /**
+     * A copy constructor. Creates a deep-copy of the {@code original}.
+     *
+     * @param original the {@code SynchronizedSummaryStatistics} instance to copy
+     * @throws NullArgumentException if original is null
+     */
+    public SynchronizedSummaryStatistics(SynchronizedSummaryStatistics original)
+    throws NullArgumentException {
+        copy(original, this);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized StatisticalSummary getSummary() {
+        return super.getSummary();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void addValue(double value) {
+        super.addValue(value);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized long getN() {
+        return super.getN();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized double getSum() {
+        return super.getSum();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized double getSumsq() {
+        return super.getSumsq();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized double getMean() {
+        return super.getMean();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized double getStandardDeviation() {
+        return super.getStandardDeviation();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized double getQuadraticMean() {
+        return super.getQuadraticMean();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized double getVariance() {
+        return super.getVariance();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized double getPopulationVariance() {
+        return super.getPopulationVariance();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized double getMax() {
+        return super.getMax();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized double getMin() {
+        return super.getMin();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized double getGeometricMean() {
+        return super.getGeometricMean();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized String toString() {
+        return super.toString();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void clear() {
+        super.clear();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized boolean equals(Object object) {
+        return super.equals(object);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized int hashCode() {
+        return super.hashCode();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized StorelessUnivariateStatistic getSumImpl() {
+        return super.getSumImpl();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void setSumImpl(StorelessUnivariateStatistic sumImpl)
+    throws MathIllegalStateException {
+        super.setSumImpl(sumImpl);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized StorelessUnivariateStatistic getSumsqImpl() {
+        return super.getSumsqImpl();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void setSumsqImpl(StorelessUnivariateStatistic sumsqImpl)
+    throws MathIllegalStateException {
+        super.setSumsqImpl(sumsqImpl);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized StorelessUnivariateStatistic getMinImpl() {
+        return super.getMinImpl();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void setMinImpl(StorelessUnivariateStatistic minImpl)
+    throws MathIllegalStateException {
+        super.setMinImpl(minImpl);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized StorelessUnivariateStatistic getMaxImpl() {
+        return super.getMaxImpl();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void setMaxImpl(StorelessUnivariateStatistic maxImpl)
+    throws MathIllegalStateException {
+        super.setMaxImpl(maxImpl);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized StorelessUnivariateStatistic getSumLogImpl() {
+        return super.getSumLogImpl();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void setSumLogImpl(StorelessUnivariateStatistic sumLogImpl)
+    throws MathIllegalStateException {
+        super.setSumLogImpl(sumLogImpl);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized StorelessUnivariateStatistic getGeoMeanImpl() {
+        return super.getGeoMeanImpl();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void setGeoMeanImpl(StorelessUnivariateStatistic geoMeanImpl)
+    throws MathIllegalStateException {
+        super.setGeoMeanImpl(geoMeanImpl);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized StorelessUnivariateStatistic getMeanImpl() {
+        return super.getMeanImpl();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void setMeanImpl(StorelessUnivariateStatistic meanImpl)
+    throws MathIllegalStateException {
+        super.setMeanImpl(meanImpl);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized StorelessUnivariateStatistic getVarianceImpl() {
+        return super.getVarianceImpl();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void setVarianceImpl(StorelessUnivariateStatistic varianceImpl)
+    throws MathIllegalStateException {
+        super.setVarianceImpl(varianceImpl);
+    }
+
+    /**
+     * Returns a copy of this SynchronizedSummaryStatistics instance with the
+     * same internal state.
+     *
+     * @return a copy of this
+     */
+    @Override
+    public synchronized SynchronizedSummaryStatistics copy() {
+        SynchronizedSummaryStatistics result =
+            new SynchronizedSummaryStatistics();
+        // No try-catch or advertised exception because arguments are guaranteed non-null
+        copy(this, result);
+        return result;
+    }
+
+    /**
+     * Copies source to dest.
+     * <p>Neither source nor dest can be null.</p>
+     * <p>Acquires synchronization lock on source, then dest before copying.</p>
+     *
+     * @param source SynchronizedSummaryStatistics to copy
+     * @param dest SynchronizedSummaryStatistics to copy to
+     * @throws NullArgumentException if either source or dest is null
+     */
+    public static void copy(SynchronizedSummaryStatistics source,
+                            SynchronizedSummaryStatistics dest)
+        throws NullArgumentException {
+        MathUtils.checkNotNull(source);
+        MathUtils.checkNotNull(dest);
+        synchronized (source) {
+            synchronized (dest) {
+                SummaryStatistics.copy(source, dest);
+            }
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/UnivariateStatistic.java b/src/main/java/org/apache/commons/math3/stat/descriptive/UnivariateStatistic.java
new file mode 100644
index 0000000..5d6c9fe
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/UnivariateStatistic.java
@@ -0,0 +1,55 @@
+/*
+ * 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.math3.stat.descriptive;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.util.MathArrays;
+
+
+/**
+ * Base interface implemented by all statistics.
+ *
+ */
+public interface UnivariateStatistic extends MathArrays.Function {
+    /**
+     * Returns the result of evaluating the statistic over the input array.
+     *
+     * @param values input array
+     * @return the value of the statistic applied to the input array
+     * @throws MathIllegalArgumentException  if values is null
+     */
+    double evaluate(double[] values) throws MathIllegalArgumentException;
+
+    /**
+     * Returns the result of evaluating the statistic over the specified entries
+     * in the input array.
+     *
+     * @param values the input array
+     * @param begin the index of the first element to include
+     * @param length the number of elements to include
+     * @return the value of the statistic applied to the included array entries
+     * @throws MathIllegalArgumentException if values is null or the indices are invalid
+     */
+    double evaluate(double[] values, int begin, int length) throws MathIllegalArgumentException;
+
+    /**
+     * Returns a copy of the statistic with the same internal state.
+     *
+     * @return a copy of the statistic
+     */
+    UnivariateStatistic copy();
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/WeightedEvaluation.java b/src/main/java/org/apache/commons/math3/stat/descriptive/WeightedEvaluation.java
new file mode 100644
index 0000000..01693dc
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/WeightedEvaluation.java
@@ -0,0 +1,57 @@
+/*
+ * 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.math3.stat.descriptive;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+
+/**
+ * Weighted evaluation for statistics.
+ *
+ * @since 2.1
+ */
+public interface WeightedEvaluation {
+
+    /**
+     * Returns the result of evaluating the statistic over the input array,
+     * using the supplied weights.
+     *
+     * @param values input array
+     * @param weights array of weights
+     * @return the value of the weighted statistic applied to the input array
+     * @throws MathIllegalArgumentException if either array is null, lengths
+     * do not match, weights contain NaN, negative or infinite values, or
+     * weights does not include at least on positive value
+     */
+    double evaluate(double[] values, double[] weights) throws MathIllegalArgumentException;
+
+    /**
+     * Returns the result of evaluating the statistic over the specified entries
+     * in the input array, using corresponding entries in the supplied weights array.
+     *
+     * @param values the input array
+     * @param weights array of weights
+     * @param begin the index of the first element to include
+     * @param length the number of elements to include
+     * @return the value of the weighted statistic applied to the included array entries
+     * @throws MathIllegalArgumentException if either array is null, lengths
+     * do not match, indices are invalid, weights contain NaN, negative or
+     * infinite values, or weights does not include at least on positive value
+     */
+    double evaluate(double[] values, double[] weights, int begin, int length)
+    throws MathIllegalArgumentException;
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/moment/FirstMoment.java b/src/main/java/org/apache/commons/math3/stat/descriptive/moment/FirstMoment.java
new file mode 100644
index 0000000..c153724
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/moment/FirstMoment.java
@@ -0,0 +1,169 @@
+/*
+ * 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.math3.stat.descriptive.moment;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.stat.descriptive.AbstractStorelessUnivariateStatistic;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * Computes the first moment (arithmetic mean).  Uses the definitional formula:
+ * <p>
+ * mean = sum(x_i) / n </p>
+ * <p>
+ * where <code>n</code> is the number of observations. </p>
+ * <p>
+ * To limit numeric errors, the value of the statistic is computed using the
+ * following recursive updating algorithm: </p>
+ * <p>
+ * <ol>
+ * <li>Initialize <code>m = </code> the first value</li>
+ * <li>For each additional value, update using <br>
+ *   <code>m = m + (new value - m) / (number of observations)</code></li>
+ * </ol></p>
+ * <p>
+ *  Returns <code>Double.NaN</code> if the dataset is empty. Note that
+ *  Double.NaN may also be returned if the input includes NaN and / or infinite
+ *  values.</p>
+ * <p>
+ * <strong>Note that this implementation is not synchronized.</strong> If
+ * multiple threads access an instance of this class concurrently, and at least
+ * one of the threads invokes the <code>increment()</code> or
+ * <code>clear()</code> method, it must be synchronized externally.</p>
+ *
+ */
+class FirstMoment extends AbstractStorelessUnivariateStatistic
+    implements Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 6112755307178490473L;
+
+
+    /** Count of values that have been added */
+    protected long n;
+
+    /** First moment of values that have been added */
+    protected double m1;
+
+    /**
+     * Deviation of most recently added value from previous first moment.
+     * Retained to prevent repeated computation in higher order moments.
+     */
+    protected double dev;
+
+    /**
+     * Deviation of most recently added value from previous first moment,
+     * normalized by previous sample size.  Retained to prevent repeated
+     * computation in higher order moments
+     */
+    protected double nDev;
+
+    /**
+     * Create a FirstMoment instance
+     */
+    FirstMoment() {
+        n = 0;
+        m1 = Double.NaN;
+        dev = Double.NaN;
+        nDev = Double.NaN;
+    }
+
+    /**
+     * Copy constructor, creates a new {@code FirstMoment} identical
+     * to the {@code original}
+     *
+     * @param original the {@code FirstMoment} instance to copy
+     * @throws NullArgumentException if original is null
+     */
+     FirstMoment(FirstMoment original) throws NullArgumentException {
+         super();
+         copy(original, this);
+     }
+
+    /**
+     * {@inheritDoc}
+     */
+     @Override
+    public void increment(final double d) {
+        if (n == 0) {
+            m1 = 0.0;
+        }
+        n++;
+        double n0 = n;
+        dev = d - m1;
+        nDev = dev / n0;
+        m1 += nDev;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void clear() {
+        m1 = Double.NaN;
+        n = 0;
+        dev = Double.NaN;
+        nDev = Double.NaN;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double getResult() {
+        return m1;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getN() {
+        return n;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FirstMoment copy() {
+        FirstMoment result = new FirstMoment();
+        // No try-catch or advertised exception because args are guaranteed non-null
+        copy(this, result);
+        return result;
+    }
+
+    /**
+     * Copies source to dest.
+     * <p>Neither source nor dest can be null.</p>
+     *
+     * @param source FirstMoment to copy
+     * @param dest FirstMoment to copy to
+     * @throws NullArgumentException if either source or dest is null
+     */
+    public static void copy(FirstMoment source, FirstMoment dest)
+        throws NullArgumentException {
+        MathUtils.checkNotNull(source);
+        MathUtils.checkNotNull(dest);
+        dest.setData(source.getDataRef());
+        dest.n = source.n;
+        dest.m1 = source.m1;
+        dest.dev = source.dev;
+        dest.nDev = source.nDev;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/moment/FourthMoment.java b/src/main/java/org/apache/commons/math3/stat/descriptive/moment/FourthMoment.java
new file mode 100644
index 0000000..0c199d8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/moment/FourthMoment.java
@@ -0,0 +1,151 @@
+/*
+ * 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.math3.stat.descriptive.moment;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * Computes a statistic related to the Fourth Central Moment.  Specifically,
+ * what is computed is the sum of
+ * <p>
+ * (x_i - xbar) ^ 4, </p>
+ * <p>
+ * where the x_i are the
+ * sample observations and xbar is the sample mean. </p>
+ * <p>
+ * The following recursive updating formula is used: </p>
+ * <p>
+ * Let <ul>
+ * <li> dev = (current obs - previous mean) </li>
+ * <li> m2 = previous value of {@link SecondMoment} </li>
+ * <li> m2 = previous value of {@link ThirdMoment} </li>
+ * <li> n = number of observations (including current obs) </li>
+ * </ul>
+ * Then </p>
+ * <p>
+ * new value = old value - 4 * (dev/n) * m3 + 6 * (dev/n)^2 * m2 + <br>
+ * [n^2 - 3 * (n-1)] * dev^4 * (n-1) / n^3 </p>
+ * <p>
+ * Returns <code>Double.NaN</code> if no data values have been added and
+ * returns <code>0</code> if there is just one value in the data set. Note that
+ * Double.NaN may also be returned if the input includes NaN and / or infinite
+ * values. </p>
+ * <p>
+ * <strong>Note that this implementation is not synchronized.</strong> If
+ * multiple threads access an instance of this class concurrently, and at least
+ * one of the threads invokes the <code>increment()</code> or
+ * <code>clear()</code> method, it must be synchronized externally. </p>
+ *
+ */
+class FourthMoment extends ThirdMoment implements Serializable{
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 4763990447117157611L;
+
+    /** fourth moment of values that have been added */
+    private double m4;
+
+    /**
+     * Create a FourthMoment instance
+     */
+    FourthMoment() {
+        super();
+        m4 = Double.NaN;
+    }
+
+    /**
+     * Copy constructor, creates a new {@code FourthMoment} identical
+     * to the {@code original}
+     *
+     * @param original the {@code FourthMoment} instance to copy
+     * @throws NullArgumentException if original is null
+     */
+     FourthMoment(FourthMoment original) throws NullArgumentException {
+         super();
+         copy(original, this);
+     }
+
+    /**
+     * {@inheritDoc}
+     */
+     @Override
+    public void increment(final double d) {
+        if (n < 1) {
+            m4 = 0.0;
+            m3 = 0.0;
+            m2 = 0.0;
+            m1 = 0.0;
+        }
+
+        double prevM3 = m3;
+        double prevM2 = m2;
+
+        super.increment(d);
+
+        double n0 = n;
+
+        m4 = m4 - 4.0 * nDev * prevM3 + 6.0 * nDevSq * prevM2 +
+            ((n0 * n0) - 3 * (n0 -1)) * (nDevSq * nDevSq * (n0 - 1) * n0);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double getResult() {
+        return m4;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void clear() {
+        super.clear();
+        m4 = Double.NaN;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FourthMoment copy() {
+        FourthMoment result = new FourthMoment();
+        // No try-catch or advertised exception because args are guaranteed non-null
+        copy(this, result);
+        return result;
+    }
+
+    /**
+     * Copies source to dest.
+     * <p>Neither source nor dest can be null.</p>
+     *
+     * @param source FourthMoment to copy
+     * @param dest FourthMoment to copy to
+     * @throws NullArgumentException if either source or dest is null
+     */
+    public static void copy(FourthMoment source, FourthMoment dest)
+        throws NullArgumentException {
+        MathUtils.checkNotNull(source);
+        MathUtils.checkNotNull(dest);
+        ThirdMoment.copy(source, dest);
+        dest.m4 = source.m4;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/moment/GeometricMean.java b/src/main/java/org/apache/commons/math3/stat/descriptive/moment/GeometricMean.java
new file mode 100644
index 0000000..bfee9df
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/moment/GeometricMean.java
@@ -0,0 +1,214 @@
+/*
+ * 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.math3.stat.descriptive.moment;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.stat.descriptive.AbstractStorelessUnivariateStatistic;
+import org.apache.commons.math3.stat.descriptive.StorelessUnivariateStatistic;
+import org.apache.commons.math3.stat.descriptive.summary.SumOfLogs;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * Returns the <a href="http://www.xycoon.com/geometric_mean.htm">
+ * geometric mean </a> of the available values.
+ * <p>
+ * Uses a {@link SumOfLogs} instance to compute sum of logs and returns
+ * <code> exp( 1/n  (sum of logs) ).</code>  Therefore, </p>
+ * <ul>
+ * <li>If any of values are < 0, the result is <code>NaN.</code></li>
+ * <li>If all values are non-negative and less than
+ * <code>Double.POSITIVE_INFINITY</code>,  but at least one value is 0, the
+ * result is <code>0.</code></li>
+ * <li>If both <code>Double.POSITIVE_INFINITY</code> and
+ * <code>Double.NEGATIVE_INFINITY</code> are among the values, the result is
+ * <code>NaN.</code></li>
+ * </ul> </p>
+ * <p>
+ * <strong>Note that this implementation is not synchronized.</strong> If
+ * multiple threads access an instance of this class concurrently, and at least
+ * one of the threads invokes the <code>increment()</code> or
+ * <code>clear()</code> method, it must be synchronized externally.</p>
+ *
+ *
+ */
+public class GeometricMean extends AbstractStorelessUnivariateStatistic implements Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -8178734905303459453L;
+
+    /** Wrapped SumOfLogs instance */
+    private StorelessUnivariateStatistic sumOfLogs;
+
+    /**
+     * Create a GeometricMean instance
+     */
+    public GeometricMean() {
+        sumOfLogs = new SumOfLogs();
+    }
+
+    /**
+     * Copy constructor, creates a new {@code GeometricMean} identical
+     * to the {@code original}
+     *
+     * @param original the {@code GeometricMean} instance to copy
+     * @throws NullArgumentException if original is null
+     */
+    public GeometricMean(GeometricMean original) throws NullArgumentException {
+        super();
+        copy(original, this);
+    }
+
+    /**
+     * Create a GeometricMean instance using the given SumOfLogs instance
+     * @param sumOfLogs sum of logs instance to use for computation
+     */
+    public GeometricMean(SumOfLogs sumOfLogs) {
+        this.sumOfLogs = sumOfLogs;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public GeometricMean copy() {
+        GeometricMean result = new GeometricMean();
+        // no try-catch or advertised exception because args guaranteed non-null
+        copy(this, result);
+        return result;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void increment(final double d) {
+        sumOfLogs.increment(d);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double getResult() {
+        if (sumOfLogs.getN() > 0) {
+            return FastMath.exp(sumOfLogs.getResult() / sumOfLogs.getN());
+        } else {
+            return Double.NaN;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void clear() {
+        sumOfLogs.clear();
+    }
+
+    /**
+     * Returns the geometric mean of the entries in the specified portion
+     * of the input array.
+     * <p>
+     * See {@link GeometricMean} for details on the computing algorithm.</p>
+     * <p>
+     * Throws <code>IllegalArgumentException</code> if the array is null.</p>
+     *
+     * @param values input array containing the values
+     * @param begin first array element to include
+     * @param length the number of elements to include
+     * @return the geometric mean or Double.NaN if length = 0 or
+     * any of the values are &lt;= 0.
+     * @throws MathIllegalArgumentException if the input array is null or the array
+     * index parameters are not valid
+     */
+    @Override
+    public double evaluate(
+        final double[] values, final int begin, final int length)
+    throws MathIllegalArgumentException {
+        return FastMath.exp(
+            sumOfLogs.evaluate(values, begin, length) / length);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getN() {
+        return sumOfLogs.getN();
+    }
+
+    /**
+     * <p>Sets the implementation for the sum of logs.</p>
+     * <p>This method must be activated before any data has been added - i.e.,
+     * before {@link #increment(double) increment} has been used to add data;
+     * otherwise an IllegalStateException will be thrown.</p>
+     *
+     * @param sumLogImpl the StorelessUnivariateStatistic instance to use
+     * for computing the log sum
+     * @throws MathIllegalStateException if data has already been added
+     *  (i.e if n > 0)
+     */
+    public void setSumLogImpl(StorelessUnivariateStatistic sumLogImpl)
+    throws MathIllegalStateException {
+        checkEmpty();
+        this.sumOfLogs = sumLogImpl;
+    }
+
+    /**
+     * Returns the currently configured sum of logs implementation
+     *
+     * @return the StorelessUnivariateStatistic implementing the log sum
+     */
+    public StorelessUnivariateStatistic getSumLogImpl() {
+        return sumOfLogs;
+    }
+
+    /**
+     * Copies source to dest.
+     * <p>Neither source nor dest can be null.</p>
+     *
+     * @param source GeometricMean to copy
+     * @param dest GeometricMean to copy to
+     * @throws NullArgumentException if either source or dest is null
+     */
+    public static void copy(GeometricMean source, GeometricMean dest)
+        throws NullArgumentException {
+        MathUtils.checkNotNull(source);
+        MathUtils.checkNotNull(dest);
+        dest.setData(source.getDataRef());
+        dest.sumOfLogs = source.sumOfLogs.copy();
+    }
+
+
+    /**
+     * Throws MathIllegalStateException if n > 0.
+     * @throws MathIllegalStateException if data has been added to this statistic
+     */
+    private void checkEmpty() throws MathIllegalStateException {
+        if (getN() > 0) {
+            throw new MathIllegalStateException(
+                    LocalizedFormats.VALUES_ADDED_BEFORE_CONFIGURING_STATISTIC,
+                    getN());
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/moment/Kurtosis.java b/src/main/java/org/apache/commons/math3/stat/descriptive/moment/Kurtosis.java
new file mode 100644
index 0000000..be04fbe
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/moment/Kurtosis.java
@@ -0,0 +1,226 @@
+/*
+ * 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.math3.stat.descriptive.moment;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.stat.descriptive.AbstractStorelessUnivariateStatistic;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+
+
+/**
+ * Computes the Kurtosis of the available values.
+ * <p>
+ * We use the following (unbiased) formula to define kurtosis:</p>
+ *  <p>
+ *  kurtosis = { [n(n+1) / (n -1)(n - 2)(n-3)] sum[(x_i - mean)^4] / std^4 } - [3(n-1)^2 / (n-2)(n-3)]
+ *  </p><p>
+ *  where n is the number of values, mean is the {@link Mean} and std is the
+ * {@link StandardDeviation}</p>
+ * <p>
+ *  Note that this statistic is undefined for n < 4.  <code>Double.Nan</code>
+ *  is returned when there is not sufficient data to compute the statistic.
+ *  Note that Double.NaN may also be returned if the input includes NaN
+ *  and / or infinite values.</p>
+ * <p>
+ * <strong>Note that this implementation is not synchronized.</strong> If
+ * multiple threads access an instance of this class concurrently, and at least
+ * one of the threads invokes the <code>increment()</code> or
+ * <code>clear()</code> method, it must be synchronized externally.</p>
+ *
+ */
+public class Kurtosis extends AbstractStorelessUnivariateStatistic  implements Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 2784465764798260919L;
+
+    /**Fourth Moment on which this statistic is based */
+    protected FourthMoment moment;
+
+    /**
+     * Determines whether or not this statistic can be incremented or cleared.
+     * <p>
+     * Statistics based on (constructed from) external moments cannot
+     * be incremented or cleared.</p>
+    */
+    protected boolean incMoment;
+
+    /**
+     * Construct a Kurtosis
+     */
+    public Kurtosis() {
+        incMoment = true;
+        moment = new FourthMoment();
+    }
+
+    /**
+     * Construct a Kurtosis from an external moment
+     *
+     * @param m4 external Moment
+     */
+    public Kurtosis(final FourthMoment m4) {
+        incMoment = false;
+        this.moment = m4;
+    }
+
+    /**
+     * Copy constructor, creates a new {@code Kurtosis} identical
+     * to the {@code original}
+     *
+     * @param original the {@code Kurtosis} instance to copy
+     * @throws NullArgumentException if original is null
+     */
+    public Kurtosis(Kurtosis original) throws NullArgumentException {
+        copy(original, this);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>Note that when {@link #Kurtosis(FourthMoment)} is used to
+     * create a Variance, this method does nothing. In that case, the
+     * FourthMoment should be incremented directly.</p>
+     */
+    @Override
+    public void increment(final double d) {
+        if (incMoment) {
+            moment.increment(d);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double getResult() {
+        double kurtosis = Double.NaN;
+        if (moment.getN() > 3) {
+            double variance = moment.m2 / (moment.n - 1);
+                if (moment.n <= 3 || variance < 10E-20) {
+                    kurtosis = 0.0;
+                } else {
+                    double n = moment.n;
+                    kurtosis =
+                        (n * (n + 1) * moment.getResult() -
+                                3 * moment.m2 * moment.m2 * (n - 1)) /
+                                ((n - 1) * (n -2) * (n -3) * variance * variance);
+                }
+        }
+        return kurtosis;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void clear() {
+        if (incMoment) {
+            moment.clear();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getN() {
+        return moment.getN();
+    }
+
+    /* UnvariateStatistic Approach  */
+
+    /**
+     * Returns the kurtosis of the entries in the specified portion of the
+     * input array.
+     * <p>
+     * See {@link Kurtosis} for details on the computing algorithm.</p>
+     * <p>
+     * Throws <code>IllegalArgumentException</code> if the array is null.</p>
+     *
+     * @param values the input array
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @return the kurtosis of the values or Double.NaN if length is less than 4
+     * @throws MathIllegalArgumentException if the input array is null or the array
+     * index parameters are not valid
+     */
+    @Override
+    public double evaluate(final double[] values,final int begin, final int length)
+    throws MathIllegalArgumentException {
+        // Initialize the kurtosis
+        double kurt = Double.NaN;
+
+        if (test(values, begin, length) && length > 3) {
+
+            // Compute the mean and standard deviation
+            Variance variance = new Variance();
+            variance.incrementAll(values, begin, length);
+            double mean = variance.moment.m1;
+            double stdDev = FastMath.sqrt(variance.getResult());
+
+            // Sum the ^4 of the distance from the mean divided by the
+            // standard deviation
+            double accum3 = 0.0;
+            for (int i = begin; i < begin + length; i++) {
+                accum3 += FastMath.pow(values[i] - mean, 4.0);
+            }
+            accum3 /= FastMath.pow(stdDev, 4.0d);
+
+            // Get N
+            double n0 = length;
+
+            double coefficientOne =
+                (n0 * (n0 + 1)) / ((n0 - 1) * (n0 - 2) * (n0 - 3));
+            double termTwo =
+                (3 * FastMath.pow(n0 - 1, 2.0)) / ((n0 - 2) * (n0 - 3));
+
+            // Calculate kurtosis
+            kurt = (coefficientOne * accum3) - termTwo;
+        }
+        return kurt;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Kurtosis copy() {
+        Kurtosis result = new Kurtosis();
+        // No try-catch because args are guaranteed non-null
+        copy(this, result);
+        return result;
+    }
+
+    /**
+     * Copies source to dest.
+     * <p>Neither source nor dest can be null.</p>
+     *
+     * @param source Kurtosis to copy
+     * @param dest Kurtosis to copy to
+     * @throws NullArgumentException if either source or dest is null
+     */
+    public static void copy(Kurtosis source, Kurtosis dest)
+        throws NullArgumentException {
+        MathUtils.checkNotNull(source);
+        MathUtils.checkNotNull(dest);
+        dest.setData(source.getDataRef());
+        dest.moment = source.moment.copy();
+        dest.incMoment = source.incMoment;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/moment/Mean.java b/src/main/java/org/apache/commons/math3/stat/descriptive/moment/Mean.java
new file mode 100644
index 0000000..aac3d78
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/moment/Mean.java
@@ -0,0 +1,286 @@
+/*
+ * 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.math3.stat.descriptive.moment;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.stat.descriptive.AbstractStorelessUnivariateStatistic;
+import org.apache.commons.math3.stat.descriptive.WeightedEvaluation;
+import org.apache.commons.math3.stat.descriptive.summary.Sum;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * <p>Computes the arithmetic mean of a set of values. Uses the definitional
+ * formula:</p>
+ * <p>
+ * mean = sum(x_i) / n
+ * </p>
+ * <p>where <code>n</code> is the number of observations.
+ * </p>
+ * <p>When {@link #increment(double)} is used to add data incrementally from a
+ * stream of (unstored) values, the value of the statistic that
+ * {@link #getResult()} returns is computed using the following recursive
+ * updating algorithm: </p>
+ * <ol>
+ * <li>Initialize <code>m = </code> the first value</li>
+ * <li>For each additional value, update using <br>
+ *   <code>m = m + (new value - m) / (number of observations)</code></li>
+ * </ol>
+ * <p> If {@link #evaluate(double[])} is used to compute the mean of an array
+ * of stored values, a two-pass, corrected algorithm is used, starting with
+ * the definitional formula computed using the array of stored values and then
+ * correcting this by adding the mean deviation of the data values from the
+ * arithmetic mean. See, e.g. "Comparison of Several Algorithms for Computing
+ * Sample Means and Variances," Robert F. Ling, Journal of the American
+ * Statistical Association, Vol. 69, No. 348 (Dec., 1974), pp. 859-866. </p>
+ * <p>
+ *  Returns <code>Double.NaN</code> if the dataset is empty. Note that
+ *  Double.NaN may also be returned if the input includes NaN and / or infinite
+ *  values.
+ * </p>
+ * <strong>Note that this implementation is not synchronized.</strong> If
+ * multiple threads access an instance of this class concurrently, and at least
+ * one of the threads invokes the <code>increment()</code> or
+ * <code>clear()</code> method, it must be synchronized externally.
+ *
+ */
+public class Mean extends AbstractStorelessUnivariateStatistic
+    implements Serializable, WeightedEvaluation {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -1296043746617791564L;
+
+    /** First moment on which this statistic is based. */
+    protected FirstMoment moment;
+
+    /**
+     * Determines whether or not this statistic can be incremented or cleared.
+     * <p>
+     * Statistics based on (constructed from) external moments cannot
+     * be incremented or cleared.</p>
+     */
+    protected boolean incMoment;
+
+    /** Constructs a Mean. */
+    public Mean() {
+        incMoment = true;
+        moment = new FirstMoment();
+    }
+
+    /**
+     * Constructs a Mean with an External Moment.
+     *
+     * @param m1 the moment
+     */
+    public Mean(final FirstMoment m1) {
+        this.moment = m1;
+        incMoment = false;
+    }
+
+    /**
+     * Copy constructor, creates a new {@code Mean} identical
+     * to the {@code original}
+     *
+     * @param original the {@code Mean} instance to copy
+     * @throws NullArgumentException if original is null
+     */
+    public Mean(Mean original) throws NullArgumentException {
+        copy(original, this);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>Note that when {@link #Mean(FirstMoment)} is used to
+     * create a Mean, this method does nothing. In that case, the
+     * FirstMoment should be incremented directly.</p>
+     */
+    @Override
+    public void increment(final double d) {
+        if (incMoment) {
+            moment.increment(d);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void clear() {
+        if (incMoment) {
+            moment.clear();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double getResult() {
+        return moment.m1;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getN() {
+        return moment.getN();
+    }
+
+    /**
+     * Returns the arithmetic mean of the entries in the specified portion of
+     * the input array, or <code>Double.NaN</code> if the designated subarray
+     * is empty.
+     * <p>
+     * Throws <code>IllegalArgumentException</code> if the array is null.</p>
+     * <p>
+     * See {@link Mean} for details on the computing algorithm.</p>
+     *
+     * @param values the input array
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @return the mean of the values or Double.NaN if length = 0
+     * @throws MathIllegalArgumentException if the array is null or the array index
+     *  parameters are not valid
+     */
+    @Override
+    public double evaluate(final double[] values,final int begin, final int length)
+    throws MathIllegalArgumentException {
+        if (test(values, begin, length)) {
+            Sum sum = new Sum();
+            double sampleSize = length;
+
+            // Compute initial estimate using definitional formula
+            double xbar = sum.evaluate(values, begin, length) / sampleSize;
+
+            // Compute correction factor in second pass
+            double correction = 0;
+            for (int i = begin; i < begin + length; i++) {
+                correction += values[i] - xbar;
+            }
+            return xbar + (correction/sampleSize);
+        }
+        return Double.NaN;
+    }
+
+    /**
+     * Returns the weighted arithmetic mean of the entries in the specified portion of
+     * the input array, or <code>Double.NaN</code> if the designated subarray
+     * is empty.
+     * <p>
+     * Throws <code>IllegalArgumentException</code> if either array is null.</p>
+     * <p>
+     * See {@link Mean} for details on the computing algorithm. The two-pass algorithm
+     * described above is used here, with weights applied in computing both the original
+     * estimate and the correction factor.</p>
+     * <p>
+     * Throws <code>IllegalArgumentException</code> if any of the following are true:
+     * <ul><li>the values array is null</li>
+     *     <li>the weights array is null</li>
+     *     <li>the weights array does not have the same length as the values array</li>
+     *     <li>the weights array contains one or more infinite values</li>
+     *     <li>the weights array contains one or more NaN values</li>
+     *     <li>the weights array contains negative values</li>
+     *     <li>the start and length arguments do not determine a valid array</li>
+     * </ul></p>
+     *
+     * @param values the input array
+     * @param weights the weights array
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @return the mean of the values or Double.NaN if length = 0
+     * @throws MathIllegalArgumentException if the parameters are not valid
+     * @since 2.1
+     */
+    public double evaluate(final double[] values, final double[] weights,
+                           final int begin, final int length) throws MathIllegalArgumentException {
+        if (test(values, weights, begin, length)) {
+            Sum sum = new Sum();
+
+            // Compute initial estimate using definitional formula
+            double sumw = sum.evaluate(weights,begin,length);
+            double xbarw = sum.evaluate(values, weights, begin, length) / sumw;
+
+            // Compute correction factor in second pass
+            double correction = 0;
+            for (int i = begin; i < begin + length; i++) {
+                correction += weights[i] * (values[i] - xbarw);
+            }
+            return xbarw + (correction/sumw);
+        }
+        return Double.NaN;
+    }
+
+    /**
+     * Returns the weighted arithmetic mean of the entries in the input array.
+     * <p>
+     * Throws <code>MathIllegalArgumentException</code> if either array is null.</p>
+     * <p>
+     * See {@link Mean} for details on the computing algorithm. The two-pass algorithm
+     * described above is used here, with weights applied in computing both the original
+     * estimate and the correction factor.</p>
+     * <p>
+     * Throws <code>MathIllegalArgumentException</code> if any of the following are true:
+     * <ul><li>the values array is null</li>
+     *     <li>the weights array is null</li>
+     *     <li>the weights array does not have the same length as the values array</li>
+     *     <li>the weights array contains one or more infinite values</li>
+     *     <li>the weights array contains one or more NaN values</li>
+     *     <li>the weights array contains negative values</li>
+     * </ul></p>
+     *
+     * @param values the input array
+     * @param weights the weights array
+     * @return the mean of the values or Double.NaN if length = 0
+     * @throws MathIllegalArgumentException if the parameters are not valid
+     * @since 2.1
+     */
+    public double evaluate(final double[] values, final double[] weights)
+    throws MathIllegalArgumentException {
+        return evaluate(values, weights, 0, values.length);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Mean copy() {
+        Mean result = new Mean();
+        // No try-catch or advertised exception because args are guaranteed non-null
+        copy(this, result);
+        return result;
+    }
+
+
+    /**
+     * Copies source to dest.
+     * <p>Neither source nor dest can be null.</p>
+     *
+     * @param source Mean to copy
+     * @param dest Mean to copy to
+     * @throws NullArgumentException if either source or dest is null
+     */
+    public static void copy(Mean source, Mean dest)
+        throws NullArgumentException {
+        MathUtils.checkNotNull(source);
+        MathUtils.checkNotNull(dest);
+        dest.setData(source.getDataRef());
+        dest.incMoment = source.incMoment;
+        dest.moment = source.moment.copy();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/moment/SecondMoment.java b/src/main/java/org/apache/commons/math3/stat/descriptive/moment/SecondMoment.java
new file mode 100644
index 0000000..12715c0
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/moment/SecondMoment.java
@@ -0,0 +1,134 @@
+/*
+ * 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.math3.stat.descriptive.moment;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * Computes a statistic related to the Second Central Moment.  Specifically,
+ * what is computed is the sum of squared deviations from the sample mean.
+ * <p>
+ * The following recursive updating formula is used:</p>
+ * <p>
+ * Let <ul>
+ * <li> dev = (current obs - previous mean) </li>
+ * <li> n = number of observations (including current obs) </li>
+ * </ul>
+ * Then</p>
+ * <p>
+ * new value = old value + dev^2 * (n -1) / n.</p>
+ * <p>
+ * Returns <code>Double.NaN</code> if no data values have been added and
+ * returns <code>0</code> if there is just one value in the data set.
+ * Note that Double.NaN may also be returned if the input includes NaN
+ * and / or infinite values.</p>
+ * <p>
+ * <strong>Note that this implementation is not synchronized.</strong> If
+ * multiple threads access an instance of this class concurrently, and at least
+ * one of the threads invokes the <code>increment()</code> or
+ * <code>clear()</code> method, it must be synchronized externally.</p>
+ *
+ */
+public class SecondMoment extends FirstMoment implements Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 3942403127395076445L;
+
+    /** second moment of values that have been added */
+    protected double m2;
+
+    /**
+     * Create a SecondMoment instance
+     */
+    public SecondMoment() {
+        super();
+        m2 = Double.NaN;
+    }
+
+    /**
+     * Copy constructor, creates a new {@code SecondMoment} identical
+     * to the {@code original}
+     *
+     * @param original the {@code SecondMoment} instance to copy
+     * @throws NullArgumentException if original is null
+     */
+    public SecondMoment(SecondMoment original)
+    throws NullArgumentException {
+        super(original);
+        this.m2 = original.m2;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void increment(final double d) {
+        if (n < 1) {
+            m1 = m2 = 0.0;
+        }
+        super.increment(d);
+        m2 += ((double) n - 1) * dev * nDev;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void clear() {
+        super.clear();
+        m2 = Double.NaN;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double getResult() {
+        return m2;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public SecondMoment copy() {
+        SecondMoment result = new SecondMoment();
+        // no try-catch or advertised NAE because args are guaranteed non-null
+        copy(this, result);
+        return result;
+    }
+
+    /**
+     * Copies source to dest.
+     * <p>Neither source nor dest can be null.</p>
+     *
+     * @param source SecondMoment to copy
+     * @param dest SecondMoment to copy to
+     * @throws NullArgumentException if either source or dest is null
+     */
+    public static void copy(SecondMoment source, SecondMoment dest)
+        throws NullArgumentException {
+        MathUtils.checkNotNull(source);
+        MathUtils.checkNotNull(dest);
+        FirstMoment.copy(source, dest);
+        dest.m2 = source.m2;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/moment/SemiVariance.java b/src/main/java/org/apache/commons/math3/stat/descriptive/moment/SemiVariance.java
new file mode 100644
index 0000000..563119a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/moment/SemiVariance.java
@@ -0,0 +1,369 @@
+/*
+ * 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.math3.stat.descriptive.moment;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.stat.descriptive.AbstractUnivariateStatistic;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * <p>Computes the semivariance of a set of values with respect to a given cutoff value.
+ * We define the <i>downside semivariance</i> of a set of values <code>x</code>
+ * against the <i>cutoff value</i> <code>cutoff</code> to be <br/>
+ * <code>&Sigma; (x[i] - target)<sup>2</sup> / df</code> <br/>
+ * where the sum is taken over all <code>i</code> such that <code>x[i] < cutoff</code>
+ * and <code>df</code> is the length of <code>x</code> (non-bias-corrected) or
+ * one less than this number (bias corrected).  The <i>upside semivariance</i>
+ * is defined similarly, with the sum taken over values of <code>x</code> that
+ * exceed the cutoff value.</p>
+ *
+ * <p>The cutoff value defaults to the mean, bias correction defaults to <code>true</code>
+ * and the "variance direction" (upside or downside) defaults to downside.  The variance direction
+ * and bias correction may be set using property setters or their values can provided as
+ * parameters to {@link #evaluate(double[], double, Direction, boolean, int, int)}.</p>
+ *
+ * <p>If the input array is null, <code>evaluate</code> methods throw
+ * <code>IllegalArgumentException.</code>  If the array has length 1, <code>0</code>
+ * is returned, regardless of the value of the <code>cutoff.</code>
+ *
+ * <p><strong>Note that this class is not intended to be threadsafe.</strong> If
+ * multiple threads access an instance of this class concurrently, and one or
+ * more of these threads invoke property setters, external synchronization must
+ * be provided to ensure correct results.</p>
+ *
+ * @since 2.1
+ */
+public class SemiVariance extends AbstractUnivariateStatistic implements Serializable {
+
+    /**
+     * The UPSIDE Direction is used to specify that the observations above the
+     * cutoff point will be used to calculate SemiVariance.
+     */
+    public static final Direction UPSIDE_VARIANCE = Direction.UPSIDE;
+
+    /**
+     * The DOWNSIDE Direction is used to specify that the observations below
+     * the cutoff point will be used to calculate SemiVariance
+     */
+    public static final Direction DOWNSIDE_VARIANCE = Direction.DOWNSIDE;
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -2653430366886024994L;
+
+    /**
+     * Determines whether or not bias correction is applied when computing the
+     * value of the statisic.  True means that bias is corrected.
+     */
+    private boolean biasCorrected = true;
+
+    /**
+     * Determines whether to calculate downside or upside SemiVariance.
+     */
+    private Direction varianceDirection = Direction.DOWNSIDE;
+
+    /**
+     * Constructs a SemiVariance with default (true) <code>biasCorrected</code>
+     * property and default (Downside) <code>varianceDirection</code> property.
+     */
+    public SemiVariance() {
+    }
+
+    /**
+     * Constructs a SemiVariance with the specified <code>biasCorrected</code>
+     * property and default (Downside) <code>varianceDirection</code> property.
+     *
+     * @param biasCorrected  setting for bias correction - true means
+     * bias will be corrected and is equivalent to using the argumentless
+     * constructor
+     */
+    public SemiVariance(final boolean biasCorrected) {
+        this.biasCorrected = biasCorrected;
+    }
+
+
+    /**
+     * Constructs a SemiVariance with the specified <code>Direction</code> property
+     * and default (true) <code>biasCorrected</code> property
+     *
+     * @param direction  setting for the direction of the SemiVariance
+     * to calculate
+     */
+    public SemiVariance(final Direction direction) {
+        this.varianceDirection = direction;
+    }
+
+
+    /**
+     * Constructs a SemiVariance with the specified <code>isBiasCorrected</code>
+     * property and the specified <code>Direction</code> property.
+     *
+     * @param corrected  setting for bias correction - true means
+     * bias will be corrected and is equivalent to using the argumentless
+     * constructor
+     *
+     * @param direction  setting for the direction of the SemiVariance
+     * to calculate
+     */
+    public SemiVariance(final boolean corrected, final Direction direction) {
+        this.biasCorrected = corrected;
+        this.varianceDirection = direction;
+    }
+
+
+    /**
+     * Copy constructor, creates a new {@code SemiVariance} identical
+     * to the {@code original}
+     *
+     * @param original the {@code SemiVariance} instance to copy
+     * @throws NullArgumentException  if original is null
+     */
+    public SemiVariance(final SemiVariance original) throws NullArgumentException {
+        copy(original, this);
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public SemiVariance copy() {
+        SemiVariance result = new SemiVariance();
+        // No try-catch or advertised exception because args are guaranteed non-null
+        copy(this, result);
+        return result;
+    }
+
+
+    /**
+     * Copies source to dest.
+     * <p>Neither source nor dest can be null.</p>
+     *
+     * @param source SemiVariance to copy
+     * @param dest SemiVariance to copy to
+     * @throws NullArgumentException if either source or dest is null
+     */
+    public static void copy(final SemiVariance source, SemiVariance dest)
+        throws NullArgumentException {
+        MathUtils.checkNotNull(source);
+        MathUtils.checkNotNull(dest);
+        dest.setData(source.getDataRef());
+        dest.biasCorrected = source.biasCorrected;
+        dest.varianceDirection = source.varianceDirection;
+    }
+
+    /**
+      * <p>Returns the {@link SemiVariance} of the designated values against the mean, using
+      * instance properties varianceDirection and biasCorrection.</p>
+      *
+      * <p>Returns <code>NaN</code> if the array is empty and throws
+      * <code>IllegalArgumentException</code> if the array is null.</p>
+      *
+      * @param values the input array
+      * @param start index of the first array element to include
+      * @param length the number of elements to include
+      * @return the SemiVariance
+      * @throws MathIllegalArgumentException if the parameters are not valid
+      *
+      */
+      @Override
+      public double evaluate(final double[] values, final int start, final int length)
+      throws MathIllegalArgumentException {
+        double m = (new Mean()).evaluate(values, start, length);
+        return evaluate(values, m, varianceDirection, biasCorrected, 0, values.length);
+      }
+
+
+      /**
+       * This method calculates {@link SemiVariance} for the entire array against the mean, using
+       * the current value of the biasCorrection instance property.
+       *
+       * @param values the input array
+       * @param direction the {@link Direction} of the semivariance
+       * @return the SemiVariance
+       * @throws MathIllegalArgumentException if values is null
+       *
+       */
+      public double evaluate(final double[] values, Direction direction)
+      throws MathIllegalArgumentException {
+          double m = (new Mean()).evaluate(values);
+          return evaluate (values, m, direction, biasCorrected, 0, values.length);
+      }
+
+      /**
+       * <p>Returns the {@link SemiVariance} of the designated values against the cutoff, using
+       * instance properties variancDirection and biasCorrection.</p>
+       *
+       * <p>Returns <code>NaN</code> if the array is empty and throws
+       * <code>MathIllegalArgumentException</code> if the array is null.</p>
+       *
+       * @param values the input array
+       * @param cutoff the reference point
+       * @return the SemiVariance
+       * @throws MathIllegalArgumentException if values is null
+       */
+      public double evaluate(final double[] values, final double cutoff)
+      throws MathIllegalArgumentException {
+          return evaluate(values, cutoff, varianceDirection, biasCorrected, 0, values.length);
+      }
+
+      /**
+       * <p>Returns the {@link SemiVariance} of the designated values against the cutoff in the
+       * given direction, using the current value of the biasCorrection instance property.</p>
+       *
+       * <p>Returns <code>NaN</code> if the array is empty and throws
+       * <code>MathIllegalArgumentException</code> if the array is null.</p>
+       *
+       * @param values the input array
+       * @param cutoff the reference point
+       * @param direction the {@link Direction} of the semivariance
+       * @return the SemiVariance
+       * @throws MathIllegalArgumentException if values is null
+       */
+      public double evaluate(final double[] values, final double cutoff, final Direction direction)
+      throws MathIllegalArgumentException {
+          return evaluate(values, cutoff, direction, biasCorrected, 0, values.length);
+      }
+
+
+     /**
+      * <p>Returns the {@link SemiVariance} of the designated values against the cutoff
+      * in the given direction with the provided bias correction.</p>
+      *
+      * <p>Returns <code>NaN</code> if the array is empty and throws
+      * <code>IllegalArgumentException</code> if the array is null.</p>
+      *
+      * @param values the input array
+      * @param cutoff the reference point
+      * @param direction the {@link Direction} of the semivariance
+      * @param corrected the BiasCorrection flag
+      * @param start index of the first array element to include
+      * @param length the number of elements to include
+      * @return the SemiVariance
+      * @throws MathIllegalArgumentException if the parameters are not valid
+      *
+      */
+    public double evaluate (final double[] values, final double cutoff, final Direction direction,
+            final boolean corrected, final int start, final int length) throws MathIllegalArgumentException {
+
+        test(values, start, length);
+        if (values.length == 0) {
+            return Double.NaN;
+        } else {
+            if (values.length == 1) {
+                return 0.0;
+            } else {
+                final boolean booleanDirection = direction.getDirection();
+
+                double dev = 0.0;
+                double sumsq = 0.0;
+                for (int i = start; i < length; i++) {
+                    if ((values[i] > cutoff) == booleanDirection) {
+                       dev = values[i] - cutoff;
+                       sumsq += dev * dev;
+                    }
+                }
+
+                if (corrected) {
+                    return sumsq / (length - 1.0);
+                } else {
+                    return sumsq / length;
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns true iff biasCorrected property is set to true.
+     *
+     * @return the value of biasCorrected.
+     */
+    public boolean isBiasCorrected() {
+        return biasCorrected;
+    }
+
+    /**
+     * Sets the biasCorrected property.
+     *
+     * @param biasCorrected new biasCorrected property value
+     */
+    public void setBiasCorrected(boolean biasCorrected) {
+        this.biasCorrected = biasCorrected;
+    }
+
+    /**
+     * Returns the varianceDirection property.
+     *
+     * @return the varianceDirection
+     */
+    public Direction getVarianceDirection () {
+        return varianceDirection;
+    }
+
+    /**
+     * Sets the variance direction
+     *
+     * @param varianceDirection the direction of the semivariance
+     */
+    public void setVarianceDirection(Direction varianceDirection) {
+        this.varianceDirection = varianceDirection;
+    }
+
+    /**
+     * The direction of the semivariance - either upside or downside. The direction
+     * is represented by boolean, with true corresponding to UPSIDE semivariance.
+     */
+    public enum Direction {
+        /**
+         * The UPSIDE Direction is used to specify that the observations above the
+         * cutoff point will be used to calculate SemiVariance
+         */
+        UPSIDE (true),
+
+        /**
+         * The DOWNSIDE Direction is used to specify that the observations below
+         * the cutoff point will be used to calculate SemiVariance
+         */
+        DOWNSIDE (false);
+
+        /**
+         *   boolean value  UPSIDE <-> true
+         */
+        private boolean direction;
+
+        /**
+         * Create a Direction with the given value.
+         *
+         * @param b boolean value representing the Direction. True corresponds to UPSIDE.
+         */
+        Direction (boolean b) {
+            direction = b;
+        }
+
+        /**
+         * Returns the value of this Direction. True corresponds to UPSIDE.
+         *
+         * @return true if direction is UPSIDE; false otherwise
+         */
+        boolean getDirection () {
+            return direction;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/moment/Skewness.java b/src/main/java/org/apache/commons/math3/stat/descriptive/moment/Skewness.java
new file mode 100644
index 0000000..b4703eb
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/moment/Skewness.java
@@ -0,0 +1,228 @@
+/*
+ * 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.math3.stat.descriptive.moment;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.stat.descriptive.AbstractStorelessUnivariateStatistic;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * Computes the skewness of the available values.
+ * <p>
+ * We use the following (unbiased) formula to define skewness:</p>
+ * <p>
+ * skewness = [n / (n -1) (n - 2)] sum[(x_i - mean)^3] / std^3 </p>
+ * <p>
+ * where n is the number of values, mean is the {@link Mean} and std is the
+ * {@link StandardDeviation} </p>
+ * <p>
+ * Note that this statistic is undefined for n < 3.  <code>Double.Nan</code>
+ * is returned when there is not sufficient data to compute the statistic.
+ * Double.NaN may also be returned if the input includes NaN and / or
+ * infinite values.</p>
+ * <p>
+ * <strong>Note that this implementation is not synchronized.</strong> If
+ * multiple threads access an instance of this class concurrently, and at least
+ * one of the threads invokes the <code>increment()</code> or
+ * <code>clear()</code> method, it must be synchronized externally. </p>
+ *
+ */
+public class Skewness extends AbstractStorelessUnivariateStatistic implements Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 7101857578996691352L;
+
+    /** Third moment on which this statistic is based */
+    protected ThirdMoment moment = null;
+
+     /**
+     * Determines whether or not this statistic can be incremented or cleared.
+     * <p>
+     * Statistics based on (constructed from) external moments cannot
+     * be incremented or cleared.</p>
+    */
+    protected boolean incMoment;
+
+    /**
+     * Constructs a Skewness
+     */
+    public Skewness() {
+        incMoment = true;
+        moment = new ThirdMoment();
+    }
+
+    /**
+     * Constructs a Skewness with an external moment
+     * @param m3 external moment
+     */
+    public Skewness(final ThirdMoment m3) {
+        incMoment = false;
+        this.moment = m3;
+    }
+
+    /**
+     * Copy constructor, creates a new {@code Skewness} identical
+     * to the {@code original}
+     *
+     * @param original the {@code Skewness} instance to copy
+     * @throws NullArgumentException if original is null
+     */
+    public Skewness(Skewness original) throws NullArgumentException {
+        copy(original, this);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>Note that when {@link #Skewness(ThirdMoment)} is used to
+     * create a Skewness, this method does nothing. In that case, the
+     * ThirdMoment should be incremented directly.</p>
+     */
+    @Override
+    public void increment(final double d) {
+        if (incMoment) {
+            moment.increment(d);
+        }
+    }
+
+    /**
+     * Returns the value of the statistic based on the values that have been added.
+     * <p>
+     * See {@link Skewness} for the definition used in the computation.</p>
+     *
+     * @return the skewness of the available values.
+     */
+    @Override
+    public double getResult() {
+
+        if (moment.n < 3) {
+            return Double.NaN;
+        }
+        double variance = moment.m2 / (moment.n - 1);
+        if (variance < 10E-20) {
+            return 0.0d;
+        } else {
+            double n0 = moment.getN();
+            return  (n0 * moment.m3) /
+            ((n0 - 1) * (n0 -2) * FastMath.sqrt(variance) * variance);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getN() {
+        return moment.getN();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void clear() {
+        if (incMoment) {
+            moment.clear();
+        }
+    }
+
+    /**
+     * Returns the Skewness of the entries in the specifed portion of the
+     * input array.
+     * <p>
+     * See {@link Skewness} for the definition used in the computation.</p>
+     * <p>
+     * Throws <code>IllegalArgumentException</code> if the array is null.</p>
+     *
+     * @param values the input array
+     * @param begin the index of the first array element to include
+     * @param length the number of elements to include
+     * @return the skewness of the values or Double.NaN if length is less than
+     * 3
+     * @throws MathIllegalArgumentException if the array is null or the array index
+     *  parameters are not valid
+     */
+    @Override
+    public double evaluate(final double[] values,final int begin,
+            final int length) throws MathIllegalArgumentException {
+
+        // Initialize the skewness
+        double skew = Double.NaN;
+
+        if (test(values, begin, length) && length > 2 ){
+            Mean mean = new Mean();
+            // Get the mean and the standard deviation
+            double m = mean.evaluate(values, begin, length);
+
+            // Calc the std, this is implemented here instead
+            // of using the standardDeviation method eliminate
+            // a duplicate pass to get the mean
+            double accum = 0.0;
+            double accum2 = 0.0;
+            for (int i = begin; i < begin + length; i++) {
+                final double d = values[i] - m;
+                accum  += d * d;
+                accum2 += d;
+            }
+            final double variance = (accum - (accum2 * accum2 / length)) / (length - 1);
+
+            double accum3 = 0.0;
+            for (int i = begin; i < begin + length; i++) {
+                final double d = values[i] - m;
+                accum3 += d * d * d;
+            }
+            accum3 /= variance * FastMath.sqrt(variance);
+
+            // Get N
+            double n0 = length;
+
+            // Calculate skewness
+            skew = (n0 / ((n0 - 1) * (n0 - 2))) * accum3;
+        }
+        return skew;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Skewness copy() {
+        Skewness result = new Skewness();
+        // No try-catch or advertised exception because args are guaranteed non-null
+        copy(this, result);
+        return result;
+    }
+
+    /**
+     * Copies source to dest.
+     * <p>Neither source nor dest can be null.</p>
+     *
+     * @param source Skewness to copy
+     * @param dest Skewness to copy to
+     * @throws NullArgumentException if either source or dest is null
+     */
+    public static void copy(Skewness source, Skewness dest)
+        throws NullArgumentException {
+        MathUtils.checkNotNull(source);
+        MathUtils.checkNotNull(dest);
+        dest.setData(source.getDataRef());
+        dest.moment = new ThirdMoment(source.moment.copy());
+        dest.incMoment = source.incMoment;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/moment/StandardDeviation.java b/src/main/java/org/apache/commons/math3/stat/descriptive/moment/StandardDeviation.java
new file mode 100644
index 0000000..a6248c5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/moment/StandardDeviation.java
@@ -0,0 +1,280 @@
+/*
+ * 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.math3.stat.descriptive.moment;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.stat.descriptive.AbstractStorelessUnivariateStatistic;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * Computes the sample standard deviation.  The standard deviation
+ * is the positive square root of the variance.  This implementation wraps a
+ * {@link Variance} instance.  The <code>isBiasCorrected</code> property of the
+ * wrapped Variance instance is exposed, so that this class can be used to
+ * compute both the "sample standard deviation" (the square root of the
+ * bias-corrected "sample variance") or the "population standard deviation"
+ * (the square root of the non-bias-corrected "population variance"). See
+ * {@link Variance} for more information.
+ * <p>
+ * <strong>Note that this implementation is not synchronized.</strong> If
+ * multiple threads access an instance of this class concurrently, and at least
+ * one of the threads invokes the <code>increment()</code> or
+ * <code>clear()</code> method, it must be synchronized externally.</p>
+ *
+ */
+public class StandardDeviation extends AbstractStorelessUnivariateStatistic
+    implements Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 5728716329662425188L;
+
+    /** Wrapped Variance instance */
+    private Variance variance = null;
+
+    /**
+     * Constructs a StandardDeviation.  Sets the underlying {@link Variance}
+     * instance's <code>isBiasCorrected</code> property to true.
+     */
+    public StandardDeviation() {
+        variance = new Variance();
+    }
+
+    /**
+     * Constructs a StandardDeviation from an external second moment.
+     *
+     * @param m2 the external moment
+     */
+    public StandardDeviation(final SecondMoment m2) {
+        variance = new Variance(m2);
+    }
+
+    /**
+     * Copy constructor, creates a new {@code StandardDeviation} identical
+     * to the {@code original}
+     *
+     * @param original the {@code StandardDeviation} instance to copy
+     * @throws NullArgumentException if original is null
+     */
+    public StandardDeviation(StandardDeviation original) throws NullArgumentException {
+        copy(original, this);
+    }
+
+    /**
+     * Contructs a StandardDeviation with the specified value for the
+     * <code>isBiasCorrected</code> property.  If this property is set to
+     * <code>true</code>, the {@link Variance} used in computing results will
+     * use the bias-corrected, or "sample" formula.  See {@link Variance} for
+     * details.
+     *
+     * @param isBiasCorrected  whether or not the variance computation will use
+     * the bias-corrected formula
+     */
+    public StandardDeviation(boolean isBiasCorrected) {
+        variance = new Variance(isBiasCorrected);
+    }
+
+    /**
+     * Contructs a StandardDeviation with the specified value for the
+     * <code>isBiasCorrected</code> property and the supplied external moment.
+     * If <code>isBiasCorrected</code> is set to <code>true</code>, the
+     * {@link Variance} used in computing results will use the bias-corrected,
+     * or "sample" formula.  See {@link Variance} for details.
+     *
+     * @param isBiasCorrected  whether or not the variance computation will use
+     * the bias-corrected formula
+      * @param m2 the external moment
+     */
+    public StandardDeviation(boolean isBiasCorrected, SecondMoment m2) {
+        variance = new Variance(isBiasCorrected, m2);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void increment(final double d) {
+        variance.increment(d);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getN() {
+        return variance.getN();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double getResult() {
+        return FastMath.sqrt(variance.getResult());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void clear() {
+        variance.clear();
+    }
+
+    /**
+     * Returns the Standard Deviation of the entries in the input array, or
+     * <code>Double.NaN</code> if the array is empty.
+     * <p>
+     * Returns 0 for a single-value (i.e. length = 1) sample.</p>
+     * <p>
+     * Throws <code>MathIllegalArgumentException</code> if the array is null.</p>
+     * <p>
+     * Does not change the internal state of the statistic.</p>
+     *
+     * @param values the input array
+     * @return the standard deviation of the values or Double.NaN if length = 0
+     * @throws MathIllegalArgumentException if the array is null
+     */
+    @Override
+    public double evaluate(final double[] values) throws MathIllegalArgumentException  {
+        return FastMath.sqrt(variance.evaluate(values));
+    }
+
+    /**
+     * Returns the Standard Deviation of the entries in the specified portion of
+     * the input array, or <code>Double.NaN</code> if the designated subarray
+     * is empty.
+     * <p>
+     * Returns 0 for a single-value (i.e. length = 1) sample. </p>
+     * <p>
+     * Throws <code>MathIllegalArgumentException</code> if the array is null.</p>
+     * <p>
+     * Does not change the internal state of the statistic.</p>
+     *
+     * @param values the input array
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @return the standard deviation of the values or Double.NaN if length = 0
+     * @throws MathIllegalArgumentException if the array is null or the array index
+     *  parameters are not valid
+     */
+    @Override
+    public double evaluate(final double[] values, final int begin, final int length)
+    throws MathIllegalArgumentException  {
+       return FastMath.sqrt(variance.evaluate(values, begin, length));
+    }
+
+    /**
+     * Returns the Standard Deviation of the entries in the specified portion of
+     * the input array, using the precomputed mean value.  Returns
+     * <code>Double.NaN</code> if the designated subarray is empty.
+     * <p>
+     * Returns 0 for a single-value (i.e. length = 1) sample.</p>
+     * <p>
+     * The formula used assumes that the supplied mean value is the arithmetic
+     * mean of the sample data, not a known population parameter.  This method
+     * is supplied only to save computation when the mean has already been
+     * computed.</p>
+     * <p>
+     * Throws <code>IllegalArgumentException</code> if the array is null.</p>
+     * <p>
+     * Does not change the internal state of the statistic.</p>
+     *
+     * @param values the input array
+     * @param mean the precomputed mean value
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @return the standard deviation of the values or Double.NaN if length = 0
+     * @throws MathIllegalArgumentException if the array is null or the array index
+     *  parameters are not valid
+     */
+    public double evaluate(final double[] values, final double mean,
+            final int begin, final int length) throws MathIllegalArgumentException  {
+        return FastMath.sqrt(variance.evaluate(values, mean, begin, length));
+    }
+
+    /**
+     * Returns the Standard Deviation of the entries in the input array, using
+     * the precomputed mean value.  Returns
+     * <code>Double.NaN</code> if the designated subarray is empty.
+     * <p>
+     * Returns 0 for a single-value (i.e. length = 1) sample.</p>
+     * <p>
+     * The formula used assumes that the supplied mean value is the arithmetic
+     * mean of the sample data, not a known population parameter.  This method
+     * is supplied only to save computation when the mean has already been
+     * computed.</p>
+     * <p>
+     * Throws <code>MathIllegalArgumentException</code> if the array is null.</p>
+     * <p>
+     * Does not change the internal state of the statistic.</p>
+     *
+     * @param values the input array
+     * @param mean the precomputed mean value
+     * @return the standard deviation of the values or Double.NaN if length = 0
+     * @throws MathIllegalArgumentException if the array is null
+     */
+    public double evaluate(final double[] values, final double mean)
+    throws MathIllegalArgumentException  {
+        return FastMath.sqrt(variance.evaluate(values, mean));
+    }
+
+    /**
+     * @return Returns the isBiasCorrected.
+     */
+    public boolean isBiasCorrected() {
+        return variance.isBiasCorrected();
+    }
+
+    /**
+     * @param isBiasCorrected The isBiasCorrected to set.
+     */
+    public void setBiasCorrected(boolean isBiasCorrected) {
+        variance.setBiasCorrected(isBiasCorrected);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public StandardDeviation copy() {
+        StandardDeviation result = new StandardDeviation();
+        // No try-catch or advertised exception because args are guaranteed non-null
+        copy(this, result);
+        return result;
+    }
+
+
+    /**
+     * Copies source to dest.
+     * <p>Neither source nor dest can be null.</p>
+     *
+     * @param source StandardDeviation to copy
+     * @param dest StandardDeviation to copy to
+     * @throws NullArgumentException if either source or dest is null
+     */
+    public static void copy(StandardDeviation source, StandardDeviation dest)
+        throws NullArgumentException {
+        MathUtils.checkNotNull(source);
+        MathUtils.checkNotNull(dest);
+        dest.setData(source.getDataRef());
+        dest.variance = source.variance.copy();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/moment/ThirdMoment.java b/src/main/java/org/apache/commons/math3/stat/descriptive/moment/ThirdMoment.java
new file mode 100644
index 0000000..43a9ca1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/moment/ThirdMoment.java
@@ -0,0 +1,148 @@
+/*
+ * 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.math3.stat.descriptive.moment;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.util.MathUtils;
+
+
+/**
+ * Computes a statistic related to the Third Central Moment.  Specifically,
+ * what is computed is the sum of cubed deviations from the sample mean.
+ * <p>
+ * The following recursive updating formula is used:</p>
+ * <p>
+ * Let <ul>
+ * <li> dev = (current obs - previous mean) </li>
+ * <li> m2 = previous value of {@link SecondMoment} </li>
+ * <li> n = number of observations (including current obs) </li>
+ * </ul>
+ * Then</p>
+ * <p>
+ * new value = old value - 3 * (dev/n) * m2 + (n-1) * (n -2) * (dev^3/n^2)</p>
+ * <p>
+ * Returns <code>Double.NaN</code> if no data values have been added and
+ * returns <code>0</code> if there is just one value in the data set.
+ * Note that Double.NaN may also be returned if the input includes NaN
+ * and / or infinite values.</p>
+ * <p>
+ * <strong>Note that this implementation is not synchronized.</strong> If
+ * multiple threads access an instance of this class concurrently, and at least
+ * one of the threads invokes the <code>increment()</code> or
+ * <code>clear()</code> method, it must be synchronized externally.</p>
+ *
+ */
+class ThirdMoment extends SecondMoment implements Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -7818711964045118679L;
+
+    /** third moment of values that have been added */
+    protected double m3;
+
+     /**
+     * Square of deviation of most recently added value from previous first
+     * moment, normalized by previous sample size.  Retained to prevent
+     * repeated computation in higher order moments.  nDevSq = nDev * nDev.
+     */
+    protected double nDevSq;
+
+    /**
+     * Create a FourthMoment instance
+     */
+    ThirdMoment() {
+        super();
+        m3 = Double.NaN;
+        nDevSq = Double.NaN;
+    }
+
+    /**
+     * Copy constructor, creates a new {@code ThirdMoment} identical
+     * to the {@code original}
+     *
+     * @param original the {@code ThirdMoment} instance to copy
+     * @throws NullArgumentException if orginal is null
+     */
+    ThirdMoment(ThirdMoment original) throws NullArgumentException {
+        copy(original, this);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void increment(final double d) {
+        if (n < 1) {
+            m3 = m2 = m1 = 0.0;
+        }
+
+        double prevM2 = m2;
+        super.increment(d);
+        nDevSq = nDev * nDev;
+        double n0 = n;
+        m3 = m3 - 3.0 * nDev * prevM2 + (n0 - 1) * (n0 - 2) * nDevSq * dev;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double getResult() {
+        return m3;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void clear() {
+        super.clear();
+        m3 = Double.NaN;
+        nDevSq = Double.NaN;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public ThirdMoment copy() {
+        ThirdMoment result = new ThirdMoment();
+        // No try-catch or advertised exception because args are guaranteed non-null
+        copy(this, result);
+        return result;
+    }
+
+    /**
+     * Copies source to dest.
+     * <p>Neither source nor dest can be null.</p>
+     *
+     * @param source ThirdMoment to copy
+     * @param dest ThirdMoment to copy to
+     * @throws NullArgumentException if either source or dest is null
+     */
+    public static void copy(ThirdMoment source, ThirdMoment dest)
+        throws NullArgumentException {
+        MathUtils.checkNotNull(source);
+        MathUtils.checkNotNull(dest);
+        SecondMoment.copy(source, dest);
+        dest.m3 = source.m3;
+        dest.nDevSq = source.nDevSq;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/moment/Variance.java b/src/main/java/org/apache/commons/math3/stat/descriptive/moment/Variance.java
new file mode 100644
index 0000000..1ba48e9
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/moment/Variance.java
@@ -0,0 +1,627 @@
+/*
+ * 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.math3.stat.descriptive.moment;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.stat.descriptive.WeightedEvaluation;
+import org.apache.commons.math3.stat.descriptive.AbstractStorelessUnivariateStatistic;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * Computes the variance of the available values.  By default, the unbiased
+ * "sample variance" definitional formula is used:
+ * <p>
+ * variance = sum((x_i - mean)^2) / (n - 1) </p>
+ * <p>
+ * where mean is the {@link Mean} and <code>n</code> is the number
+ * of sample observations.</p>
+ * <p>
+ * The definitional formula does not have good numerical properties, so
+ * this implementation does not compute the statistic using the definitional
+ * formula. <ul>
+ * <li> The <code>getResult</code> method computes the variance using
+ * updating formulas based on West's algorithm, as described in
+ * <a href="http://doi.acm.org/10.1145/359146.359152"> Chan, T. F. and
+ * J. G. Lewis 1979, <i>Communications of the ACM</i>,
+ * vol. 22 no. 9, pp. 526-531.</a></li>
+ * <li> The <code>evaluate</code> methods leverage the fact that they have the
+ * full array of values in memory to execute a two-pass algorithm.
+ * Specifically, these methods use the "corrected two-pass algorithm" from
+ * Chan, Golub, Levesque, <i>Algorithms for Computing the Sample Variance</i>,
+ * American Statistician, vol. 37, no. 3 (1983) pp. 242-247.</li></ul>
+ * Note that adding values using <code>increment</code> or
+ * <code>incrementAll</code> and then executing <code>getResult</code> will
+ * sometimes give a different, less accurate, result than executing
+ * <code>evaluate</code> with the full array of values. The former approach
+ * should only be used when the full array of values is not available.</p>
+ * <p>
+ * The "population variance"  ( sum((x_i - mean)^2) / n ) can also
+ * be computed using this statistic.  The <code>isBiasCorrected</code>
+ * property determines whether the "population" or "sample" value is
+ * returned by the <code>evaluate</code> and <code>getResult</code> methods.
+ * To compute population variances, set this property to <code>false.</code>
+ * </p>
+ * <p>
+ * <strong>Note that this implementation is not synchronized.</strong> If
+ * multiple threads access an instance of this class concurrently, and at least
+ * one of the threads invokes the <code>increment()</code> or
+ * <code>clear()</code> method, it must be synchronized externally.</p>
+ *
+ */
+public class Variance extends AbstractStorelessUnivariateStatistic implements Serializable, WeightedEvaluation {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -9111962718267217978L;
+
+    /** SecondMoment is used in incremental calculation of Variance*/
+    protected SecondMoment moment = null;
+
+    /**
+     * Whether or not {@link #increment(double)} should increment
+     * the internal second moment. When a Variance is constructed with an
+     * external SecondMoment as a constructor parameter, this property is
+     * set to false and increments must be applied to the second moment
+     * directly.
+     */
+    protected boolean incMoment = true;
+
+    /**
+     * Whether or not bias correction is applied when computing the
+     * value of the statistic. True means that bias is corrected.  See
+     * {@link Variance} for details on the formula.
+     */
+    private boolean isBiasCorrected = true;
+
+    /**
+     * Constructs a Variance with default (true) <code>isBiasCorrected</code>
+     * property.
+     */
+    public Variance() {
+        moment = new SecondMoment();
+    }
+
+    /**
+     * Constructs a Variance based on an external second moment.
+     * When this constructor is used, the statistic may only be
+     * incremented via the moment, i.e., {@link #increment(double)}
+     * does nothing; whereas {@code m2.increment(value)} increments
+     * both {@code m2} and the Variance instance constructed from it.
+     *
+     * @param m2 the SecondMoment (Third or Fourth moments work
+     * here as well.)
+     */
+    public Variance(final SecondMoment m2) {
+        incMoment = false;
+        this.moment = m2;
+    }
+
+    /**
+     * Constructs a Variance with the specified <code>isBiasCorrected</code>
+     * property
+     *
+     * @param isBiasCorrected  setting for bias correction - true means
+     * bias will be corrected and is equivalent to using the argumentless
+     * constructor
+     */
+    public Variance(boolean isBiasCorrected) {
+        moment = new SecondMoment();
+        this.isBiasCorrected = isBiasCorrected;
+    }
+
+    /**
+     * Constructs a Variance with the specified <code>isBiasCorrected</code>
+     * property and the supplied external second moment.
+     *
+     * @param isBiasCorrected  setting for bias correction - true means
+     * bias will be corrected
+     * @param m2 the SecondMoment (Third or Fourth moments work
+     * here as well.)
+     */
+    public Variance(boolean isBiasCorrected, SecondMoment m2) {
+        incMoment = false;
+        this.moment = m2;
+        this.isBiasCorrected = isBiasCorrected;
+    }
+
+    /**
+     * Copy constructor, creates a new {@code Variance} identical
+     * to the {@code original}
+     *
+     * @param original the {@code Variance} instance to copy
+     * @throws NullArgumentException if original is null
+     */
+    public Variance(Variance original) throws NullArgumentException {
+        copy(original, this);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>If all values are available, it is more accurate to use
+     * {@link #evaluate(double[])} rather than adding values one at a time
+     * using this method and then executing {@link #getResult}, since
+     * <code>evaluate</code> leverages the fact that is has the full
+     * list of values together to execute a two-pass algorithm.
+     * See {@link Variance}.</p>
+     *
+     * <p>Note also that when {@link #Variance(SecondMoment)} is used to
+     * create a Variance, this method does nothing. In that case, the
+     * SecondMoment should be incremented directly.</p>
+     */
+    @Override
+    public void increment(final double d) {
+        if (incMoment) {
+            moment.increment(d);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double getResult() {
+            if (moment.n == 0) {
+                return Double.NaN;
+            } else if (moment.n == 1) {
+                return 0d;
+            } else {
+                if (isBiasCorrected) {
+                    return moment.m2 / (moment.n - 1d);
+                } else {
+                    return moment.m2 / (moment.n);
+                }
+            }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getN() {
+        return moment.getN();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void clear() {
+        if (incMoment) {
+            moment.clear();
+        }
+    }
+
+    /**
+     * Returns the variance of the entries in the input array, or
+     * <code>Double.NaN</code> if the array is empty.
+     * <p>
+     * See {@link Variance} for details on the computing algorithm.</p>
+     * <p>
+     * Returns 0 for a single-value (i.e. length = 1) sample.</p>
+     * <p>
+     * Throws <code>MathIllegalArgumentException</code> if the array is null.</p>
+     * <p>
+     * Does not change the internal state of the statistic.</p>
+     *
+     * @param values the input array
+     * @return the variance of the values or Double.NaN if length = 0
+     * @throws MathIllegalArgumentException if the array is null
+     */
+    @Override
+    public double evaluate(final double[] values) throws MathIllegalArgumentException {
+        if (values == null) {
+            throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY);
+        }
+        return evaluate(values, 0, values.length);
+    }
+
+    /**
+     * Returns the variance of the entries in the specified portion of
+     * the input array, or <code>Double.NaN</code> if the designated subarray
+     * is empty.  Note that Double.NaN may also be returned if the input
+     * includes NaN and / or infinite values.
+     * <p>
+     * See {@link Variance} for details on the computing algorithm.</p>
+     * <p>
+     * Returns 0 for a single-value (i.e. length = 1) sample.</p>
+     * <p>
+     * Does not change the internal state of the statistic.</p>
+     * <p>
+     * Throws <code>MathIllegalArgumentException</code> if the array is null.</p>
+     *
+     * @param values the input array
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @return the variance of the values or Double.NaN if length = 0
+     * @throws MathIllegalArgumentException if the array is null or the array index
+     *  parameters are not valid
+     */
+    @Override
+    public double evaluate(final double[] values, final int begin, final int length)
+    throws MathIllegalArgumentException {
+
+        double var = Double.NaN;
+
+        if (test(values, begin, length)) {
+            clear();
+            if (length == 1) {
+                var = 0.0;
+            } else if (length > 1) {
+                Mean mean = new Mean();
+                double m = mean.evaluate(values, begin, length);
+                var = evaluate(values, m, begin, length);
+            }
+        }
+        return var;
+    }
+
+    /**
+     * <p>Returns the weighted variance of the entries in the specified portion of
+     * the input array, or <code>Double.NaN</code> if the designated subarray
+     * is empty.</p>
+     * <p>
+     * Uses the formula <pre>
+     *   &Sigma;(weights[i]*(values[i] - weightedMean)<sup>2</sup>)/(&Sigma;(weights[i]) - 1)
+     * </pre>
+     * where weightedMean is the weighted mean</p>
+     * <p>
+     * This formula will not return the same result as the unweighted variance when all
+     * weights are equal, unless all weights are equal to 1. The formula assumes that
+     * weights are to be treated as "expansion values," as will be the case if for example
+     * the weights represent frequency counts. To normalize weights so that the denominator
+     * in the variance computation equals the length of the input vector minus one, use <pre>
+     *   <code>evaluate(values, MathArrays.normalizeArray(weights, values.length)); </code>
+     * </pre>
+     * <p>
+     * Returns 0 for a single-value (i.e. length = 1) sample.</p>
+     * <p>
+     * Throws <code>IllegalArgumentException</code> if any of the following are true:
+     * <ul><li>the values array is null</li>
+     *     <li>the weights array is null</li>
+     *     <li>the weights array does not have the same length as the values array</li>
+     *     <li>the weights array contains one or more infinite values</li>
+     *     <li>the weights array contains one or more NaN values</li>
+     *     <li>the weights array contains negative values</li>
+     *     <li>the start and length arguments do not determine a valid array</li>
+     * </ul></p>
+     * <p>
+     * Does not change the internal state of the statistic.</p>
+     * <p>
+     * Throws <code>MathIllegalArgumentException</code> if either array is null.</p>
+     *
+     * @param values the input array
+     * @param weights the weights array
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @return the weighted variance of the values or Double.NaN if length = 0
+     * @throws MathIllegalArgumentException if the parameters are not valid
+     * @since 2.1
+     */
+    public double evaluate(final double[] values, final double[] weights,
+                           final int begin, final int length) throws MathIllegalArgumentException {
+
+        double var = Double.NaN;
+
+        if (test(values, weights,begin, length)) {
+            clear();
+            if (length == 1) {
+                var = 0.0;
+            } else if (length > 1) {
+                Mean mean = new Mean();
+                double m = mean.evaluate(values, weights, begin, length);
+                var = evaluate(values, weights, m, begin, length);
+            }
+        }
+        return var;
+    }
+
+    /**
+     * <p>
+     * Returns the weighted variance of the entries in the the input array.</p>
+     * <p>
+     * Uses the formula <pre>
+     *   &Sigma;(weights[i]*(values[i] - weightedMean)<sup>2</sup>)/(&Sigma;(weights[i]) - 1)
+     * </pre>
+     * where weightedMean is the weighted mean</p>
+     * <p>
+     * This formula will not return the same result as the unweighted variance when all
+     * weights are equal, unless all weights are equal to 1. The formula assumes that
+     * weights are to be treated as "expansion values," as will be the case if for example
+     * the weights represent frequency counts. To normalize weights so that the denominator
+     * in the variance computation equals the length of the input vector minus one, use <pre>
+     *   <code>evaluate(values, MathArrays.normalizeArray(weights, values.length)); </code>
+     * </pre>
+     * <p>
+     * Returns 0 for a single-value (i.e. length = 1) sample.</p>
+     * <p>
+     * Throws <code>MathIllegalArgumentException</code> if any of the following are true:
+     * <ul><li>the values array is null</li>
+     *     <li>the weights array is null</li>
+     *     <li>the weights array does not have the same length as the values array</li>
+     *     <li>the weights array contains one or more infinite values</li>
+     *     <li>the weights array contains one or more NaN values</li>
+     *     <li>the weights array contains negative values</li>
+     * </ul></p>
+     * <p>
+     * Does not change the internal state of the statistic.</p>
+     * <p>
+     * Throws <code>MathIllegalArgumentException</code> if either array is null.</p>
+     *
+     * @param values the input array
+     * @param weights the weights array
+     * @return the weighted variance of the values
+     * @throws MathIllegalArgumentException if the parameters are not valid
+     * @since 2.1
+     */
+    public double evaluate(final double[] values, final double[] weights)
+    throws MathIllegalArgumentException {
+        return evaluate(values, weights, 0, values.length);
+    }
+
+    /**
+     * Returns the variance of the entries in the specified portion of
+     * the input array, using the precomputed mean value.  Returns
+     * <code>Double.NaN</code> if the designated subarray is empty.
+     * <p>
+     * See {@link Variance} for details on the computing algorithm.</p>
+     * <p>
+     * The formula used assumes that the supplied mean value is the arithmetic
+     * mean of the sample data, not a known population parameter.  This method
+     * is supplied only to save computation when the mean has already been
+     * computed.</p>
+     * <p>
+     * Returns 0 for a single-value (i.e. length = 1) sample.</p>
+     * <p>
+     * Throws <code>MathIllegalArgumentException</code> if the array is null.</p>
+     * <p>
+     * Does not change the internal state of the statistic.</p>
+     *
+     * @param values the input array
+     * @param mean the precomputed mean value
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @return the variance of the values or Double.NaN if length = 0
+     * @throws MathIllegalArgumentException if the array is null or the array index
+     *  parameters are not valid
+     */
+    public double evaluate(final double[] values, final double mean,
+            final int begin, final int length) throws MathIllegalArgumentException {
+
+        double var = Double.NaN;
+
+        if (test(values, begin, length)) {
+            if (length == 1) {
+                var = 0.0;
+            } else if (length > 1) {
+                double accum = 0.0;
+                double dev = 0.0;
+                double accum2 = 0.0;
+                for (int i = begin; i < begin + length; i++) {
+                    dev = values[i] - mean;
+                    accum += dev * dev;
+                    accum2 += dev;
+                }
+                double len = length;
+                if (isBiasCorrected) {
+                    var = (accum - (accum2 * accum2 / len)) / (len - 1.0);
+                } else {
+                    var = (accum - (accum2 * accum2 / len)) / len;
+                }
+            }
+        }
+        return var;
+    }
+
+    /**
+     * Returns the variance of the entries in the input array, using the
+     * precomputed mean value.  Returns <code>Double.NaN</code> if the array
+     * is empty.
+     * <p>
+     * See {@link Variance} for details on the computing algorithm.</p>
+     * <p>
+     * If <code>isBiasCorrected</code> is <code>true</code> the formula used
+     * assumes that the supplied mean value is the arithmetic mean of the
+     * sample data, not a known population parameter.  If the mean is a known
+     * population parameter, or if the "population" version of the variance is
+     * desired, set <code>isBiasCorrected</code> to <code>false</code> before
+     * invoking this method.</p>
+     * <p>
+     * Returns 0 for a single-value (i.e. length = 1) sample.</p>
+     * <p>
+     * Throws <code>MathIllegalArgumentException</code> if the array is null.</p>
+     * <p>
+     * Does not change the internal state of the statistic.</p>
+     *
+     * @param values the input array
+     * @param mean the precomputed mean value
+     * @return the variance of the values or Double.NaN if the array is empty
+     * @throws MathIllegalArgumentException if the array is null
+     */
+    public double evaluate(final double[] values, final double mean) throws MathIllegalArgumentException {
+        return evaluate(values, mean, 0, values.length);
+    }
+
+    /**
+     * Returns the weighted variance of the entries in the specified portion of
+     * the input array, using the precomputed weighted mean value.  Returns
+     * <code>Double.NaN</code> if the designated subarray is empty.
+     * <p>
+     * Uses the formula <pre>
+     *   &Sigma;(weights[i]*(values[i] - mean)<sup>2</sup>)/(&Sigma;(weights[i]) - 1)
+     * </pre></p>
+     * <p>
+     * The formula used assumes that the supplied mean value is the weighted arithmetic
+     * mean of the sample data, not a known population parameter. This method
+     * is supplied only to save computation when the mean has already been
+     * computed.</p>
+     * <p>
+     * This formula will not return the same result as the unweighted variance when all
+     * weights are equal, unless all weights are equal to 1. The formula assumes that
+     * weights are to be treated as "expansion values," as will be the case if for example
+     * the weights represent frequency counts. To normalize weights so that the denominator
+     * in the variance computation equals the length of the input vector minus one, use <pre>
+     *   <code>evaluate(values, MathArrays.normalizeArray(weights, values.length), mean); </code>
+     * </pre>
+     * <p>
+     * Returns 0 for a single-value (i.e. length = 1) sample.</p>
+     * <p>
+     * Throws <code>MathIllegalArgumentException</code> if any of the following are true:
+     * <ul><li>the values array is null</li>
+     *     <li>the weights array is null</li>
+     *     <li>the weights array does not have the same length as the values array</li>
+     *     <li>the weights array contains one or more infinite values</li>
+     *     <li>the weights array contains one or more NaN values</li>
+     *     <li>the weights array contains negative values</li>
+     *     <li>the start and length arguments do not determine a valid array</li>
+     * </ul></p>
+     * <p>
+     * Does not change the internal state of the statistic.</p>
+     *
+     * @param values the input array
+     * @param weights the weights array
+     * @param mean the precomputed weighted mean value
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @return the variance of the values or Double.NaN if length = 0
+     * @throws MathIllegalArgumentException if the parameters are not valid
+     * @since 2.1
+     */
+    public double evaluate(final double[] values, final double[] weights,
+    final double mean, final int begin, final int length)
+    throws MathIllegalArgumentException {
+
+        double var = Double.NaN;
+
+        if (test(values, weights, begin, length)) {
+            if (length == 1) {
+                var = 0.0;
+            } else if (length > 1) {
+                double accum = 0.0;
+                double dev = 0.0;
+                double accum2 = 0.0;
+                for (int i = begin; i < begin + length; i++) {
+                    dev = values[i] - mean;
+                    accum += weights[i] * (dev * dev);
+                    accum2 += weights[i] * dev;
+                }
+
+                double sumWts = 0;
+                for (int i = begin; i < begin + length; i++) {
+                    sumWts += weights[i];
+                }
+
+                if (isBiasCorrected) {
+                    var = (accum - (accum2 * accum2 / sumWts)) / (sumWts - 1.0);
+                } else {
+                    var = (accum - (accum2 * accum2 / sumWts)) / sumWts;
+                }
+            }
+        }
+        return var;
+    }
+
+    /**
+     * <p>Returns the weighted variance of the values in the input array, using
+     * the precomputed weighted mean value.</p>
+     * <p>
+     * Uses the formula <pre>
+     *   &Sigma;(weights[i]*(values[i] - mean)<sup>2</sup>)/(&Sigma;(weights[i]) - 1)
+     * </pre></p>
+     * <p>
+     * The formula used assumes that the supplied mean value is the weighted arithmetic
+     * mean of the sample data, not a known population parameter. This method
+     * is supplied only to save computation when the mean has already been
+     * computed.</p>
+     * <p>
+     * This formula will not return the same result as the unweighted variance when all
+     * weights are equal, unless all weights are equal to 1. The formula assumes that
+     * weights are to be treated as "expansion values," as will be the case if for example
+     * the weights represent frequency counts. To normalize weights so that the denominator
+     * in the variance computation equals the length of the input vector minus one, use <pre>
+     *   <code>evaluate(values, MathArrays.normalizeArray(weights, values.length), mean); </code>
+     * </pre>
+     * <p>
+     * Returns 0 for a single-value (i.e. length = 1) sample.</p>
+     * <p>
+     * Throws <code>MathIllegalArgumentException</code> if any of the following are true:
+     * <ul><li>the values array is null</li>
+     *     <li>the weights array is null</li>
+     *     <li>the weights array does not have the same length as the values array</li>
+     *     <li>the weights array contains one or more infinite values</li>
+     *     <li>the weights array contains one or more NaN values</li>
+     *     <li>the weights array contains negative values</li>
+     * </ul></p>
+     * <p>
+     * Does not change the internal state of the statistic.</p>
+     *
+     * @param values the input array
+     * @param weights the weights array
+     * @param mean the precomputed weighted mean value
+     * @return the variance of the values or Double.NaN if length = 0
+     * @throws MathIllegalArgumentException if the parameters are not valid
+     * @since 2.1
+     */
+    public double evaluate(final double[] values, final double[] weights, final double mean)
+    throws MathIllegalArgumentException {
+        return evaluate(values, weights, mean, 0, values.length);
+    }
+
+    /**
+     * @return Returns the isBiasCorrected.
+     */
+    public boolean isBiasCorrected() {
+        return isBiasCorrected;
+    }
+
+    /**
+     * @param biasCorrected The isBiasCorrected to set.
+     */
+    public void setBiasCorrected(boolean biasCorrected) {
+        this.isBiasCorrected = biasCorrected;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Variance copy() {
+        Variance result = new Variance();
+        // No try-catch or advertised exception because parameters are guaranteed non-null
+        copy(this, result);
+        return result;
+    }
+
+    /**
+     * Copies source to dest.
+     * <p>Neither source nor dest can be null.</p>
+     *
+     * @param source Variance to copy
+     * @param dest Variance to copy to
+     * @throws NullArgumentException if either source or dest is null
+     */
+    public static void copy(Variance source, Variance dest)
+        throws NullArgumentException {
+        MathUtils.checkNotNull(source);
+        MathUtils.checkNotNull(dest);
+        dest.setData(source.getDataRef());
+        dest.moment = source.moment.copy();
+        dest.isBiasCorrected = source.isBiasCorrected;
+        dest.incMoment = source.incMoment;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/moment/VectorialCovariance.java b/src/main/java/org/apache/commons/math3/stat/descriptive/moment/VectorialCovariance.java
new file mode 100644
index 0000000..7f6f903
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/moment/VectorialCovariance.java
@@ -0,0 +1,157 @@
+/*
+ * 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.math3.stat.descriptive.moment;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.linear.MatrixUtils;
+import org.apache.commons.math3.linear.RealMatrix;
+
+/**
+ * Returns the covariance matrix of the available vectors.
+ * @since 1.2
+ */
+public class VectorialCovariance implements Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 4118372414238930270L;
+
+    /** Sums for each component. */
+    private final double[] sums;
+
+    /** Sums of products for each component. */
+    private final double[] productsSums;
+
+    /** Indicator for bias correction. */
+    private final boolean isBiasCorrected;
+
+    /** Number of vectors in the sample. */
+    private long n;
+
+    /** Constructs a VectorialCovariance.
+     * @param dimension vectors dimension
+     * @param isBiasCorrected if true, computed the unbiased sample covariance,
+     * otherwise computes the biased population covariance
+     */
+    public VectorialCovariance(int dimension, boolean isBiasCorrected) {
+        sums         = new double[dimension];
+        productsSums = new double[dimension * (dimension + 1) / 2];
+        n            = 0;
+        this.isBiasCorrected = isBiasCorrected;
+    }
+
+    /**
+     * Add a new vector to the sample.
+     * @param v vector to add
+     * @throws DimensionMismatchException if the vector does not have the right dimension
+     */
+    public void increment(double[] v) throws DimensionMismatchException {
+        if (v.length != sums.length) {
+            throw new DimensionMismatchException(v.length, sums.length);
+        }
+        int k = 0;
+        for (int i = 0; i < v.length; ++i) {
+            sums[i] += v[i];
+            for (int j = 0; j <= i; ++j) {
+                productsSums[k++] += v[i] * v[j];
+            }
+        }
+        n++;
+    }
+
+    /**
+     * Get the covariance matrix.
+     * @return covariance matrix
+     */
+    public RealMatrix getResult() {
+
+        int dimension = sums.length;
+        RealMatrix result = MatrixUtils.createRealMatrix(dimension, dimension);
+
+        if (n > 1) {
+            double c = 1.0 / (n * (isBiasCorrected ? (n - 1) : n));
+            int k = 0;
+            for (int i = 0; i < dimension; ++i) {
+                for (int j = 0; j <= i; ++j) {
+                    double e = c * (n * productsSums[k++] - sums[i] * sums[j]);
+                    result.setEntry(i, j, e);
+                    result.setEntry(j, i, e);
+                }
+            }
+        }
+
+        return result;
+
+    }
+
+    /**
+     * Get the number of vectors in the sample.
+     * @return number of vectors in the sample
+     */
+    public long getN() {
+        return n;
+    }
+
+    /**
+     * Clears the internal state of the Statistic
+     */
+    public void clear() {
+        n = 0;
+        Arrays.fill(sums, 0.0);
+        Arrays.fill(productsSums, 0.0);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + (isBiasCorrected ? 1231 : 1237);
+        result = prime * result + (int) (n ^ (n >>> 32));
+        result = prime * result + Arrays.hashCode(productsSums);
+        result = prime * result + Arrays.hashCode(sums);
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof VectorialCovariance)) {
+            return false;
+        }
+        VectorialCovariance other = (VectorialCovariance) obj;
+        if (isBiasCorrected != other.isBiasCorrected) {
+            return false;
+        }
+        if (n != other.n) {
+            return false;
+        }
+        if (!Arrays.equals(productsSums, other.productsSums)) {
+            return false;
+        }
+        if (!Arrays.equals(sums, other.sums)) {
+            return false;
+        }
+        return true;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/moment/VectorialMean.java b/src/main/java/org/apache/commons/math3/stat/descriptive/moment/VectorialMean.java
new file mode 100644
index 0000000..e06b3bc
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/moment/VectorialMean.java
@@ -0,0 +1,105 @@
+/*
+ * 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.math3.stat.descriptive.moment;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+
+/**
+ * Returns the arithmetic mean of the available vectors.
+ * @since 1.2
+ */
+public class VectorialMean implements Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 8223009086481006892L;
+
+    /** Means for each component. */
+    private final Mean[] means;
+
+    /** Constructs a VectorialMean.
+     * @param dimension vectors dimension
+     */
+    public VectorialMean(int dimension) {
+        means = new Mean[dimension];
+        for (int i = 0; i < dimension; ++i) {
+            means[i] = new Mean();
+        }
+    }
+
+    /**
+     * Add a new vector to the sample.
+     * @param v vector to add
+     * @throws DimensionMismatchException if the vector does not have the right dimension
+     */
+    public void increment(double[] v) throws DimensionMismatchException {
+        if (v.length != means.length) {
+            throw new DimensionMismatchException(v.length, means.length);
+        }
+        for (int i = 0; i < v.length; ++i) {
+            means[i].increment(v[i]);
+        }
+    }
+
+    /**
+     * Get the mean vector.
+     * @return mean vector
+     */
+    public double[] getResult() {
+        double[] result = new double[means.length];
+        for (int i = 0; i < result.length; ++i) {
+            result[i] = means[i].getResult();
+        }
+        return result;
+    }
+
+    /**
+     * Get the number of vectors in the sample.
+     * @return number of vectors in the sample
+     */
+    public long getN() {
+        return (means.length == 0) ? 0 : means[0].getN();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + Arrays.hashCode(means);
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof VectorialMean)) {
+            return false;
+        }
+        VectorialMean other = (VectorialMean) obj;
+        if (!Arrays.equals(means, other.means)) {
+            return false;
+        }
+        return true;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/moment/package-info.java b/src/main/java/org/apache/commons/math3/stat/descriptive/moment/package-info.java
new file mode 100644
index 0000000..e23ead7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/moment/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+/**
+ * Summary statistics based on moments.
+ */
+package org.apache.commons.math3.stat.descriptive.moment;
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/package-info.java b/src/main/java/org/apache/commons/math3/stat/descriptive/package-info.java
new file mode 100644
index 0000000..92fa5b3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/package-info.java
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+/**
+ *
+ *        Generic univariate summary statistic objects.
+ *
+ *        <h3>UnivariateStatistic API Usage Examples:</h3>
+ *
+ *        <h4>UnivariateStatistic:</h4>
+ *        <code>/&lowast; evaluation approach &lowast;/<br/>
+ *          double[] values = new double[] { 1, 2, 3, 4, 5 };<br/>
+ *          <span style="font-weight: bold;">UnivariateStatistic stat = new Mean();</span><br/>
+ *          out.println("mean = " + <span style="font-weight: bold;">stat.evaluate(values)</span>);<br/>
+ *        </code>
+ *
+ *        <h4>StorelessUnivariateStatistic:</h4>
+ *        <code>/&lowast; incremental approach &lowast;/<br/>
+ *          double[] values = new double[] { 1, 2, 3, 4, 5 };<br/>
+ *          <span style="font-weight: bold;">StorelessUnivariateStatistic stat = new Mean();</span><br/>
+ *          out.println("mean before adding a value is NaN = " + <span style="font-weight: bold;">stat.getResult()</span>);<br/>
+ *          for (int i = 0; i &lt; values.length; i++) {<br/>
+ *            &nbsp;&nbsp;&nbsp; <span style="font-weight: bold;">stat.increment(values[i]);</span><br/>
+ *            &nbsp;&nbsp;&nbsp; out.println("current mean = " + <span style="font-weight: bold;">stat2.getResult()</span>);<br/>
+ *          }<br/>
+ *          <span style="font-weight: bold;"> stat.clear();</span><br/>
+ *          out.println("mean after clear is NaN = " + <span style="font-weight: bold;">stat.getResult()</span>);
+ *        </code>
+ *
+ */
+package org.apache.commons.math3.stat.descriptive;
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/rank/Max.java b/src/main/java/org/apache/commons/math3/stat/descriptive/rank/Max.java
new file mode 100644
index 0000000..75f145f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/rank/Max.java
@@ -0,0 +1,171 @@
+/*
+ * 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.math3.stat.descriptive.rank;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.stat.descriptive.AbstractStorelessUnivariateStatistic;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * Returns the maximum of the available values.
+ * <p>
+ * <ul>
+ * <li>The result is <code>NaN</code> iff all values are <code>NaN</code>
+ * (i.e. <code>NaN</code> values have no impact on the value of the statistic).</li>
+ * <li>If any of the values equals <code>Double.POSITIVE_INFINITY</code>,
+ * the result is <code>Double.POSITIVE_INFINITY.</code></li>
+ * </ul></p>
+* <p>
+ * <strong>Note that this implementation is not synchronized.</strong> If
+ * multiple threads access an instance of this class concurrently, and at least
+ * one of the threads invokes the <code>increment()</code> or
+ * <code>clear()</code> method, it must be synchronized externally.</p>
+ *
+ */
+public class Max extends AbstractStorelessUnivariateStatistic implements Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -5593383832225844641L;
+
+    /** Number of values that have been added */
+    private long n;
+
+    /** Current value of the statistic */
+    private double value;
+
+    /**
+     * Create a Max instance
+     */
+    public Max() {
+        n = 0;
+        value = Double.NaN;
+    }
+
+    /**
+     * Copy constructor, creates a new {@code Max} identical
+     * to the {@code original}
+     *
+     * @param original the {@code Max} instance to copy
+     * @throws NullArgumentException if original is null
+     */
+    public Max(Max original) throws NullArgumentException {
+        copy(original, this);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void increment(final double d) {
+        if (d > value || Double.isNaN(value)) {
+            value = d;
+        }
+        n++;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void clear() {
+        value = Double.NaN;
+        n = 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double getResult() {
+        return value;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getN() {
+        return n;
+    }
+
+    /**
+     * Returns the maximum of the entries in the specified portion of
+     * the input array, or <code>Double.NaN</code> if the designated subarray
+     * is empty.
+     * <p>
+     * Throws <code>MathIllegalArgumentException</code> if the array is null or
+     * the array index parameters are not valid.</p>
+     * <p>
+     * <ul>
+     * <li>The result is <code>NaN</code> iff all values are <code>NaN</code>
+     * (i.e. <code>NaN</code> values have no impact on the value of the statistic).</li>
+     * <li>If any of the values equals <code>Double.POSITIVE_INFINITY</code>,
+     * the result is <code>Double.POSITIVE_INFINITY.</code></li>
+     * </ul></p>
+     *
+     * @param values the input array
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @return the maximum of the values or Double.NaN if length = 0
+     * @throws MathIllegalArgumentException if the array is null or the array index
+     *  parameters are not valid
+     */
+    @Override
+    public double evaluate(final double[] values, final int begin, final int length)
+    throws MathIllegalArgumentException {
+        double max = Double.NaN;
+        if (test(values, begin, length)) {
+            max = values[begin];
+            for (int i = begin; i < begin + length; i++) {
+                if (!Double.isNaN(values[i])) {
+                    max = (max > values[i]) ? max : values[i];
+                }
+            }
+        }
+        return max;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Max copy() {
+        Max result = new Max();
+        // No try-catch or advertised exception because args are non-null
+        copy(this, result);
+        return result;
+    }
+
+    /**
+     * Copies source to dest.
+     * <p>Neither source nor dest can be null.</p>
+     *
+     * @param source Max to copy
+     * @param dest Max to copy to
+     * @throws NullArgumentException if either source or dest is null
+     */
+    public static void copy(Max source, Max dest)
+        throws NullArgumentException {
+        MathUtils.checkNotNull(source);
+        MathUtils.checkNotNull(dest);
+        dest.setData(source.getDataRef());
+        dest.n = source.n;
+        dest.value = source.value;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/rank/Median.java b/src/main/java/org/apache/commons/math3/stat/descriptive/rank/Median.java
new file mode 100644
index 0000000..6350a0b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/rank/Median.java
@@ -0,0 +1,97 @@
+/*
+ * 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.math3.stat.descriptive.rank;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.stat.ranking.NaNStrategy;
+import org.apache.commons.math3.util.KthSelector;
+
+
+/**
+ * Returns the median of the available values.  This is the same as the 50th percentile.
+ * See {@link Percentile} for a description of the algorithm used.
+ * <p>
+ * <strong>Note that this implementation is not synchronized.</strong> If
+ * multiple threads access an instance of this class concurrently, and at least
+ * one of the threads invokes the <code>increment()</code> or
+ * <code>clear()</code> method, it must be synchronized externally.</p>
+ *
+ */
+public class Median extends Percentile implements Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -3961477041290915687L;
+
+    /** Fixed quantile. */
+    private static final double FIXED_QUANTILE_50 = 50.0;
+
+    /**
+     * Default constructor.
+     */
+    public Median() {
+        // No try-catch or advertised exception - arg is valid
+        super(FIXED_QUANTILE_50);
+    }
+
+    /**
+     * Copy constructor, creates a new {@code Median} identical
+     * to the {@code original}
+     *
+     * @param original the {@code Median} instance to copy
+     * @throws NullArgumentException if original is null
+     */
+    public Median(Median original) throws NullArgumentException {
+        super(original);
+    }
+
+    /**
+     * Constructs a Median with the specific {@link EstimationType}, {@link NaNStrategy} and {@link PivotingStrategy}.
+     *
+     * @param estimationType one of the percentile {@link EstimationType  estimation types}
+     * @param nanStrategy one of {@link NaNStrategy} to handle with NaNs
+     * @param kthSelector {@link KthSelector} to use for pivoting during search
+     * @throws MathIllegalArgumentException if p is not within (0,100]
+     * @throws NullArgumentException if type or NaNStrategy passed is null
+     */
+    private Median(final EstimationType estimationType, final NaNStrategy nanStrategy,
+                   final KthSelector kthSelector)
+        throws MathIllegalArgumentException {
+        super(FIXED_QUANTILE_50, estimationType, nanStrategy, kthSelector);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Median withEstimationType(final EstimationType newEstimationType) {
+        return new Median(newEstimationType, getNaNStrategy(), getKthSelector());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Median withNaNStrategy(final NaNStrategy newNaNStrategy) {
+        return new Median(getEstimationType(), newNaNStrategy, getKthSelector());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Median withKthSelector(final KthSelector newKthSelector) {
+        return new Median(getEstimationType(), getNaNStrategy(), newKthSelector);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/rank/Min.java b/src/main/java/org/apache/commons/math3/stat/descriptive/rank/Min.java
new file mode 100644
index 0000000..c87e6f1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/rank/Min.java
@@ -0,0 +1,171 @@
+/*
+ * 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.math3.stat.descriptive.rank;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.stat.descriptive.AbstractStorelessUnivariateStatistic;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * Returns the minimum of the available values.
+ * <p>
+ * <ul>
+ * <li>The result is <code>NaN</code> iff all values are <code>NaN</code>
+ * (i.e. <code>NaN</code> values have no impact on the value of the statistic).</li>
+ * <li>If any of the values equals <code>Double.NEGATIVE_INFINITY</code>,
+ * the result is <code>Double.NEGATIVE_INFINITY.</code></li>
+ * </ul></p>
+ * <p>
+ * <strong>Note that this implementation is not synchronized.</strong> If
+ * multiple threads access an instance of this class concurrently, and at least
+ * one of the threads invokes the <code>increment()</code> or
+ * <code>clear()</code> method, it must be synchronized externally.</p>
+ *
+ */
+public class Min extends AbstractStorelessUnivariateStatistic implements Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -2941995784909003131L;
+
+    /**Number of values that have been added */
+    private long n;
+
+    /**Current value of the statistic */
+    private double value;
+
+    /**
+     * Create a Min instance
+     */
+    public Min() {
+        n = 0;
+        value = Double.NaN;
+    }
+
+    /**
+     * Copy constructor, creates a new {@code Min} identical
+     * to the {@code original}
+     *
+     * @param original the {@code Min} instance to copy
+     * @throws NullArgumentException if original is null
+     */
+    public Min(Min original) throws NullArgumentException {
+        copy(original, this);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void increment(final double d) {
+        if (d < value || Double.isNaN(value)) {
+            value = d;
+        }
+        n++;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void clear() {
+        value = Double.NaN;
+        n = 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double getResult() {
+        return value;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getN() {
+        return n;
+    }
+
+    /**
+     * Returns the minimum of the entries in the specified portion of
+     * the input array, or <code>Double.NaN</code> if the designated subarray
+     * is empty.
+     * <p>
+     * Throws <code>MathIllegalArgumentException</code> if the array is null or
+     * the array index parameters are not valid.</p>
+     * <p>
+     * <ul>
+     * <li>The result is <code>NaN</code> iff all values are <code>NaN</code>
+     * (i.e. <code>NaN</code> values have no impact on the value of the statistic).</li>
+     * <li>If any of the values equals <code>Double.NEGATIVE_INFINITY</code>,
+     * the result is <code>Double.NEGATIVE_INFINITY.</code></li>
+     * </ul> </p>
+     *
+     * @param values the input array
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @return the minimum of the values or Double.NaN if length = 0
+     * @throws MathIllegalArgumentException if the array is null or the array index
+     *  parameters are not valid
+     */
+    @Override
+    public double evaluate(final double[] values,final int begin, final int length)
+    throws MathIllegalArgumentException {
+        double min = Double.NaN;
+        if (test(values, begin, length)) {
+            min = values[begin];
+            for (int i = begin; i < begin + length; i++) {
+                if (!Double.isNaN(values[i])) {
+                    min = (min < values[i]) ? min : values[i];
+                }
+            }
+        }
+        return min;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Min copy() {
+        Min result = new Min();
+        // No try-catch or advertised exception - args are non-null
+        copy(this, result);
+        return result;
+    }
+
+    /**
+     * Copies source to dest.
+     * <p>Neither source nor dest can be null.</p>
+     *
+     * @param source Min to copy
+     * @param dest Min to copy to
+     * @throws NullArgumentException if either source or dest is null
+     */
+    public static void copy(Min source, Min dest)
+        throws NullArgumentException {
+        MathUtils.checkNotNull(source);
+        MathUtils.checkNotNull(dest);
+        dest.setData(source.getDataRef());
+        dest.n = source.n;
+        dest.value = source.value;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/rank/PSquarePercentile.java b/src/main/java/org/apache/commons/math3/stat/descriptive/rank/PSquarePercentile.java
new file mode 100644
index 0000000..b8bc274
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/rank/PSquarePercentile.java
@@ -0,0 +1,997 @@
+/*
+ * 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.math3.stat.descriptive.rank;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.analysis.interpolation.LinearInterpolator;
+import org.apache.commons.math3.analysis.interpolation.NevilleInterpolator;
+import org.apache.commons.math3.analysis.interpolation.UnivariateInterpolator;
+import org.apache.commons.math3.exception.InsufficientDataException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.stat.descriptive.AbstractStorelessUnivariateStatistic;
+import org.apache.commons.math3.stat.descriptive.StorelessUnivariateStatistic;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.util.MathUtils;
+import org.apache.commons.math3.util.Precision;
+
+/**
+ * A {@link StorelessUnivariateStatistic} estimating percentiles using the
+ * <ahref=http://www.cs.wustl.edu/~jain/papers/ftp/psqr.pdf>P<SUP>2</SUP></a>
+ * Algorithm as explained by <a href=http://www.cse.wustl.edu/~jain/>Raj
+ * Jain</a> and Imrich Chlamtac in
+ * <a href=http://www.cse.wustl.edu/~jain/papers/psqr.htm>P<SUP>2</SUP> Algorithm
+ * for Dynamic Calculation of Quantiles and Histogram Without Storing
+ * Observations</a>.
+ * <p>
+ * Note: This implementation is not synchronized and produces an approximate
+ * result. For small samples, where data can be stored and processed in memory,
+ * {@link Percentile} should be used.</p>
+ *
+ */
+public class PSquarePercentile extends AbstractStorelessUnivariateStatistic
+        implements StorelessUnivariateStatistic, Serializable {
+
+    /**
+     * The maximum array size used for psquare algorithm
+     */
+    private static final int PSQUARE_CONSTANT = 5;
+
+    /**
+     * A Default quantile needed in case if user prefers to use default no
+     * argument constructor.
+     */
+    private static final double DEFAULT_QUANTILE_DESIRED = 50d;
+
+    /**
+     * Serial ID
+     */
+    private static final long serialVersionUID = 2283912083175715479L;
+
+    /**
+     * A decimal formatter for print convenience
+     */
+    private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat(
+            "00.00");
+
+    /**
+     * Initial list of 5 numbers corresponding to 5 markers. <b>NOTE:</b>watch
+     * out for the add methods that are overloaded
+     */
+    private final List<Double> initialFive = new FixedCapacityList<Double>(
+            PSQUARE_CONSTANT);
+
+    /**
+     * The quantile needed should be in range of 0-1. The constructor
+     * {@link #PSquarePercentile(double)} ensures that passed in percentile is
+     * divided by 100.
+     */
+    private final double quantile;
+
+    /**
+     * lastObservation is the last observation value/input sample. No need to
+     * serialize
+     */
+    private transient double lastObservation;
+
+    /**
+     * Markers is the marker collection object which comes to effect
+     * only after 5 values are inserted
+     */
+    private PSquareMarkers markers = null;
+
+    /**
+     * Computed p value (i,e percentile value of data set hither to received)
+     */
+    private double pValue = Double.NaN;
+
+    /**
+     * Counter to count the values/observations accepted into this data set
+     */
+    private long countOfObservations;
+
+    /**
+     * Constructs a PSquarePercentile with the specific percentile value.
+     * @param p the percentile
+     * @throws OutOfRangeException  if p is not greater than 0 and less
+     * than or equal to 100
+     */
+    public PSquarePercentile(final double p) {
+        if (p > 100 || p < 0) {
+            throw new OutOfRangeException(LocalizedFormats.OUT_OF_RANGE,
+                    p, 0, 100);
+        }
+        this.quantile = p / 100d;// always set it within (0,1]
+    }
+
+    /**
+     * Default constructor that assumes a {@link #DEFAULT_QUANTILE_DESIRED
+     * default quantile} needed
+     */
+    PSquarePercentile() {
+        this(DEFAULT_QUANTILE_DESIRED);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int hashCode() {
+        double result = getResult();
+        result = Double.isNaN(result) ? 37 : result;
+        final double markersHash = markers == null ? 0 : markers.hashCode();
+        final double[] toHash = {result, quantile, markersHash, countOfObservations};
+        return Arrays.hashCode(toHash);
+    }
+
+    /**
+     * Returns true iff {@code o} is a {@code PSquarePercentile} returning the
+     * same values as this for {@code getResult()} and {@code getN()} and also
+     * having equal markers
+     *
+     * @param o object to compare
+     * @return true if {@code o} is a {@code PSquarePercentile} with
+     * equivalent internal state
+     */
+    @Override
+    public boolean equals(Object o) {
+        boolean result = false;
+        if (this == o) {
+            result = true;
+        } else if (o != null && o instanceof PSquarePercentile) {
+            PSquarePercentile that = (PSquarePercentile) o;
+            boolean isNotNull = markers != null && that.markers != null;
+            boolean isNull = markers == null && that.markers == null;
+            result = isNotNull ? markers.equals(that.markers) : isNull;
+            // markers as in the case of first
+            // five observations
+            result = result && getN() == that.getN();
+        }
+        return result;
+    }
+
+    /**
+     * {@inheritDoc}The internal state updated due to the new value in this
+     * context is basically of the marker positions and computation of the
+     * approximate quantile.
+     *
+     * @param observation the observation currently being added.
+     */
+    @Override
+    public void increment(final double observation) {
+        // Increment counter
+        countOfObservations++;
+
+        // Store last observation
+        this.lastObservation = observation;
+
+        // 0. Use Brute force for <5
+        if (markers == null) {
+            if (initialFive.add(observation)) {
+                Collections.sort(initialFive);
+                pValue =
+                        initialFive
+                                .get((int) (quantile * (initialFive.size() - 1)));
+                return;
+            }
+            // 1. Initialize once after 5th observation
+            markers = newMarkers(initialFive, quantile);
+        }
+        // 2. process a Data Point and return pValue
+        pValue = markers.processDataPoint(observation);
+    }
+
+    /**
+     * Returns a string containing the last observation, the current estimate
+     * of the quantile and all markers.
+     *
+     * @return string representation of state data
+     */
+    @Override
+    public String toString() {
+
+        if (markers == null) {
+            return String.format("obs=%s pValue=%s",
+                    DECIMAL_FORMAT.format(lastObservation),
+                    DECIMAL_FORMAT.format(pValue));
+        } else {
+            return String.format("obs=%s markers=%s",
+                    DECIMAL_FORMAT.format(lastObservation), markers.toString());
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getN() {
+        return countOfObservations;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public StorelessUnivariateStatistic copy() {
+        // multiply quantile by 100 now as anyway constructor divides it by 100
+        PSquarePercentile copy = new PSquarePercentile(100d * quantile);
+
+        if (markers != null) {
+            copy.markers = (PSquareMarkers) markers.clone();
+        }
+        copy.countOfObservations = countOfObservations;
+        copy.pValue = pValue;
+        copy.initialFive.clear();
+        copy.initialFive.addAll(initialFive);
+        return copy;
+    }
+
+    /**
+     * Returns the quantile estimated by this statistic in the range [0.0-1.0]
+     *
+     * @return quantile estimated by {@link #getResult()}
+     */
+    public double quantile() {
+        return quantile;
+    }
+
+    /**
+     * {@inheritDoc}. This basically clears all the markers, the
+     * initialFive list and sets countOfObservations to 0.
+     */
+    @Override
+    public void clear() {
+        markers = null;
+        initialFive.clear();
+        countOfObservations = 0L;
+        pValue = Double.NaN;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double getResult() {
+        if (Double.compare(quantile, 1d) == 0) {
+            pValue = maximum();
+        } else if (Double.compare(quantile, 0d) == 0) {
+            pValue = minimum();
+        }
+        return pValue;
+    }
+
+    /**
+     * @return maximum in the data set added to this statistic
+     */
+    private double maximum() {
+        double val = Double.NaN;
+        if (markers != null) {
+            val = markers.height(PSQUARE_CONSTANT);
+        } else if (!initialFive.isEmpty()) {
+            val = initialFive.get(initialFive.size() - 1);
+        }
+        return val;
+    }
+
+    /**
+     * @return minimum in the data set added to this statistic
+     */
+    private double minimum() {
+        double val = Double.NaN;
+        if (markers != null) {
+            val = markers.height(1);
+        } else if (!initialFive.isEmpty()) {
+            val = initialFive.get(0);
+        }
+        return val;
+    }
+
+    /**
+     * Markers is an encapsulation of the five markers/buckets as indicated in
+     * the original works.
+     */
+    private static class Markers implements PSquareMarkers, Serializable {
+        /**
+         * Serial version id
+         */
+        private static final long serialVersionUID = 1L;
+
+        /** Low marker index */
+        private static final int LOW = 2;
+
+        /** High marker index */
+        private static final int HIGH = 4;
+
+        /**
+         * Array of 5+1 Markers (The first marker is dummy just so we
+         * can match the rest of indexes [1-5] indicated in the original works
+         * which follows unit based index)
+         */
+        private final Marker[] markerArray;
+
+        /**
+         * Kth cell belonging to [1-5] of the markerArray. No need for
+         * this to be serialized
+         */
+        private transient int k = -1;
+
+        /**
+         * Constructor
+         *
+         * @param theMarkerArray marker array to be used
+         */
+        private Markers(final Marker[] theMarkerArray) {
+            MathUtils.checkNotNull(theMarkerArray);
+            markerArray = theMarkerArray;
+            for (int i = 1; i < PSQUARE_CONSTANT; i++) {
+                markerArray[i].previous(markerArray[i - 1])
+                        .next(markerArray[i + 1]).index(i);
+            }
+            markerArray[0].previous(markerArray[0]).next(markerArray[1])
+                    .index(0);
+            markerArray[5].previous(markerArray[4]).next(markerArray[5])
+                    .index(5);
+        }
+
+        /**
+         * Constructor
+         *
+         * @param initialFive elements required to build Marker
+         * @param p quantile required to be computed
+         */
+        private Markers(final List<Double> initialFive, final double p) {
+            this(createMarkerArray(initialFive, p));
+        }
+
+        /**
+         * Creates a marker array using initial five elements and a quantile
+         *
+         * @param initialFive list of initial five elements
+         * @param p the pth quantile
+         * @return Marker array
+         */
+        private static Marker[] createMarkerArray(
+                final List<Double> initialFive, final double p) {
+            final int countObserved =
+                    initialFive == null ? -1 : initialFive.size();
+            if (countObserved < PSQUARE_CONSTANT) {
+                throw new InsufficientDataException(
+                        LocalizedFormats.INSUFFICIENT_OBSERVED_POINTS_IN_SAMPLE,
+                        countObserved, PSQUARE_CONSTANT);
+            }
+            Collections.sort(initialFive);
+            return new Marker[] {
+                    new Marker(),// Null Marker
+                    new Marker(initialFive.get(0), 1, 0, 1),
+                    new Marker(initialFive.get(1), 1 + 2 * p, p / 2, 2),
+                    new Marker(initialFive.get(2), 1 + 4 * p, p, 3),
+                    new Marker(initialFive.get(3), 3 + 2 * p, (1 + p) / 2, 4),
+                    new Marker(initialFive.get(4), 5, 1, 5) };
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public int hashCode() {
+            return Arrays.deepHashCode(markerArray);
+        }
+
+        /**
+         * {@inheritDoc}.This equals method basically checks for marker array to
+         * be deep equals.
+         *
+         * @param o is the other object
+         * @return true if the object compares with this object are equivalent
+         */
+        @Override
+        public boolean equals(Object o) {
+            boolean result = false;
+            if (this == o) {
+                result = true;
+            } else if (o != null && o instanceof Markers) {
+                Markers that = (Markers) o;
+                result = Arrays.deepEquals(markerArray, that.markerArray);
+            }
+            return result;
+        }
+
+        /**
+         * Process a data point
+         *
+         * @param inputDataPoint is the data point passed
+         * @return computed percentile
+         */
+        public double processDataPoint(final double inputDataPoint) {
+
+            // 1. Find cell and update minima and maxima
+            final int kthCell = findCellAndUpdateMinMax(inputDataPoint);
+
+            // 2. Increment positions
+            incrementPositions(1, kthCell + 1, 5);
+
+            // 2a. Update desired position with increments
+            updateDesiredPositions();
+
+            // 3. Adjust heights of m[2-4] if necessary
+            adjustHeightsOfMarkers();
+
+            // 4. Return percentile
+            return getPercentileValue();
+        }
+
+        /**
+         * Returns the percentile computed thus far.
+         *
+         * @return height of mid point marker
+         */
+        public double getPercentileValue() {
+            return height(3);
+        }
+
+        /**
+         * Finds the cell where the input observation / value fits.
+         *
+         * @param observation the input value to be checked for
+         * @return kth cell (of the markers ranging from 1-5) where observed
+         *         sample fits
+         */
+        private int findCellAndUpdateMinMax(final double observation) {
+            k = -1;
+            if (observation < height(1)) {
+                markerArray[1].markerHeight = observation;
+                k = 1;
+            } else if (observation < height(2)) {
+                k = 1;
+            } else if (observation < height(3)) {
+                k = 2;
+            } else if (observation < height(4)) {
+                k = 3;
+            } else if (observation <= height(5)) {
+                k = 4;
+            } else {
+                markerArray[5].markerHeight = observation;
+                k = 4;
+            }
+            return k;
+        }
+
+        /**
+         * Adjust marker heights by setting quantile estimates to middle markers.
+         */
+        private void adjustHeightsOfMarkers() {
+            for (int i = LOW; i <= HIGH; i++) {
+                estimate(i);
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public double estimate(final int index) {
+            if (index < LOW || index > HIGH) {
+                throw new OutOfRangeException(index, LOW, HIGH);
+            }
+            return markerArray[index].estimate();
+        }
+
+        /**
+         * Increment positions by d. Refer to algorithm paper for the
+         * definition of d.
+         *
+         * @param d The increment value for the position
+         * @param startIndex start index of the marker array
+         * @param endIndex end index of the marker array
+         */
+        private void incrementPositions(final int d, final int startIndex,
+                final int endIndex) {
+            for (int i = startIndex; i <= endIndex; i++) {
+                markerArray[i].incrementPosition(d);
+            }
+        }
+
+        /**
+         * Desired positions incremented by bucket width. The bucket width is
+         * basically the desired increments.
+         */
+        private void updateDesiredPositions() {
+            for (int i = 1; i < markerArray.length; i++) {
+                markerArray[i].updateDesiredPosition();
+            }
+        }
+
+        /**
+         * Sets previous and next markers after default read is done.
+         *
+         * @param anInputStream the input stream to be deserialized
+         * @throws ClassNotFoundException thrown when a desired class not found
+         * @throws IOException thrown due to any io errors
+         */
+        private void readObject(ObjectInputStream anInputStream)
+                throws ClassNotFoundException, IOException {
+            // always perform the default de-serialization first
+            anInputStream.defaultReadObject();
+            // Build links
+            for (int i = 1; i < PSQUARE_CONSTANT; i++) {
+                markerArray[i].previous(markerArray[i - 1])
+                        .next(markerArray[i + 1]).index(i);
+            }
+            markerArray[0].previous(markerArray[0]).next(markerArray[1])
+                    .index(0);
+            markerArray[5].previous(markerArray[4]).next(markerArray[5])
+                    .index(5);
+        }
+
+        /**
+         * Return marker height given index
+         *
+         * @param markerIndex index of marker within (1,6)
+         * @return marker height
+         */
+        public double height(final int markerIndex) {
+            if (markerIndex >= markerArray.length || markerIndex <= 0) {
+                throw new OutOfRangeException(markerIndex, 1,
+                        markerArray.length);
+            }
+            return markerArray[markerIndex].markerHeight;
+        }
+
+        /**
+         * {@inheritDoc}.Clone Markers
+         *
+         * @return cloned object
+         */
+        @Override
+        public Object clone() {
+            return new Markers(new Marker[] { new Marker(),
+                    (Marker) markerArray[1].clone(),
+                    (Marker) markerArray[2].clone(),
+                    (Marker) markerArray[3].clone(),
+                    (Marker) markerArray[4].clone(),
+                    (Marker) markerArray[5].clone() });
+
+        }
+
+        /**
+         * Returns string representation of the Marker array.
+         *
+         * @return Markers as a string
+         */
+        @Override
+        public String toString() {
+            return String.format("m1=[%s],m2=[%s],m3=[%s],m4=[%s],m5=[%s]",
+                    markerArray[1].toString(), markerArray[2].toString(),
+                    markerArray[3].toString(), markerArray[4].toString(),
+                    markerArray[5].toString());
+        }
+
+    }
+
+    /**
+     * The class modeling the attributes of the marker of the P-square algorithm
+     */
+    private static class Marker implements Serializable, Cloneable {
+
+        /**
+         * Serial Version ID
+         */
+        private static final long serialVersionUID = -3575879478288538431L;
+
+        /**
+         * The marker index which is just a serial number for the marker in the
+         * marker array of 5+1.
+         */
+        private int index;
+
+        /**
+         * The integral marker position. Refer to the variable n in the original
+         * works.
+         */
+        private double intMarkerPosition;
+
+        /**
+         * Desired marker position. Refer to the variable n' in the original
+         * works.
+         */
+        private double desiredMarkerPosition;
+
+        /**
+         * Marker height or the quantile. Refer to the variable q in the
+         * original works.
+         */
+        private double markerHeight;
+
+        /**
+         * Desired marker increment. Refer to the variable dn' in the original
+         * works.
+         */
+        private double desiredMarkerIncrement;
+
+        /**
+         * Next and previous markers for easy linked navigation in loops. this
+         * is not serialized as they can be rebuilt during deserialization.
+         */
+        private transient Marker next;
+
+        /**
+         * The previous marker links
+         */
+        private transient Marker previous;
+
+        /**
+         * Nonlinear interpolator
+         */
+        private final UnivariateInterpolator nonLinear =
+                new NevilleInterpolator();
+
+        /**
+         * Linear interpolator which is not serializable
+         */
+        private transient UnivariateInterpolator linear =
+                new LinearInterpolator();
+
+        /**
+         * Default constructor
+         */
+        private Marker() {
+            this.next = this.previous = this;
+        }
+
+        /**
+         * Constructor of the marker with parameters
+         *
+         * @param heightOfMarker represent the quantile value
+         * @param makerPositionDesired represent the desired marker position
+         * @param markerPositionIncrement represent increments for position
+         * @param markerPositionNumber represent the position number of marker
+         */
+        private Marker(double heightOfMarker, double makerPositionDesired,
+                double markerPositionIncrement, double markerPositionNumber) {
+            this();
+            this.markerHeight = heightOfMarker;
+            this.desiredMarkerPosition = makerPositionDesired;
+            this.desiredMarkerIncrement = markerPositionIncrement;
+            this.intMarkerPosition = markerPositionNumber;
+        }
+
+        /**
+         * Sets the previous marker.
+         *
+         * @param previousMarker the previous marker to the current marker in
+         *            the array of markers
+         * @return this instance
+         */
+        private Marker previous(final Marker previousMarker) {
+            MathUtils.checkNotNull(previousMarker);
+            this.previous = previousMarker;
+            return this;
+        }
+
+        /**
+         * Sets the next marker.
+         *
+         * @param nextMarker the next marker to the current marker in the array
+         *            of markers
+         * @return this instance
+         */
+        private Marker next(final Marker nextMarker) {
+            MathUtils.checkNotNull(nextMarker);
+            this.next = nextMarker;
+            return this;
+        }
+
+        /**
+         * Sets the index of the marker.
+         *
+         * @param indexOfMarker the array index of the marker in marker array
+         * @return this instance
+         */
+        private Marker index(final int indexOfMarker) {
+            this.index = indexOfMarker;
+            return this;
+        }
+
+        /**
+         * Update desired Position with increment.
+         */
+        private void updateDesiredPosition() {
+            desiredMarkerPosition += desiredMarkerIncrement;
+        }
+
+        /**
+         * Increment Position by d.
+         *
+         * @param d a delta value to increment
+         */
+        private void incrementPosition(final int d) {
+            intMarkerPosition += d;
+        }
+
+        /**
+         * Difference between desired and actual position
+         *
+         * @return difference between desired and actual position
+         */
+        private double difference() {
+            return desiredMarkerPosition - intMarkerPosition;
+        }
+
+        /**
+         * Estimate the quantile for the current marker.
+         *
+         * @return estimated quantile
+         */
+        private double estimate() {
+            final double di = difference();
+            final boolean isNextHigher =
+                    next.intMarkerPosition - intMarkerPosition > 1;
+            final boolean isPreviousLower =
+                    previous.intMarkerPosition - intMarkerPosition < -1;
+
+            if (di >= 1 && isNextHigher || di <= -1 && isPreviousLower) {
+                final int d = di >= 0 ? 1 : -1;
+                final double[] xval =
+                        new double[] { previous.intMarkerPosition,
+                                intMarkerPosition, next.intMarkerPosition };
+                final double[] yval =
+                        new double[] { previous.markerHeight, markerHeight,
+                                next.markerHeight };
+                final double xD = intMarkerPosition + d;
+
+                UnivariateFunction univariateFunction =
+                        nonLinear.interpolate(xval, yval);
+                markerHeight = univariateFunction.value(xD);
+
+                // If parabolic estimate is bad then turn linear
+                if (isEstimateBad(yval, markerHeight)) {
+                    int delta = xD - xval[1] > 0 ? 1 : -1;
+                    final double[] xBad =
+                            new double[] { xval[1], xval[1 + delta] };
+                    final double[] yBad =
+                            new double[] { yval[1], yval[1 + delta] };
+                    MathArrays.sortInPlace(xBad, yBad);// since d can be +/- 1
+                    univariateFunction = linear.interpolate(xBad, yBad);
+                    markerHeight = univariateFunction.value(xD);
+                }
+                incrementPosition(d);
+            }
+            return markerHeight;
+        }
+
+        /**
+         * Check if parabolic/nonlinear estimate is bad by checking if the
+         * ordinate found is beyond the y[0] and y[2].
+         *
+         * @param y the array to get the bounds
+         * @param yD the estimate
+         * @return true if yD is a bad estimate
+         */
+        private boolean isEstimateBad(final double[] y, final double yD) {
+            return yD <= y[0] || yD >= y[2];
+        }
+
+        /**
+         * {@inheritDoc}<i>This equals method checks for marker attributes and
+         * as well checks if navigation pointers (next and previous) are the same
+         * between this and passed in object</i>
+         *
+         * @param o Other object
+         * @return true if this equals passed in other object o
+         */
+        @Override
+        public boolean equals(Object o) {
+            boolean result = false;
+            if (this == o) {
+                result = true;
+            } else if (o != null && o instanceof Marker) {
+                Marker that = (Marker) o;
+
+                result = Double.compare(markerHeight, that.markerHeight) == 0;
+                result =
+                        result &&
+                                Double.compare(intMarkerPosition,
+                                        that.intMarkerPosition) == 0;
+                result =
+                        result &&
+                                Double.compare(desiredMarkerPosition,
+                                        that.desiredMarkerPosition) == 0;
+                result =
+                        result &&
+                                Double.compare(desiredMarkerIncrement,
+                                        that.desiredMarkerIncrement) == 0;
+
+                result = result && next.index == that.next.index;
+                result = result && previous.index == that.previous.index;
+            }
+            return result;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public int hashCode() {
+            return Arrays.hashCode(new double[] {markerHeight, intMarkerPosition,
+                desiredMarkerIncrement, desiredMarkerPosition, previous.index, next.index});
+        }
+
+        /**
+         * Read Object to deserialize.
+         *
+         * @param anInstream Stream Object data
+         * @throws IOException thrown for IO Errors
+         * @throws ClassNotFoundException thrown for class not being found
+         */
+        private void readObject(ObjectInputStream anInstream)
+                throws ClassNotFoundException, IOException {
+            anInstream.defaultReadObject();
+            previous=next=this;
+            linear = new LinearInterpolator();
+        }
+
+        /**
+         * Clone this instance.
+         *
+         * @return cloned marker
+         */
+        @Override
+        public Object clone() {
+            return new Marker(markerHeight, desiredMarkerPosition,
+                    desiredMarkerIncrement, intMarkerPosition);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public String toString() {
+            return String.format(
+                    "index=%.0f,n=%.0f,np=%.2f,q=%.2f,dn=%.2f,prev=%d,next=%d",
+                    (double) index, Precision.round(intMarkerPosition, 0),
+                    Precision.round(desiredMarkerPosition, 2),
+                    Precision.round(markerHeight, 2),
+                    Precision.round(desiredMarkerIncrement, 2), previous.index,
+                    next.index);
+        }
+    }
+
+    /**
+     * A simple fixed capacity list that has an upper bound to growth.
+     * Once its capacity is reached, {@code add} is a no-op, returning
+     * {@code false}.
+     *
+     * @param <E>
+     */
+    private static class FixedCapacityList<E> extends ArrayList<E> implements
+            Serializable {
+        /**
+         * Serialization Version Id
+         */
+        private static final long serialVersionUID = 2283952083075725479L;
+        /**
+         * Capacity of the list
+         */
+        private final int capacity;
+
+        /**
+         * This constructor constructs the list with given capacity and as well
+         * as stores the capacity
+         *
+         * @param fixedCapacity the capacity to be fixed for this list
+         */
+        FixedCapacityList(final int fixedCapacity) {
+            super(fixedCapacity);
+            this.capacity = fixedCapacity;
+        }
+
+        /**
+         * {@inheritDoc} In addition it checks if the {@link #size()} returns a
+         * size that is within capacity and if true it adds; otherwise the list
+         * contents are unchanged and {@code false} is returned.
+         *
+         * @return true if addition is successful and false otherwise
+         */
+        @Override
+        public boolean add(final E e) {
+            return size() < capacity ? super.add(e) : false;
+        }
+
+        /**
+         * {@inheritDoc} In addition it checks if the sum of Collection size and
+         * this instance's {@link #size()} returns a value that is within
+         * capacity and if true it adds the collection; otherwise the list
+         * contents are unchanged and {@code false} is returned.
+         *
+         * @return true if addition is successful and false otherwise
+         */
+        @Override
+        public boolean addAll(Collection<? extends E> collection) {
+            boolean isCollectionLess =
+                    collection != null &&
+                            collection.size() + size() <= capacity;
+            return isCollectionLess ? super.addAll(collection) : false;
+        }
+    }
+
+    /**
+     * A creation method to build Markers
+     *
+     * @param initialFive list of initial five elements
+     * @param p the quantile desired
+     * @return an instance of PSquareMarkers
+     */
+    public static PSquareMarkers newMarkers(final List<Double> initialFive,
+            final double p) {
+        return new Markers(initialFive, p);
+    }
+
+    /**
+     * An interface that encapsulates abstractions of the
+     * P-square algorithm markers as is explained in the original works. This
+     * interface is exposed with protected access to help in testability.
+     */
+    protected interface PSquareMarkers extends Cloneable {
+        /**
+         * Returns Percentile value computed thus far.
+         *
+         * @return percentile
+         */
+        double getPercentileValue();
+
+        /**
+         * A clone function to clone the current instance. It's created as an
+         * interface method as well for convenience though Cloneable is just a
+         * marker interface.
+         *
+         * @return clone of this instance
+         */
+        Object clone();
+
+        /**
+         * Returns the marker height (or percentile) of a given marker index.
+         *
+         * @param markerIndex is the index of marker in the marker array
+         * @return percentile value of the marker index passed
+         * @throws OutOfRangeException in case the index is not within [1-5]
+         */
+        double height(final int markerIndex);
+
+        /**
+         * Process a data point by moving the marker heights based on estimator.
+         *
+         * @param inputDataPoint is the data point passed
+         * @return computed percentile
+         */
+        double processDataPoint(final double inputDataPoint);
+
+        /**
+         * An Estimate of the percentile value of a given Marker
+         *
+         * @param index the marker's index in the array of markers
+         * @return percentile estimate
+         * @throws OutOfRangeException in case if index is not within [1-5]
+         */
+        double estimate(final int index);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/rank/Percentile.java b/src/main/java/org/apache/commons/math3/stat/descriptive/rank/Percentile.java
new file mode 100644
index 0000000..bba9e7c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/rank/Percentile.java
@@ -0,0 +1,1072 @@
+/*
+ * 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.math3.stat.descriptive.rank;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.BitSet;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.MathUnsupportedOperationException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.stat.descriptive.AbstractUnivariateStatistic;
+import org.apache.commons.math3.stat.ranking.NaNStrategy;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.KthSelector;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.util.MathUtils;
+import org.apache.commons.math3.util.MedianOf3PivotingStrategy;
+import org.apache.commons.math3.util.PivotingStrategyInterface;
+import org.apache.commons.math3.util.Precision;
+
+/**
+ * Provides percentile computation.
+ * <p>
+ * There are several commonly used methods for estimating percentiles (a.k.a.
+ * quantiles) based on sample data.  For large samples, the different methods
+ * agree closely, but when sample sizes are small, different methods will give
+ * significantly different results.  The algorithm implemented here works as follows:
+ * <ol>
+ * <li>Let <code>n</code> be the length of the (sorted) array and
+ * <code>0 < p <= 100</code> be the desired percentile.</li>
+ * <li>If <code> n = 1 </code> return the unique array element (regardless of
+ * the value of <code>p</code>); otherwise </li>
+ * <li>Compute the estimated percentile position
+ * <code> pos = p * (n + 1) / 100</code> and the difference, <code>d</code>
+ * between <code>pos</code> and <code>floor(pos)</code> (i.e. the fractional
+ * part of <code>pos</code>).</li>
+ * <li> If <code>pos < 1</code> return the smallest element in the array.</li>
+ * <li> Else if <code>pos >= n</code> return the largest element in the array.</li>
+ * <li> Else let <code>lower</code> be the element in position
+ * <code>floor(pos)</code> in the array and let <code>upper</code> be the
+ * next element in the array.  Return <code>lower + d * (upper - lower)</code>
+ * </li>
+ * </ol></p>
+ * <p>
+ * To compute percentiles, the data must be at least partially ordered.  Input
+ * arrays are copied and recursively partitioned using an ordering definition.
+ * The ordering used by <code>Arrays.sort(double[])</code> is the one determined
+ * by {@link java.lang.Double#compareTo(Double)}.  This ordering makes
+ * <code>Double.NaN</code> larger than any other value (including
+ * <code>Double.POSITIVE_INFINITY</code>).  Therefore, for example, the median
+ * (50th percentile) of
+ * <code>{0, 1, 2, 3, 4, Double.NaN}</code> evaluates to <code>2.5.</code></p>
+ * <p>
+ * Since percentile estimation usually involves interpolation between array
+ * elements, arrays containing  <code>NaN</code> or infinite values will often
+ * result in <code>NaN</code> or infinite values returned.</p>
+ * <p>
+ * Further, to include different estimation types such as R1, R2 as mentioned in
+ * <a href="http://en.wikipedia.org/wiki/Quantile">Quantile page(wikipedia)</a>,
+ * a type specific NaN handling strategy is used to closely match with the
+ * typically observed results from popular tools like R(R1-R9), Excel(R7).</p>
+ * <p>
+ * Since 2.2, Percentile uses only selection instead of complete sorting
+ * and caches selection algorithm state between calls to the various
+ * {@code evaluate} methods. This greatly improves efficiency, both for a single
+ * percentile and multiple percentile computations. To maximize performance when
+ * multiple percentiles are computed based on the same data, users should set the
+ * data array once using either one of the {@link #evaluate(double[], double)} or
+ * {@link #setData(double[])} methods and thereafter {@link #evaluate(double)}
+ * with just the percentile provided.
+ * </p>
+ * <p>
+ * <strong>Note that this implementation is not synchronized.</strong> If
+ * multiple threads access an instance of this class concurrently, and at least
+ * one of the threads invokes the <code>increment()</code> or
+ * <code>clear()</code> method, it must be synchronized externally.</p>
+ *
+ */
+public class Percentile extends AbstractUnivariateStatistic implements Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -8091216485095130416L;
+
+    /** Maximum number of partitioning pivots cached (each level double the number of pivots). */
+    private static final int MAX_CACHED_LEVELS = 10;
+
+    /** Maximum number of cached pivots in the pivots cached array */
+    private static final int PIVOTS_HEAP_LENGTH = 0x1 << MAX_CACHED_LEVELS - 1;
+
+    /** Default KthSelector used with default pivoting strategy */
+    private final KthSelector kthSelector;
+
+    /** Any of the {@link EstimationType}s such as {@link EstimationType#LEGACY CM} can be used. */
+    private final EstimationType estimationType;
+
+    /** NaN Handling of the input as defined by {@link NaNStrategy} */
+    private final NaNStrategy nanStrategy;
+
+    /** Determines what percentile is computed when evaluate() is activated
+     * with no quantile argument */
+    private double quantile;
+
+    /** Cached pivots. */
+    private int[] cachedPivots;
+
+    /**
+     * Constructs a Percentile with the following defaults.
+     * <ul>
+     *   <li>default quantile: 50.0, can be reset with {@link #setQuantile(double)}</li>
+     *   <li>default estimation type: {@link EstimationType#LEGACY},
+     *   can be reset with {@link #withEstimationType(EstimationType)}</li>
+     *   <li>default NaN strategy: {@link NaNStrategy#REMOVED},
+     *   can be reset with {@link #withNaNStrategy(NaNStrategy)}</li>
+     *   <li>a KthSelector that makes use of {@link MedianOf3PivotingStrategy},
+     *   can be reset with {@link #withKthSelector(KthSelector)}</li>
+     * </ul>
+     */
+    public Percentile() {
+        // No try-catch or advertised exception here - arg is valid
+        this(50.0);
+    }
+
+    /**
+     * Constructs a Percentile with the specific quantile value and the following
+     * <ul>
+     *   <li>default method type: {@link EstimationType#LEGACY}</li>
+     *   <li>default NaN strategy: {@link NaNStrategy#REMOVED}</li>
+     *   <li>a Kth Selector : {@link KthSelector}</li>
+     * </ul>
+     * @param quantile the quantile
+     * @throws MathIllegalArgumentException  if p is not greater than 0 and less
+     * than or equal to 100
+     */
+    public Percentile(final double quantile) throws MathIllegalArgumentException {
+        this(quantile, EstimationType.LEGACY, NaNStrategy.REMOVED,
+             new KthSelector(new MedianOf3PivotingStrategy()));
+    }
+
+    /**
+     * Copy constructor, creates a new {@code Percentile} identical
+     * to the {@code original}
+     *
+     * @param original the {@code Percentile} instance to copy
+     * @throws NullArgumentException if original is null
+     */
+    public Percentile(final Percentile original) throws NullArgumentException {
+
+        MathUtils.checkNotNull(original);
+        estimationType   = original.getEstimationType();
+        nanStrategy      = original.getNaNStrategy();
+        kthSelector      = original.getKthSelector();
+
+        setData(original.getDataRef());
+        if (original.cachedPivots != null) {
+            System.arraycopy(original.cachedPivots, 0, cachedPivots, 0, original.cachedPivots.length);
+        }
+        setQuantile(original.quantile);
+
+    }
+
+    /**
+     * Constructs a Percentile with the specific quantile value,
+     * {@link EstimationType}, {@link NaNStrategy} and {@link KthSelector}.
+     *
+     * @param quantile the quantile to be computed
+     * @param estimationType one of the percentile {@link EstimationType  estimation types}
+     * @param nanStrategy one of {@link NaNStrategy} to handle with NaNs
+     * @param kthSelector a {@link KthSelector} to use for pivoting during search
+     * @throws MathIllegalArgumentException if p is not within (0,100]
+     * @throws NullArgumentException if type or NaNStrategy passed is null
+     */
+    protected Percentile(final double quantile,
+                         final EstimationType estimationType,
+                         final NaNStrategy nanStrategy,
+                         final KthSelector kthSelector)
+        throws MathIllegalArgumentException {
+        setQuantile(quantile);
+        cachedPivots = null;
+        MathUtils.checkNotNull(estimationType);
+        MathUtils.checkNotNull(nanStrategy);
+        MathUtils.checkNotNull(kthSelector);
+        this.estimationType = estimationType;
+        this.nanStrategy = nanStrategy;
+        this.kthSelector = kthSelector;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setData(final double[] values) {
+        if (values == null) {
+            cachedPivots = null;
+        } else {
+            cachedPivots = new int[PIVOTS_HEAP_LENGTH];
+            Arrays.fill(cachedPivots, -1);
+        }
+        super.setData(values);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setData(final double[] values, final int begin, final int length)
+    throws MathIllegalArgumentException {
+        if (values == null) {
+            cachedPivots = null;
+        } else {
+            cachedPivots = new int[PIVOTS_HEAP_LENGTH];
+            Arrays.fill(cachedPivots, -1);
+        }
+        super.setData(values, begin, length);
+    }
+
+    /**
+     * Returns the result of evaluating the statistic over the stored data.
+     * <p>
+     * The stored array is the one which was set by previous calls to
+     * {@link #setData(double[])}
+     * </p>
+     * @param p the percentile value to compute
+     * @return the value of the statistic applied to the stored data
+     * @throws MathIllegalArgumentException if p is not a valid quantile value
+     * (p must be greater than 0 and less than or equal to 100)
+     */
+    public double evaluate(final double p) throws MathIllegalArgumentException {
+        return evaluate(getDataRef(), p);
+    }
+
+    /**
+     * Returns an estimate of the <code>p</code>th percentile of the values
+     * in the <code>values</code> array.
+     * <p>
+     * Calls to this method do not modify the internal <code>quantile</code>
+     * state of this statistic.</p>
+     * <p>
+     * <ul>
+     * <li>Returns <code>Double.NaN</code> if <code>values</code> has length
+     * <code>0</code></li>
+     * <li>Returns (for any value of <code>p</code>) <code>values[0]</code>
+     *  if <code>values</code> has length <code>1</code></li>
+     * <li>Throws <code>MathIllegalArgumentException</code> if <code>values</code>
+     * is null or p is not a valid quantile value (p must be greater than 0
+     * and less than or equal to 100) </li>
+     * </ul></p>
+     * <p>
+     * See {@link Percentile} for a description of the percentile estimation
+     * algorithm used.</p>
+     *
+     * @param values input array of values
+     * @param p the percentile value to compute
+     * @return the percentile value or Double.NaN if the array is empty
+     * @throws MathIllegalArgumentException if <code>values</code> is null
+     *     or p is invalid
+     */
+    public double evaluate(final double[] values, final double p)
+    throws MathIllegalArgumentException {
+        test(values, 0, 0);
+        return evaluate(values, 0, values.length, p);
+    }
+
+    /**
+     * Returns an estimate of the <code>quantile</code>th percentile of the
+     * designated values in the <code>values</code> array.  The quantile
+     * estimated is determined by the <code>quantile</code> property.
+     * <p>
+     * <ul>
+     * <li>Returns <code>Double.NaN</code> if <code>length = 0</code></li>
+     * <li>Returns (for any value of <code>quantile</code>)
+     * <code>values[begin]</code> if <code>length = 1 </code></li>
+     * <li>Throws <code>MathIllegalArgumentException</code> if <code>values</code>
+     * is null, or <code>start</code> or <code>length</code> is invalid</li>
+     * </ul></p>
+     * <p>
+     * See {@link Percentile} for a description of the percentile estimation
+     * algorithm used.</p>
+     *
+     * @param values the input array
+     * @param start index of the first array element to include
+     * @param length the number of elements to include
+     * @return the percentile value
+     * @throws MathIllegalArgumentException if the parameters are not valid
+     *
+     */
+    @Override
+    public double evaluate(final double[] values, final int start, final int length)
+    throws MathIllegalArgumentException {
+        return evaluate(values, start, length, quantile);
+    }
+
+     /**
+     * Returns an estimate of the <code>p</code>th percentile of the values
+     * in the <code>values</code> array, starting with the element in (0-based)
+     * position <code>begin</code> in the array and including <code>length</code>
+     * values.
+     * <p>
+     * Calls to this method do not modify the internal <code>quantile</code>
+     * state of this statistic.</p>
+     * <p>
+     * <ul>
+     * <li>Returns <code>Double.NaN</code> if <code>length = 0</code></li>
+     * <li>Returns (for any value of <code>p</code>) <code>values[begin]</code>
+     *  if <code>length = 1 </code></li>
+     * <li>Throws <code>MathIllegalArgumentException</code> if <code>values</code>
+     *  is null , <code>begin</code> or <code>length</code> is invalid, or
+     * <code>p</code> is not a valid quantile value (p must be greater than 0
+     * and less than or equal to 100)</li>
+     * </ul></p>
+     * <p>
+     * See {@link Percentile} for a description of the percentile estimation
+     * algorithm used.</p>
+     *
+     * @param values array of input values
+     * @param p  the percentile to compute
+     * @param begin  the first (0-based) element to include in the computation
+     * @param length  the number of array elements to include
+     * @return  the percentile value
+     * @throws MathIllegalArgumentException if the parameters are not valid or the
+     * input array is null
+     */
+    public double evaluate(final double[] values, final int begin,
+                           final int length, final double p)
+        throws MathIllegalArgumentException {
+
+        test(values, begin, length);
+        if (p > 100 || p <= 0) {
+            throw new OutOfRangeException(
+                    LocalizedFormats.OUT_OF_BOUNDS_QUANTILE_VALUE, p, 0, 100);
+        }
+        if (length == 0) {
+            return Double.NaN;
+        }
+        if (length == 1) {
+            return values[begin]; // always return single value for n = 1
+        }
+
+        final double[] work = getWorkArray(values, begin, length);
+        final int[] pivotsHeap = getPivots(values);
+        return work.length == 0 ? Double.NaN :
+                    estimationType.evaluate(work, pivotsHeap, p, kthSelector);
+    }
+
+    /** Select a pivot index as the median of three
+     * <p>
+     * <b>Note:</b> With the effect of allowing {@link KthSelector} to be set on
+     * {@link Percentile} instances(thus indirectly {@link PivotingStrategy})
+     * this method wont take effect any more and hence is unsupported.
+     * @param work data array
+     * @param begin index of the first element of the slice
+     * @param end index after the last element of the slice
+     * @return the index of the median element chosen between the
+     * first, the middle and the last element of the array slice
+     * @deprecated Please refrain from using this method (as it wont take effect)
+     * and instead use {@link Percentile#withKthSelector(newKthSelector)} if
+     * required.
+     *
+     */
+    @Deprecated
+    int medianOf3(final double[] work, final int begin, final int end) {
+        return new MedianOf3PivotingStrategy().pivotIndex(work, begin, end);
+        //throw new MathUnsupportedOperationException();
+    }
+
+    /**
+     * Returns the value of the quantile field (determines what percentile is
+     * computed when evaluate() is called with no quantile argument).
+     *
+     * @return quantile set while construction or {@link #setQuantile(double)}
+     */
+    public double getQuantile() {
+        return quantile;
+    }
+
+    /**
+     * Sets the value of the quantile field (determines what percentile is
+     * computed when evaluate() is called with no quantile argument).
+     *
+     * @param p a value between 0 < p <= 100
+     * @throws MathIllegalArgumentException  if p is not greater than 0 and less
+     * than or equal to 100
+     */
+    public void setQuantile(final double p) throws MathIllegalArgumentException {
+        if (p <= 0 || p > 100) {
+            throw new OutOfRangeException(
+                    LocalizedFormats.OUT_OF_BOUNDS_QUANTILE_VALUE, p, 0, 100);
+        }
+        quantile = p;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Percentile copy() {
+        return new Percentile(this);
+    }
+
+    /**
+     * Copies source to dest.
+     * @param source Percentile to copy
+     * @param dest Percentile to copy to
+     * @exception MathUnsupportedOperationException always thrown since 3.4
+     * @deprecated as of 3.4 this method does not work anymore, as it fails to
+     * copy internal states between instances configured with different
+     * {@link EstimationType estimation type}, {@link NaNStrategy NaN handling strategies}
+     * and {@link KthSelector kthSelector}, it therefore always
+     * throw {@link MathUnsupportedOperationException}
+      */
+    @Deprecated
+    public static void copy(final Percentile source, final Percentile dest)
+        throws MathUnsupportedOperationException {
+        throw new MathUnsupportedOperationException();
+    }
+
+    /**
+     * Get the work array to operate. Makes use of prior {@code storedData} if
+     * it exists or else do a check on NaNs and copy a subset of the array
+     * defined by begin and length parameters. The set {@link #nanStrategy} will
+     * be used to either retain/remove/replace any NaNs present before returning
+     * the resultant array.
+     *
+     * @param values the array of numbers
+     * @param begin index to start reading the array
+     * @param length the length of array to be read from the begin index
+     * @return work array sliced from values in the range [begin,begin+length)
+     * @throws MathIllegalArgumentException if values or indices are invalid
+     */
+    protected double[] getWorkArray(final double[] values, final int begin, final int length) {
+            final double[] work;
+            if (values == getDataRef()) {
+                work = getDataRef();
+            } else {
+                switch (nanStrategy) {
+                case MAXIMAL:// Replace NaNs with +INFs
+                    work = replaceAndSlice(values, begin, length, Double.NaN, Double.POSITIVE_INFINITY);
+                    break;
+                case MINIMAL:// Replace NaNs with -INFs
+                    work = replaceAndSlice(values, begin, length, Double.NaN, Double.NEGATIVE_INFINITY);
+                    break;
+                case REMOVED:// Drop NaNs from data
+                    work = removeAndSlice(values, begin, length, Double.NaN);
+                    break;
+                case FAILED:// just throw exception as NaN is un-acceptable
+                    work = copyOf(values, begin, length);
+                    MathArrays.checkNotNaN(work);
+                    break;
+                default: //FIXED
+                    work = copyOf(values,begin,length);
+                    break;
+                }
+            }
+            return work;
+    }
+
+    /**
+     * Make a copy of the array for the slice defined by array part from
+     * [begin, begin+length)
+     * @param values the input array
+     * @param begin start index of the array to include
+     * @param length number of elements to include from begin
+     * @return copy of a slice of the original array
+     */
+    private static double[] copyOf(final double[] values, final int begin, final int length) {
+        MathArrays.verifyValues(values, begin, length);
+        return MathArrays.copyOfRange(values, begin, begin + length);
+    }
+
+    /**
+     * Replace every occurrence of a given value with a replacement value in a
+     * copied slice of array defined by array part from [begin, begin+length).
+     * @param values the input array
+     * @param begin start index of the array to include
+     * @param length number of elements to include from begin
+     * @param original the value to be replaced with
+     * @param replacement the value to be used for replacement
+     * @return the copy of sliced array with replaced values
+     */
+    private static double[] replaceAndSlice(final double[] values,
+                                            final int begin, final int length,
+                                            final double original,
+                                            final double replacement) {
+        final double[] temp = copyOf(values, begin, length);
+        for(int i = 0; i < length; i++) {
+            temp[i] = Precision.equalsIncludingNaN(original, temp[i]) ?
+                      replacement : temp[i];
+        }
+        return temp;
+    }
+
+    /**
+     * Remove the occurrence of a given value in a copied slice of array
+     * defined by the array part from [begin, begin+length).
+     * @param values the input array
+     * @param begin start index of the array to include
+     * @param length number of elements to include from begin
+     * @param removedValue the value to be removed from the sliced array
+     * @return the copy of the sliced array after removing the removedValue
+     */
+    private static double[] removeAndSlice(final double[] values,
+                                           final int begin, final int length,
+                                           final double removedValue) {
+        MathArrays.verifyValues(values, begin, length);
+        final double[] temp;
+        //BitSet(length) to indicate where the removedValue is located
+        final BitSet bits = new BitSet(length);
+        for (int i = begin; i < begin+length; i++) {
+            if (Precision.equalsIncludingNaN(removedValue, values[i])) {
+                bits.set(i - begin);
+            }
+        }
+        //Check if empty then create a new copy
+        if (bits.isEmpty()) {
+            temp = copyOf(values, begin, length); // Nothing removed, just copy
+        } else if(bits.cardinality() == length){
+            temp = new double[0];                 // All removed, just empty
+        }else {                                   // Some removable, so new
+            temp = new double[length - bits.cardinality()];
+            int start = begin;  //start index from source array (i.e values)
+            int dest = 0;       //dest index in destination array(i.e temp)
+            int nextOne = -1;   //nextOne is the index of bit set of next one
+            int bitSetPtr = 0;  //bitSetPtr is start index pointer of bitset
+            while ((nextOne = bits.nextSetBit(bitSetPtr)) != -1) {
+                final int lengthToCopy = nextOne - bitSetPtr;
+                System.arraycopy(values, start, temp, dest, lengthToCopy);
+                dest += lengthToCopy;
+                start = begin + (bitSetPtr = bits.nextClearBit(nextOne));
+            }
+            //Copy any residue past start index till begin+length
+            if (start < begin + length) {
+                System.arraycopy(values,start,temp,dest,begin + length - start);
+            }
+        }
+        return temp;
+    }
+
+    /**
+     * Get pivots which is either cached or a newly created one
+     *
+     * @param values array containing the input numbers
+     * @return cached pivots or a newly created one
+     */
+    private int[] getPivots(final double[] values) {
+        final int[] pivotsHeap;
+        if (values == getDataRef()) {
+            pivotsHeap = cachedPivots;
+        } else {
+            pivotsHeap = new int[PIVOTS_HEAP_LENGTH];
+            Arrays.fill(pivotsHeap, -1);
+        }
+        return pivotsHeap;
+    }
+
+    /**
+     * Get the estimation {@link EstimationType type} used for computation.
+     *
+     * @return the {@code estimationType} set
+     */
+    public EstimationType getEstimationType() {
+        return estimationType;
+    }
+
+    /**
+     * Build a new instance similar to the current one except for the
+     * {@link EstimationType estimation type}.
+     * <p>
+     * This method is intended to be used as part of a fluent-type builder
+     * pattern. Building finely tune instances should be done as follows:
+     * </p>
+     * <pre>
+     *   Percentile customized = new Percentile(quantile).
+     *                           withEstimationType(estimationType).
+     *                           withNaNStrategy(nanStrategy).
+     *                           withKthSelector(kthSelector);
+     * </pre>
+     * <p>
+     * If any of the {@code withXxx} method is omitted, the default value for
+     * the corresponding customization parameter will be used.
+     * </p>
+     * @param newEstimationType estimation type for the new instance
+     * @return a new instance, with changed estimation type
+     * @throws NullArgumentException when newEstimationType is null
+     */
+    public Percentile withEstimationType(final EstimationType newEstimationType) {
+        return new Percentile(quantile, newEstimationType, nanStrategy, kthSelector);
+    }
+
+    /**
+     * Get the {@link NaNStrategy NaN Handling} strategy used for computation.
+     * @return {@code NaN Handling} strategy set during construction
+     */
+    public NaNStrategy getNaNStrategy() {
+        return nanStrategy;
+    }
+
+    /**
+     * Build a new instance similar to the current one except for the
+     * {@link NaNStrategy NaN handling} strategy.
+     * <p>
+     * This method is intended to be used as part of a fluent-type builder
+     * pattern. Building finely tune instances should be done as follows:
+     * </p>
+     * <pre>
+     *   Percentile customized = new Percentile(quantile).
+     *                           withEstimationType(estimationType).
+     *                           withNaNStrategy(nanStrategy).
+     *                           withKthSelector(kthSelector);
+     * </pre>
+     * <p>
+     * If any of the {@code withXxx} method is omitted, the default value for
+     * the corresponding customization parameter will be used.
+     * </p>
+     * @param newNaNStrategy NaN strategy for the new instance
+     * @return a new instance, with changed NaN handling strategy
+     * @throws NullArgumentException when newNaNStrategy is null
+     */
+    public Percentile withNaNStrategy(final NaNStrategy newNaNStrategy) {
+        return new Percentile(quantile, estimationType, newNaNStrategy, kthSelector);
+    }
+
+    /**
+     * Get the {@link KthSelector kthSelector} used for computation.
+     * @return the {@code kthSelector} set
+     */
+    public KthSelector getKthSelector() {
+        return kthSelector;
+    }
+
+    /**
+     * Get the {@link PivotingStrategyInterface} used in KthSelector for computation.
+     * @return the pivoting strategy set
+     */
+    public PivotingStrategyInterface getPivotingStrategy() {
+        return kthSelector.getPivotingStrategy();
+    }
+
+    /**
+     * Build a new instance similar to the current one except for the
+     * {@link KthSelector kthSelector} instance specifically set.
+     * <p>
+     * This method is intended to be used as part of a fluent-type builder
+     * pattern. Building finely tune instances should be done as follows:
+     * </p>
+     * <pre>
+     *   Percentile customized = new Percentile(quantile).
+     *                           withEstimationType(estimationType).
+     *                           withNaNStrategy(nanStrategy).
+     *                           withKthSelector(newKthSelector);
+     * </pre>
+     * <p>
+     * If any of the {@code withXxx} method is omitted, the default value for
+     * the corresponding customization parameter will be used.
+     * </p>
+     * @param newKthSelector KthSelector for the new instance
+     * @return a new instance, with changed KthSelector
+     * @throws NullArgumentException when newKthSelector is null
+     */
+    public Percentile withKthSelector(final KthSelector newKthSelector) {
+        return new Percentile(quantile, estimationType, nanStrategy,
+                                newKthSelector);
+    }
+
+    /**
+     * An enum for various estimation strategies of a percentile referred in
+     * <a href="http://en.wikipedia.org/wiki/Quantile">wikipedia on quantile</a>
+     * with the names of enum matching those of types mentioned in
+     * wikipedia.
+     * <p>
+     * Each enum corresponding to the specific type of estimation in wikipedia
+     * implements  the respective formulae that specializes in the below aspects
+     * <ul>
+     * <li>An <b>index method</b> to calculate approximate index of the
+     * estimate</li>
+     * <li>An <b>estimate method</b> to estimate a value found at the earlier
+     * computed index</li>
+     * <li>A <b> minLimit</b> on the quantile for which first element of sorted
+     * input is returned as an estimate </li>
+     * <li>A <b> maxLimit</b> on the quantile for which last element of sorted
+     * input is returned as an estimate </li>
+     * </ul>
+     * <p>
+     * Users can now create {@link Percentile} by explicitly passing this enum;
+     * such as by invoking {@link Percentile#withEstimationType(EstimationType)}
+     * <p>
+     * References:
+     * <ol>
+     * <li>
+     * <a href="http://en.wikipedia.org/wiki/Quantile">Wikipedia on quantile</a>
+     * </li>
+     * <li>
+     * <a href="https://www.amherst.edu/media/view/129116/.../Sample+Quantiles.pdf">
+     * Hyndman, R. J. and Fan, Y. (1996) Sample quantiles in statistical
+     * packages, American Statistician 50, 361–365</a> </li>
+     * <li>
+     * <a href="http://stat.ethz.ch/R-manual/R-devel/library/stats/html/quantile.html">
+     * R-Manual </a></li>
+     * </ol>
+     *
+     */
+    public enum EstimationType {
+        /**
+         * This is the default type used in the {@link Percentile}.This method
+         * has the following formulae for index and estimates<br>
+         * \( \begin{align}
+         * &amp;index    = (N+1)p\ \\
+         * &amp;estimate = x_{\lceil h\,-\,1/2 \rceil} \\
+         * &amp;minLimit = 0 \\
+         * &amp;maxLimit = 1 \\
+         * \end{align}\)
+         */
+        LEGACY("Legacy Apache Commons Math") {
+            /**
+             * {@inheritDoc}.This method in particular makes use of existing
+             * Apache Commons Math style of picking up the index.
+             */
+            @Override
+            protected double index(final double p, final int length) {
+                final double minLimit = 0d;
+                final double maxLimit = 1d;
+                return Double.compare(p, minLimit) == 0 ? 0 :
+                       Double.compare(p, maxLimit) == 0 ?
+                               length : p * (length + 1);
+            }
+        },
+        /**
+         * The method R_1 has the following formulae for index and estimates<br>
+         * \( \begin{align}
+         * &amp;index= Np + 1/2\,  \\
+         * &amp;estimate= x_{\lceil h\,-\,1/2 \rceil} \\
+         * &amp;minLimit = 0 \\
+         * \end{align}\)
+         */
+        R_1("R-1") {
+
+            @Override
+            protected double index(final double p, final int length) {
+                final double minLimit = 0d;
+                return Double.compare(p, minLimit) == 0 ? 0 : length * p + 0.5;
+            }
+
+            /**
+             * {@inheritDoc}This method in particular for R_1 uses ceil(pos-0.5)
+             */
+            @Override
+            protected double estimate(final double[] values,
+                                      final int[] pivotsHeap, final double pos,
+                                      final int length, final KthSelector selector) {
+                return super.estimate(values, pivotsHeap, FastMath.ceil(pos - 0.5), length, selector);
+            }
+
+        },
+        /**
+         * The method R_2 has the following formulae for index and estimates<br>
+         * \( \begin{align}
+         * &amp;index= Np + 1/2\, \\
+         * &amp;estimate=\frac{x_{\lceil h\,-\,1/2 \rceil} +
+         * x_{\lfloor h\,+\,1/2 \rfloor}}{2} \\
+         * &amp;minLimit = 0 \\
+         * &amp;maxLimit = 1 \\
+         * \end{align}\)
+         */
+        R_2("R-2") {
+
+            @Override
+            protected double index(final double p, final int length) {
+                final double minLimit = 0d;
+                final double maxLimit = 1d;
+                return Double.compare(p, maxLimit) == 0 ? length :
+                       Double.compare(p, minLimit) == 0 ? 0 : length * p + 0.5;
+            }
+
+            /**
+             * {@inheritDoc}This method in particular for R_2 averages the
+             * values at ceil(p+0.5) and floor(p-0.5).
+             */
+            @Override
+            protected double estimate(final double[] values,
+                                      final int[] pivotsHeap, final double pos,
+                                      final int length, final KthSelector selector) {
+                final double low =
+                        super.estimate(values, pivotsHeap, FastMath.ceil(pos - 0.5), length, selector);
+                final double high =
+                        super.estimate(values, pivotsHeap,FastMath.floor(pos + 0.5), length, selector);
+                return (low + high) / 2;
+            }
+
+        },
+        /**
+         * The method R_3 has the following formulae for index and estimates<br>
+         * \( \begin{align}
+         * &amp;index= Np \\
+         * &amp;estimate= x_{\lfloor h \rceil}\, \\
+         * &amp;minLimit = 0.5/N \\
+         * \end{align}\)
+         */
+        R_3("R-3") {
+            @Override
+            protected double index(final double p, final int length) {
+                final double minLimit = 1d/2 / length;
+                return Double.compare(p, minLimit) <= 0 ?
+                        0 : FastMath.rint(length * p);
+            }
+
+        },
+        /**
+         * The method R_4 has the following formulae for index and estimates<br>
+         * \( \begin{align}
+         * &amp;index= Np\, \\
+         * &amp;estimate= x_{\lfloor h \rfloor} + (h -
+         * \lfloor h \rfloor) (x_{\lfloor h \rfloor + 1} - x_{\lfloor h
+         * \rfloor}) \\
+         * &amp;minLimit = 1/N \\
+         * &amp;maxLimit = 1 \\
+         * \end{align}\)
+         */
+        R_4("R-4") {
+            @Override
+            protected double index(final double p, final int length) {
+                final double minLimit = 1d / length;
+                final double maxLimit = 1d;
+                return Double.compare(p, minLimit) < 0 ? 0 :
+                       Double.compare(p, maxLimit) == 0 ? length : length * p;
+            }
+
+        },
+        /**
+         * The method R_5 has the following formulae for index and estimates<br>
+         * \( \begin{align}
+         * &amp;index= Np + 1/2\\
+         * &amp;estimate= x_{\lfloor h \rfloor} + (h -
+         * \lfloor h \rfloor) (x_{\lfloor h \rfloor + 1} - x_{\lfloor h
+         * \rfloor}) \\
+         * &amp;minLimit = 0.5/N \\
+         * &amp;maxLimit = (N-0.5)/N
+         * \end{align}\)
+         */
+        R_5("R-5"){
+
+            @Override
+            protected double index(final double p, final int length) {
+                final double minLimit = 1d/2 / length;
+                final double maxLimit = (length - 0.5) / length;
+                return Double.compare(p, minLimit) < 0 ? 0 :
+                       Double.compare(p, maxLimit) >= 0 ?
+                               length : length * p + 0.5;
+            }
+        },
+        /**
+         * The method R_6 has the following formulae for index and estimates<br>
+         * \( \begin{align}
+         * &amp;index= (N + 1)p \\
+         * &amp;estimate= x_{\lfloor h \rfloor} + (h -
+         * \lfloor h \rfloor) (x_{\lfloor h \rfloor + 1} - x_{\lfloor h
+         * \rfloor}) \\
+         * &amp;minLimit = 1/(N+1) \\
+         * &amp;maxLimit = N/(N+1) \\
+         * \end{align}\)
+         * <p>
+         * <b>Note:</b> This method computes the index in a manner very close to
+         * the default Commons Math Percentile existing implementation. However
+         * the difference to be noted is in picking up the limits with which
+         * first element (p&lt;1(N+1)) and last elements (p&gt;N/(N+1))are done.
+         * While in default case; these are done with p=0 and p=1 respectively.
+         */
+        R_6("R-6"){
+
+            @Override
+            protected double index(final double p, final int length) {
+                final double minLimit = 1d / (length + 1);
+                final double maxLimit = 1d * length / (length + 1);
+                return Double.compare(p, minLimit) < 0 ? 0 :
+                       Double.compare(p, maxLimit) >= 0 ?
+                               length : (length + 1) * p;
+            }
+        },
+
+        /**
+         * The method R_7 implements Microsoft Excel style computation has the
+         * following formulae for index and estimates.<br>
+         * \( \begin{align}
+         * &amp;index = (N-1)p + 1 \\
+         * &amp;estimate = x_{\lfloor h \rfloor} + (h -
+         * \lfloor h \rfloor) (x_{\lfloor h \rfloor + 1} - x_{\lfloor h
+         * \rfloor}) \\
+         * &amp;minLimit = 0 \\
+         * &amp;maxLimit = 1 \\
+         * \end{align}\)
+         */
+        R_7("R-7") {
+            @Override
+            protected double index(final double p, final int length) {
+                final double minLimit = 0d;
+                final double maxLimit = 1d;
+                return Double.compare(p, minLimit) == 0 ? 0 :
+                       Double.compare(p, maxLimit) == 0 ?
+                               length : 1 + (length - 1) * p;
+            }
+
+        },
+
+        /**
+         * The method R_8 has the following formulae for index and estimates<br>
+         * \( \begin{align}
+         * &amp;index = (N + 1/3)p + 1/3  \\
+         * &amp;estimate = x_{\lfloor h \rfloor} + (h -
+           \lfloor h \rfloor) (x_{\lfloor h \rfloor + 1} - x_{\lfloor h
+         * \rfloor}) \\
+         * &amp;minLimit = (2/3)/(N+1/3) \\
+         * &amp;maxLimit = (N-1/3)/(N+1/3) \\
+         * \end{align}\)
+         * <p>
+         * As per Ref [2,3] this approach is most recommended as it provides
+         * an approximate median-unbiased estimate regardless of distribution.
+         */
+        R_8("R-8") {
+            @Override
+            protected double index(final double p, final int length) {
+                final double minLimit = 2 * (1d / 3) / (length + 1d / 3);
+                final double maxLimit =
+                        (length - 1d / 3) / (length + 1d / 3);
+                return Double.compare(p, minLimit) < 0 ? 0 :
+                       Double.compare(p, maxLimit) >= 0 ? length :
+                           (length + 1d / 3) * p + 1d / 3;
+            }
+        },
+
+        /**
+         * The method R_9 has the following formulae for index and estimates<br>
+         * \( \begin{align}
+         * &amp;index = (N + 1/4)p + 3/8\\
+         * &amp;estimate = x_{\lfloor h \rfloor} + (h -
+           \lfloor h \rfloor) (x_{\lfloor h \rfloor + 1} - x_{\lfloor h
+         * \rfloor}) \\
+         * &amp;minLimit = (5/8)/(N+1/4) \\
+         * &amp;maxLimit = (N-3/8)/(N+1/4) \\
+         * \end{align}\)
+         */
+        R_9("R-9") {
+            @Override
+            protected double index(final double p, final int length) {
+                final double minLimit = 5d/8 / (length + 0.25);
+                final double maxLimit = (length - 3d/8) / (length + 0.25);
+                return Double.compare(p, minLimit) < 0 ? 0 :
+                       Double.compare(p, maxLimit) >= 0 ? length :
+                               (length + 0.25) * p + 3d/8;
+            }
+
+        },
+        ;
+
+        /** Simple name such as R-1, R-2 corresponding to those in wikipedia. */
+        private final String name;
+
+        /**
+         * Constructor
+         *
+         * @param type name of estimation type as per wikipedia
+         */
+        EstimationType(final String type) {
+            this.name = type;
+        }
+
+        /**
+         * Finds the index of array that can be used as starting index to
+         * {@link #estimate(double[], int[], double, int, KthSelector) estimate}
+         * percentile. The calculation of index calculation is specific to each
+         * {@link EstimationType}.
+         *
+         * @param p the p<sup>th</sup> quantile
+         * @param length the total number of array elements in the work array
+         * @return a computed real valued index as explained in the wikipedia
+         */
+        protected abstract double index(final double p, final int length);
+
+        /**
+         * Estimation based on K<sup>th</sup> selection. This may be overridden
+         * in specific enums to compute slightly different estimations.
+         *
+         * @param work array of numbers to be used for finding the percentile
+         * @param pos indicated positional index prior computed from calling
+         *            {@link #index(double, int)}
+         * @param pivotsHeap an earlier populated cache if exists; will be used
+         * @param length size of array considered
+         * @param selector a {@link KthSelector} used for pivoting during search
+         * @return estimated percentile
+         */
+        protected double estimate(final double[] work, final int[] pivotsHeap,
+                                  final double pos, final int length,
+                                  final KthSelector selector) {
+
+            final double fpos = FastMath.floor(pos);
+            final int intPos = (int) fpos;
+            final double dif = pos - fpos;
+
+            if (pos < 1) {
+                return selector.select(work, pivotsHeap, 0);
+            }
+            if (pos >= length) {
+                return selector.select(work, pivotsHeap, length - 1);
+            }
+
+            final double lower = selector.select(work, pivotsHeap, intPos - 1);
+            final double upper = selector.select(work, pivotsHeap, intPos);
+            return lower + dif * (upper - lower);
+        }
+
+        /**
+         * Evaluate method to compute the percentile for a given bounded array
+         * using earlier computed pivots heap.<br>
+         * This basically calls the {@link #index(double, int) index} and then
+         * {@link #estimate(double[], int[], double, int, KthSelector) estimate}
+         * functions to return the estimated percentile value.
+         *
+         * @param work array of numbers to be used for finding the percentile
+         * @param pivotsHeap a prior cached heap which can speed up estimation
+         * @param p the p<sup>th</sup> quantile to be computed
+         * @param selector a {@link KthSelector} used for pivoting during search
+         * @return estimated percentile
+         * @throws OutOfRangeException if p is out of range
+         * @throws NullArgumentException if work array is null
+         */
+        protected double evaluate(final double[] work, final int[] pivotsHeap, final double p,
+                                  final KthSelector selector) {
+            MathUtils.checkNotNull(work);
+            if (p > 100 || p <= 0) {
+                throw new OutOfRangeException(LocalizedFormats.OUT_OF_BOUNDS_QUANTILE_VALUE,
+                                              p, 0, 100);
+            }
+            return estimate(work, pivotsHeap, index(p/100d, work.length), work.length, selector);
+        }
+
+        /**
+         * Evaluate method to compute the percentile for a given bounded array.
+         * This basically calls the {@link #index(double, int) index} and then
+         * {@link #estimate(double[], int[], double, int, KthSelector) estimate}
+         * functions to return the estimated percentile value. Please
+         * note that this method does not make use of cached pivots.
+         *
+         * @param work array of numbers to be used for finding the percentile
+         * @param p the p<sup>th</sup> quantile to be computed
+         * @return estimated percentile
+         * @param selector a {@link KthSelector} used for pivoting during search
+         * @throws OutOfRangeException if length or p is out of range
+         * @throws NullArgumentException if work array is null
+         */
+        public double evaluate(final double[] work, final double p, final KthSelector selector) {
+            return this.evaluate(work, null, p, selector);
+        }
+
+        /**
+         * Gets the name of the enum
+         *
+         * @return the name
+         */
+        String getName() {
+            return name;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/rank/package-info.java b/src/main/java/org/apache/commons/math3/stat/descriptive/rank/package-info.java
new file mode 100644
index 0000000..da37b37
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/rank/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+/**
+ * Summary statistics based on ranks.
+ */
+package org.apache.commons.math3.stat.descriptive.rank;
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/summary/Product.java b/src/main/java/org/apache/commons/math3/stat/descriptive/summary/Product.java
new file mode 100644
index 0000000..7d313a5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/summary/Product.java
@@ -0,0 +1,230 @@
+/*
+ * 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.math3.stat.descriptive.summary;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.stat.descriptive.AbstractStorelessUnivariateStatistic;
+import org.apache.commons.math3.stat.descriptive.WeightedEvaluation;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * Returns the product of the available values.
+ * <p>
+ * If there are no values in the dataset, then 1 is returned.
+ *  If any of the values are
+ * <code>NaN</code>, then <code>NaN</code> is returned.</p>
+ * <p>
+ * <strong>Note that this implementation is not synchronized.</strong> If
+ * multiple threads access an instance of this class concurrently, and at least
+ * one of the threads invokes the <code>increment()</code> or
+ * <code>clear()</code> method, it must be synchronized externally.</p>
+ *
+ */
+public class Product extends AbstractStorelessUnivariateStatistic implements Serializable, WeightedEvaluation {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 2824226005990582538L;
+
+    /**The number of values that have been added */
+    private long n;
+
+    /**
+     * The current Running Product.
+     */
+    private double value;
+
+    /**
+     * Create a Product instance
+     */
+    public Product() {
+        n = 0;
+        value = 1;
+    }
+
+    /**
+     * Copy constructor, creates a new {@code Product} identical
+     * to the {@code original}
+     *
+     * @param original the {@code Product} instance to copy
+     * @throws NullArgumentException  if original is null
+     */
+    public Product(Product original) throws NullArgumentException {
+        copy(original, this);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void increment(final double d) {
+        value *= d;
+        n++;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double getResult() {
+        return value;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getN() {
+        return n;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void clear() {
+        value = 1;
+        n = 0;
+    }
+
+    /**
+     * Returns the product of the entries in the specified portion of
+     * the input array, or <code>Double.NaN</code> if the designated subarray
+     * is empty.
+     * <p>
+     * Throws <code>MathIllegalArgumentException</code> if the array is null.</p>
+     *
+     * @param values the input array
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @return the product of the values or 1 if length = 0
+     * @throws MathIllegalArgumentException if the array is null or the array index
+     *  parameters are not valid
+     */
+    @Override
+    public double evaluate(final double[] values, final int begin, final int length)
+    throws MathIllegalArgumentException {
+        double product = Double.NaN;
+        if (test(values, begin, length, true)) {
+            product = 1.0;
+            for (int i = begin; i < begin + length; i++) {
+                product *= values[i];
+            }
+        }
+        return product;
+    }
+
+    /**
+     * <p>Returns the weighted product of the entries in the specified portion of
+     * the input array, or <code>Double.NaN</code> if the designated subarray
+     * is empty.</p>
+     *
+     * <p>Throws <code>MathIllegalArgumentException</code> if any of the following are true:
+     * <ul><li>the values array is null</li>
+     *     <li>the weights array is null</li>
+     *     <li>the weights array does not have the same length as the values array</li>
+     *     <li>the weights array contains one or more infinite values</li>
+     *     <li>the weights array contains one or more NaN values</li>
+     *     <li>the weights array contains negative values</li>
+     *     <li>the start and length arguments do not determine a valid array</li>
+     * </ul></p>
+     *
+     * <p>Uses the formula, <pre>
+     *    weighted product = &prod;values[i]<sup>weights[i]</sup>
+     * </pre>
+     * that is, the weights are applied as exponents when computing the weighted product.</p>
+     *
+     * @param values the input array
+     * @param weights the weights array
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @return the product of the values or 1 if length = 0
+     * @throws MathIllegalArgumentException if the parameters are not valid
+     * @since 2.1
+     */
+    public double evaluate(final double[] values, final double[] weights,
+        final int begin, final int length) throws MathIllegalArgumentException {
+        double product = Double.NaN;
+        if (test(values, weights, begin, length, true)) {
+            product = 1.0;
+            for (int i = begin; i < begin + length; i++) {
+                product *= FastMath.pow(values[i], weights[i]);
+            }
+        }
+        return product;
+    }
+
+    /**
+     * <p>Returns the weighted product of the entries in the input array.</p>
+     *
+     * <p>Throws <code>MathIllegalArgumentException</code> if any of the following are true:
+     * <ul><li>the values array is null</li>
+     *     <li>the weights array is null</li>
+     *     <li>the weights array does not have the same length as the values array</li>
+     *     <li>the weights array contains one or more infinite values</li>
+     *     <li>the weights array contains one or more NaN values</li>
+     *     <li>the weights array contains negative values</li>
+     * </ul></p>
+     *
+     * <p>Uses the formula, <pre>
+     *    weighted product = &prod;values[i]<sup>weights[i]</sup>
+     * </pre>
+     * that is, the weights are applied as exponents when computing the weighted product.</p>
+     *
+     * @param values the input array
+     * @param weights the weights array
+     * @return the product of the values or Double.NaN if length = 0
+     * @throws MathIllegalArgumentException if the parameters are not valid
+     * @since 2.1
+     */
+    public double evaluate(final double[] values, final double[] weights)
+    throws MathIllegalArgumentException {
+        return evaluate(values, weights, 0, values.length);
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Product copy() {
+        Product result = new Product();
+        // No try-catch or advertised exception because args are valid
+        copy(this, result);
+        return result;
+    }
+
+    /**
+     * Copies source to dest.
+     * <p>Neither source nor dest can be null.</p>
+     *
+     * @param source Product to copy
+     * @param dest Product to copy to
+     * @throws NullArgumentException if either source or dest is null
+     */
+    public static void copy(Product source, Product dest)
+        throws NullArgumentException {
+        MathUtils.checkNotNull(source);
+        MathUtils.checkNotNull(dest);
+        dest.setData(source.getDataRef());
+        dest.n = source.n;
+        dest.value = source.value;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/summary/Sum.java b/src/main/java/org/apache/commons/math3/stat/descriptive/summary/Sum.java
new file mode 100644
index 0000000..e12b6a1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/summary/Sum.java
@@ -0,0 +1,226 @@
+/*
+ * 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.math3.stat.descriptive.summary;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.stat.descriptive.AbstractStorelessUnivariateStatistic;
+import org.apache.commons.math3.util.MathUtils;
+
+
+/**
+  * Returns the sum of the available values.
+ * <p>
+ * If there are no values in the dataset, then 0 is returned.
+ * If any of the values are
+ * <code>NaN</code>, then <code>NaN</code> is returned.</p>
+ * <p>
+ * <strong>Note that this implementation is not synchronized.</strong> If
+ * multiple threads access an instance of this class concurrently, and at least
+ * one of the threads invokes the <code>increment()</code> or
+ * <code>clear()</code> method, it must be synchronized externally.</p>
+ *
+ */
+public class Sum extends AbstractStorelessUnivariateStatistic implements Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -8231831954703408316L;
+
+    /** */
+    private long n;
+
+    /**
+     * The currently running sum.
+     */
+    private double value;
+
+    /**
+     * Create a Sum instance
+     */
+    public Sum() {
+        n = 0;
+        value = 0;
+    }
+
+    /**
+     * Copy constructor, creates a new {@code Sum} identical
+     * to the {@code original}
+     *
+     * @param original the {@code Sum} instance to copy
+     * @throws NullArgumentException if original is null
+     */
+    public Sum(Sum original) throws NullArgumentException {
+        copy(original, this);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void increment(final double d) {
+        value += d;
+        n++;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double getResult() {
+        return value;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getN() {
+        return n;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void clear() {
+        value = 0;
+        n = 0;
+    }
+
+    /**
+     * The sum of the entries in the specified portion of
+     * the input array, or 0 if the designated subarray
+     * is empty.
+     * <p>
+     * Throws <code>MathIllegalArgumentException</code> if the array is null.</p>
+     *
+     * @param values the input array
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @return the sum of the values or 0 if length = 0
+     * @throws MathIllegalArgumentException if the array is null or the array index
+     *  parameters are not valid
+     */
+    @Override
+    public double evaluate(final double[] values, final int begin, final int length)
+    throws MathIllegalArgumentException {
+        double sum = Double.NaN;
+        if (test(values, begin, length, true)) {
+            sum = 0.0;
+            for (int i = begin; i < begin + length; i++) {
+                sum += values[i];
+            }
+        }
+        return sum;
+    }
+
+    /**
+     * The weighted sum of the entries in the specified portion of
+     * the input array, or 0 if the designated subarray
+     * is empty.
+     * <p>
+     * Throws <code>MathIllegalArgumentException</code> if any of the following are true:
+     * <ul><li>the values array is null</li>
+     *     <li>the weights array is null</li>
+     *     <li>the weights array does not have the same length as the values array</li>
+     *     <li>the weights array contains one or more infinite values</li>
+     *     <li>the weights array contains one or more NaN values</li>
+     *     <li>the weights array contains negative values</li>
+     *     <li>the start and length arguments do not determine a valid array</li>
+     * </ul></p>
+     * <p>
+     * Uses the formula, <pre>
+     *    weighted sum = &Sigma;(values[i] * weights[i])
+     * </pre></p>
+     *
+     * @param values the input array
+     * @param weights the weights array
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @return the sum of the values or 0 if length = 0
+     * @throws MathIllegalArgumentException if the parameters are not valid
+     * @since 2.1
+     */
+    public double evaluate(final double[] values, final double[] weights,
+        final int begin, final int length) throws MathIllegalArgumentException {
+        double sum = Double.NaN;
+        if (test(values, weights, begin, length, true)) {
+            sum = 0.0;
+            for (int i = begin; i < begin + length; i++) {
+                sum += values[i] * weights[i];
+            }
+        }
+        return sum;
+    }
+
+    /**
+     * The weighted sum of the entries in the the input array.
+     * <p>
+     * Throws <code>MathIllegalArgumentException</code> if any of the following are true:
+     * <ul><li>the values array is null</li>
+     *     <li>the weights array is null</li>
+     *     <li>the weights array does not have the same length as the values array</li>
+     *     <li>the weights array contains one or more infinite values</li>
+     *     <li>the weights array contains one or more NaN values</li>
+     *     <li>the weights array contains negative values</li>
+     * </ul></p>
+     * <p>
+     * Uses the formula, <pre>
+     *    weighted sum = &Sigma;(values[i] * weights[i])
+     * </pre></p>
+     *
+     * @param values the input array
+     * @param weights the weights array
+     * @return the sum of the values or Double.NaN if length = 0
+     * @throws MathIllegalArgumentException if the parameters are not valid
+     * @since 2.1
+     */
+    public double evaluate(final double[] values, final double[] weights)
+    throws MathIllegalArgumentException {
+        return evaluate(values, weights, 0, values.length);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Sum copy() {
+        Sum result = new Sum();
+        // No try-catch or advertised exception because args are valid
+        copy(this, result);
+        return result;
+    }
+
+    /**
+     * Copies source to dest.
+     * <p>Neither source nor dest can be null.</p>
+     *
+     * @param source Sum to copy
+     * @param dest Sum to copy to
+     * @throws NullArgumentException if either source or dest is null
+     */
+    public static void copy(Sum source, Sum dest)
+        throws NullArgumentException {
+        MathUtils.checkNotNull(source);
+        MathUtils.checkNotNull(dest);
+        dest.setData(source.getDataRef());
+        dest.n = source.n;
+        dest.value = source.value;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/summary/SumOfLogs.java b/src/main/java/org/apache/commons/math3/stat/descriptive/summary/SumOfLogs.java
new file mode 100644
index 0000000..19718af
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/summary/SumOfLogs.java
@@ -0,0 +1,170 @@
+/*
+ * 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.math3.stat.descriptive.summary;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.stat.descriptive.AbstractStorelessUnivariateStatistic;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * Returns the sum of the natural logs for this collection of values.
+ * <p>
+ * Uses {@link org.apache.commons.math3.util.FastMath#log(double)} to compute the logs.
+ * Therefore,
+ * <ul>
+ * <li>If any of values are &lt; 0, the result is <code>NaN.</code></li>
+ * <li>If all values are non-negative and less than
+ * <code>Double.POSITIVE_INFINITY</code>,  but at least one value is 0, the
+ * result is <code>Double.NEGATIVE_INFINITY.</code></li>
+ * <li>If both <code>Double.POSITIVE_INFINITY</code> and
+ * <code>Double.NEGATIVE_INFINITY</code> are among the values, the result is
+ * <code>NaN.</code></li>
+ * </ul></p>
+ * <p>
+ * <strong>Note that this implementation is not synchronized.</strong> If
+ * multiple threads access an instance of this class concurrently, and at least
+ * one of the threads invokes the <code>increment()</code> or
+ * <code>clear()</code> method, it must be synchronized externally.</p>
+ *
+ */
+public class SumOfLogs extends AbstractStorelessUnivariateStatistic implements Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -370076995648386763L;
+
+    /**Number of values that have been added */
+    private int n;
+
+    /**
+     * The currently running value
+     */
+    private double value;
+
+    /**
+     * Create a SumOfLogs instance
+     */
+    public SumOfLogs() {
+       value = 0d;
+       n = 0;
+    }
+
+    /**
+     * Copy constructor, creates a new {@code SumOfLogs} identical
+     * to the {@code original}
+     *
+     * @param original the {@code SumOfLogs} instance to copy
+     * @throws NullArgumentException if original is null
+     */
+    public SumOfLogs(SumOfLogs original) throws NullArgumentException {
+        copy(original, this);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void increment(final double d) {
+        value += FastMath.log(d);
+        n++;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double getResult() {
+        return value;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getN() {
+        return n;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void clear() {
+        value = 0d;
+        n = 0;
+    }
+
+    /**
+     * Returns the sum of the natural logs of the entries in the specified portion of
+     * the input array, or <code>Double.NaN</code> if the designated subarray
+     * is empty.
+     * <p>
+     * Throws <code>MathIllegalArgumentException</code> if the array is null.</p>
+     * <p>
+     * See {@link SumOfLogs}.</p>
+     *
+     * @param values the input array
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @return the sum of the natural logs of the values or 0 if
+     * length = 0
+     * @throws MathIllegalArgumentException if the array is null or the array index
+     *  parameters are not valid
+     */
+    @Override
+    public double evaluate(final double[] values, final int begin, final int length)
+    throws MathIllegalArgumentException {
+        double sumLog = Double.NaN;
+        if (test(values, begin, length, true)) {
+            sumLog = 0.0;
+            for (int i = begin; i < begin + length; i++) {
+                sumLog += FastMath.log(values[i]);
+            }
+        }
+        return sumLog;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public SumOfLogs copy() {
+        SumOfLogs result = new SumOfLogs();
+        // No try-catch or advertised exception here because args are valid
+        copy(this, result);
+        return result;
+    }
+
+    /**
+     * Copies source to dest.
+     * <p>Neither source nor dest can be null.</p>
+     *
+     * @param source SumOfLogs to copy
+     * @param dest SumOfLogs to copy to
+     * @throws NullArgumentException if either source or dest is null
+     */
+    public static void copy(SumOfLogs source, SumOfLogs dest)
+        throws NullArgumentException {
+        MathUtils.checkNotNull(source);
+        MathUtils.checkNotNull(dest);
+        dest.setData(source.getDataRef());
+        dest.n = source.n;
+        dest.value = source.value;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/summary/SumOfSquares.java b/src/main/java/org/apache/commons/math3/stat/descriptive/summary/SumOfSquares.java
new file mode 100644
index 0000000..161d8c8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/summary/SumOfSquares.java
@@ -0,0 +1,159 @@
+/*
+ * 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.math3.stat.descriptive.summary;
+
+import java.io.Serializable;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.stat.descriptive.AbstractStorelessUnivariateStatistic;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * Returns the sum of the squares of the available values.
+ * <p>
+ * If there are no values in the dataset, then 0 is returned.
+ * If any of the values are
+ * <code>NaN</code>, then <code>NaN</code> is returned.</p>
+ * <p>
+ * <strong>Note that this implementation is not synchronized.</strong> If
+ * multiple threads access an instance of this class concurrently, and at least
+ * one of the threads invokes the <code>increment()</code> or
+ * <code>clear()</code> method, it must be synchronized externally.</p>
+ *
+ */
+public class SumOfSquares extends AbstractStorelessUnivariateStatistic implements Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 1460986908574398008L;
+
+    /** */
+    private long n;
+
+    /**
+     * The currently running sumSq
+     */
+    private double value;
+
+    /**
+     * Create a SumOfSquares instance
+     */
+    public SumOfSquares() {
+        n = 0;
+        value = 0;
+    }
+
+    /**
+     * Copy constructor, creates a new {@code SumOfSquares} identical
+     * to the {@code original}
+     *
+     * @param original the {@code SumOfSquares} instance to copy
+     * @throws NullArgumentException if original is null
+     */
+    public SumOfSquares(SumOfSquares original) throws NullArgumentException {
+        copy(original, this);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void increment(final double d) {
+        value += d * d;
+        n++;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double getResult() {
+        return value;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getN() {
+        return n;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void clear() {
+        value = 0;
+        n = 0;
+    }
+
+    /**
+     * Returns the sum of the squares of the entries in the specified portion of
+     * the input array, or <code>Double.NaN</code> if the designated subarray
+     * is empty.
+     * <p>
+     * Throws <code>MathIllegalArgumentException</code> if the array is null.</p>
+     *
+     * @param values the input array
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @return the sum of the squares of the values or 0 if length = 0
+     * @throws MathIllegalArgumentException if the array is null or the array index
+     *  parameters are not valid
+     */
+    @Override
+    public double evaluate(final double[] values,final int begin, final int length)
+    throws MathIllegalArgumentException {
+        double sumSq = Double.NaN;
+        if (test(values, begin, length, true)) {
+            sumSq = 0.0;
+            for (int i = begin; i < begin + length; i++) {
+                sumSq += values[i] * values[i];
+            }
+        }
+        return sumSq;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public SumOfSquares copy() {
+        SumOfSquares result = new SumOfSquares();
+        // no try-catch or advertised exception here because args are valid
+        copy(this, result);
+        return result;
+    }
+
+    /**
+     * Copies source to dest.
+     * <p>Neither source nor dest can be null.</p>
+     *
+     * @param source SumOfSquares to copy
+     * @param dest SumOfSquares to copy to
+     * @throws NullArgumentException if either source or dest is null
+     */
+    public static void copy(SumOfSquares source, SumOfSquares dest)
+        throws NullArgumentException {
+        MathUtils.checkNotNull(source);
+        MathUtils.checkNotNull(dest);
+        dest.setData(source.getDataRef());
+        dest.n = source.n;
+        dest.value = source.value;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/descriptive/summary/package-info.java b/src/main/java/org/apache/commons/math3/stat/descriptive/summary/package-info.java
new file mode 100644
index 0000000..2f07145
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/descriptive/summary/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+/**
+ * Other summary statistics.
+ */
+package org.apache.commons.math3.stat.descriptive.summary;
diff --git a/src/main/java/org/apache/commons/math3/stat/inference/AlternativeHypothesis.java b/src/main/java/org/apache/commons/math3/stat/inference/AlternativeHypothesis.java
new file mode 100644
index 0000000..527067e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/inference/AlternativeHypothesis.java
@@ -0,0 +1,40 @@
+/*
+ * 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.math3.stat.inference;
+
+/**
+ * Represents an alternative hypothesis for a hypothesis test.
+ *
+ * @since 3.3
+ */
+public enum AlternativeHypothesis {
+
+    /**
+     * Represents a two-sided test. H0: p=p0, H1: p &ne; p0
+     */
+    TWO_SIDED,
+
+    /**
+     * Represents a right-sided test. H0: p &le; p0, H1: p &gt; p0.
+     */
+    GREATER_THAN,
+
+    /**
+     * Represents a left-sided test. H0: p &ge; p0, H1: p &lt; p0.
+     */
+    LESS_THAN
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/inference/BinomialTest.java b/src/main/java/org/apache/commons/math3/stat/inference/BinomialTest.java
new file mode 100644
index 0000000..2efe091
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/inference/BinomialTest.java
@@ -0,0 +1,160 @@
+/*
+ * 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.math3.stat.inference;
+
+import org.apache.commons.math3.distribution.BinomialDistribution;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.MathInternalError;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Implements binomial test statistics.
+ * <p>
+ * Exact test for the statistical significance of deviations from a
+ * theoretically expected distribution of observations into two categories.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Binomial_test">Binomial test (Wikipedia)</a>
+ * @since 3.3
+ */
+public class BinomialTest {
+
+    /**
+     * Returns whether the null hypothesis can be rejected with the given confidence level.
+     * <p>
+     * <strong>Preconditions</strong>:
+     * <ul>
+     * <li>Number of trials must be &ge; 0.</li>
+     * <li>Number of successes must be &ge; 0.</li>
+     * <li>Number of successes must be &le; number of trials.</li>
+     * <li>Probability must be &ge; 0 and &le; 1.</li>
+     * </ul>
+     *
+     * @param numberOfTrials number of trials performed
+     * @param numberOfSuccesses number of successes observed
+     * @param probability assumed probability of a single trial under the null hypothesis
+     * @param alternativeHypothesis type of hypothesis being evaluated (one- or two-sided)
+     * @param alpha significance level of the test
+     * @return true if the null hypothesis can be rejected with confidence {@code 1 - alpha}
+     * @throws NotPositiveException if {@code numberOfTrials} or {@code numberOfSuccesses} is negative
+     * @throws OutOfRangeException if {@code probability} is not between 0 and 1
+     * @throws MathIllegalArgumentException if {@code numberOfTrials} &lt; {@code numberOfSuccesses} or
+     * if {@code alternateHypothesis} is null.
+     * @see AlternativeHypothesis
+     */
+    public boolean binomialTest(int numberOfTrials, int numberOfSuccesses, double probability,
+                                AlternativeHypothesis alternativeHypothesis, double alpha) {
+        double pValue = binomialTest(numberOfTrials, numberOfSuccesses, probability, alternativeHypothesis);
+        return pValue < alpha;
+    }
+
+    /**
+     * Returns the <i>observed significance level</i>, or
+     * <a href="http://www.cas.lancs.ac.uk/glossary_v1.1/hyptest.html#pvalue">p-value</a>,
+     * associated with a <a href="http://en.wikipedia.org/wiki/Binomial_test"> Binomial test</a>.
+     * <p>
+     * The number returned is the smallest significance level at which one can reject the null hypothesis.
+     * The form of the hypothesis depends on {@code alternativeHypothesis}.</p>
+     * <p>
+     * The p-Value represents the likelihood of getting a result at least as extreme as the sample,
+     * given the provided {@code probability} of success on a single trial. For single-sided tests,
+     * this value can be directly derived from the Binomial distribution. For the two-sided test,
+     * the implementation works as follows: we start by looking at the most extreme cases
+     * (0 success and n success where n is the number of trials from the sample) and determine their likelihood.
+     * The lower value is added to the p-Value (if both values are equal, both are added). Then we continue with
+     * the next extreme value, until we added the value for the actual observed sample.</p>
+     * <p>
+     * <strong>Preconditions</strong>:
+     * <ul>
+     * <li>Number of trials must be &ge; 0.</li>
+     * <li>Number of successes must be &ge; 0.</li>
+     * <li>Number of successes must be &le; number of trials.</li>
+     * <li>Probability must be &ge; 0 and &le; 1.</li>
+     * </ul></p>
+     *
+     * @param numberOfTrials number of trials performed
+     * @param numberOfSuccesses number of successes observed
+     * @param probability assumed probability of a single trial under the null hypothesis
+     * @param alternativeHypothesis type of hypothesis being evaluated (one- or two-sided)
+     * @return p-value
+     * @throws NotPositiveException if {@code numberOfTrials} or {@code numberOfSuccesses} is negative
+     * @throws OutOfRangeException if {@code probability} is not between 0 and 1
+     * @throws MathIllegalArgumentException if {@code numberOfTrials} &lt; {@code numberOfSuccesses} or
+     * if {@code alternateHypothesis} is null.
+     * @see AlternativeHypothesis
+     */
+    public double binomialTest(int numberOfTrials, int numberOfSuccesses, double probability,
+                               AlternativeHypothesis alternativeHypothesis) {
+        if (numberOfTrials < 0) {
+            throw new NotPositiveException(numberOfTrials);
+        }
+        if (numberOfSuccesses < 0) {
+            throw new NotPositiveException(numberOfSuccesses);
+        }
+        if (probability < 0 || probability > 1) {
+            throw new OutOfRangeException(probability, 0, 1);
+        }
+        if (numberOfTrials < numberOfSuccesses) {
+            throw new MathIllegalArgumentException(
+                LocalizedFormats.BINOMIAL_INVALID_PARAMETERS_ORDER,
+                numberOfTrials, numberOfSuccesses);
+        }
+        if (alternativeHypothesis == null) {
+            throw new NullArgumentException();
+        }
+
+        // pass a null rng to avoid unneeded overhead as we will not sample from this distribution
+        final BinomialDistribution distribution = new BinomialDistribution(null, numberOfTrials, probability);
+        switch (alternativeHypothesis) {
+        case GREATER_THAN:
+            return 1 - distribution.cumulativeProbability(numberOfSuccesses - 1);
+        case LESS_THAN:
+            return distribution.cumulativeProbability(numberOfSuccesses);
+        case TWO_SIDED:
+            int criticalValueLow = 0;
+            int criticalValueHigh = numberOfTrials;
+            double pTotal = 0;
+
+            while (true) {
+                double pLow = distribution.probability(criticalValueLow);
+                double pHigh = distribution.probability(criticalValueHigh);
+
+                if (pLow == pHigh) {
+                    pTotal += 2 * pLow;
+                    criticalValueLow++;
+                    criticalValueHigh--;
+                } else if (pLow < pHigh) {
+                    pTotal += pLow;
+                    criticalValueLow++;
+                } else {
+                    pTotal += pHigh;
+                    criticalValueHigh--;
+                }
+
+                if (criticalValueLow > numberOfSuccesses || criticalValueHigh < numberOfSuccesses) {
+                    break;
+                }
+            }
+            return pTotal;
+        default:
+            throw new MathInternalError(LocalizedFormats. OUT_OF_RANGE_SIMPLE, alternativeHypothesis,
+                      AlternativeHypothesis.TWO_SIDED, AlternativeHypothesis.LESS_THAN);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/inference/ChiSquareTest.java b/src/main/java/org/apache/commons/math3/stat/inference/ChiSquareTest.java
new file mode 100644
index 0000000..7e97ac1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/inference/ChiSquareTest.java
@@ -0,0 +1,602 @@
+/*
+ * 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.math3.stat.inference;
+
+import org.apache.commons.math3.distribution.ChiSquaredDistribution;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.ZeroException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * Implements Chi-Square test statistics.
+ *
+ * <p>This implementation handles both known and unknown distributions.</p>
+ *
+ * <p>Two samples tests can be used when the distribution is unknown <i>a priori</i>
+ * but provided by one sample, or when the hypothesis under test is that the two
+ * samples come from the same underlying distribution.</p>
+ *
+ */
+public class ChiSquareTest {
+
+    /**
+     * Construct a ChiSquareTest
+     */
+    public ChiSquareTest() {
+        super();
+    }
+
+    /**
+     * Computes the <a href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda35f.htm">
+     * Chi-Square statistic</a> comparing <code>observed</code> and <code>expected</code>
+     * frequency counts.
+     * <p>
+     * This statistic can be used to perform a Chi-Square test evaluating the null
+     * hypothesis that the observed counts follow the expected distribution.</p>
+     * <p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>Expected counts must all be positive.
+     * </li>
+     * <li>Observed counts must all be &ge; 0.
+     * </li>
+     * <li>The observed and expected arrays must have the same length and
+     * their common length must be at least 2.
+     * </li></ul></p><p>
+     * If any of the preconditions are not met, an
+     * <code>IllegalArgumentException</code> is thrown.</p>
+     * <p><strong>Note: </strong>This implementation rescales the
+     * <code>expected</code> array if necessary to ensure that the sum of the
+     * expected and observed counts are equal.</p>
+     *
+     * @param observed array of observed frequency counts
+     * @param expected array of expected frequency counts
+     * @return chiSquare test statistic
+     * @throws NotPositiveException if <code>observed</code> has negative entries
+     * @throws NotStrictlyPositiveException if <code>expected</code> has entries that are
+     * not strictly positive
+     * @throws DimensionMismatchException if the arrays length is less than 2
+     */
+    public double chiSquare(final double[] expected, final long[] observed)
+        throws NotPositiveException, NotStrictlyPositiveException,
+        DimensionMismatchException {
+
+        if (expected.length < 2) {
+            throw new DimensionMismatchException(expected.length, 2);
+        }
+        if (expected.length != observed.length) {
+            throw new DimensionMismatchException(expected.length, observed.length);
+        }
+        MathArrays.checkPositive(expected);
+        MathArrays.checkNonNegative(observed);
+
+        double sumExpected = 0d;
+        double sumObserved = 0d;
+        for (int i = 0; i < observed.length; i++) {
+            sumExpected += expected[i];
+            sumObserved += observed[i];
+        }
+        double ratio = 1.0d;
+        boolean rescale = false;
+        if (FastMath.abs(sumExpected - sumObserved) > 10E-6) {
+            ratio = sumObserved / sumExpected;
+            rescale = true;
+        }
+        double sumSq = 0.0d;
+        for (int i = 0; i < observed.length; i++) {
+            if (rescale) {
+                final double dev = observed[i] - ratio * expected[i];
+                sumSq += dev * dev / (ratio * expected[i]);
+            } else {
+                final double dev = observed[i] - expected[i];
+                sumSq += dev * dev / expected[i];
+            }
+        }
+        return sumSq;
+
+    }
+
+    /**
+     * Returns the <i>observed significance level</i>, or <a href=
+     * "http://www.cas.lancs.ac.uk/glossary_v1.1/hyptest.html#pvalue">
+     * p-value</a>, associated with a
+     * <a href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda35f.htm">
+     * Chi-square goodness of fit test</a> comparing the <code>observed</code>
+     * frequency counts to those in the <code>expected</code> array.
+     * <p>
+     * The number returned is the smallest significance level at which one can reject
+     * the null hypothesis that the observed counts conform to the frequency distribution
+     * described by the expected counts.</p>
+     * <p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>Expected counts must all be positive.
+     * </li>
+     * <li>Observed counts must all be &ge; 0.
+     * </li>
+     * <li>The observed and expected arrays must have the same length and
+     * their common length must be at least 2.
+     * </li></ul></p><p>
+     * If any of the preconditions are not met, an
+     * <code>IllegalArgumentException</code> is thrown.</p>
+     * <p><strong>Note: </strong>This implementation rescales the
+     * <code>expected</code> array if necessary to ensure that the sum of the
+     * expected and observed counts are equal.</p>
+     *
+     * @param observed array of observed frequency counts
+     * @param expected array of expected frequency counts
+     * @return p-value
+     * @throws NotPositiveException if <code>observed</code> has negative entries
+     * @throws NotStrictlyPositiveException if <code>expected</code> has entries that are
+     * not strictly positive
+     * @throws DimensionMismatchException if the arrays length is less than 2
+     * @throws MaxCountExceededException if an error occurs computing the p-value
+     */
+    public double chiSquareTest(final double[] expected, final long[] observed)
+        throws NotPositiveException, NotStrictlyPositiveException,
+        DimensionMismatchException, MaxCountExceededException {
+
+        // pass a null rng to avoid unneeded overhead as we will not sample from this distribution
+        final ChiSquaredDistribution distribution =
+            new ChiSquaredDistribution(null, expected.length - 1.0);
+        return 1.0 - distribution.cumulativeProbability(chiSquare(expected, observed));
+    }
+
+    /**
+     * Performs a <a href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda35f.htm">
+     * Chi-square goodness of fit test</a> evaluating the null hypothesis that the
+     * observed counts conform to the frequency distribution described by the expected
+     * counts, with significance level <code>alpha</code>.  Returns true iff the null
+     * hypothesis can be rejected with 100 * (1 - alpha) percent confidence.
+     * <p>
+     * <strong>Example:</strong><br>
+     * To test the hypothesis that <code>observed</code> follows
+     * <code>expected</code> at the 99% level, use </p><p>
+     * <code>chiSquareTest(expected, observed, 0.01) </code></p>
+     * <p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>Expected counts must all be positive.
+     * </li>
+     * <li>Observed counts must all be &ge; 0.
+     * </li>
+     * <li>The observed and expected arrays must have the same length and
+     * their common length must be at least 2.
+     * <li> <code> 0 &lt; alpha &lt; 0.5 </code>
+     * </li></ul></p><p>
+     * If any of the preconditions are not met, an
+     * <code>IllegalArgumentException</code> is thrown.</p>
+     * <p><strong>Note: </strong>This implementation rescales the
+     * <code>expected</code> array if necessary to ensure that the sum of the
+     * expected and observed counts are equal.</p>
+     *
+     * @param observed array of observed frequency counts
+     * @param expected array of expected frequency counts
+     * @param alpha significance level of the test
+     * @return true iff null hypothesis can be rejected with confidence
+     * 1 - alpha
+     * @throws NotPositiveException if <code>observed</code> has negative entries
+     * @throws NotStrictlyPositiveException if <code>expected</code> has entries that are
+     * not strictly positive
+     * @throws DimensionMismatchException if the arrays length is less than 2
+     * @throws OutOfRangeException if <code>alpha</code> is not in the range (0, 0.5]
+     * @throws MaxCountExceededException if an error occurs computing the p-value
+     */
+    public boolean chiSquareTest(final double[] expected, final long[] observed,
+                                 final double alpha)
+        throws NotPositiveException, NotStrictlyPositiveException,
+        DimensionMismatchException, OutOfRangeException, MaxCountExceededException {
+
+        if ((alpha <= 0) || (alpha > 0.5)) {
+            throw new OutOfRangeException(LocalizedFormats.OUT_OF_BOUND_SIGNIFICANCE_LEVEL,
+                                          alpha, 0, 0.5);
+        }
+        return chiSquareTest(expected, observed) < alpha;
+
+    }
+
+    /**
+     *  Computes the Chi-Square statistic associated with a
+     * <a href="http://www.itl.nist.gov/div898/handbook/prc/section4/prc45.htm">
+     *  chi-square test of independence</a> based on the input <code>counts</code>
+     *  array, viewed as a two-way table.
+     * <p>
+     * The rows of the 2-way table are
+     * <code>count[0], ... , count[count.length - 1] </code></p>
+     * <p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>All counts must be &ge; 0.
+     * </li>
+     * <li>The count array must be rectangular (i.e. all count[i] subarrays
+     *  must have the same length).
+     * </li>
+     * <li>The 2-way table represented by <code>counts</code> must have at
+     *  least 2 columns and at least 2 rows.
+     * </li>
+     * </li></ul></p><p>
+     * If any of the preconditions are not met, an
+     * <code>IllegalArgumentException</code> is thrown.</p>
+     *
+     * @param counts array representation of 2-way table
+     * @return chiSquare test statistic
+     * @throws NullArgumentException if the array is null
+     * @throws DimensionMismatchException if the array is not rectangular
+     * @throws NotPositiveException if {@code counts} has negative entries
+     */
+    public double chiSquare(final long[][] counts)
+        throws NullArgumentException, NotPositiveException,
+        DimensionMismatchException {
+
+        checkArray(counts);
+        int nRows = counts.length;
+        int nCols = counts[0].length;
+
+        // compute row, column and total sums
+        double[] rowSum = new double[nRows];
+        double[] colSum = new double[nCols];
+        double total = 0.0d;
+        for (int row = 0; row < nRows; row++) {
+            for (int col = 0; col < nCols; col++) {
+                rowSum[row] += counts[row][col];
+                colSum[col] += counts[row][col];
+                total += counts[row][col];
+            }
+        }
+
+        // compute expected counts and chi-square
+        double sumSq = 0.0d;
+        double expected = 0.0d;
+        for (int row = 0; row < nRows; row++) {
+            for (int col = 0; col < nCols; col++) {
+                expected = (rowSum[row] * colSum[col]) / total;
+                sumSq += ((counts[row][col] - expected) *
+                        (counts[row][col] - expected)) / expected;
+            }
+        }
+        return sumSq;
+
+    }
+
+    /**
+     * Returns the <i>observed significance level</i>, or <a href=
+     * "http://www.cas.lancs.ac.uk/glossary_v1.1/hyptest.html#pvalue">
+     * p-value</a>, associated with a
+     * <a href="http://www.itl.nist.gov/div898/handbook/prc/section4/prc45.htm">
+     * chi-square test of independence</a> based on the input <code>counts</code>
+     * array, viewed as a two-way table.
+     * <p>
+     * The rows of the 2-way table are
+     * <code>count[0], ... , count[count.length - 1] </code></p>
+     * <p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>All counts must be &ge; 0.
+     * </li>
+     * <li>The count array must be rectangular (i.e. all count[i] subarrays must have
+     *     the same length).
+     * </li>
+     * <li>The 2-way table represented by <code>counts</code> must have at least 2
+     *     columns and at least 2 rows.
+     * </li>
+     * </li></ul></p><p>
+     * If any of the preconditions are not met, an
+     * <code>IllegalArgumentException</code> is thrown.</p>
+     *
+     * @param counts array representation of 2-way table
+     * @return p-value
+     * @throws NullArgumentException if the array is null
+     * @throws DimensionMismatchException if the array is not rectangular
+     * @throws NotPositiveException if {@code counts} has negative entries
+     * @throws MaxCountExceededException if an error occurs computing the p-value
+     */
+    public double chiSquareTest(final long[][] counts)
+        throws NullArgumentException, DimensionMismatchException,
+        NotPositiveException, MaxCountExceededException {
+
+        checkArray(counts);
+        double df = ((double) counts.length -1) * ((double) counts[0].length - 1);
+        // pass a null rng to avoid unneeded overhead as we will not sample from this distribution
+        final ChiSquaredDistribution distribution = new ChiSquaredDistribution(df);
+        return 1 - distribution.cumulativeProbability(chiSquare(counts));
+
+    }
+
+    /**
+     * Performs a <a href="http://www.itl.nist.gov/div898/handbook/prc/section4/prc45.htm">
+     * chi-square test of independence</a> evaluating the null hypothesis that the
+     * classifications represented by the counts in the columns of the input 2-way table
+     * are independent of the rows, with significance level <code>alpha</code>.
+     * Returns true iff the null hypothesis can be rejected with 100 * (1 - alpha) percent
+     * confidence.
+     * <p>
+     * The rows of the 2-way table are
+     * <code>count[0], ... , count[count.length - 1] </code></p>
+     * <p>
+     * <strong>Example:</strong><br>
+     * To test the null hypothesis that the counts in
+     * <code>count[0], ... , count[count.length - 1] </code>
+     *  all correspond to the same underlying probability distribution at the 99% level, use</p>
+     * <p><code>chiSquareTest(counts, 0.01)</code></p>
+     * <p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>All counts must be &ge; 0.
+     * </li>
+     * <li>The count array must be rectangular (i.e. all count[i] subarrays must have the
+     *     same length).</li>
+     * <li>The 2-way table represented by <code>counts</code> must have at least 2 columns and
+     *     at least 2 rows.</li>
+     * </li></ul></p><p>
+     * If any of the preconditions are not met, an
+     * <code>IllegalArgumentException</code> is thrown.</p>
+     *
+     * @param counts array representation of 2-way table
+     * @param alpha significance level of the test
+     * @return true iff null hypothesis can be rejected with confidence
+     * 1 - alpha
+     * @throws NullArgumentException if the array is null
+     * @throws DimensionMismatchException if the array is not rectangular
+     * @throws NotPositiveException if {@code counts} has any negative entries
+     * @throws OutOfRangeException if <code>alpha</code> is not in the range (0, 0.5]
+     * @throws MaxCountExceededException if an error occurs computing the p-value
+     */
+    public boolean chiSquareTest(final long[][] counts, final double alpha)
+        throws NullArgumentException, DimensionMismatchException,
+        NotPositiveException, OutOfRangeException, MaxCountExceededException {
+
+        if ((alpha <= 0) || (alpha > 0.5)) {
+            throw new OutOfRangeException(LocalizedFormats.OUT_OF_BOUND_SIGNIFICANCE_LEVEL,
+                                          alpha, 0, 0.5);
+        }
+        return chiSquareTest(counts) < alpha;
+
+    }
+
+    /**
+     * <p>Computes a
+     * <a href="http://www.itl.nist.gov/div898/software/dataplot/refman1/auxillar/chi2samp.htm">
+     * Chi-Square two sample test statistic</a> comparing bin frequency counts
+     * in <code>observed1</code> and <code>observed2</code>.  The
+     * sums of frequency counts in the two samples are not required to be the
+     * same.  The formula used to compute the test statistic is</p>
+     * <code>
+     * &sum;[(K * observed1[i] - observed2[i]/K)<sup>2</sup> / (observed1[i] + observed2[i])]
+     * </code> where
+     * <br/><code>K = &sqrt;[&sum(observed2 / &sum;(observed1)]</code>
+     * </p>
+     * <p>This statistic can be used to perform a Chi-Square test evaluating the
+     * null hypothesis that both observed counts follow the same distribution.</p>
+     * <p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>Observed counts must be non-negative.
+     * </li>
+     * <li>Observed counts for a specific bin must not both be zero.
+     * </li>
+     * <li>Observed counts for a specific sample must not all be 0.
+     * </li>
+     * <li>The arrays <code>observed1</code> and <code>observed2</code> must have
+     * the same length and their common length must be at least 2.
+     * </li></ul></p><p>
+     * If any of the preconditions are not met, an
+     * <code>IllegalArgumentException</code> is thrown.</p>
+     *
+     * @param observed1 array of observed frequency counts of the first data set
+     * @param observed2 array of observed frequency counts of the second data set
+     * @return chiSquare test statistic
+     * @throws DimensionMismatchException the the length of the arrays does not match
+     * @throws NotPositiveException if any entries in <code>observed1</code> or
+     * <code>observed2</code> are negative
+     * @throws ZeroException if either all counts of <code>observed1</code> or
+     * <code>observed2</code> are zero, or if the count at some index is zero
+     * for both arrays
+     * @since 1.2
+     */
+    public double chiSquareDataSetsComparison(long[] observed1, long[] observed2)
+        throws DimensionMismatchException, NotPositiveException, ZeroException {
+
+        // Make sure lengths are same
+        if (observed1.length < 2) {
+            throw new DimensionMismatchException(observed1.length, 2);
+        }
+        if (observed1.length != observed2.length) {
+            throw new DimensionMismatchException(observed1.length, observed2.length);
+        }
+
+        // Ensure non-negative counts
+        MathArrays.checkNonNegative(observed1);
+        MathArrays.checkNonNegative(observed2);
+
+        // Compute and compare count sums
+        long countSum1 = 0;
+        long countSum2 = 0;
+        boolean unequalCounts = false;
+        double weight = 0.0;
+        for (int i = 0; i < observed1.length; i++) {
+            countSum1 += observed1[i];
+            countSum2 += observed2[i];
+        }
+        // Ensure neither sample is uniformly 0
+        if (countSum1 == 0 || countSum2 == 0) {
+            throw new ZeroException();
+        }
+        // Compare and compute weight only if different
+        unequalCounts = countSum1 != countSum2;
+        if (unequalCounts) {
+            weight = FastMath.sqrt((double) countSum1 / (double) countSum2);
+        }
+        // Compute ChiSquare statistic
+        double sumSq = 0.0d;
+        double dev = 0.0d;
+        double obs1 = 0.0d;
+        double obs2 = 0.0d;
+        for (int i = 0; i < observed1.length; i++) {
+            if (observed1[i] == 0 && observed2[i] == 0) {
+                throw new ZeroException(LocalizedFormats.OBSERVED_COUNTS_BOTTH_ZERO_FOR_ENTRY, i);
+            } else {
+                obs1 = observed1[i];
+                obs2 = observed2[i];
+                if (unequalCounts) { // apply weights
+                    dev = obs1/weight - obs2 * weight;
+                } else {
+                    dev = obs1 - obs2;
+                }
+                sumSq += (dev * dev) / (obs1 + obs2);
+            }
+        }
+        return sumSq;
+    }
+
+    /**
+     * <p>Returns the <i>observed significance level</i>, or <a href=
+     * "http://www.cas.lancs.ac.uk/glossary_v1.1/hyptest.html#pvalue">
+     * p-value</a>, associated with a Chi-Square two sample test comparing
+     * bin frequency counts in <code>observed1</code> and
+     * <code>observed2</code>.
+     * </p>
+     * <p>The number returned is the smallest significance level at which one
+     * can reject the null hypothesis that the observed counts conform to the
+     * same distribution.
+     * </p>
+     * <p>See {@link #chiSquareDataSetsComparison(long[], long[])} for details
+     * on the formula used to compute the test statistic. The degrees of
+     * of freedom used to perform the test is one less than the common length
+     * of the input observed count arrays.
+     * </p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>Observed counts must be non-negative.
+     * </li>
+     * <li>Observed counts for a specific bin must not both be zero.
+     * </li>
+     * <li>Observed counts for a specific sample must not all be 0.
+     * </li>
+     * <li>The arrays <code>observed1</code> and <code>observed2</code> must
+     * have the same length and
+     * their common length must be at least 2.
+     * </li></ul><p>
+     * If any of the preconditions are not met, an
+     * <code>IllegalArgumentException</code> is thrown.</p>
+     *
+     * @param observed1 array of observed frequency counts of the first data set
+     * @param observed2 array of observed frequency counts of the second data set
+     * @return p-value
+     * @throws DimensionMismatchException the the length of the arrays does not match
+     * @throws NotPositiveException if any entries in <code>observed1</code> or
+     * <code>observed2</code> are negative
+     * @throws ZeroException if either all counts of <code>observed1</code> or
+     * <code>observed2</code> are zero, or if the count at the same index is zero
+     * for both arrays
+     * @throws MaxCountExceededException if an error occurs computing the p-value
+     * @since 1.2
+     */
+    public double chiSquareTestDataSetsComparison(long[] observed1, long[] observed2)
+        throws DimensionMismatchException, NotPositiveException, ZeroException,
+        MaxCountExceededException {
+
+        // pass a null rng to avoid unneeded overhead as we will not sample from this distribution
+        final ChiSquaredDistribution distribution =
+                new ChiSquaredDistribution(null, (double) observed1.length - 1);
+        return 1 - distribution.cumulativeProbability(
+                chiSquareDataSetsComparison(observed1, observed2));
+
+    }
+
+    /**
+     * <p>Performs a Chi-Square two sample test comparing two binned data
+     * sets. The test evaluates the null hypothesis that the two lists of
+     * observed counts conform to the same frequency distribution, with
+     * significance level <code>alpha</code>.  Returns true iff the null
+     * hypothesis can be rejected with 100 * (1 - alpha) percent confidence.
+     * </p>
+     * <p>See {@link #chiSquareDataSetsComparison(long[], long[])} for
+     * details on the formula used to compute the Chisquare statistic used
+     * in the test. The degrees of of freedom used to perform the test is
+     * one less than the common length of the input observed count arrays.
+     * </p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>Observed counts must be non-negative.
+     * </li>
+     * <li>Observed counts for a specific bin must not both be zero.
+     * </li>
+     * <li>Observed counts for a specific sample must not all be 0.
+     * </li>
+     * <li>The arrays <code>observed1</code> and <code>observed2</code> must
+     * have the same length and their common length must be at least 2.
+     * </li>
+     * <li> <code> 0 < alpha < 0.5 </code>
+     * </li></ul><p>
+     * If any of the preconditions are not met, an
+     * <code>IllegalArgumentException</code> is thrown.</p>
+     *
+     * @param observed1 array of observed frequency counts of the first data set
+     * @param observed2 array of observed frequency counts of the second data set
+     * @param alpha significance level of the test
+     * @return true iff null hypothesis can be rejected with confidence
+     * 1 - alpha
+     * @throws DimensionMismatchException the the length of the arrays does not match
+     * @throws NotPositiveException if any entries in <code>observed1</code> or
+     * <code>observed2</code> are negative
+     * @throws ZeroException if either all counts of <code>observed1</code> or
+     * <code>observed2</code> are zero, or if the count at the same index is zero
+     * for both arrays
+     * @throws OutOfRangeException if <code>alpha</code> is not in the range (0, 0.5]
+     * @throws MaxCountExceededException if an error occurs performing the test
+     * @since 1.2
+     */
+    public boolean chiSquareTestDataSetsComparison(final long[] observed1,
+                                                   final long[] observed2,
+                                                   final double alpha)
+        throws DimensionMismatchException, NotPositiveException,
+        ZeroException, OutOfRangeException, MaxCountExceededException {
+
+        if (alpha <= 0 ||
+            alpha > 0.5) {
+            throw new OutOfRangeException(LocalizedFormats.OUT_OF_BOUND_SIGNIFICANCE_LEVEL,
+                                          alpha, 0, 0.5);
+        }
+        return chiSquareTestDataSetsComparison(observed1, observed2) < alpha;
+
+    }
+
+    /**
+     * Checks to make sure that the input long[][] array is rectangular,
+     * has at least 2 rows and 2 columns, and has all non-negative entries.
+     *
+     * @param in input 2-way table to check
+     * @throws NullArgumentException if the array is null
+     * @throws DimensionMismatchException if the array is not valid
+     * @throws NotPositiveException if the array contains any negative entries
+     */
+    private void checkArray(final long[][] in)
+        throws NullArgumentException, DimensionMismatchException,
+        NotPositiveException {
+
+        if (in.length < 2) {
+            throw new DimensionMismatchException(in.length, 2);
+        }
+
+        if (in[0].length < 2) {
+            throw new DimensionMismatchException(in[0].length, 2);
+        }
+
+        MathArrays.checkRectangular(in);
+        MathArrays.checkNonNegative(in);
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/inference/GTest.java b/src/main/java/org/apache/commons/math3/stat/inference/GTest.java
new file mode 100644
index 0000000..de1fbe3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/inference/GTest.java
@@ -0,0 +1,538 @@
+/*
+ * 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.math3.stat.inference;
+
+import org.apache.commons.math3.distribution.ChiSquaredDistribution;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.ZeroException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * Implements <a href="http://en.wikipedia.org/wiki/G-test">G Test</a>
+ * statistics.
+ *
+ * <p>This is known in statistical genetics as the McDonald-Kreitman test.
+ * The implementation handles both known and unknown distributions.</p>
+ *
+ * <p>Two samples tests can be used when the distribution is unknown <i>a priori</i>
+ * but provided by one sample, or when the hypothesis under test is that the two
+ * samples come from the same underlying distribution.</p>
+ *
+ * @since 3.1
+ */
+public class GTest {
+
+    /**
+     * Computes the <a href="http://en.wikipedia.org/wiki/G-test">G statistic
+     * for Goodness of Fit</a> comparing {@code observed} and {@code expected}
+     * frequency counts.
+     *
+     * <p>This statistic can be used to perform a G test (Log-Likelihood Ratio
+     * Test) evaluating the null hypothesis that the observed counts follow the
+     * expected distribution.</p>
+     *
+     * <p><strong>Preconditions</strong>: <ul>
+     * <li>Expected counts must all be positive. </li>
+     * <li>Observed counts must all be &ge; 0. </li>
+     * <li>The observed and expected arrays must have the same length and their
+     * common length must be at least 2. </li></ul></p>
+     *
+     * <p>If any of the preconditions are not met, a
+     * {@code MathIllegalArgumentException} is thrown.</p>
+     *
+     * <p><strong>Note:</strong>This implementation rescales the
+     * {@code expected} array if necessary to ensure that the sum of the
+     * expected and observed counts are equal.</p>
+     *
+     * @param observed array of observed frequency counts
+     * @param expected array of expected frequency counts
+     * @return G-Test statistic
+     * @throws NotPositiveException if {@code observed} has negative entries
+     * @throws NotStrictlyPositiveException if {@code expected} has entries that
+     * are not strictly positive
+     * @throws DimensionMismatchException if the array lengths do not match or
+     * are less than 2.
+     */
+    public double g(final double[] expected, final long[] observed)
+            throws NotPositiveException, NotStrictlyPositiveException,
+            DimensionMismatchException {
+
+        if (expected.length < 2) {
+            throw new DimensionMismatchException(expected.length, 2);
+        }
+        if (expected.length != observed.length) {
+            throw new DimensionMismatchException(expected.length, observed.length);
+        }
+        MathArrays.checkPositive(expected);
+        MathArrays.checkNonNegative(observed);
+
+        double sumExpected = 0d;
+        double sumObserved = 0d;
+        for (int i = 0; i < observed.length; i++) {
+            sumExpected += expected[i];
+            sumObserved += observed[i];
+        }
+        double ratio = 1d;
+        boolean rescale = false;
+        if (FastMath.abs(sumExpected - sumObserved) > 10E-6) {
+            ratio = sumObserved / sumExpected;
+            rescale = true;
+        }
+        double sum = 0d;
+        for (int i = 0; i < observed.length; i++) {
+            final double dev = rescale ?
+                    FastMath.log((double) observed[i] / (ratio * expected[i])) :
+                        FastMath.log((double) observed[i] / expected[i]);
+            sum += ((double) observed[i]) * dev;
+        }
+        return 2d * sum;
+    }
+
+    /**
+     * Returns the <i>observed significance level</i>, or <a href=
+     * "http://www.cas.lancs.ac.uk/glossary_v1.1/hyptest.html#pvalue"> p-value</a>,
+     * associated with a G-Test for goodness of fit</a> comparing the
+     * {@code observed} frequency counts to those in the {@code expected} array.
+     *
+     * <p>The number returned is the smallest significance level at which one
+     * can reject the null hypothesis that the observed counts conform to the
+     * frequency distribution described by the expected counts.</p>
+     *
+     * <p>The probability returned is the tail probability beyond
+     * {@link #g(double[], long[]) g(expected, observed)}
+     * in the ChiSquare distribution with degrees of freedom one less than the
+     * common length of {@code expected} and {@code observed}.</p>
+     *
+     * <p> <strong>Preconditions</strong>: <ul>
+     * <li>Expected counts must all be positive. </li>
+     * <li>Observed counts must all be &ge; 0. </li>
+     * <li>The observed and expected arrays must have the
+     * same length and their common length must be at least 2.</li>
+     * </ul></p>
+     *
+     * <p>If any of the preconditions are not met, a
+     * {@code MathIllegalArgumentException} is thrown.</p>
+     *
+     * <p><strong>Note:</strong>This implementation rescales the
+     * {@code expected} array if necessary to ensure that the sum of the
+     *  expected and observed counts are equal.</p>
+     *
+     * @param observed array of observed frequency counts
+     * @param expected array of expected frequency counts
+     * @return p-value
+     * @throws NotPositiveException if {@code observed} has negative entries
+     * @throws NotStrictlyPositiveException if {@code expected} has entries that
+     * are not strictly positive
+     * @throws DimensionMismatchException if the array lengths do not match or
+     * are less than 2.
+     * @throws MaxCountExceededException if an error occurs computing the
+     * p-value.
+     */
+    public double gTest(final double[] expected, final long[] observed)
+            throws NotPositiveException, NotStrictlyPositiveException,
+            DimensionMismatchException, MaxCountExceededException {
+
+        // pass a null rng to avoid unneeded overhead as we will not sample from this distribution
+        final ChiSquaredDistribution distribution =
+                new ChiSquaredDistribution(null, expected.length - 1.0);
+        return 1.0 - distribution.cumulativeProbability(g(expected, observed));
+    }
+
+    /**
+     * Returns the intrinsic (Hardy-Weinberg proportions) p-Value, as described
+     * in p64-69 of McDonald, J.H. 2009. Handbook of Biological Statistics
+     * (2nd ed.). Sparky House Publishing, Baltimore, Maryland.
+     *
+     * <p> The probability returned is the tail probability beyond
+     * {@link #g(double[], long[]) g(expected, observed)}
+     * in the ChiSquare distribution with degrees of freedom two less than the
+     * common length of {@code expected} and {@code observed}.</p>
+     *
+     * @param observed array of observed frequency counts
+     * @param expected array of expected frequency counts
+     * @return p-value
+     * @throws NotPositiveException if {@code observed} has negative entries
+     * @throws NotStrictlyPositiveException {@code expected} has entries that are
+     * not strictly positive
+     * @throws DimensionMismatchException if the array lengths do not match or
+     * are less than 2.
+     * @throws MaxCountExceededException if an error occurs computing the
+     * p-value.
+     */
+    public double gTestIntrinsic(final double[] expected, final long[] observed)
+            throws NotPositiveException, NotStrictlyPositiveException,
+            DimensionMismatchException, MaxCountExceededException {
+
+        // pass a null rng to avoid unneeded overhead as we will not sample from this distribution
+        final ChiSquaredDistribution distribution =
+                new ChiSquaredDistribution(null, expected.length - 2.0);
+        return 1.0 - distribution.cumulativeProbability(g(expected, observed));
+    }
+
+    /**
+     * Performs a G-Test (Log-Likelihood Ratio Test) for goodness of fit
+     * evaluating the null hypothesis that the observed counts conform to the
+     * frequency distribution described by the expected counts, with
+     * significance level {@code alpha}. Returns true iff the null
+     * hypothesis can be rejected with {@code 100 * (1 - alpha)} percent confidence.
+     *
+     * <p><strong>Example:</strong><br> To test the hypothesis that
+     * {@code observed} follows {@code expected} at the 99% level,
+     * use </p><p>
+     * {@code gTest(expected, observed, 0.01)}</p>
+     *
+     * <p>Returns true iff {@link #gTest(double[], long[])
+     *  gTestGoodnessOfFitPValue(expected, observed)} < alpha</p>
+     *
+     * <p><strong>Preconditions</strong>: <ul>
+     * <li>Expected counts must all be positive. </li>
+     * <li>Observed counts must all be &ge; 0. </li>
+     * <li>The observed and expected arrays must have the same length and their
+     * common length must be at least 2.
+     * <li> {@code 0 < alpha < 0.5} </li></ul></p>
+     *
+     * <p>If any of the preconditions are not met, a
+     * {@code MathIllegalArgumentException} is thrown.</p>
+     *
+     * <p><strong>Note:</strong>This implementation rescales the
+     * {@code expected} array if necessary to ensure that the sum of the
+     * expected and observed counts are equal.</p>
+     *
+     * @param observed array of observed frequency counts
+     * @param expected array of expected frequency counts
+     * @param alpha significance level of the test
+     * @return true iff null hypothesis can be rejected with confidence 1 -
+     * alpha
+     * @throws NotPositiveException if {@code observed} has negative entries
+     * @throws NotStrictlyPositiveException if {@code expected} has entries that
+     * are not strictly positive
+     * @throws DimensionMismatchException if the array lengths do not match or
+     * are less than 2.
+     * @throws MaxCountExceededException if an error occurs computing the
+     * p-value.
+     * @throws OutOfRangeException if alpha is not strictly greater than zero
+     * and less than or equal to 0.5
+     */
+    public boolean gTest(final double[] expected, final long[] observed,
+            final double alpha)
+            throws NotPositiveException, NotStrictlyPositiveException,
+            DimensionMismatchException, OutOfRangeException, MaxCountExceededException {
+
+        if ((alpha <= 0) || (alpha > 0.5)) {
+            throw new OutOfRangeException(LocalizedFormats.OUT_OF_BOUND_SIGNIFICANCE_LEVEL,
+                    alpha, 0, 0.5);
+        }
+        return gTest(expected, observed) < alpha;
+    }
+
+    /**
+     * Calculates the <a href=
+     * "http://en.wikipedia.org/wiki/Entropy_%28information_theory%29">Shannon
+     * entropy</a> for 2 Dimensional Matrix.  The value returned is the entropy
+     * of the vector formed by concatenating the rows (or columns) of {@code k}
+     * to form a vector. See {@link #entropy(long[])}.
+     *
+     * @param k 2 Dimensional Matrix of long values (for ex. the counts of a
+     * trials)
+     * @return Shannon Entropy of the given Matrix
+     *
+     */
+    private double entropy(final long[][] k) {
+        double h = 0d;
+        double sum_k = 0d;
+        for (int i = 0; i < k.length; i++) {
+            for (int j = 0; j < k[i].length; j++) {
+                sum_k += (double) k[i][j];
+            }
+        }
+        for (int i = 0; i < k.length; i++) {
+            for (int j = 0; j < k[i].length; j++) {
+                if (k[i][j] != 0) {
+                    final double p_ij = (double) k[i][j] / sum_k;
+                    h += p_ij * FastMath.log(p_ij);
+                }
+            }
+        }
+        return -h;
+    }
+
+    /**
+     * Calculates the <a href="http://en.wikipedia.org/wiki/Entropy_%28information_theory%29">
+     * Shannon entropy</a> for a vector.  The values of {@code k} are taken to be
+     * incidence counts of the values of a random variable. What is returned is <br/>
+     * &sum;p<sub>i</sub>log(p<sub>i</sub><br/>
+     * where p<sub>i</sub> = k[i] / (sum of elements in k)
+     *
+     * @param k Vector (for ex. Row Sums of a trials)
+     * @return Shannon Entropy of the given Vector
+     *
+     */
+    private double entropy(final long[] k) {
+        double h = 0d;
+        double sum_k = 0d;
+        for (int i = 0; i < k.length; i++) {
+            sum_k += (double) k[i];
+        }
+        for (int i = 0; i < k.length; i++) {
+            if (k[i] != 0) {
+                final double p_i = (double) k[i] / sum_k;
+                h += p_i * FastMath.log(p_i);
+            }
+        }
+        return -h;
+    }
+
+    /**
+     * <p>Computes a G (Log-Likelihood Ratio) two sample test statistic for
+     * independence comparing frequency counts in
+     * {@code observed1} and {@code observed2}. The sums of frequency
+     * counts in the two samples are not required to be the same. The formula
+     * used to compute the test statistic is </p>
+     *
+     * <p>{@code 2 * totalSum * [H(rowSums) + H(colSums) - H(k)]}</p>
+     *
+     * <p> where {@code H} is the
+     * <a href="http://en.wikipedia.org/wiki/Entropy_%28information_theory%29">
+     * Shannon Entropy</a> of the random variable formed by viewing the elements
+     * of the argument array as incidence counts; <br/>
+     * {@code k} is a matrix with rows {@code [observed1, observed2]}; <br/>
+     * {@code rowSums, colSums} are the row/col sums of {@code k}; <br>
+     * and {@code totalSum} is the overall sum of all entries in {@code k}.</p>
+     *
+     * <p>This statistic can be used to perform a G test evaluating the null
+     * hypothesis that both observed counts are independent </p>
+     *
+     * <p> <strong>Preconditions</strong>: <ul>
+     * <li>Observed counts must be non-negative. </li>
+     * <li>Observed counts for a specific bin must not both be zero. </li>
+     * <li>Observed counts for a specific sample must not all be  0. </li>
+     * <li>The arrays {@code observed1} and {@code observed2} must have
+     * the same length and their common length must be at least 2. </li></ul></p>
+     *
+     * <p>If any of the preconditions are not met, a
+     * {@code MathIllegalArgumentException} is thrown.</p>
+     *
+     * @param observed1 array of observed frequency counts of the first data set
+     * @param observed2 array of observed frequency counts of the second data
+     * set
+     * @return G-Test statistic
+     * @throws DimensionMismatchException the the lengths of the arrays do not
+     * match or their common length is less than 2
+     * @throws NotPositiveException if any entry in {@code observed1} or
+     * {@code observed2} is negative
+     * @throws ZeroException if either all counts of
+     * {@code observed1} or {@code observed2} are zero, or if the count
+     * at the same index is zero for both arrays.
+     */
+    public double gDataSetsComparison(final long[] observed1, final long[] observed2)
+            throws DimensionMismatchException, NotPositiveException, ZeroException {
+
+        // Make sure lengths are same
+        if (observed1.length < 2) {
+            throw new DimensionMismatchException(observed1.length, 2);
+        }
+        if (observed1.length != observed2.length) {
+            throw new DimensionMismatchException(observed1.length, observed2.length);
+        }
+
+        // Ensure non-negative counts
+        MathArrays.checkNonNegative(observed1);
+        MathArrays.checkNonNegative(observed2);
+
+        // Compute and compare count sums
+        long countSum1 = 0;
+        long countSum2 = 0;
+
+        // Compute and compare count sums
+        final long[] collSums = new long[observed1.length];
+        final long[][] k = new long[2][observed1.length];
+
+        for (int i = 0; i < observed1.length; i++) {
+            if (observed1[i] == 0 && observed2[i] == 0) {
+                throw new ZeroException(LocalizedFormats.OBSERVED_COUNTS_BOTTH_ZERO_FOR_ENTRY, i);
+            } else {
+                countSum1 += observed1[i];
+                countSum2 += observed2[i];
+                collSums[i] = observed1[i] + observed2[i];
+                k[0][i] = observed1[i];
+                k[1][i] = observed2[i];
+            }
+        }
+        // Ensure neither sample is uniformly 0
+        if (countSum1 == 0 || countSum2 == 0) {
+            throw new ZeroException();
+        }
+        final long[] rowSums = {countSum1, countSum2};
+        final double sum = (double) countSum1 + (double) countSum2;
+        return 2 * sum * (entropy(rowSums) + entropy(collSums) - entropy(k));
+    }
+
+    /**
+     * Calculates the root log-likelihood ratio for 2 state Datasets. See
+     * {@link #gDataSetsComparison(long[], long[] )}.
+     *
+     * <p>Given two events A and B, let k11 be the number of times both events
+     * occur, k12 the incidence of B without A, k21 the count of A without B,
+     * and k22 the number of times neither A nor B occurs.  What is returned
+     * by this method is </p>
+     *
+     * <p>{@code (sgn) sqrt(gValueDataSetsComparison({k11, k12}, {k21, k22})}</p>
+     *
+     * <p>where {@code sgn} is -1 if {@code k11 / (k11 + k12) < k21 / (k21 + k22))};<br/>
+     * 1 otherwise.</p>
+     *
+     * <p>Signed root LLR has two advantages over the basic LLR: a) it is positive
+     * where k11 is bigger than expected, negative where it is lower b) if there is
+     * no difference it is asymptotically normally distributed. This allows one
+     * to talk about "number of standard deviations" which is a more common frame
+     * of reference than the chi^2 distribution.</p>
+     *
+     * @param k11 number of times the two events occurred together (AB)
+     * @param k12 number of times the second event occurred WITHOUT the
+     * first event (notA,B)
+     * @param k21 number of times the first event occurred WITHOUT the
+     * second event (A, notB)
+     * @param k22 number of times something else occurred (i.e. was neither
+     * of these events (notA, notB)
+     * @return root log-likelihood ratio
+     *
+     */
+    public double rootLogLikelihoodRatio(final long k11, long k12,
+            final long k21, final long k22) {
+        final double llr = gDataSetsComparison(
+                new long[]{k11, k12}, new long[]{k21, k22});
+        double sqrt = FastMath.sqrt(llr);
+        if ((double) k11 / (k11 + k12) < (double) k21 / (k21 + k22)) {
+            sqrt = -sqrt;
+        }
+        return sqrt;
+    }
+
+    /**
+     * <p>Returns the <i>observed significance level</i>, or <a href=
+     * "http://www.cas.lancs.ac.uk/glossary_v1.1/hyptest.html#pvalue">
+     * p-value</a>, associated with a G-Value (Log-Likelihood Ratio) for two
+     * sample test comparing bin frequency counts in {@code observed1} and
+     * {@code observed2}.</p>
+     *
+     * <p>The number returned is the smallest significance level at which one
+     * can reject the null hypothesis that the observed counts conform to the
+     * same distribution. </p>
+     *
+     * <p>See {@link #gTest(double[], long[])} for details
+     * on how the p-value is computed.  The degrees of of freedom used to
+     * perform the test is one less than the common length of the input observed
+     * count arrays.</p>
+     *
+     * <p><strong>Preconditions</strong>:
+     * <ul> <li>Observed counts must be non-negative. </li>
+     * <li>Observed counts for a specific bin must not both be zero. </li>
+     * <li>Observed counts for a specific sample must not all be 0. </li>
+     * <li>The arrays {@code observed1} and {@code observed2} must
+     * have the same length and their common length must be at least 2. </li>
+     * </ul><p>
+     * <p> If any of the preconditions are not met, a
+     * {@code MathIllegalArgumentException} is thrown.</p>
+     *
+     * @param observed1 array of observed frequency counts of the first data set
+     * @param observed2 array of observed frequency counts of the second data
+     * set
+     * @return p-value
+     * @throws DimensionMismatchException the the length of the arrays does not
+     * match or their common length is less than 2
+     * @throws NotPositiveException if any of the entries in {@code observed1} or
+     * {@code observed2} are negative
+     * @throws ZeroException if either all counts of {@code observed1} or
+     * {@code observed2} are zero, or if the count at some index is
+     * zero for both arrays
+     * @throws MaxCountExceededException if an error occurs computing the
+     * p-value.
+     */
+    public double gTestDataSetsComparison(final long[] observed1,
+            final long[] observed2)
+            throws DimensionMismatchException, NotPositiveException, ZeroException,
+            MaxCountExceededException {
+
+        // pass a null rng to avoid unneeded overhead as we will not sample from this distribution
+        final ChiSquaredDistribution distribution =
+                new ChiSquaredDistribution(null, (double) observed1.length - 1);
+        return 1 - distribution.cumulativeProbability(
+                gDataSetsComparison(observed1, observed2));
+    }
+
+    /**
+     * <p>Performs a G-Test (Log-Likelihood Ratio Test) comparing two binned
+     * data sets. The test evaluates the null hypothesis that the two lists
+     * of observed counts conform to the same frequency distribution, with
+     * significance level {@code alpha}. Returns true iff the null
+     * hypothesis can be rejected  with 100 * (1 - alpha) percent confidence.
+     * </p>
+     * <p>See {@link #gDataSetsComparison(long[], long[])} for details
+     * on the formula used to compute the G (LLR) statistic used in the test and
+     * {@link #gTest(double[], long[])} for information on how
+     * the observed significance level is computed. The degrees of of freedom used
+     * to perform the test is one less than the common length of the input observed
+     * count arrays. </p>
+     *
+     * <strong>Preconditions</strong>: <ul>
+     * <li>Observed counts must be non-negative. </li>
+     * <li>Observed counts for a specific bin must not both be zero. </li>
+     * <li>Observed counts for a specific sample must not all be 0. </li>
+     * <li>The arrays {@code observed1} and {@code observed2} must
+     * have the same length and their common length must be at least 2. </li>
+     * <li>{@code 0 < alpha < 0.5} </li></ul></p>
+     *
+     * <p>If any of the preconditions are not met, a
+     * {@code MathIllegalArgumentException} is thrown.</p>
+     *
+     * @param observed1 array of observed frequency counts of the first data set
+     * @param observed2 array of observed frequency counts of the second data
+     * set
+     * @param alpha significance level of the test
+     * @return true iff null hypothesis can be rejected with confidence 1 -
+     * alpha
+     * @throws DimensionMismatchException the the length of the arrays does not
+     * match
+     * @throws NotPositiveException if any of the entries in {@code observed1} or
+     * {@code observed2} are negative
+     * @throws ZeroException if either all counts of {@code observed1} or
+     * {@code observed2} are zero, or if the count at some index is
+     * zero for both arrays
+     * @throws OutOfRangeException if {@code alpha} is not in the range
+     * (0, 0.5]
+     * @throws MaxCountExceededException if an error occurs performing the test
+     */
+    public boolean gTestDataSetsComparison(
+            final long[] observed1,
+            final long[] observed2,
+            final double alpha)
+            throws DimensionMismatchException, NotPositiveException,
+            ZeroException, OutOfRangeException, MaxCountExceededException {
+
+        if (alpha <= 0 || alpha > 0.5) {
+            throw new OutOfRangeException(
+                    LocalizedFormats.OUT_OF_BOUND_SIGNIFICANCE_LEVEL, alpha, 0, 0.5);
+        }
+        return gTestDataSetsComparison(observed1, observed2) < alpha;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/inference/KolmogorovSmirnovTest.java b/src/main/java/org/apache/commons/math3/stat/inference/KolmogorovSmirnovTest.java
new file mode 100644
index 0000000..6b70e9b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/inference/KolmogorovSmirnovTest.java
@@ -0,0 +1,1270 @@
+/*
+ * 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.math3.stat.inference;
+
+import java.math.BigDecimal;
+import java.util.Arrays;
+import java.util.HashSet;
+
+import org.apache.commons.math3.distribution.EnumeratedRealDistribution;
+import org.apache.commons.math3.distribution.RealDistribution;
+import org.apache.commons.math3.distribution.UniformRealDistribution;
+import org.apache.commons.math3.exception.InsufficientDataException;
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.MathInternalError;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.TooManyIterationsException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.fraction.BigFraction;
+import org.apache.commons.math3.fraction.BigFractionField;
+import org.apache.commons.math3.fraction.FractionConversionException;
+import org.apache.commons.math3.linear.Array2DRowFieldMatrix;
+import org.apache.commons.math3.linear.FieldMatrix;
+import org.apache.commons.math3.linear.MatrixUtils;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.random.JDKRandomGenerator;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well19937c;
+import org.apache.commons.math3.util.CombinatoricsUtils;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * Implementation of the <a href="http://en.wikipedia.org/wiki/Kolmogorov-Smirnov_test">
+ * Kolmogorov-Smirnov (K-S) test</a> for equality of continuous distributions.
+ * <p>
+ * The K-S test uses a statistic based on the maximum deviation of the empirical distribution of
+ * sample data points from the distribution expected under the null hypothesis. For one-sample tests
+ * evaluating the null hypothesis that a set of sample data points follow a given distribution, the
+ * test statistic is \(D_n=\sup_x |F_n(x)-F(x)|\), where \(F\) is the expected distribution and
+ * \(F_n\) is the empirical distribution of the \(n\) sample data points. The distribution of
+ * \(D_n\) is estimated using a method based on [1] with certain quick decisions for extreme values
+ * given in [2].
+ * </p>
+ * <p>
+ * Two-sample tests are also supported, evaluating the null hypothesis that the two samples
+ * {@code x} and {@code y} come from the same underlying distribution. In this case, the test
+ * statistic is \(D_{n,m}=\sup_t | F_n(t)-F_m(t)|\) where \(n\) is the length of {@code x}, \(m\) is
+ * the length of {@code y}, \(F_n\) is the empirical distribution that puts mass \(1/n\) at each of
+ * the values in {@code x} and \(F_m\) is the empirical distribution of the {@code y} values. The
+ * default 2-sample test method, {@link #kolmogorovSmirnovTest(double[], double[])} works as
+ * follows:
+ * <ul>
+ * <li>For small samples (where the product of the sample sizes is less than
+ * {@value #LARGE_SAMPLE_PRODUCT}), the method presented in [4] is used to compute the
+ * exact p-value for the 2-sample test.</li>
+ * <li>When the product of the sample sizes exceeds {@value #LARGE_SAMPLE_PRODUCT}, the asymptotic
+ * distribution of \(D_{n,m}\) is used. See {@link #approximateP(double, int, int)} for details on
+ * the approximation.</li>
+ * </ul></p><p>
+ * If the product of the sample sizes is less than {@value #LARGE_SAMPLE_PRODUCT} and the sample
+ * data contains ties, random jitter is added to the sample data to break ties before applying
+ * the algorithm above. Alternatively, the {@link #bootstrap(double[], double[], int, boolean)}
+ * method, modeled after <a href="http://sekhon.berkeley.edu/matching/ks.boot.html">ks.boot</a>
+ * in the R Matching package [3], can be used if ties are known to be present in the data.
+ * </p>
+ * <p>
+ * In the two-sample case, \(D_{n,m}\) has a discrete distribution. This makes the p-value
+ * associated with the null hypothesis \(H_0 : D_{n,m} \ge d \) differ from \(H_0 : D_{n,m} > d \)
+ * by the mass of the observed value \(d\). To distinguish these, the two-sample tests use a boolean
+ * {@code strict} parameter. This parameter is ignored for large samples.
+ * </p>
+ * <p>
+ * The methods used by the 2-sample default implementation are also exposed directly:
+ * <ul>
+ * <li>{@link #exactP(double, int, int, boolean)} computes exact 2-sample p-values</li>
+ * <li>{@link #approximateP(double, int, int)} uses the asymptotic distribution The {@code boolean}
+ * arguments in the first two methods allow the probability used to estimate the p-value to be
+ * expressed using strict or non-strict inequality. See
+ * {@link #kolmogorovSmirnovTest(double[], double[], boolean)}.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * References:
+ * <ul>
+ * <li>[1] <a href="http://www.jstatsoft.org/v08/i18/"> Evaluating Kolmogorov's Distribution</a> by
+ * George Marsaglia, Wai Wan Tsang, and Jingbo Wang</li>
+ * <li>[2] <a href="http://www.jstatsoft.org/v39/i11/"> Computing the Two-Sided Kolmogorov-Smirnov
+ * Distribution</a> by Richard Simard and Pierre L'Ecuyer</li>
+ * <li>[3] Jasjeet S. Sekhon. 2011. <a href="http://www.jstatsoft.org/article/view/v042i07">
+ * Multivariate and Propensity Score Matching Software with Automated Balance Optimization:
+ * The Matching package for R</a> Journal of Statistical Software, 42(7): 1-52.</li>
+ * <li>[4] Wilcox, Rand. 2012. Introduction to Robust Estimation and Hypothesis Testing,
+ * Chapter 5, 3rd Ed. Academic Press.</li>
+ * </ul>
+ * <br/>
+ * Note that [1] contains an error in computing h, refer to <a
+ * href="https://issues.apache.org/jira/browse/MATH-437">MATH-437</a> for details.
+ * </p>
+ *
+ * @since 3.3
+ */
+public class KolmogorovSmirnovTest {
+
+    /**
+     * Bound on the number of partial sums in {@link #ksSum(double, double, int)}
+     */
+    protected static final int MAXIMUM_PARTIAL_SUM_COUNT = 100000;
+
+    /** Convergence criterion for {@link #ksSum(double, double, int)} */
+    protected static final double KS_SUM_CAUCHY_CRITERION = 1E-20;
+
+    /** Convergence criterion for the sums in #pelzGood(double, double, int)} */
+    protected static final double PG_SUM_RELATIVE_ERROR = 1.0e-10;
+
+    /** No longer used. */
+    @Deprecated
+    protected static final int SMALL_SAMPLE_PRODUCT = 200;
+
+    /**
+     * When product of sample sizes exceeds this value, 2-sample K-S test uses asymptotic
+     * distribution to compute the p-value.
+     */
+    protected static final int LARGE_SAMPLE_PRODUCT = 10000;
+
+    /** Default number of iterations used by {@link #monteCarloP(double, int, int, boolean, int)}.
+     *  Deprecated as of version 3.6, as this method is no longer needed. */
+    @Deprecated
+    protected static final int MONTE_CARLO_ITERATIONS = 1000000;
+
+    /** Random data generator used by {@link #monteCarloP(double, int, int, boolean, int)} */
+    private final RandomGenerator rng;
+
+    /**
+     * Construct a KolmogorovSmirnovTest instance with a default random data generator.
+     */
+    public KolmogorovSmirnovTest() {
+        rng = new Well19937c();
+    }
+
+    /**
+     * Construct a KolmogorovSmirnovTest with the provided random data generator.
+     * The #monteCarloP(double, int, int, boolean, int) that uses the generator supplied to this
+     * constructor is deprecated as of version 3.6.
+     *
+     * @param rng random data generator used by {@link #monteCarloP(double, int, int, boolean, int)}
+     */
+    @Deprecated
+    public KolmogorovSmirnovTest(RandomGenerator rng) {
+        this.rng = rng;
+    }
+
+    /**
+     * Computes the <i>p-value</i>, or <i>observed significance level</i>, of a one-sample <a
+     * href="http://en.wikipedia.org/wiki/Kolmogorov-Smirnov_test"> Kolmogorov-Smirnov test</a>
+     * evaluating the null hypothesis that {@code data} conforms to {@code distribution}. If
+     * {@code exact} is true, the distribution used to compute the p-value is computed using
+     * extended precision. See {@link #cdfExact(double, int)}.
+     *
+     * @param distribution reference distribution
+     * @param data sample being being evaluated
+     * @param exact whether or not to force exact computation of the p-value
+     * @return the p-value associated with the null hypothesis that {@code data} is a sample from
+     *         {@code distribution}
+     * @throws InsufficientDataException if {@code data} does not have length at least 2
+     * @throws NullArgumentException if {@code data} is null
+     */
+    public double kolmogorovSmirnovTest(RealDistribution distribution, double[] data, boolean exact) {
+        return 1d - cdf(kolmogorovSmirnovStatistic(distribution, data), data.length, exact);
+    }
+
+    /**
+     * Computes the one-sample Kolmogorov-Smirnov test statistic, \(D_n=\sup_x |F_n(x)-F(x)|\) where
+     * \(F\) is the distribution (cdf) function associated with {@code distribution}, \(n\) is the
+     * length of {@code data} and \(F_n\) is the empirical distribution that puts mass \(1/n\) at
+     * each of the values in {@code data}.
+     *
+     * @param distribution reference distribution
+     * @param data sample being evaluated
+     * @return Kolmogorov-Smirnov statistic \(D_n\)
+     * @throws InsufficientDataException if {@code data} does not have length at least 2
+     * @throws NullArgumentException if {@code data} is null
+     */
+    public double kolmogorovSmirnovStatistic(RealDistribution distribution, double[] data) {
+        checkArray(data);
+        final int n = data.length;
+        final double nd = n;
+        final double[] dataCopy = new double[n];
+        System.arraycopy(data, 0, dataCopy, 0, n);
+        Arrays.sort(dataCopy);
+        double d = 0d;
+        for (int i = 1; i <= n; i++) {
+            final double yi = distribution.cumulativeProbability(dataCopy[i - 1]);
+            final double currD = FastMath.max(yi - (i - 1) / nd, i / nd - yi);
+            if (currD > d) {
+                d = currD;
+            }
+        }
+        return d;
+    }
+
+    /**
+     * Computes the <i>p-value</i>, or <i>observed significance level</i>, of a two-sample <a
+     * href="http://en.wikipedia.org/wiki/Kolmogorov-Smirnov_test"> Kolmogorov-Smirnov test</a>
+     * evaluating the null hypothesis that {@code x} and {@code y} are samples drawn from the same
+     * probability distribution. Specifically, what is returned is an estimate of the probability
+     * that the {@link #kolmogorovSmirnovStatistic(double[], double[])} associated with a randomly
+     * selected partition of the combined sample into subsamples of sizes {@code x.length} and
+     * {@code y.length} will strictly exceed (if {@code strict} is {@code true}) or be at least as
+     * large as {@code strict = false}) as {@code kolmogorovSmirnovStatistic(x, y)}.
+     * <ul>
+     * <li>For small samples (where the product of the sample sizes is less than
+     * {@value #LARGE_SAMPLE_PRODUCT}), the exact p-value is computed using the method presented
+     * in [4], implemented in {@link #exactP(double, int, int, boolean)}. </li>
+     * <li>When the product of the sample sizes exceeds {@value #LARGE_SAMPLE_PRODUCT}, the
+     * asymptotic distribution of \(D_{n,m}\) is used. See {@link #approximateP(double, int, int)}
+     * for details on the approximation.</li>
+     * </ul><p>
+     * If {@code x.length * y.length} < {@value #LARGE_SAMPLE_PRODUCT} and the combined set of values in
+     * {@code x} and {@code y} contains ties, random jitter is added to {@code x} and {@code y} to
+     * break ties before computing \(D_{n,m}\) and the p-value. The jitter is uniformly distributed
+     * on (-minDelta / 2, minDelta / 2) where minDelta is the smallest pairwise difference between
+     * values in the combined sample.</p>
+     * <p>
+     * If ties are known to be present in the data, {@link #bootstrap(double[], double[], int, boolean)}
+     * may be used as an alternative method for estimating the p-value.</p>
+     *
+     * @param x first sample dataset
+     * @param y second sample dataset
+     * @param strict whether or not the probability to compute is expressed as a strict inequality
+     *        (ignored for large samples)
+     * @return p-value associated with the null hypothesis that {@code x} and {@code y} represent
+     *         samples from the same distribution
+     * @throws InsufficientDataException if either {@code x} or {@code y} does not have length at
+     *         least 2
+     * @throws NullArgumentException if either {@code x} or {@code y} is null
+     * @see #bootstrap(double[], double[], int, boolean)
+     */
+    public double kolmogorovSmirnovTest(double[] x, double[] y, boolean strict) {
+        final long lengthProduct = (long) x.length * y.length;
+        double[] xa = null;
+        double[] ya = null;
+        if (lengthProduct < LARGE_SAMPLE_PRODUCT && hasTies(x,y)) {
+            xa = MathArrays.copyOf(x);
+            ya = MathArrays.copyOf(y);
+            fixTies(xa, ya);
+        } else {
+            xa = x;
+            ya = y;
+        }
+        if (lengthProduct < LARGE_SAMPLE_PRODUCT) {
+            return exactP(kolmogorovSmirnovStatistic(xa, ya), x.length, y.length, strict);
+        }
+        return approximateP(kolmogorovSmirnovStatistic(x, y), x.length, y.length);
+    }
+
+    /**
+     * Computes the <i>p-value</i>, or <i>observed significance level</i>, of a two-sample <a
+     * href="http://en.wikipedia.org/wiki/Kolmogorov-Smirnov_test"> Kolmogorov-Smirnov test</a>
+     * evaluating the null hypothesis that {@code x} and {@code y} are samples drawn from the same
+     * probability distribution. Assumes the strict form of the inequality used to compute the
+     * p-value. See {@link #kolmogorovSmirnovTest(RealDistribution, double[], boolean)}.
+     *
+     * @param x first sample dataset
+     * @param y second sample dataset
+     * @return p-value associated with the null hypothesis that {@code x} and {@code y} represent
+     *         samples from the same distribution
+     * @throws InsufficientDataException if either {@code x} or {@code y} does not have length at
+     *         least 2
+     * @throws NullArgumentException if either {@code x} or {@code y} is null
+     */
+    public double kolmogorovSmirnovTest(double[] x, double[] y) {
+        return kolmogorovSmirnovTest(x, y, true);
+    }
+
+    /**
+     * Computes the two-sample Kolmogorov-Smirnov test statistic, \(D_{n,m}=\sup_x |F_n(x)-F_m(x)|\)
+     * where \(n\) is the length of {@code x}, \(m\) is the length of {@code y}, \(F_n\) is the
+     * empirical distribution that puts mass \(1/n\) at each of the values in {@code x} and \(F_m\)
+     * is the empirical distribution of the {@code y} values.
+     *
+     * @param x first sample
+     * @param y second sample
+     * @return test statistic \(D_{n,m}\) used to evaluate the null hypothesis that {@code x} and
+     *         {@code y} represent samples from the same underlying distribution
+     * @throws InsufficientDataException if either {@code x} or {@code y} does not have length at
+     *         least 2
+     * @throws NullArgumentException if either {@code x} or {@code y} is null
+     */
+    public double kolmogorovSmirnovStatistic(double[] x, double[] y) {
+        return integralKolmogorovSmirnovStatistic(x, y)/((double)(x.length * (long)y.length));
+    }
+
+    /**
+     * Computes the two-sample Kolmogorov-Smirnov test statistic, \(D_{n,m}=\sup_x |F_n(x)-F_m(x)|\)
+     * where \(n\) is the length of {@code x}, \(m\) is the length of {@code y}, \(F_n\) is the
+     * empirical distribution that puts mass \(1/n\) at each of the values in {@code x} and \(F_m\)
+     * is the empirical distribution of the {@code y} values. Finally \(n m D_{n,m}\) is returned
+     * as long value.
+     *
+     * @param x first sample
+     * @param y second sample
+     * @return test statistic \(n m D_{n,m}\) used to evaluate the null hypothesis that {@code x} and
+     *         {@code y} represent samples from the same underlying distribution
+     * @throws InsufficientDataException if either {@code x} or {@code y} does not have length at
+     *         least 2
+     * @throws NullArgumentException if either {@code x} or {@code y} is null
+     */
+    private long integralKolmogorovSmirnovStatistic(double[] x, double[] y) {
+        checkArray(x);
+        checkArray(y);
+        // Copy and sort the sample arrays
+        final double[] sx = MathArrays.copyOf(x);
+        final double[] sy = MathArrays.copyOf(y);
+        Arrays.sort(sx);
+        Arrays.sort(sy);
+        final int n = sx.length;
+        final int m = sy.length;
+
+        int rankX = 0;
+        int rankY = 0;
+        long curD = 0l;
+
+        // Find the max difference between cdf_x and cdf_y
+        long supD = 0l;
+        do {
+            double z = Double.compare(sx[rankX], sy[rankY]) <= 0 ? sx[rankX] : sy[rankY];
+            while(rankX < n && Double.compare(sx[rankX], z) == 0) {
+                rankX += 1;
+                curD += m;
+            }
+            while(rankY < m && Double.compare(sy[rankY], z) == 0) {
+                rankY += 1;
+                curD -= n;
+            }
+            if (curD > supD) {
+                supD = curD;
+            }
+            else if (-curD > supD) {
+                supD = -curD;
+            }
+        } while(rankX < n && rankY < m);
+        return supD;
+    }
+
+    /**
+     * Computes the <i>p-value</i>, or <i>observed significance level</i>, of a one-sample <a
+     * href="http://en.wikipedia.org/wiki/Kolmogorov-Smirnov_test"> Kolmogorov-Smirnov test</a>
+     * evaluating the null hypothesis that {@code data} conforms to {@code distribution}.
+     *
+     * @param distribution reference distribution
+     * @param data sample being being evaluated
+     * @return the p-value associated with the null hypothesis that {@code data} is a sample from
+     *         {@code distribution}
+     * @throws InsufficientDataException if {@code data} does not have length at least 2
+     * @throws NullArgumentException if {@code data} is null
+     */
+    public double kolmogorovSmirnovTest(RealDistribution distribution, double[] data) {
+        return kolmogorovSmirnovTest(distribution, data, false);
+    }
+
+    /**
+     * Performs a <a href="http://en.wikipedia.org/wiki/Kolmogorov-Smirnov_test"> Kolmogorov-Smirnov
+     * test</a> evaluating the null hypothesis that {@code data} conforms to {@code distribution}.
+     *
+     * @param distribution reference distribution
+     * @param data sample being being evaluated
+     * @param alpha significance level of the test
+     * @return true iff the null hypothesis that {@code data} is a sample from {@code distribution}
+     *         can be rejected with confidence 1 - {@code alpha}
+     * @throws InsufficientDataException if {@code data} does not have length at least 2
+     * @throws NullArgumentException if {@code data} is null
+     */
+    public boolean kolmogorovSmirnovTest(RealDistribution distribution, double[] data, double alpha) {
+        if ((alpha <= 0) || (alpha > 0.5)) {
+            throw new OutOfRangeException(LocalizedFormats.OUT_OF_BOUND_SIGNIFICANCE_LEVEL, alpha, 0, 0.5);
+        }
+        return kolmogorovSmirnovTest(distribution, data) < alpha;
+    }
+
+    /**
+     * Estimates the <i>p-value</i> of a two-sample
+     * <a href="http://en.wikipedia.org/wiki/Kolmogorov-Smirnov_test"> Kolmogorov-Smirnov test</a>
+     * evaluating the null hypothesis that {@code x} and {@code y} are samples drawn from the same
+     * probability distribution. This method estimates the p-value by repeatedly sampling sets of size
+     * {@code x.length} and {@code y.length} from the empirical distribution of the combined sample.
+     * When {@code strict} is true, this is equivalent to the algorithm implemented in the R function
+     * {@code ks.boot}, described in <pre>
+     * Jasjeet S. Sekhon. 2011. 'Multivariate and Propensity Score Matching
+     * Software with Automated Balance Optimization: The Matching package for R.'
+     * Journal of Statistical Software, 42(7): 1-52.
+     * </pre>
+     * @param x first sample
+     * @param y second sample
+     * @param iterations number of bootstrap resampling iterations
+     * @param strict whether or not the null hypothesis is expressed as a strict inequality
+     * @return estimated p-value
+     */
+    public double bootstrap(double[] x, double[] y, int iterations, boolean strict) {
+        final int xLength = x.length;
+        final int yLength = y.length;
+        final double[] combined = new double[xLength + yLength];
+        System.arraycopy(x, 0, combined, 0, xLength);
+        System.arraycopy(y, 0, combined, xLength, yLength);
+        final EnumeratedRealDistribution dist = new EnumeratedRealDistribution(rng, combined);
+        final long d = integralKolmogorovSmirnovStatistic(x, y);
+        int greaterCount = 0;
+        int equalCount = 0;
+        double[] curX;
+        double[] curY;
+        long curD;
+        for (int i = 0; i < iterations; i++) {
+            curX = dist.sample(xLength);
+            curY = dist.sample(yLength);
+            curD = integralKolmogorovSmirnovStatistic(curX, curY);
+            if (curD > d) {
+                greaterCount++;
+            } else if (curD == d) {
+                equalCount++;
+            }
+        }
+        return strict ? greaterCount / (double) iterations :
+            (greaterCount + equalCount) / (double) iterations;
+    }
+
+    /**
+     * Computes {@code bootstrap(x, y, iterations, true)}.
+     * This is equivalent to ks.boot(x,y, nboots=iterations) using the R Matching
+     * package function. See #bootstrap(double[], double[], int, boolean).
+     *
+     * @param x first sample
+     * @param y second sample
+     * @param iterations number of bootstrap resampling iterations
+     * @return estimated p-value
+     */
+    public double bootstrap(double[] x, double[] y, int iterations) {
+        return bootstrap(x, y, iterations, true);
+    }
+
+    /**
+     * Calculates \(P(D_n < d)\) using the method described in [1] with quick decisions for extreme
+     * values given in [2] (see above). The result is not exact as with
+     * {@link #cdfExact(double, int)} because calculations are based on
+     * {@code double} rather than {@link org.apache.commons.math3.fraction.BigFraction}.
+     *
+     * @param d statistic
+     * @param n sample size
+     * @return \(P(D_n < d)\)
+     * @throws MathArithmeticException if algorithm fails to convert {@code h} to a
+     *         {@link org.apache.commons.math3.fraction.BigFraction} in expressing {@code d} as \((k
+     *         - h) / m\) for integer {@code k, m} and \(0 \le h < 1\)
+     */
+    public double cdf(double d, int n)
+        throws MathArithmeticException {
+        return cdf(d, n, false);
+    }
+
+    /**
+     * Calculates {@code P(D_n < d)}. The result is exact in the sense that BigFraction/BigReal is
+     * used everywhere at the expense of very slow execution time. Almost never choose this in real
+     * applications unless you are very sure; this is almost solely for verification purposes.
+     * Normally, you would choose {@link #cdf(double, int)}. See the class
+     * javadoc for definitions and algorithm description.
+     *
+     * @param d statistic
+     * @param n sample size
+     * @return \(P(D_n < d)\)
+     * @throws MathArithmeticException if the algorithm fails to convert {@code h} to a
+     *         {@link org.apache.commons.math3.fraction.BigFraction} in expressing {@code d} as \((k
+     *         - h) / m\) for integer {@code k, m} and \(0 \le h < 1\)
+     */
+    public double cdfExact(double d, int n)
+        throws MathArithmeticException {
+        return cdf(d, n, true);
+    }
+
+    /**
+     * Calculates {@code P(D_n < d)} using method described in [1] with quick decisions for extreme
+     * values given in [2] (see above).
+     *
+     * @param d statistic
+     * @param n sample size
+     * @param exact whether the probability should be calculated exact using
+     *        {@link org.apache.commons.math3.fraction.BigFraction} everywhere at the expense of
+     *        very slow execution time, or if {@code double} should be used convenient places to
+     *        gain speed. Almost never choose {@code true} in real applications unless you are very
+     *        sure; {@code true} is almost solely for verification purposes.
+     * @return \(P(D_n < d)\)
+     * @throws MathArithmeticException if algorithm fails to convert {@code h} to a
+     *         {@link org.apache.commons.math3.fraction.BigFraction} in expressing {@code d} as \((k
+     *         - h) / m\) for integer {@code k, m} and \(0 \le h < 1\).
+     */
+    public double cdf(double d, int n, boolean exact)
+        throws MathArithmeticException {
+
+        final double ninv = 1 / ((double) n);
+        final double ninvhalf = 0.5 * ninv;
+
+        if (d <= ninvhalf) {
+            return 0;
+        } else if (ninvhalf < d && d <= ninv) {
+            double res = 1;
+            final double f = 2 * d - ninv;
+            // n! f^n = n*f * (n-1)*f * ... * 1*x
+            for (int i = 1; i <= n; ++i) {
+                res *= i * f;
+            }
+            return res;
+        } else if (1 - ninv <= d && d < 1) {
+            return 1 - 2 * Math.pow(1 - d, n);
+        } else if (1 <= d) {
+            return 1;
+        }
+        if (exact) {
+            return exactK(d, n);
+        }
+        if (n <= 140) {
+            return roundedK(d, n);
+        }
+        return pelzGood(d, n);
+    }
+
+    /**
+     * Calculates the exact value of {@code P(D_n < d)} using the method described in [1] (reference
+     * in class javadoc above) and {@link org.apache.commons.math3.fraction.BigFraction} (see
+     * above).
+     *
+     * @param d statistic
+     * @param n sample size
+     * @return the two-sided probability of \(P(D_n < d)\)
+     * @throws MathArithmeticException if algorithm fails to convert {@code h} to a
+     *         {@link org.apache.commons.math3.fraction.BigFraction} in expressing {@code d} as \((k
+     *         - h) / m\) for integer {@code k, m} and \(0 \le h < 1\).
+     */
+    private double exactK(double d, int n)
+        throws MathArithmeticException {
+
+        final int k = (int) Math.ceil(n * d);
+
+        final FieldMatrix<BigFraction> H = this.createExactH(d, n);
+        final FieldMatrix<BigFraction> Hpower = H.power(n);
+
+        BigFraction pFrac = Hpower.getEntry(k - 1, k - 1);
+
+        for (int i = 1; i <= n; ++i) {
+            pFrac = pFrac.multiply(i).divide(n);
+        }
+
+        /*
+         * BigFraction.doubleValue converts numerator to double and the denominator to double and
+         * divides afterwards. That gives NaN quite easy. This does not (scale is the number of
+         * digits):
+         */
+        return pFrac.bigDecimalValue(20, BigDecimal.ROUND_HALF_UP).doubleValue();
+    }
+
+    /**
+     * Calculates {@code P(D_n < d)} using method described in [1] and doubles (see above).
+     *
+     * @param d statistic
+     * @param n sample size
+     * @return \(P(D_n < d)\)
+     */
+    private double roundedK(double d, int n) {
+
+        final int k = (int) Math.ceil(n * d);
+        final RealMatrix H = this.createRoundedH(d, n);
+        final RealMatrix Hpower = H.power(n);
+
+        double pFrac = Hpower.getEntry(k - 1, k - 1);
+        for (int i = 1; i <= n; ++i) {
+            pFrac *= (double) i / (double) n;
+        }
+
+        return pFrac;
+    }
+
+    /**
+     * Computes the Pelz-Good approximation for \(P(D_n < d)\) as described in [2] in the class javadoc.
+     *
+     * @param d value of d-statistic (x in [2])
+     * @param n sample size
+     * @return \(P(D_n < d)\)
+     * @since 3.4
+     */
+    public double pelzGood(double d, int n) {
+        // Change the variable since approximation is for the distribution evaluated at d / sqrt(n)
+        final double sqrtN = FastMath.sqrt(n);
+        final double z = d * sqrtN;
+        final double z2 = d * d * n;
+        final double z4 = z2 * z2;
+        final double z6 = z4 * z2;
+        final double z8 = z4 * z4;
+
+        // Eventual return value
+        double ret = 0;
+
+        // Compute K_0(z)
+        double sum = 0;
+        double increment = 0;
+        double kTerm = 0;
+        double z2Term = MathUtils.PI_SQUARED / (8 * z2);
+        int k = 1;
+        for (; k < MAXIMUM_PARTIAL_SUM_COUNT; k++) {
+            kTerm = 2 * k - 1;
+            increment = FastMath.exp(-z2Term * kTerm * kTerm);
+            sum += increment;
+            if (increment <= PG_SUM_RELATIVE_ERROR * sum) {
+                break;
+            }
+        }
+        if (k == MAXIMUM_PARTIAL_SUM_COUNT) {
+            throw new TooManyIterationsException(MAXIMUM_PARTIAL_SUM_COUNT);
+        }
+        ret = sum * FastMath.sqrt(2 * FastMath.PI) / z;
+
+        // K_1(z)
+        // Sum is -inf to inf, but k term is always (k + 1/2) ^ 2, so really have
+        // twice the sum from k = 0 to inf (k = -1 is same as 0, -2 same as 1, ...)
+        final double twoZ2 = 2 * z2;
+        sum = 0;
+        kTerm = 0;
+        double kTerm2 = 0;
+        for (k = 0; k < MAXIMUM_PARTIAL_SUM_COUNT; k++) {
+            kTerm = k + 0.5;
+            kTerm2 = kTerm * kTerm;
+            increment = (MathUtils.PI_SQUARED * kTerm2 - z2) * FastMath.exp(-MathUtils.PI_SQUARED * kTerm2 / twoZ2);
+            sum += increment;
+            if (FastMath.abs(increment) < PG_SUM_RELATIVE_ERROR * FastMath.abs(sum)) {
+                break;
+            }
+        }
+        if (k == MAXIMUM_PARTIAL_SUM_COUNT) {
+            throw new TooManyIterationsException(MAXIMUM_PARTIAL_SUM_COUNT);
+        }
+        final double sqrtHalfPi = FastMath.sqrt(FastMath.PI / 2);
+        // Instead of doubling sum, divide by 3 instead of 6
+        ret += sum * sqrtHalfPi / (3 * z4 * sqrtN);
+
+        // K_2(z)
+        // Same drill as K_1, but with two doubly infinite sums, all k terms are even powers.
+        final double z4Term = 2 * z4;
+        final double z6Term = 6 * z6;
+        z2Term = 5 * z2;
+        final double pi4 = MathUtils.PI_SQUARED * MathUtils.PI_SQUARED;
+        sum = 0;
+        kTerm = 0;
+        kTerm2 = 0;
+        for (k = 0; k < MAXIMUM_PARTIAL_SUM_COUNT; k++) {
+            kTerm = k + 0.5;
+            kTerm2 = kTerm * kTerm;
+            increment =  (z6Term + z4Term + MathUtils.PI_SQUARED * (z4Term - z2Term) * kTerm2 +
+                    pi4 * (1 - twoZ2) * kTerm2 * kTerm2) * FastMath.exp(-MathUtils.PI_SQUARED * kTerm2 / twoZ2);
+            sum += increment;
+            if (FastMath.abs(increment) < PG_SUM_RELATIVE_ERROR * FastMath.abs(sum)) {
+                break;
+            }
+        }
+        if (k == MAXIMUM_PARTIAL_SUM_COUNT) {
+            throw new TooManyIterationsException(MAXIMUM_PARTIAL_SUM_COUNT);
+        }
+        double sum2 = 0;
+        kTerm2 = 0;
+        for (k = 1; k < MAXIMUM_PARTIAL_SUM_COUNT; k++) {
+            kTerm2 = k * k;
+            increment = MathUtils.PI_SQUARED * kTerm2 * FastMath.exp(-MathUtils.PI_SQUARED * kTerm2 / twoZ2);
+            sum2 += increment;
+            if (FastMath.abs(increment) < PG_SUM_RELATIVE_ERROR * FastMath.abs(sum2)) {
+                break;
+            }
+        }
+        if (k == MAXIMUM_PARTIAL_SUM_COUNT) {
+            throw new TooManyIterationsException(MAXIMUM_PARTIAL_SUM_COUNT);
+        }
+        // Again, adjust coefficients instead of doubling sum, sum2
+        ret += (sqrtHalfPi / n) * (sum / (36 * z2 * z2 * z2 * z) - sum2 / (18 * z2 * z));
+
+        // K_3(z) One more time with feeling - two doubly infinite sums, all k powers even.
+        // Multiply coefficient denominators by 2, so omit doubling sums.
+        final double pi6 = pi4 * MathUtils.PI_SQUARED;
+        sum = 0;
+        double kTerm4 = 0;
+        double kTerm6 = 0;
+        for (k = 0; k < MAXIMUM_PARTIAL_SUM_COUNT; k++) {
+            kTerm = k + 0.5;
+            kTerm2 = kTerm * kTerm;
+            kTerm4 = kTerm2 * kTerm2;
+            kTerm6 = kTerm4 * kTerm2;
+            increment = (pi6 * kTerm6 * (5 - 30 * z2) + pi4 * kTerm4 * (-60 * z2 + 212 * z4) +
+                            MathUtils.PI_SQUARED * kTerm2 * (135 * z4 - 96 * z6) - 30 * z6 - 90 * z8) *
+                    FastMath.exp(-MathUtils.PI_SQUARED * kTerm2 / twoZ2);
+            sum += increment;
+            if (FastMath.abs(increment) < PG_SUM_RELATIVE_ERROR * FastMath.abs(sum)) {
+                break;
+            }
+        }
+        if (k == MAXIMUM_PARTIAL_SUM_COUNT) {
+            throw new TooManyIterationsException(MAXIMUM_PARTIAL_SUM_COUNT);
+        }
+        sum2 = 0;
+        for (k = 1; k < MAXIMUM_PARTIAL_SUM_COUNT; k++) {
+            kTerm2 = k * k;
+            kTerm4 = kTerm2 * kTerm2;
+            increment = (-pi4 * kTerm4 + 3 * MathUtils.PI_SQUARED * kTerm2 * z2) *
+                    FastMath.exp(-MathUtils.PI_SQUARED * kTerm2 / twoZ2);
+            sum2 += increment;
+            if (FastMath.abs(increment) < PG_SUM_RELATIVE_ERROR * FastMath.abs(sum2)) {
+                break;
+            }
+        }
+        if (k == MAXIMUM_PARTIAL_SUM_COUNT) {
+            throw new TooManyIterationsException(MAXIMUM_PARTIAL_SUM_COUNT);
+        }
+        return ret + (sqrtHalfPi / (sqrtN * n)) * (sum / (3240 * z6 * z4) +
+                + sum2 / (108 * z6));
+
+    }
+
+    /***
+     * Creates {@code H} of size {@code m x m} as described in [1] (see above).
+     *
+     * @param d statistic
+     * @param n sample size
+     * @return H matrix
+     * @throws NumberIsTooLargeException if fractional part is greater than 1
+     * @throws FractionConversionException if algorithm fails to convert {@code h} to a
+     *         {@link org.apache.commons.math3.fraction.BigFraction} in expressing {@code d} as \((k
+     *         - h) / m\) for integer {@code k, m} and \(0 <= h < 1\).
+     */
+    private FieldMatrix<BigFraction> createExactH(double d, int n)
+        throws NumberIsTooLargeException, FractionConversionException {
+
+        final int k = (int) Math.ceil(n * d);
+        final int m = 2 * k - 1;
+        final double hDouble = k - n * d;
+        if (hDouble >= 1) {
+            throw new NumberIsTooLargeException(hDouble, 1.0, false);
+        }
+        BigFraction h = null;
+        try {
+            h = new BigFraction(hDouble, 1.0e-20, 10000);
+        } catch (final FractionConversionException e1) {
+            try {
+                h = new BigFraction(hDouble, 1.0e-10, 10000);
+            } catch (final FractionConversionException e2) {
+                h = new BigFraction(hDouble, 1.0e-5, 10000);
+            }
+        }
+        final BigFraction[][] Hdata = new BigFraction[m][m];
+
+        /*
+         * Start by filling everything with either 0 or 1.
+         */
+        for (int i = 0; i < m; ++i) {
+            for (int j = 0; j < m; ++j) {
+                if (i - j + 1 < 0) {
+                    Hdata[i][j] = BigFraction.ZERO;
+                } else {
+                    Hdata[i][j] = BigFraction.ONE;
+                }
+            }
+        }
+
+        /*
+         * Setting up power-array to avoid calculating the same value twice: hPowers[0] = h^1 ...
+         * hPowers[m-1] = h^m
+         */
+        final BigFraction[] hPowers = new BigFraction[m];
+        hPowers[0] = h;
+        for (int i = 1; i < m; ++i) {
+            hPowers[i] = h.multiply(hPowers[i - 1]);
+        }
+
+        /*
+         * First column and last row has special values (each other reversed).
+         */
+        for (int i = 0; i < m; ++i) {
+            Hdata[i][0] = Hdata[i][0].subtract(hPowers[i]);
+            Hdata[m - 1][i] = Hdata[m - 1][i].subtract(hPowers[m - i - 1]);
+        }
+
+        /*
+         * [1] states: "For 1/2 < h < 1 the bottom left element of the matrix should be (1 - 2*h^m +
+         * (2h - 1)^m )/m!" Since 0 <= h < 1, then if h > 1/2 is sufficient to check:
+         */
+        if (h.compareTo(BigFraction.ONE_HALF) == 1) {
+            Hdata[m - 1][0] = Hdata[m - 1][0].add(h.multiply(2).subtract(1).pow(m));
+        }
+
+        /*
+         * Aside from the first column and last row, the (i, j)-th element is 1/(i - j + 1)! if i -
+         * j + 1 >= 0, else 0. 1's and 0's are already put, so only division with (i - j + 1)! is
+         * needed in the elements that have 1's. There is no need to calculate (i - j + 1)! and then
+         * divide - small steps avoid overflows. Note that i - j + 1 > 0 <=> i + 1 > j instead of
+         * j'ing all the way to m. Also note that it is started at g = 2 because dividing by 1 isn't
+         * really necessary.
+         */
+        for (int i = 0; i < m; ++i) {
+            for (int j = 0; j < i + 1; ++j) {
+                if (i - j + 1 > 0) {
+                    for (int g = 2; g <= i - j + 1; ++g) {
+                        Hdata[i][j] = Hdata[i][j].divide(g);
+                    }
+                }
+            }
+        }
+        return new Array2DRowFieldMatrix<BigFraction>(BigFractionField.getInstance(), Hdata);
+    }
+
+    /***
+     * Creates {@code H} of size {@code m x m} as described in [1] (see above)
+     * using double-precision.
+     *
+     * @param d statistic
+     * @param n sample size
+     * @return H matrix
+     * @throws NumberIsTooLargeException if fractional part is greater than 1
+     */
+    private RealMatrix createRoundedH(double d, int n)
+        throws NumberIsTooLargeException {
+
+        final int k = (int) Math.ceil(n * d);
+        final int m = 2 * k - 1;
+        final double h = k - n * d;
+        if (h >= 1) {
+            throw new NumberIsTooLargeException(h, 1.0, false);
+        }
+        final double[][] Hdata = new double[m][m];
+
+        /*
+         * Start by filling everything with either 0 or 1.
+         */
+        for (int i = 0; i < m; ++i) {
+            for (int j = 0; j < m; ++j) {
+                if (i - j + 1 < 0) {
+                    Hdata[i][j] = 0;
+                } else {
+                    Hdata[i][j] = 1;
+                }
+            }
+        }
+
+        /*
+         * Setting up power-array to avoid calculating the same value twice: hPowers[0] = h^1 ...
+         * hPowers[m-1] = h^m
+         */
+        final double[] hPowers = new double[m];
+        hPowers[0] = h;
+        for (int i = 1; i < m; ++i) {
+            hPowers[i] = h * hPowers[i - 1];
+        }
+
+        /*
+         * First column and last row has special values (each other reversed).
+         */
+        for (int i = 0; i < m; ++i) {
+            Hdata[i][0] = Hdata[i][0] - hPowers[i];
+            Hdata[m - 1][i] -= hPowers[m - i - 1];
+        }
+
+        /*
+         * [1] states: "For 1/2 < h < 1 the bottom left element of the matrix should be (1 - 2*h^m +
+         * (2h - 1)^m )/m!" Since 0 <= h < 1, then if h > 1/2 is sufficient to check:
+         */
+        if (Double.compare(h, 0.5) > 0) {
+            Hdata[m - 1][0] += FastMath.pow(2 * h - 1, m);
+        }
+
+        /*
+         * Aside from the first column and last row, the (i, j)-th element is 1/(i - j + 1)! if i -
+         * j + 1 >= 0, else 0. 1's and 0's are already put, so only division with (i - j + 1)! is
+         * needed in the elements that have 1's. There is no need to calculate (i - j + 1)! and then
+         * divide - small steps avoid overflows. Note that i - j + 1 > 0 <=> i + 1 > j instead of
+         * j'ing all the way to m. Also note that it is started at g = 2 because dividing by 1 isn't
+         * really necessary.
+         */
+        for (int i = 0; i < m; ++i) {
+            for (int j = 0; j < i + 1; ++j) {
+                if (i - j + 1 > 0) {
+                    for (int g = 2; g <= i - j + 1; ++g) {
+                        Hdata[i][j] /= g;
+                    }
+                }
+            }
+        }
+        return MatrixUtils.createRealMatrix(Hdata);
+    }
+
+    /**
+     * Verifies that {@code array} has length at least 2.
+     *
+     * @param array array to test
+     * @throws NullArgumentException if array is null
+     * @throws InsufficientDataException if array is too short
+     */
+    private void checkArray(double[] array) {
+        if (array == null) {
+            throw new NullArgumentException(LocalizedFormats.NULL_NOT_ALLOWED);
+        }
+        if (array.length < 2) {
+            throw new InsufficientDataException(LocalizedFormats.INSUFFICIENT_OBSERVED_POINTS_IN_SAMPLE, array.length,
+                                                2);
+        }
+    }
+
+    /**
+     * Computes \( 1 + 2 \sum_{i=1}^\infty (-1)^i e^{-2 i^2 t^2} \) stopping when successive partial
+     * sums are within {@code tolerance} of one another, or when {@code maxIterations} partial sums
+     * have been computed. If the sum does not converge before {@code maxIterations} iterations a
+     * {@link TooManyIterationsException} is thrown.
+     *
+     * @param t argument
+     * @param tolerance Cauchy criterion for partial sums
+     * @param maxIterations maximum number of partial sums to compute
+     * @return Kolmogorov sum evaluated at t
+     * @throws TooManyIterationsException if the series does not converge
+     */
+    public double ksSum(double t, double tolerance, int maxIterations) {
+        if (t == 0.0) {
+            return 0.0;
+        }
+
+        // TODO: for small t (say less than 1), the alternative expansion in part 3 of [1]
+        // from class javadoc should be used.
+
+        final double x = -2 * t * t;
+        int sign = -1;
+        long i = 1;
+        double partialSum = 0.5d;
+        double delta = 1;
+        while (delta > tolerance && i < maxIterations) {
+            delta = FastMath.exp(x * i * i);
+            partialSum += sign * delta;
+            sign *= -1;
+            i++;
+        }
+        if (i == maxIterations) {
+            throw new TooManyIterationsException(maxIterations);
+        }
+        return partialSum * 2;
+    }
+
+    /**
+     * Given a d-statistic in the range [0, 1] and the two sample sizes n and m,
+     * an integral d-statistic in the range [0, n*m] is calculated, that can be used for
+     * comparison with other integral d-statistics. Depending whether {@code strict} is
+     * {@code true} or not, the returned value divided by (n*m) is greater than
+     * (resp greater than or equal to) the given d value (allowing some tolerance).
+     *
+     * @param d a d-statistic in the range [0, 1]
+     * @param n first sample size
+     * @param m second sample size
+     * @param strict whether the returned value divided by (n*m) is allowed to be equal to d
+     * @return the integral d-statistic in the range [0, n*m]
+     */
+    private static long calculateIntegralD(double d, int n, int m, boolean strict) {
+        final double tol = 1e-12;  // d-values within tol of one another are considered equal
+        long nm = n * (long)m;
+        long upperBound = (long)FastMath.ceil((d - tol) * nm);
+        long lowerBound = (long)FastMath.floor((d + tol) * nm);
+        if (strict && lowerBound == upperBound) {
+            return upperBound + 1l;
+        }
+        else {
+            return upperBound;
+        }
+    }
+
+    /**
+     * Computes \(P(D_{n,m} > d)\) if {@code strict} is {@code true}; otherwise \(P(D_{n,m} \ge
+     * d)\), where \(D_{n,m}\) is the 2-sample Kolmogorov-Smirnov statistic. See
+     * {@link #kolmogorovSmirnovStatistic(double[], double[])} for the definition of \(D_{n,m}\).
+     * <p>
+     * The returned probability is exact, implemented by unwinding the recursive function
+     * definitions presented in [4] (class javadoc).
+     * </p>
+     *
+     * @param d D-statistic value
+     * @param n first sample size
+     * @param m second sample size
+     * @param strict whether or not the probability to compute is expressed as a strict inequality
+     * @return probability that a randomly selected m-n partition of m + n generates \(D_{n,m}\)
+     *         greater than (resp. greater than or equal to) {@code d}
+     */
+    public double exactP(double d, int n, int m, boolean strict) {
+       return 1 - n(m, n, m, n, calculateIntegralD(d, m, n, strict), strict) /
+               CombinatoricsUtils.binomialCoefficientDouble(n + m, m);
+    }
+
+    /**
+     * Uses the Kolmogorov-Smirnov distribution to approximate \(P(D_{n,m} > d)\) where \(D_{n,m}\)
+     * is the 2-sample Kolmogorov-Smirnov statistic. See
+     * {@link #kolmogorovSmirnovStatistic(double[], double[])} for the definition of \(D_{n,m}\).
+     * <p>
+     * Specifically, what is returned is \(1 - k(d \sqrt{mn / (m + n)})\) where \(k(t) = 1 + 2
+     * \sum_{i=1}^\infty (-1)^i e^{-2 i^2 t^2}\). See {@link #ksSum(double, double, int)} for
+     * details on how convergence of the sum is determined. This implementation passes {@code ksSum}
+     * {@value #KS_SUM_CAUCHY_CRITERION} as {@code tolerance} and
+     * {@value #MAXIMUM_PARTIAL_SUM_COUNT} as {@code maxIterations}.
+     * </p>
+     *
+     * @param d D-statistic value
+     * @param n first sample size
+     * @param m second sample size
+     * @return approximate probability that a randomly selected m-n partition of m + n generates
+     *         \(D_{n,m}\) greater than {@code d}
+     */
+    public double approximateP(double d, int n, int m) {
+        final double dm = m;
+        final double dn = n;
+        return 1 - ksSum(d * FastMath.sqrt((dm * dn) / (dm + dn)),
+                         KS_SUM_CAUCHY_CRITERION, MAXIMUM_PARTIAL_SUM_COUNT);
+    }
+
+    /**
+     * Fills a boolean array randomly with a fixed number of {@code true} values.
+     * The method uses a simplified version of the Fisher-Yates shuffle algorithm.
+     * By processing first the {@code true} values followed by the remaining {@code false} values
+     * less random numbers need to be generated. The method is optimized for the case
+     * that the number of {@code true} values is larger than or equal to the number of
+     * {@code false} values.
+     *
+     * @param b boolean array
+     * @param numberOfTrueValues number of {@code true} values the boolean array should finally have
+     * @param rng random data generator
+     */
+    static void fillBooleanArrayRandomlyWithFixedNumberTrueValues(final boolean[] b, final int numberOfTrueValues, final RandomGenerator rng) {
+        Arrays.fill(b, true);
+        for (int k = numberOfTrueValues; k < b.length; k++) {
+            final int r = rng.nextInt(k + 1);
+            b[(b[r]) ? r : k] = false;
+        }
+    }
+
+    /**
+     * Uses Monte Carlo simulation to approximate \(P(D_{n,m} > d)\) where \(D_{n,m}\) is the
+     * 2-sample Kolmogorov-Smirnov statistic. See
+     * {@link #kolmogorovSmirnovStatistic(double[], double[])} for the definition of \(D_{n,m}\).
+     * <p>
+     * The simulation generates {@code iterations} random partitions of {@code m + n} into an
+     * {@code n} set and an {@code m} set, computing \(D_{n,m}\) for each partition and returning
+     * the proportion of values that are greater than {@code d}, or greater than or equal to
+     * {@code d} if {@code strict} is {@code false}.
+     * </p>
+     *
+     * @param d D-statistic value
+     * @param n first sample size
+     * @param m second sample size
+     * @param iterations number of random partitions to generate
+     * @param strict whether or not the probability to compute is expressed as a strict inequality
+     * @return proportion of randomly generated m-n partitions of m + n that result in \(D_{n,m}\)
+     *         greater than (resp. greater than or equal to) {@code d}
+     */
+    public double monteCarloP(final double d, final int n, final int m, final boolean strict,
+                              final int iterations) {
+        return integralMonteCarloP(calculateIntegralD(d, n, m, strict), n, m, iterations);
+    }
+
+    /**
+     * Uses Monte Carlo simulation to approximate \(P(D_{n,m} >= d/(n*m))\) where \(D_{n,m}\) is the
+     * 2-sample Kolmogorov-Smirnov statistic.
+     * <p>
+     * Here d is the D-statistic represented as long value.
+     * The real D-statistic is obtained by dividing d by n*m.
+     * See also {@link #monteCarloP(double, int, int, boolean, int)}.
+     *
+     * @param d integral D-statistic
+     * @param n first sample size
+     * @param m second sample size
+     * @param iterations number of random partitions to generate
+     * @return proportion of randomly generated m-n partitions of m + n that result in \(D_{n,m}\)
+     *         greater than or equal to {@code d/(n*m))}
+     */
+    private double integralMonteCarloP(final long d, final int n, final int m, final int iterations) {
+
+        // ensure that nn is always the max of (n, m) to require fewer random numbers
+        final int nn = FastMath.max(n, m);
+        final int mm = FastMath.min(n, m);
+        final int sum = nn + mm;
+
+        int tail = 0;
+        final boolean b[] = new boolean[sum];
+        for (int i = 0; i < iterations; i++) {
+            fillBooleanArrayRandomlyWithFixedNumberTrueValues(b, nn, rng);
+            long curD = 0l;
+            for(int j = 0; j < b.length; ++j) {
+                if (b[j]) {
+                    curD += mm;
+                    if (curD >= d) {
+                        tail++;
+                        break;
+                    }
+                } else {
+                    curD -= nn;
+                    if (curD <= -d) {
+                        tail++;
+                        break;
+                    }
+                }
+            }
+        }
+        return (double) tail / iterations;
+    }
+
+    /**
+     * If there are no ties in the combined dataset formed from x and y, this
+     * method is a no-op.  If there are ties, a uniform random deviate in
+     * (-minDelta / 2, minDelta / 2) - {0} is added to each value in x and y, where
+     * minDelta is the minimum difference between unequal values in the combined
+     * sample.  A fixed seed is used to generate the jitter, so repeated activations
+     * with the same input arrays result in the same values.
+     *
+     * NOTE: if there are ties in the data, this method overwrites the data in
+     * x and y with the jittered values.
+     *
+     * @param x first sample
+     * @param y second sample
+     */
+    private static void fixTies(double[] x, double[] y) {
+       final double[] values = MathArrays.unique(MathArrays.concatenate(x,y));
+       if (values.length == x.length + y.length) {
+           return;  // There are no ties
+       }
+
+       // Find the smallest difference between values, or 1 if all values are the same
+       double minDelta = 1;
+       double prev = values[0];
+       double delta = 1;
+       for (int i = 1; i < values.length; i++) {
+          delta = prev - values[i];
+          if (delta < minDelta) {
+              minDelta = delta;
+          }
+          prev = values[i];
+       }
+       minDelta /= 2;
+
+       // Add jitter using a fixed seed (so same arguments always give same results),
+       // low-initialization-overhead generator
+       final RealDistribution dist =
+               new UniformRealDistribution(new JDKRandomGenerator(100), -minDelta, minDelta);
+
+       // It is theoretically possible that jitter does not break ties, so repeat
+       // until all ties are gone.  Bound the loop and throw MIE if bound is exceeded.
+       int ct = 0;
+       boolean ties = true;
+       do {
+           jitter(x, dist);
+           jitter(y, dist);
+           ties = hasTies(x, y);
+           ct++;
+       } while (ties && ct < 1000);
+       if (ties) {
+           throw new MathInternalError(); // Should never happen
+       }
+    }
+
+    /**
+     * Returns true iff there are ties in the combined sample
+     * formed from x and y.
+     *
+     * @param x first sample
+     * @param y second sample
+     * @return true if x and y together contain ties
+     */
+    private static boolean hasTies(double[] x, double[] y) {
+        final HashSet<Double> values = new HashSet<Double>();
+            for (int i = 0; i < x.length; i++) {
+                if (!values.add(x[i])) {
+                    return true;
+                }
+            }
+            for (int i = 0; i < y.length; i++) {
+                if (!values.add(y[i])) {
+                    return true;
+                }
+            }
+        return false;
+    }
+
+    /**
+     * Adds random jitter to {@code data} using deviates sampled from {@code dist}.
+     * <p>
+     * Note that jitter is applied in-place - i.e., the array
+     * values are overwritten with the result of applying jitter.</p>
+     *
+     * @param data input/output data array - entries overwritten by the method
+     * @param dist probability distribution to sample for jitter values
+     * @throws NullPointerException if either of the parameters is null
+     */
+    private static void jitter(double[] data, RealDistribution dist) {
+        for (int i = 0; i < data.length; i++) {
+            data[i] += dist.sample();
+        }
+    }
+
+    /**
+     * The function C(i, j) defined in [4] (class javadoc), formula (5.5).
+     * defined to return 1 if |i/n - j/m| <= c; 0 otherwise. Here c is scaled up
+     * and recoded as a long to avoid rounding errors in comparison tests, so what
+     * is actually tested is |im - jn| <= cmn.
+     *
+     * @param i first path parameter
+     * @param j second path paramter
+     * @param m first sample size
+     * @param n second sample size
+     * @param cmn integral D-statistic (see {@link #calculateIntegralD(double, int, int, boolean)})
+     * @param strict whether or not the null hypothesis uses strict inequality
+     * @return C(i,j) for given m, n, c
+     */
+    private static int c(int i, int j, int m, int n, long cmn, boolean strict) {
+        if (strict) {
+            return FastMath.abs(i*(long)n - j*(long)m) <= cmn ? 1 : 0;
+        }
+        return FastMath.abs(i*(long)n - j*(long)m) < cmn ? 1 : 0;
+    }
+
+    /**
+     * The function N(i, j) defined in [4] (class javadoc).
+     * Returns the number of paths over the lattice {(i,j) : 0 <= i <= n, 0 <= j <= m}
+     * from (0,0) to (i,j) satisfying C(h,k, m, n, c) = 1 for each (h,k) on the path.
+     * The return value is integral, but subject to overflow, so it is maintained and
+     * returned as a double.
+     *
+     * @param i first path parameter
+     * @param j second path parameter
+     * @param m first sample size
+     * @param n second sample size
+     * @param cnm integral D-statistic (see {@link #calculateIntegralD(double, int, int, boolean)})
+     * @param strict whether or not the null hypothesis uses strict inequality
+     * @return number or paths to (i, j) from (0,0) representing D-values as large as c for given m, n
+     */
+    private static double n(int i, int j, int m, int n, long cnm, boolean strict) {
+        /*
+         * Unwind the recursive definition given in [4].
+         * Compute n(1,1), n(1,2)...n(2,1), n(2,2)... up to n(i,j), one row at a time.
+         * When n(i,*) are being computed, lag[] holds the values of n(i - 1, *).
+         */
+        final double[] lag = new double[n];
+        double last = 0;
+        for (int k = 0; k < n; k++) {
+            lag[k] = c(0, k + 1, m, n, cnm, strict);
+        }
+        for (int k = 1; k <= i; k++) {
+            last = c(k, 0, m, n, cnm, strict);
+            for (int l = 1; l <= j; l++) {
+                lag[l - 1] = c(k, l, m, n, cnm, strict) * (last + lag[l - 1]);
+                last = lag[l - 1];
+            }
+        }
+        return last;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/inference/MannWhitneyUTest.java b/src/main/java/org/apache/commons/math3/stat/inference/MannWhitneyUTest.java
new file mode 100644
index 0000000..82fddb3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/inference/MannWhitneyUTest.java
@@ -0,0 +1,238 @@
+/*
+ * 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.math3.stat.inference;
+
+import org.apache.commons.math3.distribution.NormalDistribution;
+import org.apache.commons.math3.exception.ConvergenceException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.stat.ranking.NaNStrategy;
+import org.apache.commons.math3.stat.ranking.NaturalRanking;
+import org.apache.commons.math3.stat.ranking.TiesStrategy;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * An implementation of the Mann-Whitney U test (also called Wilcoxon rank-sum test).
+ *
+ */
+public class MannWhitneyUTest {
+
+    /** Ranking algorithm. */
+    private NaturalRanking naturalRanking;
+
+    /**
+     * Create a test instance using where NaN's are left in place and ties get
+     * the average of applicable ranks. Use this unless you are very sure of
+     * what you are doing.
+     */
+    public MannWhitneyUTest() {
+        naturalRanking = new NaturalRanking(NaNStrategy.FIXED,
+                TiesStrategy.AVERAGE);
+    }
+
+    /**
+     * Create a test instance using the given strategies for NaN's and ties.
+     * Only use this if you are sure of what you are doing.
+     *
+     * @param nanStrategy
+     *            specifies the strategy that should be used for Double.NaN's
+     * @param tiesStrategy
+     *            specifies the strategy that should be used for ties
+     */
+    public MannWhitneyUTest(final NaNStrategy nanStrategy,
+                            final TiesStrategy tiesStrategy) {
+        naturalRanking = new NaturalRanking(nanStrategy, tiesStrategy);
+    }
+
+    /**
+     * Ensures that the provided arrays fulfills the assumptions.
+     *
+     * @param x first sample
+     * @param y second sample
+     * @throws NullArgumentException if {@code x} or {@code y} are {@code null}.
+     * @throws NoDataException if {@code x} or {@code y} are zero-length.
+     */
+    private void ensureDataConformance(final double[] x, final double[] y)
+        throws NullArgumentException, NoDataException {
+
+        if (x == null ||
+            y == null) {
+            throw new NullArgumentException();
+        }
+        if (x.length == 0 ||
+            y.length == 0) {
+            throw new NoDataException();
+        }
+    }
+
+    /** Concatenate the samples into one array.
+     * @param x first sample
+     * @param y second sample
+     * @return concatenated array
+     */
+    private double[] concatenateSamples(final double[] x, final double[] y) {
+        final double[] z = new double[x.length + y.length];
+
+        System.arraycopy(x, 0, z, 0, x.length);
+        System.arraycopy(y, 0, z, x.length, y.length);
+
+        return z;
+    }
+
+    /**
+     * Computes the <a
+     * href="http://en.wikipedia.org/wiki/Mann%E2%80%93Whitney_U"> Mann-Whitney
+     * U statistic</a> comparing mean for two independent samples possibly of
+     * different length.
+     * <p>
+     * This statistic can be used to perform a Mann-Whitney U test evaluating
+     * the null hypothesis that the two independent samples has equal mean.
+     * </p>
+     * <p>
+     * Let X<sub>i</sub> denote the i'th individual of the first sample and
+     * Y<sub>j</sub> the j'th individual in the second sample. Note that the
+     * samples would often have different length.
+     * </p>
+     * <p>
+     * <strong>Preconditions</strong>:
+     * <ul>
+     * <li>All observations in the two samples are independent.</li>
+     * <li>The observations are at least ordinal (continuous are also ordinal).</li>
+     * </ul>
+     * </p>
+     *
+     * @param x the first sample
+     * @param y the second sample
+     * @return Mann-Whitney U statistic (maximum of U<sup>x</sup> and U<sup>y</sup>)
+     * @throws NullArgumentException if {@code x} or {@code y} are {@code null}.
+     * @throws NoDataException if {@code x} or {@code y} are zero-length.
+     */
+    public double mannWhitneyU(final double[] x, final double[] y)
+        throws NullArgumentException, NoDataException {
+
+        ensureDataConformance(x, y);
+
+        final double[] z = concatenateSamples(x, y);
+        final double[] ranks = naturalRanking.rank(z);
+
+        double sumRankX = 0;
+
+        /*
+         * The ranks for x is in the first x.length entries in ranks because x
+         * is in the first x.length entries in z
+         */
+        for (int i = 0; i < x.length; ++i) {
+            sumRankX += ranks[i];
+        }
+
+        /*
+         * U1 = R1 - (n1 * (n1 + 1)) / 2 where R1 is sum of ranks for sample 1,
+         * e.g. x, n1 is the number of observations in sample 1.
+         */
+        final double U1 = sumRankX - ((long) x.length * (x.length + 1)) / 2;
+
+        /*
+         * It can be shown that U1 + U2 = n1 * n2
+         */
+        final double U2 = (long) x.length * y.length - U1;
+
+        return FastMath.max(U1, U2);
+    }
+
+    /**
+     * @param Umin smallest Mann-Whitney U value
+     * @param n1 number of subjects in first sample
+     * @param n2 number of subjects in second sample
+     * @return two-sided asymptotic p-value
+     * @throws ConvergenceException if the p-value can not be computed
+     * due to a convergence error
+     * @throws MaxCountExceededException if the maximum number of
+     * iterations is exceeded
+     */
+    private double calculateAsymptoticPValue(final double Umin,
+                                             final int n1,
+                                             final int n2)
+        throws ConvergenceException, MaxCountExceededException {
+
+        /* long multiplication to avoid overflow (double not used due to efficiency
+         * and to avoid precision loss)
+         */
+        final long n1n2prod = (long) n1 * n2;
+
+        // http://en.wikipedia.org/wiki/Mann%E2%80%93Whitney_U#Normal_approximation
+        final double EU = n1n2prod / 2.0;
+        final double VarU = n1n2prod * (n1 + n2 + 1) / 12.0;
+
+        final double z = (Umin - EU) / FastMath.sqrt(VarU);
+
+        // No try-catch or advertised exception because args are valid
+        // pass a null rng to avoid unneeded overhead as we will not sample from this distribution
+        final NormalDistribution standardNormal = new NormalDistribution(null, 0, 1);
+
+        return 2 * standardNormal.cumulativeProbability(z);
+    }
+
+    /**
+     * Returns the asymptotic <i>observed significance level</i>, or <a href=
+     * "http://www.cas.lancs.ac.uk/glossary_v1.1/hyptest.html#pvalue">
+     * p-value</a>, associated with a <a
+     * href="http://en.wikipedia.org/wiki/Mann%E2%80%93Whitney_U"> Mann-Whitney
+     * U statistic</a> comparing mean for two independent samples.
+     * <p>
+     * Let X<sub>i</sub> denote the i'th individual of the first sample and
+     * Y<sub>j</sub> the j'th individual in the second sample. Note that the
+     * samples would often have different length.
+     * </p>
+     * <p>
+     * <strong>Preconditions</strong>:
+     * <ul>
+     * <li>All observations in the two samples are independent.</li>
+     * <li>The observations are at least ordinal (continuous are also ordinal).</li>
+     * </ul>
+     * </p><p>
+     * Ties give rise to biased variance at the moment. See e.g. <a
+     * href="http://mlsc.lboro.ac.uk/resources/statistics/Mannwhitney.pdf"
+     * >http://mlsc.lboro.ac.uk/resources/statistics/Mannwhitney.pdf</a>.</p>
+     *
+     * @param x the first sample
+     * @param y the second sample
+     * @return asymptotic p-value
+     * @throws NullArgumentException if {@code x} or {@code y} are {@code null}.
+     * @throws NoDataException if {@code x} or {@code y} are zero-length.
+     * @throws ConvergenceException if the p-value can not be computed due to a
+     * convergence error
+     * @throws MaxCountExceededException if the maximum number of iterations
+     * is exceeded
+     */
+    public double mannWhitneyUTest(final double[] x, final double[] y)
+        throws NullArgumentException, NoDataException,
+        ConvergenceException, MaxCountExceededException {
+
+        ensureDataConformance(x, y);
+
+        final double Umax = mannWhitneyU(x, y);
+
+        /*
+         * It can be shown that U1 + U2 = n1 * n2
+         */
+        final double Umin = (long) x.length * y.length - Umax;
+
+        return calculateAsymptoticPValue(Umin, x.length, y.length);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/inference/OneWayAnova.java b/src/main/java/org/apache/commons/math3/stat/inference/OneWayAnova.java
new file mode 100644
index 0000000..d0c5fc1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/inference/OneWayAnova.java
@@ -0,0 +1,355 @@
+/*
+ * 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.math3.stat.inference;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.apache.commons.math3.distribution.FDistribution;
+import org.apache.commons.math3.exception.ConvergenceException;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.stat.descriptive.SummaryStatistics;
+import org.apache.commons.math3.util.MathUtils;
+
+/**
+ * Implements one-way ANOVA (analysis of variance) statistics.
+ *
+ * <p> Tests for differences between two or more categories of univariate data
+ * (for example, the body mass index of accountants, lawyers, doctors and
+ * computer programmers).  When two categories are given, this is equivalent to
+ * the {@link org.apache.commons.math3.stat.inference.TTest}.
+ * </p><p>
+ * Uses the {@link org.apache.commons.math3.distribution.FDistribution
+ * commons-math F Distribution implementation} to estimate exact p-values.</p>
+ * <p>This implementation is based on a description at
+ * http://faculty.vassar.edu/lowry/ch13pt1.html</p>
+ * <pre>
+ * Abbreviations: bg = between groups,
+ *                wg = within groups,
+ *                ss = sum squared deviations
+ * </pre>
+ *
+ * @since 1.2
+ */
+public class OneWayAnova {
+
+    /**
+     * Default constructor.
+     */
+    public OneWayAnova() {
+    }
+
+    /**
+     * Computes the ANOVA F-value for a collection of <code>double[]</code>
+     * arrays.
+     *
+     * <p><strong>Preconditions</strong>: <ul>
+     * <li>The categoryData <code>Collection</code> must contain
+     * <code>double[]</code> arrays.</li>
+     * <li> There must be at least two <code>double[]</code> arrays in the
+     * <code>categoryData</code> collection and each of these arrays must
+     * contain at least two values.</li></ul></p><p>
+     * This implementation computes the F statistic using the definitional
+     * formula<pre>
+     *   F = msbg/mswg</pre>
+     * where<pre>
+     *  msbg = between group mean square
+     *  mswg = within group mean square</pre>
+     * are as defined <a href="http://faculty.vassar.edu/lowry/ch13pt1.html">
+     * here</a></p>
+     *
+     * @param categoryData <code>Collection</code> of <code>double[]</code>
+     * arrays each containing data for one category
+     * @return Fvalue
+     * @throws NullArgumentException if <code>categoryData</code> is <code>null</code>
+     * @throws DimensionMismatchException if the length of the <code>categoryData</code>
+     * array is less than 2 or a contained <code>double[]</code> array does not have
+     * at least two values
+     */
+    public double anovaFValue(final Collection<double[]> categoryData)
+        throws NullArgumentException, DimensionMismatchException {
+
+        AnovaStats a = anovaStats(categoryData);
+        return a.F;
+
+    }
+
+    /**
+     * Computes the ANOVA P-value for a collection of <code>double[]</code>
+     * arrays.
+     *
+     * <p><strong>Preconditions</strong>: <ul>
+     * <li>The categoryData <code>Collection</code> must contain
+     * <code>double[]</code> arrays.</li>
+     * <li> There must be at least two <code>double[]</code> arrays in the
+     * <code>categoryData</code> collection and each of these arrays must
+     * contain at least two values.</li></ul></p><p>
+     * This implementation uses the
+     * {@link org.apache.commons.math3.distribution.FDistribution
+     * commons-math F Distribution implementation} to estimate the exact
+     * p-value, using the formula<pre>
+     *   p = 1 - cumulativeProbability(F)</pre>
+     * where <code>F</code> is the F value and <code>cumulativeProbability</code>
+     * is the commons-math implementation of the F distribution.</p>
+     *
+     * @param categoryData <code>Collection</code> of <code>double[]</code>
+     * arrays each containing data for one category
+     * @return Pvalue
+     * @throws NullArgumentException if <code>categoryData</code> is <code>null</code>
+     * @throws DimensionMismatchException if the length of the <code>categoryData</code>
+     * array is less than 2 or a contained <code>double[]</code> array does not have
+     * at least two values
+     * @throws ConvergenceException if the p-value can not be computed due to a convergence error
+     * @throws MaxCountExceededException if the maximum number of iterations is exceeded
+     */
+    public double anovaPValue(final Collection<double[]> categoryData)
+        throws NullArgumentException, DimensionMismatchException,
+        ConvergenceException, MaxCountExceededException {
+
+        final AnovaStats a = anovaStats(categoryData);
+        // No try-catch or advertised exception because args are valid
+        // pass a null rng to avoid unneeded overhead as we will not sample from this distribution
+        final FDistribution fdist = new FDistribution(null, a.dfbg, a.dfwg);
+        return 1.0 - fdist.cumulativeProbability(a.F);
+
+    }
+
+    /**
+     * Computes the ANOVA P-value for a collection of {@link SummaryStatistics}.
+     *
+     * <p><strong>Preconditions</strong>: <ul>
+     * <li>The categoryData <code>Collection</code> must contain
+     * {@link SummaryStatistics}.</li>
+     * <li> There must be at least two {@link SummaryStatistics} in the
+     * <code>categoryData</code> collection and each of these statistics must
+     * contain at least two values.</li></ul></p><p>
+     * This implementation uses the
+     * {@link org.apache.commons.math3.distribution.FDistribution
+     * commons-math F Distribution implementation} to estimate the exact
+     * p-value, using the formula<pre>
+     *   p = 1 - cumulativeProbability(F)</pre>
+     * where <code>F</code> is the F value and <code>cumulativeProbability</code>
+     * is the commons-math implementation of the F distribution.</p>
+     *
+     * @param categoryData <code>Collection</code> of {@link SummaryStatistics}
+     * each containing data for one category
+     * @param allowOneElementData if true, allow computation for one catagory
+     * only or for one data element per category
+     * @return Pvalue
+     * @throws NullArgumentException if <code>categoryData</code> is <code>null</code>
+     * @throws DimensionMismatchException if the length of the <code>categoryData</code>
+     * array is less than 2 or a contained {@link SummaryStatistics} does not have
+     * at least two values
+     * @throws ConvergenceException if the p-value can not be computed due to a convergence error
+     * @throws MaxCountExceededException if the maximum number of iterations is exceeded
+     * @since 3.2
+     */
+    public double anovaPValue(final Collection<SummaryStatistics> categoryData,
+                              final boolean allowOneElementData)
+        throws NullArgumentException, DimensionMismatchException,
+               ConvergenceException, MaxCountExceededException {
+
+        final AnovaStats a = anovaStats(categoryData, allowOneElementData);
+        // pass a null rng to avoid unneeded overhead as we will not sample from this distribution
+        final FDistribution fdist = new FDistribution(null, a.dfbg, a.dfwg);
+        return 1.0 - fdist.cumulativeProbability(a.F);
+
+    }
+
+    /**
+     * This method calls the method that actually does the calculations (except
+     * P-value).
+     *
+     * @param categoryData
+     *            <code>Collection</code> of <code>double[]</code> arrays each
+     *            containing data for one category
+     * @return computed AnovaStats
+     * @throws NullArgumentException
+     *             if <code>categoryData</code> is <code>null</code>
+     * @throws DimensionMismatchException
+     *             if the length of the <code>categoryData</code> array is less
+     *             than 2 or a contained <code>double[]</code> array does not
+     *             contain at least two values
+     */
+    private AnovaStats anovaStats(final Collection<double[]> categoryData)
+        throws NullArgumentException, DimensionMismatchException {
+
+        MathUtils.checkNotNull(categoryData);
+
+        final Collection<SummaryStatistics> categoryDataSummaryStatistics =
+                new ArrayList<SummaryStatistics>(categoryData.size());
+
+        // convert arrays to SummaryStatistics
+        for (final double[] data : categoryData) {
+            final SummaryStatistics dataSummaryStatistics = new SummaryStatistics();
+            categoryDataSummaryStatistics.add(dataSummaryStatistics);
+            for (final double val : data) {
+                dataSummaryStatistics.addValue(val);
+            }
+        }
+
+        return anovaStats(categoryDataSummaryStatistics, false);
+
+    }
+
+    /**
+     * Performs an ANOVA test, evaluating the null hypothesis that there
+     * is no difference among the means of the data categories.
+     *
+     * <p><strong>Preconditions</strong>: <ul>
+     * <li>The categoryData <code>Collection</code> must contain
+     * <code>double[]</code> arrays.</li>
+     * <li> There must be at least two <code>double[]</code> arrays in the
+     * <code>categoryData</code> collection and each of these arrays must
+     * contain at least two values.</li>
+     * <li>alpha must be strictly greater than 0 and less than or equal to 0.5.
+     * </li></ul></p><p>
+     * This implementation uses the
+     * {@link org.apache.commons.math3.distribution.FDistribution
+     * commons-math F Distribution implementation} to estimate the exact
+     * p-value, using the formula<pre>
+     *   p = 1 - cumulativeProbability(F)</pre>
+     * where <code>F</code> is the F value and <code>cumulativeProbability</code>
+     * is the commons-math implementation of the F distribution.</p>
+     * <p>True is returned iff the estimated p-value is less than alpha.</p>
+     *
+     * @param categoryData <code>Collection</code> of <code>double[]</code>
+     * arrays each containing data for one category
+     * @param alpha significance level of the test
+     * @return true if the null hypothesis can be rejected with
+     * confidence 1 - alpha
+     * @throws NullArgumentException if <code>categoryData</code> is <code>null</code>
+     * @throws DimensionMismatchException if the length of the <code>categoryData</code>
+     * array is less than 2 or a contained <code>double[]</code> array does not have
+     * at least two values
+     * @throws OutOfRangeException if <code>alpha</code> is not in the range (0, 0.5]
+     * @throws ConvergenceException if the p-value can not be computed due to a convergence error
+     * @throws MaxCountExceededException if the maximum number of iterations is exceeded
+     */
+    public boolean anovaTest(final Collection<double[]> categoryData,
+                             final double alpha)
+        throws NullArgumentException, DimensionMismatchException,
+        OutOfRangeException, ConvergenceException, MaxCountExceededException {
+
+        if ((alpha <= 0) || (alpha > 0.5)) {
+            throw new OutOfRangeException(
+                    LocalizedFormats.OUT_OF_BOUND_SIGNIFICANCE_LEVEL,
+                    alpha, 0, 0.5);
+        }
+        return anovaPValue(categoryData) < alpha;
+
+    }
+
+    /**
+     * This method actually does the calculations (except P-value).
+     *
+     * @param categoryData <code>Collection</code> of <code>double[]</code>
+     * arrays each containing data for one category
+     * @param allowOneElementData if true, allow computation for one catagory
+     * only or for one data element per category
+     * @return computed AnovaStats
+     * @throws NullArgumentException if <code>categoryData</code> is <code>null</code>
+     * @throws DimensionMismatchException if <code>allowOneElementData</code> is false and the number of
+     * categories is less than 2 or a contained SummaryStatistics does not contain
+     * at least two values
+     */
+    private AnovaStats anovaStats(final Collection<SummaryStatistics> categoryData,
+                                  final boolean allowOneElementData)
+        throws NullArgumentException, DimensionMismatchException {
+
+        MathUtils.checkNotNull(categoryData);
+
+        if (!allowOneElementData) {
+            // check if we have enough categories
+            if (categoryData.size() < 2) {
+                throw new DimensionMismatchException(LocalizedFormats.TWO_OR_MORE_CATEGORIES_REQUIRED,
+                                                     categoryData.size(), 2);
+            }
+
+            // check if each category has enough data
+            for (final SummaryStatistics array : categoryData) {
+                if (array.getN() <= 1) {
+                    throw new DimensionMismatchException(LocalizedFormats.TWO_OR_MORE_VALUES_IN_CATEGORY_REQUIRED,
+                                                         (int) array.getN(), 2);
+                }
+            }
+        }
+
+        int dfwg = 0;
+        double sswg = 0;
+        double totsum = 0;
+        double totsumsq = 0;
+        int totnum = 0;
+
+        for (final SummaryStatistics data : categoryData) {
+
+            final double sum = data.getSum();
+            final double sumsq = data.getSumsq();
+            final int num = (int) data.getN();
+            totnum += num;
+            totsum += sum;
+            totsumsq += sumsq;
+
+            dfwg += num - 1;
+            final double ss = sumsq - ((sum * sum) / num);
+            sswg += ss;
+        }
+
+        final double sst = totsumsq - ((totsum * totsum) / totnum);
+        final double ssbg = sst - sswg;
+        final int dfbg = categoryData.size() - 1;
+        final double msbg = ssbg / dfbg;
+        final double mswg = sswg / dfwg;
+        final double F = msbg / mswg;
+
+        return new AnovaStats(dfbg, dfwg, F);
+
+    }
+
+    /**
+        Convenience class to pass dfbg,dfwg,F values around within OneWayAnova.
+        No get/set methods provided.
+    */
+    private static class AnovaStats {
+
+        /** Degrees of freedom in numerator (between groups). */
+        private final int dfbg;
+
+        /** Degrees of freedom in denominator (within groups). */
+        private final int dfwg;
+
+        /** Statistic. */
+        private final double F;
+
+        /**
+         * Constructor
+         * @param dfbg degrees of freedom in numerator (between groups)
+         * @param dfwg degrees of freedom in denominator (within groups)
+         * @param F statistic
+         */
+        private AnovaStats(int dfbg, int dfwg, double F) {
+            this.dfbg = dfbg;
+            this.dfwg = dfwg;
+            this.F = F;
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/inference/TTest.java b/src/main/java/org/apache/commons/math3/stat/inference/TTest.java
new file mode 100644
index 0000000..b0f76f6
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/inference/TTest.java
@@ -0,0 +1,1184 @@
+/*
+ * 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.math3.stat.inference;
+
+import org.apache.commons.math3.distribution.TDistribution;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.stat.StatUtils;
+import org.apache.commons.math3.stat.descriptive.StatisticalSummary;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * An implementation for Student's t-tests.
+ * <p>
+ * Tests can be:<ul>
+ * <li>One-sample or two-sample</li>
+ * <li>One-sided or two-sided</li>
+ * <li>Paired or unpaired (for two-sample tests)</li>
+ * <li>Homoscedastic (equal variance assumption) or heteroscedastic
+ * (for two sample tests)</li>
+ * <li>Fixed significance level (boolean-valued) or returning p-values.
+ * </li></ul></p>
+ * <p>
+ * Test statistics are available for all tests.  Methods including "Test" in
+ * in their names perform tests, all other methods return t-statistics.  Among
+ * the "Test" methods, <code>double-</code>valued methods return p-values;
+ * <code>boolean-</code>valued methods perform fixed significance level tests.
+ * Significance levels are always specified as numbers between 0 and 0.5
+ * (e.g. tests at the 95% level  use <code>alpha=0.05</code>).</p>
+ * <p>
+ * Input to tests can be either <code>double[]</code> arrays or
+ * {@link StatisticalSummary} instances.</p><p>
+ * Uses commons-math {@link org.apache.commons.math3.distribution.TDistribution}
+ * implementation to estimate exact p-values.</p>
+ *
+ */
+public class TTest {
+    /**
+     * Computes a paired, 2-sample t-statistic based on the data in the input
+     * arrays.  The t-statistic returned is equivalent to what would be returned by
+     * computing the one-sample t-statistic {@link #t(double, double[])}, with
+     * <code>mu = 0</code> and the sample array consisting of the (signed)
+     * differences between corresponding entries in <code>sample1</code> and
+     * <code>sample2.</code>
+     * <p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>The input arrays must have the same length and their common length
+     * must be at least 2.
+     * </li></ul></p>
+     *
+     * @param sample1 array of sample data values
+     * @param sample2 array of sample data values
+     * @return t statistic
+     * @throws NullArgumentException if the arrays are <code>null</code>
+     * @throws NoDataException if the arrays are empty
+     * @throws DimensionMismatchException if the length of the arrays is not equal
+     * @throws NumberIsTooSmallException if the length of the arrays is &lt; 2
+     */
+    public double pairedT(final double[] sample1, final double[] sample2)
+        throws NullArgumentException, NoDataException,
+        DimensionMismatchException, NumberIsTooSmallException {
+
+        checkSampleData(sample1);
+        checkSampleData(sample2);
+        double meanDifference = StatUtils.meanDifference(sample1, sample2);
+        return t(meanDifference, 0,
+                 StatUtils.varianceDifference(sample1, sample2, meanDifference),
+                 sample1.length);
+
+    }
+
+    /**
+     * Returns the <i>observed significance level</i>, or
+     * <i> p-value</i>, associated with a paired, two-sample, two-tailed t-test
+     * based on the data in the input arrays.
+     * <p>
+     * The number returned is the smallest significance level
+     * at which one can reject the null hypothesis that the mean of the paired
+     * differences is 0 in favor of the two-sided alternative that the mean paired
+     * difference is not equal to 0. For a one-sided test, divide the returned
+     * value by 2.</p>
+     * <p>
+     * This test is equivalent to a one-sample t-test computed using
+     * {@link #tTest(double, double[])} with <code>mu = 0</code> and the sample
+     * array consisting of the signed differences between corresponding elements of
+     * <code>sample1</code> and <code>sample2.</code></p>
+     * <p>
+     * <strong>Usage Note:</strong><br>
+     * The validity of the p-value depends on the assumptions of the parametric
+     * t-test procedure, as discussed
+     * <a href="http://www.basic.nwu.edu/statguidefiles/ttest_unpaired_ass_viol.html">
+     * here</a></p>
+     * <p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>The input array lengths must be the same and their common length must
+     * be at least 2.
+     * </li></ul></p>
+     *
+     * @param sample1 array of sample data values
+     * @param sample2 array of sample data values
+     * @return p-value for t-test
+     * @throws NullArgumentException if the arrays are <code>null</code>
+     * @throws NoDataException if the arrays are empty
+     * @throws DimensionMismatchException if the length of the arrays is not equal
+     * @throws NumberIsTooSmallException if the length of the arrays is &lt; 2
+     * @throws MaxCountExceededException if an error occurs computing the p-value
+     */
+    public double pairedTTest(final double[] sample1, final double[] sample2)
+        throws NullArgumentException, NoDataException, DimensionMismatchException,
+        NumberIsTooSmallException, MaxCountExceededException {
+
+        double meanDifference = StatUtils.meanDifference(sample1, sample2);
+        return tTest(meanDifference, 0,
+                StatUtils.varianceDifference(sample1, sample2, meanDifference),
+                sample1.length);
+
+    }
+
+    /**
+     * Performs a paired t-test evaluating the null hypothesis that the
+     * mean of the paired differences between <code>sample1</code> and
+     * <code>sample2</code> is 0 in favor of the two-sided alternative that the
+     * mean paired difference is not equal to 0, with significance level
+     * <code>alpha</code>.
+     * <p>
+     * Returns <code>true</code> iff the null hypothesis can be rejected with
+     * confidence <code>1 - alpha</code>.  To perform a 1-sided test, use
+     * <code>alpha * 2</code></p>
+     * <p>
+     * <strong>Usage Note:</strong><br>
+     * The validity of the test depends on the assumptions of the parametric
+     * t-test procedure, as discussed
+     * <a href="http://www.basic.nwu.edu/statguidefiles/ttest_unpaired_ass_viol.html">
+     * here</a></p>
+     * <p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>The input array lengths must be the same and their common length
+     * must be at least 2.
+     * </li>
+     * <li> <code> 0 &lt; alpha &lt; 0.5 </code>
+     * </li></ul></p>
+     *
+     * @param sample1 array of sample data values
+     * @param sample2 array of sample data values
+     * @param alpha significance level of the test
+     * @return true if the null hypothesis can be rejected with
+     * confidence 1 - alpha
+     * @throws NullArgumentException if the arrays are <code>null</code>
+     * @throws NoDataException if the arrays are empty
+     * @throws DimensionMismatchException if the length of the arrays is not equal
+     * @throws NumberIsTooSmallException if the length of the arrays is &lt; 2
+     * @throws OutOfRangeException if <code>alpha</code> is not in the range (0, 0.5]
+     * @throws MaxCountExceededException if an error occurs computing the p-value
+     */
+    public boolean pairedTTest(final double[] sample1, final double[] sample2,
+                               final double alpha)
+        throws NullArgumentException, NoDataException, DimensionMismatchException,
+        NumberIsTooSmallException, OutOfRangeException, MaxCountExceededException {
+
+        checkSignificanceLevel(alpha);
+        return pairedTTest(sample1, sample2) < alpha;
+
+    }
+
+    /**
+     * Computes a <a href="http://www.itl.nist.gov/div898/handbook/prc/section2/prc22.htm#formula">
+     * t statistic </a> given observed values and a comparison constant.
+     * <p>
+     * This statistic can be used to perform a one sample t-test for the mean.
+     * </p><p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>The observed array length must be at least 2.
+     * </li></ul></p>
+     *
+     * @param mu comparison constant
+     * @param observed array of values
+     * @return t statistic
+     * @throws NullArgumentException if <code>observed</code> is <code>null</code>
+     * @throws NumberIsTooSmallException if the length of <code>observed</code> is &lt; 2
+     */
+    public double t(final double mu, final double[] observed)
+        throws NullArgumentException, NumberIsTooSmallException {
+
+        checkSampleData(observed);
+        // No try-catch or advertised exception because args have just been checked
+        return t(StatUtils.mean(observed), mu, StatUtils.variance(observed),
+                observed.length);
+
+    }
+
+    /**
+     * Computes a <a href="http://www.itl.nist.gov/div898/handbook/prc/section2/prc22.htm#formula">
+     * t statistic </a> to use in comparing the mean of the dataset described by
+     * <code>sampleStats</code> to <code>mu</code>.
+     * <p>
+     * This statistic can be used to perform a one sample t-test for the mean.
+     * </p><p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li><code>observed.getN() &ge; 2</code>.
+     * </li></ul></p>
+     *
+     * @param mu comparison constant
+     * @param sampleStats DescriptiveStatistics holding sample summary statitstics
+     * @return t statistic
+     * @throws NullArgumentException if <code>sampleStats</code> is <code>null</code>
+     * @throws NumberIsTooSmallException if the number of samples is &lt; 2
+     */
+    public double t(final double mu, final StatisticalSummary sampleStats)
+        throws NullArgumentException, NumberIsTooSmallException {
+
+        checkSampleData(sampleStats);
+        return t(sampleStats.getMean(), mu, sampleStats.getVariance(),
+                 sampleStats.getN());
+
+    }
+
+    /**
+     * Computes a 2-sample t statistic,  under the hypothesis of equal
+     * subpopulation variances.  To compute a t-statistic without the
+     * equal variances hypothesis, use {@link #t(double[], double[])}.
+     * <p>
+     * This statistic can be used to perform a (homoscedastic) two-sample
+     * t-test to compare sample means.</p>
+     * <p>
+     * The t-statistic is</p>
+     * <p>
+     * &nbsp;&nbsp;<code>  t = (m1 - m2) / (sqrt(1/n1 +1/n2) sqrt(var))</code>
+     * </p><p>
+     * where <strong><code>n1</code></strong> is the size of first sample;
+     * <strong><code> n2</code></strong> is the size of second sample;
+     * <strong><code> m1</code></strong> is the mean of first sample;
+     * <strong><code> m2</code></strong> is the mean of second sample</li>
+     * </ul>
+     * and <strong><code>var</code></strong> is the pooled variance estimate:
+     * </p><p>
+     * <code>var = sqrt(((n1 - 1)var1 + (n2 - 1)var2) / ((n1-1) + (n2-1)))</code>
+     * </p><p>
+     * with <strong><code>var1</code></strong> the variance of the first sample and
+     * <strong><code>var2</code></strong> the variance of the second sample.
+     * </p><p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>The observed array lengths must both be at least 2.
+     * </li></ul></p>
+     *
+     * @param sample1 array of sample data values
+     * @param sample2 array of sample data values
+     * @return t statistic
+     * @throws NullArgumentException if the arrays are <code>null</code>
+     * @throws NumberIsTooSmallException if the length of the arrays is &lt; 2
+     */
+    public double homoscedasticT(final double[] sample1, final double[] sample2)
+        throws NullArgumentException, NumberIsTooSmallException {
+
+        checkSampleData(sample1);
+        checkSampleData(sample2);
+        // No try-catch or advertised exception because args have just been checked
+        return homoscedasticT(StatUtils.mean(sample1), StatUtils.mean(sample2),
+                              StatUtils.variance(sample1), StatUtils.variance(sample2),
+                              sample1.length, sample2.length);
+
+    }
+
+    /**
+     * Computes a 2-sample t statistic, without the hypothesis of equal
+     * subpopulation variances.  To compute a t-statistic assuming equal
+     * variances, use {@link #homoscedasticT(double[], double[])}.
+     * <p>
+     * This statistic can be used to perform a two-sample t-test to compare
+     * sample means.</p>
+     * <p>
+     * The t-statistic is</p>
+     * <p>
+     * &nbsp;&nbsp; <code>  t = (m1 - m2) / sqrt(var1/n1 + var2/n2)</code>
+     * </p><p>
+     *  where <strong><code>n1</code></strong> is the size of the first sample
+     * <strong><code> n2</code></strong> is the size of the second sample;
+     * <strong><code> m1</code></strong> is the mean of the first sample;
+     * <strong><code> m2</code></strong> is the mean of the second sample;
+     * <strong><code> var1</code></strong> is the variance of the first sample;
+     * <strong><code> var2</code></strong> is the variance of the second sample;
+     * </p><p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>The observed array lengths must both be at least 2.
+     * </li></ul></p>
+     *
+     * @param sample1 array of sample data values
+     * @param sample2 array of sample data values
+     * @return t statistic
+     * @throws NullArgumentException if the arrays are <code>null</code>
+     * @throws NumberIsTooSmallException if the length of the arrays is &lt; 2
+     */
+    public double t(final double[] sample1, final double[] sample2)
+        throws NullArgumentException, NumberIsTooSmallException {
+
+        checkSampleData(sample1);
+        checkSampleData(sample2);
+        // No try-catch or advertised exception because args have just been checked
+        return t(StatUtils.mean(sample1), StatUtils.mean(sample2),
+                 StatUtils.variance(sample1), StatUtils.variance(sample2),
+                 sample1.length, sample2.length);
+
+    }
+
+    /**
+     * Computes a 2-sample t statistic </a>, comparing the means of the datasets
+     * described by two {@link StatisticalSummary} instances, without the
+     * assumption of equal subpopulation variances.  Use
+     * {@link #homoscedasticT(StatisticalSummary, StatisticalSummary)} to
+     * compute a t-statistic under the equal variances assumption.
+     * <p>
+     * This statistic can be used to perform a two-sample t-test to compare
+     * sample means.</p>
+     * <p>
+      * The returned  t-statistic is</p>
+     * <p>
+     * &nbsp;&nbsp; <code>  t = (m1 - m2) / sqrt(var1/n1 + var2/n2)</code>
+     * </p><p>
+     * where <strong><code>n1</code></strong> is the size of the first sample;
+     * <strong><code> n2</code></strong> is the size of the second sample;
+     * <strong><code> m1</code></strong> is the mean of the first sample;
+     * <strong><code> m2</code></strong> is the mean of the second sample
+     * <strong><code> var1</code></strong> is the variance of the first sample;
+     * <strong><code> var2</code></strong> is the variance of the second sample
+     * </p><p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>The datasets described by the two Univariates must each contain
+     * at least 2 observations.
+     * </li></ul></p>
+     *
+     * @param sampleStats1 StatisticalSummary describing data from the first sample
+     * @param sampleStats2 StatisticalSummary describing data from the second sample
+     * @return t statistic
+     * @throws NullArgumentException if the sample statistics are <code>null</code>
+     * @throws NumberIsTooSmallException if the number of samples is &lt; 2
+     */
+    public double t(final StatisticalSummary sampleStats1,
+                    final StatisticalSummary sampleStats2)
+        throws NullArgumentException, NumberIsTooSmallException {
+
+        checkSampleData(sampleStats1);
+        checkSampleData(sampleStats2);
+        return t(sampleStats1.getMean(), sampleStats2.getMean(),
+                 sampleStats1.getVariance(), sampleStats2.getVariance(),
+                 sampleStats1.getN(), sampleStats2.getN());
+
+    }
+
+    /**
+     * Computes a 2-sample t statistic, comparing the means of the datasets
+     * described by two {@link StatisticalSummary} instances, under the
+     * assumption of equal subpopulation variances.  To compute a t-statistic
+     * without the equal variances assumption, use
+     * {@link #t(StatisticalSummary, StatisticalSummary)}.
+     * <p>
+     * This statistic can be used to perform a (homoscedastic) two-sample
+     * t-test to compare sample means.</p>
+     * <p>
+     * The t-statistic returned is</p>
+     * <p>
+     * &nbsp;&nbsp;<code>  t = (m1 - m2) / (sqrt(1/n1 +1/n2) sqrt(var))</code>
+     * </p><p>
+     * where <strong><code>n1</code></strong> is the size of first sample;
+     * <strong><code> n2</code></strong> is the size of second sample;
+     * <strong><code> m1</code></strong> is the mean of first sample;
+     * <strong><code> m2</code></strong> is the mean of second sample
+     * and <strong><code>var</code></strong> is the pooled variance estimate:
+     * </p><p>
+     * <code>var = sqrt(((n1 - 1)var1 + (n2 - 1)var2) / ((n1-1) + (n2-1)))</code>
+     * </p><p>
+     * with <strong><code>var1</code></strong> the variance of the first sample and
+     * <strong><code>var2</code></strong> the variance of the second sample.
+     * </p><p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>The datasets described by the two Univariates must each contain
+     * at least 2 observations.
+     * </li></ul></p>
+     *
+     * @param sampleStats1 StatisticalSummary describing data from the first sample
+     * @param sampleStats2 StatisticalSummary describing data from the second sample
+     * @return t statistic
+     * @throws NullArgumentException if the sample statistics are <code>null</code>
+     * @throws NumberIsTooSmallException if the number of samples is &lt; 2
+     */
+    public double homoscedasticT(final StatisticalSummary sampleStats1,
+                                 final StatisticalSummary sampleStats2)
+        throws NullArgumentException, NumberIsTooSmallException {
+
+        checkSampleData(sampleStats1);
+        checkSampleData(sampleStats2);
+        return homoscedasticT(sampleStats1.getMean(), sampleStats2.getMean(),
+                              sampleStats1.getVariance(), sampleStats2.getVariance(),
+                              sampleStats1.getN(), sampleStats2.getN());
+
+    }
+
+    /**
+     * Returns the <i>observed significance level</i>, or
+     * <i>p-value</i>, associated with a one-sample, two-tailed t-test
+     * comparing the mean of the input array with the constant <code>mu</code>.
+     * <p>
+     * The number returned is the smallest significance level
+     * at which one can reject the null hypothesis that the mean equals
+     * <code>mu</code> in favor of the two-sided alternative that the mean
+     * is different from <code>mu</code>. For a one-sided test, divide the
+     * returned value by 2.</p>
+     * <p>
+     * <strong>Usage Note:</strong><br>
+     * The validity of the test depends on the assumptions of the parametric
+     * t-test procedure, as discussed
+     * <a href="http://www.basic.nwu.edu/statguidefiles/ttest_unpaired_ass_viol.html">here</a>
+     * </p><p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>The observed array length must be at least 2.
+     * </li></ul></p>
+     *
+     * @param mu constant value to compare sample mean against
+     * @param sample array of sample data values
+     * @return p-value
+     * @throws NullArgumentException if the sample array is <code>null</code>
+     * @throws NumberIsTooSmallException if the length of the array is &lt; 2
+     * @throws MaxCountExceededException if an error occurs computing the p-value
+     */
+    public double tTest(final double mu, final double[] sample)
+        throws NullArgumentException, NumberIsTooSmallException,
+        MaxCountExceededException {
+
+        checkSampleData(sample);
+        // No try-catch or advertised exception because args have just been checked
+        return tTest(StatUtils.mean(sample), mu, StatUtils.variance(sample),
+                     sample.length);
+
+    }
+
+    /**
+     * Performs a <a href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda353.htm">
+     * two-sided t-test</a> evaluating the null hypothesis that the mean of the population from
+     * which <code>sample</code> is drawn equals <code>mu</code>.
+     * <p>
+     * Returns <code>true</code> iff the null hypothesis can be
+     * rejected with confidence <code>1 - alpha</code>.  To
+     * perform a 1-sided test, use <code>alpha * 2</code></p>
+     * <p>
+     * <strong>Examples:</strong><br><ol>
+     * <li>To test the (2-sided) hypothesis <code>sample mean = mu </code> at
+     * the 95% level, use <br><code>tTest(mu, sample, 0.05) </code>
+     * </li>
+     * <li>To test the (one-sided) hypothesis <code> sample mean < mu </code>
+     * at the 99% level, first verify that the measured sample mean is less
+     * than <code>mu</code> and then use
+     * <br><code>tTest(mu, sample, 0.02) </code>
+     * </li></ol></p>
+     * <p>
+     * <strong>Usage Note:</strong><br>
+     * The validity of the test depends on the assumptions of the one-sample
+     * parametric t-test procedure, as discussed
+     * <a href="http://www.basic.nwu.edu/statguidefiles/sg_glos.html#one-sample">here</a>
+     * </p><p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>The observed array length must be at least 2.
+     * </li></ul></p>
+     *
+     * @param mu constant value to compare sample mean against
+     * @param sample array of sample data values
+     * @param alpha significance level of the test
+     * @return p-value
+     * @throws NullArgumentException if the sample array is <code>null</code>
+     * @throws NumberIsTooSmallException if the length of the array is &lt; 2
+     * @throws OutOfRangeException if <code>alpha</code> is not in the range (0, 0.5]
+     * @throws MaxCountExceededException if an error computing the p-value
+     */
+    public boolean tTest(final double mu, final double[] sample, final double alpha)
+        throws NullArgumentException, NumberIsTooSmallException,
+        OutOfRangeException, MaxCountExceededException {
+
+        checkSignificanceLevel(alpha);
+        return tTest(mu, sample) < alpha;
+
+    }
+
+    /**
+     * Returns the <i>observed significance level</i>, or
+     * <i>p-value</i>, associated with a one-sample, two-tailed t-test
+     * comparing the mean of the dataset described by <code>sampleStats</code>
+     * with the constant <code>mu</code>.
+     * <p>
+     * The number returned is the smallest significance level
+     * at which one can reject the null hypothesis that the mean equals
+     * <code>mu</code> in favor of the two-sided alternative that the mean
+     * is different from <code>mu</code>. For a one-sided test, divide the
+     * returned value by 2.</p>
+     * <p>
+     * <strong>Usage Note:</strong><br>
+     * The validity of the test depends on the assumptions of the parametric
+     * t-test procedure, as discussed
+     * <a href="http://www.basic.nwu.edu/statguidefiles/ttest_unpaired_ass_viol.html">
+     * here</a></p>
+     * <p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>The sample must contain at least 2 observations.
+     * </li></ul></p>
+     *
+     * @param mu constant value to compare sample mean against
+     * @param sampleStats StatisticalSummary describing sample data
+     * @return p-value
+     * @throws NullArgumentException if <code>sampleStats</code> is <code>null</code>
+     * @throws NumberIsTooSmallException if the number of samples is &lt; 2
+     * @throws MaxCountExceededException if an error occurs computing the p-value
+     */
+    public double tTest(final double mu, final StatisticalSummary sampleStats)
+        throws NullArgumentException, NumberIsTooSmallException,
+        MaxCountExceededException {
+
+        checkSampleData(sampleStats);
+        return tTest(sampleStats.getMean(), mu, sampleStats.getVariance(),
+                     sampleStats.getN());
+
+    }
+
+    /**
+     * Performs a <a href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda353.htm">
+     * two-sided t-test</a> evaluating the null hypothesis that the mean of the
+     * population from which the dataset described by <code>stats</code> is
+     * drawn equals <code>mu</code>.
+     * <p>
+     * Returns <code>true</code> iff the null hypothesis can be rejected with
+     * confidence <code>1 - alpha</code>.  To  perform a 1-sided test, use
+     * <code>alpha * 2.</code></p>
+     * <p>
+     * <strong>Examples:</strong><br><ol>
+     * <li>To test the (2-sided) hypothesis <code>sample mean = mu </code> at
+     * the 95% level, use <br><code>tTest(mu, sampleStats, 0.05) </code>
+     * </li>
+     * <li>To test the (one-sided) hypothesis <code> sample mean < mu </code>
+     * at the 99% level, first verify that the measured sample mean is less
+     * than <code>mu</code> and then use
+     * <br><code>tTest(mu, sampleStats, 0.02) </code>
+     * </li></ol></p>
+     * <p>
+     * <strong>Usage Note:</strong><br>
+     * The validity of the test depends on the assumptions of the one-sample
+     * parametric t-test procedure, as discussed
+     * <a href="http://www.basic.nwu.edu/statguidefiles/sg_glos.html#one-sample">here</a>
+     * </p><p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>The sample must include at least 2 observations.
+     * </li></ul></p>
+     *
+     * @param mu constant value to compare sample mean against
+     * @param sampleStats StatisticalSummary describing sample data values
+     * @param alpha significance level of the test
+     * @return p-value
+     * @throws NullArgumentException if <code>sampleStats</code> is <code>null</code>
+     * @throws NumberIsTooSmallException if the number of samples is &lt; 2
+     * @throws OutOfRangeException if <code>alpha</code> is not in the range (0, 0.5]
+     * @throws MaxCountExceededException if an error occurs computing the p-value
+     */
+    public boolean tTest(final double mu, final StatisticalSummary sampleStats,
+                         final double alpha)
+    throws NullArgumentException, NumberIsTooSmallException,
+    OutOfRangeException, MaxCountExceededException {
+
+        checkSignificanceLevel(alpha);
+        return tTest(mu, sampleStats) < alpha;
+
+    }
+
+    /**
+     * Returns the <i>observed significance level</i>, or
+     * <i>p-value</i>, associated with a two-sample, two-tailed t-test
+     * comparing the means of the input arrays.
+     * <p>
+     * The number returned is the smallest significance level
+     * at which one can reject the null hypothesis that the two means are
+     * equal in favor of the two-sided alternative that they are different.
+     * For a one-sided test, divide the returned value by 2.</p>
+     * <p>
+     * The test does not assume that the underlying popuation variances are
+     * equal  and it uses approximated degrees of freedom computed from the
+     * sample data to compute the p-value.  The t-statistic used is as defined in
+     * {@link #t(double[], double[])} and the Welch-Satterthwaite approximation
+     * to the degrees of freedom is used,
+     * as described
+     * <a href="http://www.itl.nist.gov/div898/handbook/prc/section3/prc31.htm">
+     * here.</a>  To perform the test under the assumption of equal subpopulation
+     * variances, use {@link #homoscedasticTTest(double[], double[])}.</p>
+     * <p>
+     * <strong>Usage Note:</strong><br>
+     * The validity of the p-value depends on the assumptions of the parametric
+     * t-test procedure, as discussed
+     * <a href="http://www.basic.nwu.edu/statguidefiles/ttest_unpaired_ass_viol.html">
+     * here</a></p>
+     * <p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>The observed array lengths must both be at least 2.
+     * </li></ul></p>
+     *
+     * @param sample1 array of sample data values
+     * @param sample2 array of sample data values
+     * @return p-value for t-test
+     * @throws NullArgumentException if the arrays are <code>null</code>
+     * @throws NumberIsTooSmallException if the length of the arrays is &lt; 2
+     * @throws MaxCountExceededException if an error occurs computing the p-value
+     */
+    public double tTest(final double[] sample1, final double[] sample2)
+        throws NullArgumentException, NumberIsTooSmallException,
+        MaxCountExceededException {
+
+        checkSampleData(sample1);
+        checkSampleData(sample2);
+        // No try-catch or advertised exception because args have just been checked
+        return tTest(StatUtils.mean(sample1), StatUtils.mean(sample2),
+                     StatUtils.variance(sample1), StatUtils.variance(sample2),
+                     sample1.length, sample2.length);
+
+    }
+
+    /**
+     * Returns the <i>observed significance level</i>, or
+     * <i>p-value</i>, associated with a two-sample, two-tailed t-test
+     * comparing the means of the input arrays, under the assumption that
+     * the two samples are drawn from subpopulations with equal variances.
+     * To perform the test without the equal variances assumption, use
+     * {@link #tTest(double[], double[])}.</p>
+     * <p>
+     * The number returned is the smallest significance level
+     * at which one can reject the null hypothesis that the two means are
+     * equal in favor of the two-sided alternative that they are different.
+     * For a one-sided test, divide the returned value by 2.</p>
+     * <p>
+     * A pooled variance estimate is used to compute the t-statistic.  See
+     * {@link #homoscedasticT(double[], double[])}. The sum of the sample sizes
+     * minus 2 is used as the degrees of freedom.</p>
+     * <p>
+     * <strong>Usage Note:</strong><br>
+     * The validity of the p-value depends on the assumptions of the parametric
+     * t-test procedure, as discussed
+     * <a href="http://www.basic.nwu.edu/statguidefiles/ttest_unpaired_ass_viol.html">
+     * here</a></p>
+     * <p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>The observed array lengths must both be at least 2.
+     * </li></ul></p>
+     *
+     * @param sample1 array of sample data values
+     * @param sample2 array of sample data values
+     * @return p-value for t-test
+     * @throws NullArgumentException if the arrays are <code>null</code>
+     * @throws NumberIsTooSmallException if the length of the arrays is &lt; 2
+     * @throws MaxCountExceededException if an error occurs computing the p-value
+     */
+    public double homoscedasticTTest(final double[] sample1, final double[] sample2)
+        throws NullArgumentException, NumberIsTooSmallException,
+        MaxCountExceededException {
+
+        checkSampleData(sample1);
+        checkSampleData(sample2);
+        // No try-catch or advertised exception because args have just been checked
+        return homoscedasticTTest(StatUtils.mean(sample1),
+                                  StatUtils.mean(sample2),
+                                  StatUtils.variance(sample1),
+                                  StatUtils.variance(sample2),
+                                  sample1.length, sample2.length);
+
+    }
+
+    /**
+     * Performs a
+     * <a href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda353.htm">
+     * two-sided t-test</a> evaluating the null hypothesis that <code>sample1</code>
+     * and <code>sample2</code> are drawn from populations with the same mean,
+     * with significance level <code>alpha</code>.  This test does not assume
+     * that the subpopulation variances are equal.  To perform the test assuming
+     * equal variances, use
+     * {@link #homoscedasticTTest(double[], double[], double)}.
+     * <p>
+     * Returns <code>true</code> iff the null hypothesis that the means are
+     * equal can be rejected with confidence <code>1 - alpha</code>.  To
+     * perform a 1-sided test, use <code>alpha * 2</code></p>
+     * <p>
+     * See {@link #t(double[], double[])} for the formula used to compute the
+     * t-statistic.  Degrees of freedom are approximated using the
+     * <a href="http://www.itl.nist.gov/div898/handbook/prc/section3/prc31.htm">
+     * Welch-Satterthwaite approximation.</a></p>
+     * <p>
+     * <strong>Examples:</strong><br><ol>
+     * <li>To test the (2-sided) hypothesis <code>mean 1 = mean 2 </code> at
+     * the 95% level,  use
+     * <br><code>tTest(sample1, sample2, 0.05). </code>
+     * </li>
+     * <li>To test the (one-sided) hypothesis <code> mean 1 < mean 2 </code>,
+     * at the 99% level, first verify that the measured  mean of <code>sample 1</code>
+     * is less than the mean of <code>sample 2</code> and then use
+     * <br><code>tTest(sample1, sample2, 0.02) </code>
+     * </li></ol></p>
+     * <p>
+     * <strong>Usage Note:</strong><br>
+     * The validity of the test depends on the assumptions of the parametric
+     * t-test procedure, as discussed
+     * <a href="http://www.basic.nwu.edu/statguidefiles/ttest_unpaired_ass_viol.html">
+     * here</a></p>
+     * <p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>The observed array lengths must both be at least 2.
+     * </li>
+     * <li> <code> 0 < alpha < 0.5 </code>
+     * </li></ul></p>
+     *
+     * @param sample1 array of sample data values
+     * @param sample2 array of sample data values
+     * @param alpha significance level of the test
+     * @return true if the null hypothesis can be rejected with
+     * confidence 1 - alpha
+     * @throws NullArgumentException if the arrays are <code>null</code>
+     * @throws NumberIsTooSmallException if the length of the arrays is &lt; 2
+     * @throws OutOfRangeException if <code>alpha</code> is not in the range (0, 0.5]
+     * @throws MaxCountExceededException if an error occurs computing the p-value
+     */
+    public boolean tTest(final double[] sample1, final double[] sample2,
+                         final double alpha)
+        throws NullArgumentException, NumberIsTooSmallException,
+        OutOfRangeException, MaxCountExceededException {
+
+        checkSignificanceLevel(alpha);
+        return tTest(sample1, sample2) < alpha;
+
+    }
+
+    /**
+     * Performs a
+     * <a href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda353.htm">
+     * two-sided t-test</a> evaluating the null hypothesis that <code>sample1</code>
+     * and <code>sample2</code> are drawn from populations with the same mean,
+     * with significance level <code>alpha</code>,  assuming that the
+     * subpopulation variances are equal.  Use
+     * {@link #tTest(double[], double[], double)} to perform the test without
+     * the assumption of equal variances.
+     * <p>
+     * Returns <code>true</code> iff the null hypothesis that the means are
+     * equal can be rejected with confidence <code>1 - alpha</code>.  To
+     * perform a 1-sided test, use <code>alpha * 2.</code>  To perform the test
+     * without the assumption of equal subpopulation variances, use
+     * {@link #tTest(double[], double[], double)}.</p>
+     * <p>
+     * A pooled variance estimate is used to compute the t-statistic. See
+     * {@link #t(double[], double[])} for the formula. The sum of the sample
+     * sizes minus 2 is used as the degrees of freedom.</p>
+     * <p>
+     * <strong>Examples:</strong><br><ol>
+     * <li>To test the (2-sided) hypothesis <code>mean 1 = mean 2 </code> at
+     * the 95% level, use <br><code>tTest(sample1, sample2, 0.05). </code>
+     * </li>
+     * <li>To test the (one-sided) hypothesis <code> mean 1 < mean 2, </code>
+     * at the 99% level, first verify that the measured mean of
+     * <code>sample 1</code> is less than the mean of <code>sample 2</code>
+     * and then use
+     * <br><code>tTest(sample1, sample2, 0.02) </code>
+     * </li></ol></p>
+     * <p>
+     * <strong>Usage Note:</strong><br>
+     * The validity of the test depends on the assumptions of the parametric
+     * t-test procedure, as discussed
+     * <a href="http://www.basic.nwu.edu/statguidefiles/ttest_unpaired_ass_viol.html">
+     * here</a></p>
+     * <p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>The observed array lengths must both be at least 2.
+     * </li>
+     * <li> <code> 0 < alpha < 0.5 </code>
+     * </li></ul></p>
+     *
+     * @param sample1 array of sample data values
+     * @param sample2 array of sample data values
+     * @param alpha significance level of the test
+     * @return true if the null hypothesis can be rejected with
+     * confidence 1 - alpha
+     * @throws NullArgumentException if the arrays are <code>null</code>
+     * @throws NumberIsTooSmallException if the length of the arrays is &lt; 2
+     * @throws OutOfRangeException if <code>alpha</code> is not in the range (0, 0.5]
+     * @throws MaxCountExceededException if an error occurs computing the p-value
+     */
+    public boolean homoscedasticTTest(final double[] sample1, final double[] sample2,
+                                      final double alpha)
+        throws NullArgumentException, NumberIsTooSmallException,
+        OutOfRangeException, MaxCountExceededException {
+
+        checkSignificanceLevel(alpha);
+        return homoscedasticTTest(sample1, sample2) < alpha;
+
+    }
+
+    /**
+     * Returns the <i>observed significance level</i>, or
+     * <i>p-value</i>, associated with a two-sample, two-tailed t-test
+     * comparing the means of the datasets described by two StatisticalSummary
+     * instances.
+     * <p>
+     * The number returned is the smallest significance level
+     * at which one can reject the null hypothesis that the two means are
+     * equal in favor of the two-sided alternative that they are different.
+     * For a one-sided test, divide the returned value by 2.</p>
+     * <p>
+     * The test does not assume that the underlying population variances are
+     * equal  and it uses approximated degrees of freedom computed from the
+     * sample data to compute the p-value.   To perform the test assuming
+     * equal variances, use
+     * {@link #homoscedasticTTest(StatisticalSummary, StatisticalSummary)}.</p>
+     * <p>
+     * <strong>Usage Note:</strong><br>
+     * The validity of the p-value depends on the assumptions of the parametric
+     * t-test procedure, as discussed
+     * <a href="http://www.basic.nwu.edu/statguidefiles/ttest_unpaired_ass_viol.html">
+     * here</a></p>
+     * <p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>The datasets described by the two Univariates must each contain
+     * at least 2 observations.
+     * </li></ul></p>
+     *
+     * @param sampleStats1  StatisticalSummary describing data from the first sample
+     * @param sampleStats2  StatisticalSummary describing data from the second sample
+     * @return p-value for t-test
+     * @throws NullArgumentException if the sample statistics are <code>null</code>
+     * @throws NumberIsTooSmallException if the number of samples is &lt; 2
+     * @throws MaxCountExceededException if an error occurs computing the p-value
+     */
+    public double tTest(final StatisticalSummary sampleStats1,
+                        final StatisticalSummary sampleStats2)
+        throws NullArgumentException, NumberIsTooSmallException,
+        MaxCountExceededException {
+
+        checkSampleData(sampleStats1);
+        checkSampleData(sampleStats2);
+        return tTest(sampleStats1.getMean(), sampleStats2.getMean(),
+                     sampleStats1.getVariance(), sampleStats2.getVariance(),
+                     sampleStats1.getN(), sampleStats2.getN());
+
+    }
+
+    /**
+     * Returns the <i>observed significance level</i>, or
+     * <i>p-value</i>, associated with a two-sample, two-tailed t-test
+     * comparing the means of the datasets described by two StatisticalSummary
+     * instances, under the hypothesis of equal subpopulation variances. To
+     * perform a test without the equal variances assumption, use
+     * {@link #tTest(StatisticalSummary, StatisticalSummary)}.
+     * <p>
+     * The number returned is the smallest significance level
+     * at which one can reject the null hypothesis that the two means are
+     * equal in favor of the two-sided alternative that they are different.
+     * For a one-sided test, divide the returned value by 2.</p>
+     * <p>
+     * See {@link #homoscedasticT(double[], double[])} for the formula used to
+     * compute the t-statistic. The sum of the  sample sizes minus 2 is used as
+     * the degrees of freedom.</p>
+     * <p>
+     * <strong>Usage Note:</strong><br>
+     * The validity of the p-value depends on the assumptions of the parametric
+     * t-test procedure, as discussed
+     * <a href="http://www.basic.nwu.edu/statguidefiles/ttest_unpaired_ass_viol.html">here</a>
+     * </p><p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>The datasets described by the two Univariates must each contain
+     * at least 2 observations.
+     * </li></ul></p>
+     *
+     * @param sampleStats1  StatisticalSummary describing data from the first sample
+     * @param sampleStats2  StatisticalSummary describing data from the second sample
+     * @return p-value for t-test
+     * @throws NullArgumentException if the sample statistics are <code>null</code>
+     * @throws NumberIsTooSmallException if the number of samples is &lt; 2
+     * @throws MaxCountExceededException if an error occurs computing the p-value
+     */
+    public double homoscedasticTTest(final StatisticalSummary sampleStats1,
+                                     final StatisticalSummary sampleStats2)
+        throws NullArgumentException, NumberIsTooSmallException,
+        MaxCountExceededException {
+
+        checkSampleData(sampleStats1);
+        checkSampleData(sampleStats2);
+        return homoscedasticTTest(sampleStats1.getMean(),
+                                  sampleStats2.getMean(),
+                                  sampleStats1.getVariance(),
+                                  sampleStats2.getVariance(),
+                                  sampleStats1.getN(), sampleStats2.getN());
+
+    }
+
+    /**
+     * Performs a
+     * <a href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda353.htm">
+     * two-sided t-test</a> evaluating the null hypothesis that
+     * <code>sampleStats1</code> and <code>sampleStats2</code> describe
+     * datasets drawn from populations with the same mean, with significance
+     * level <code>alpha</code>.   This test does not assume that the
+     * subpopulation variances are equal.  To perform the test under the equal
+     * variances assumption, use
+     * {@link #homoscedasticTTest(StatisticalSummary, StatisticalSummary)}.
+     * <p>
+     * Returns <code>true</code> iff the null hypothesis that the means are
+     * equal can be rejected with confidence <code>1 - alpha</code>.  To
+     * perform a 1-sided test, use <code>alpha * 2</code></p>
+     * <p>
+     * See {@link #t(double[], double[])} for the formula used to compute the
+     * t-statistic.  Degrees of freedom are approximated using the
+     * <a href="http://www.itl.nist.gov/div898/handbook/prc/section3/prc31.htm">
+     * Welch-Satterthwaite approximation.</a></p>
+     * <p>
+     * <strong>Examples:</strong><br><ol>
+     * <li>To test the (2-sided) hypothesis <code>mean 1 = mean 2 </code> at
+     * the 95%, use
+     * <br><code>tTest(sampleStats1, sampleStats2, 0.05) </code>
+     * </li>
+     * <li>To test the (one-sided) hypothesis <code> mean 1 < mean 2 </code>
+     * at the 99% level,  first verify that the measured mean of
+     * <code>sample 1</code> is less than  the mean of <code>sample 2</code>
+     * and then use
+     * <br><code>tTest(sampleStats1, sampleStats2, 0.02) </code>
+     * </li></ol></p>
+     * <p>
+     * <strong>Usage Note:</strong><br>
+     * The validity of the test depends on the assumptions of the parametric
+     * t-test procedure, as discussed
+     * <a href="http://www.basic.nwu.edu/statguidefiles/ttest_unpaired_ass_viol.html">
+     * here</a></p>
+     * <p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>The datasets described by the two Univariates must each contain
+     * at least 2 observations.
+     * </li>
+     * <li> <code> 0 < alpha < 0.5 </code>
+     * </li></ul></p>
+     *
+     * @param sampleStats1 StatisticalSummary describing sample data values
+     * @param sampleStats2 StatisticalSummary describing sample data values
+     * @param alpha significance level of the test
+     * @return true if the null hypothesis can be rejected with
+     * confidence 1 - alpha
+     * @throws NullArgumentException if the sample statistics are <code>null</code>
+     * @throws NumberIsTooSmallException if the number of samples is &lt; 2
+     * @throws OutOfRangeException if <code>alpha</code> is not in the range (0, 0.5]
+     * @throws MaxCountExceededException if an error occurs computing the p-value
+     */
+    public boolean tTest(final StatisticalSummary sampleStats1,
+                         final StatisticalSummary sampleStats2,
+                         final double alpha)
+        throws NullArgumentException, NumberIsTooSmallException,
+        OutOfRangeException, MaxCountExceededException {
+
+        checkSignificanceLevel(alpha);
+        return tTest(sampleStats1, sampleStats2) < alpha;
+
+    }
+
+    //----------------------------------------------- Protected methods
+
+    /**
+     * Computes approximate degrees of freedom for 2-sample t-test.
+     *
+     * @param v1 first sample variance
+     * @param v2 second sample variance
+     * @param n1 first sample n
+     * @param n2 second sample n
+     * @return approximate degrees of freedom
+     */
+    protected double df(double v1, double v2, double n1, double n2) {
+        return (((v1 / n1) + (v2 / n2)) * ((v1 / n1) + (v2 / n2))) /
+        ((v1 * v1) / (n1 * n1 * (n1 - 1d)) + (v2 * v2) /
+                (n2 * n2 * (n2 - 1d)));
+    }
+
+    /**
+     * Computes t test statistic for 1-sample t-test.
+     *
+     * @param m sample mean
+     * @param mu constant to test against
+     * @param v sample variance
+     * @param n sample n
+     * @return t test statistic
+     */
+    protected double t(final double m, final double mu,
+                       final double v, final double n) {
+        return (m - mu) / FastMath.sqrt(v / n);
+    }
+
+    /**
+     * Computes t test statistic for 2-sample t-test.
+     * <p>
+     * Does not assume that subpopulation variances are equal.</p>
+     *
+     * @param m1 first sample mean
+     * @param m2 second sample mean
+     * @param v1 first sample variance
+     * @param v2 second sample variance
+     * @param n1 first sample n
+     * @param n2 second sample n
+     * @return t test statistic
+     */
+    protected double t(final double m1, final double m2,
+                       final double v1, final double v2,
+                       final double n1, final double n2)  {
+        return (m1 - m2) / FastMath.sqrt((v1 / n1) + (v2 / n2));
+    }
+
+    /**
+     * Computes t test statistic for 2-sample t-test under the hypothesis
+     * of equal subpopulation variances.
+     *
+     * @param m1 first sample mean
+     * @param m2 second sample mean
+     * @param v1 first sample variance
+     * @param v2 second sample variance
+     * @param n1 first sample n
+     * @param n2 second sample n
+     * @return t test statistic
+     */
+    protected double homoscedasticT(final double m1, final double m2,
+                                    final double v1, final double v2,
+                                    final double n1, final double n2)  {
+        final double pooledVariance = ((n1  - 1) * v1 + (n2 -1) * v2 ) / (n1 + n2 - 2);
+        return (m1 - m2) / FastMath.sqrt(pooledVariance * (1d / n1 + 1d / n2));
+    }
+
+    /**
+     * Computes p-value for 2-sided, 1-sample t-test.
+     *
+     * @param m sample mean
+     * @param mu constant to test against
+     * @param v sample variance
+     * @param n sample n
+     * @return p-value
+     * @throws MaxCountExceededException if an error occurs computing the p-value
+     * @throws MathIllegalArgumentException if n is not greater than 1
+     */
+    protected double tTest(final double m, final double mu,
+                           final double v, final double n)
+        throws MaxCountExceededException, MathIllegalArgumentException {
+
+        final double t = FastMath.abs(t(m, mu, v, n));
+        // pass a null rng to avoid unneeded overhead as we will not sample from this distribution
+        final TDistribution distribution = new TDistribution(null, n - 1);
+        return 2.0 * distribution.cumulativeProbability(-t);
+
+    }
+
+    /**
+     * Computes p-value for 2-sided, 2-sample t-test.
+     * <p>
+     * Does not assume subpopulation variances are equal. Degrees of freedom
+     * are estimated from the data.</p>
+     *
+     * @param m1 first sample mean
+     * @param m2 second sample mean
+     * @param v1 first sample variance
+     * @param v2 second sample variance
+     * @param n1 first sample n
+     * @param n2 second sample n
+     * @return p-value
+     * @throws MaxCountExceededException if an error occurs computing the p-value
+     * @throws NotStrictlyPositiveException if the estimated degrees of freedom is not
+     * strictly positive
+     */
+    protected double tTest(final double m1, final double m2,
+                           final double v1, final double v2,
+                           final double n1, final double n2)
+        throws MaxCountExceededException, NotStrictlyPositiveException {
+
+        final double t = FastMath.abs(t(m1, m2, v1, v2, n1, n2));
+        final double degreesOfFreedom = df(v1, v2, n1, n2);
+        // pass a null rng to avoid unneeded overhead as we will not sample from this distribution
+        final TDistribution distribution = new TDistribution(null, degreesOfFreedom);
+        return 2.0 * distribution.cumulativeProbability(-t);
+
+    }
+
+    /**
+     * Computes p-value for 2-sided, 2-sample t-test, under the assumption
+     * of equal subpopulation variances.
+     * <p>
+     * The sum of the sample sizes minus 2 is used as degrees of freedom.</p>
+     *
+     * @param m1 first sample mean
+     * @param m2 second sample mean
+     * @param v1 first sample variance
+     * @param v2 second sample variance
+     * @param n1 first sample n
+     * @param n2 second sample n
+     * @return p-value
+     * @throws MaxCountExceededException if an error occurs computing the p-value
+     * @throws NotStrictlyPositiveException if the estimated degrees of freedom is not
+     * strictly positive
+     */
+    protected double homoscedasticTTest(double m1, double m2,
+                                        double v1, double v2,
+                                        double n1, double n2)
+        throws MaxCountExceededException, NotStrictlyPositiveException {
+
+        final double t = FastMath.abs(homoscedasticT(m1, m2, v1, v2, n1, n2));
+        final double degreesOfFreedom = n1 + n2 - 2;
+        // pass a null rng to avoid unneeded overhead as we will not sample from this distribution
+        final TDistribution distribution = new TDistribution(null, degreesOfFreedom);
+        return 2.0 * distribution.cumulativeProbability(-t);
+
+    }
+
+    /**
+     * Check significance level.
+     *
+     * @param alpha significance level
+     * @throws OutOfRangeException if the significance level is out of bounds.
+     */
+    private void checkSignificanceLevel(final double alpha)
+        throws OutOfRangeException {
+
+        if (alpha <= 0 || alpha > 0.5) {
+            throw new OutOfRangeException(LocalizedFormats.SIGNIFICANCE_LEVEL,
+                                          alpha, 0.0, 0.5);
+        }
+
+    }
+
+    /**
+     * Check sample data.
+     *
+     * @param data Sample data.
+     * @throws NullArgumentException if {@code data} is {@code null}.
+     * @throws NumberIsTooSmallException if there is not enough sample data.
+     */
+    private void checkSampleData(final double[] data)
+        throws NullArgumentException, NumberIsTooSmallException {
+
+        if (data == null) {
+            throw new NullArgumentException();
+        }
+        if (data.length < 2) {
+            throw new NumberIsTooSmallException(
+                    LocalizedFormats.INSUFFICIENT_DATA_FOR_T_STATISTIC,
+                    data.length, 2, true);
+        }
+
+    }
+
+    /**
+     * Check sample data.
+     *
+     * @param stat Statistical summary.
+     * @throws NullArgumentException if {@code data} is {@code null}.
+     * @throws NumberIsTooSmallException if there is not enough sample data.
+     */
+    private void checkSampleData(final StatisticalSummary stat)
+        throws NullArgumentException, NumberIsTooSmallException {
+
+        if (stat == null) {
+            throw new NullArgumentException();
+        }
+        if (stat.getN() < 2) {
+            throw new NumberIsTooSmallException(
+                    LocalizedFormats.INSUFFICIENT_DATA_FOR_T_STATISTIC,
+                    stat.getN(), 2, true);
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/inference/TestUtils.java b/src/main/java/org/apache/commons/math3/stat/inference/TestUtils.java
new file mode 100644
index 0000000..a92fb19
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/inference/TestUtils.java
@@ -0,0 +1,547 @@
+/*
+ * 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.math3.stat.inference;
+
+import java.util.Collection;
+
+import org.apache.commons.math3.distribution.RealDistribution;
+import org.apache.commons.math3.exception.ConvergenceException;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.InsufficientDataException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.ZeroException;
+import org.apache.commons.math3.stat.descriptive.StatisticalSummary;
+
+/**
+ * A collection of static methods to create inference test instances or to
+ * perform inference tests.
+ *
+ * @since 1.1
+ */
+public class TestUtils  {
+
+    /** Singleton TTest instance. */
+    private static final TTest T_TEST = new TTest();
+
+    /** Singleton ChiSquareTest instance. */
+    private static final ChiSquareTest CHI_SQUARE_TEST = new ChiSquareTest();
+
+    /** Singleton OneWayAnova instance. */
+    private static final OneWayAnova ONE_WAY_ANANOVA = new OneWayAnova();
+
+    /** Singleton G-Test instance. */
+    private static final GTest G_TEST = new GTest();
+
+    /** Singleton K-S test instance */
+    private static final KolmogorovSmirnovTest KS_TEST = new KolmogorovSmirnovTest();
+
+    /**
+     * Prevent instantiation.
+     */
+    private TestUtils() {
+        super();
+    }
+
+    // CHECKSTYLE: stop JavadocMethodCheck
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.TTest#homoscedasticT(double[], double[])
+     */
+    public static double homoscedasticT(final double[] sample1, final double[] sample2)
+        throws NullArgumentException, NumberIsTooSmallException {
+        return T_TEST.homoscedasticT(sample1, sample2);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.TTest#homoscedasticT(org.apache.commons.math3.stat.descriptive.StatisticalSummary, org.apache.commons.math3.stat.descriptive.StatisticalSummary)
+     */
+    public static double homoscedasticT(final StatisticalSummary sampleStats1,
+                                        final StatisticalSummary sampleStats2)
+        throws NullArgumentException, NumberIsTooSmallException {
+        return T_TEST.homoscedasticT(sampleStats1, sampleStats2);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.TTest#homoscedasticTTest(double[], double[], double)
+     */
+    public static boolean homoscedasticTTest(final double[] sample1, final double[] sample2,
+                                             final double alpha)
+        throws NullArgumentException, NumberIsTooSmallException,
+        OutOfRangeException, MaxCountExceededException {
+        return T_TEST.homoscedasticTTest(sample1, sample2, alpha);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.TTest#homoscedasticTTest(double[], double[])
+     */
+    public static double homoscedasticTTest(final double[] sample1, final double[] sample2)
+        throws NullArgumentException, NumberIsTooSmallException, MaxCountExceededException {
+        return T_TEST.homoscedasticTTest(sample1, sample2);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.TTest#homoscedasticTTest(org.apache.commons.math3.stat.descriptive.StatisticalSummary, org.apache.commons.math3.stat.descriptive.StatisticalSummary)
+     */
+    public static double homoscedasticTTest(final StatisticalSummary sampleStats1,
+                                            final StatisticalSummary sampleStats2)
+        throws NullArgumentException, NumberIsTooSmallException, MaxCountExceededException {
+        return T_TEST.homoscedasticTTest(sampleStats1, sampleStats2);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.TTest#pairedT(double[], double[])
+     */
+    public static double pairedT(final double[] sample1, final double[] sample2)
+        throws NullArgumentException, NoDataException,
+        DimensionMismatchException, NumberIsTooSmallException {
+        return T_TEST.pairedT(sample1, sample2);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.TTest#pairedTTest(double[], double[], double)
+     */
+    public static boolean pairedTTest(final double[] sample1, final double[] sample2,
+                                      final double alpha)
+        throws NullArgumentException, NoDataException, DimensionMismatchException,
+        NumberIsTooSmallException, OutOfRangeException, MaxCountExceededException {
+        return T_TEST.pairedTTest(sample1, sample2, alpha);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.TTest#pairedTTest(double[], double[])
+     */
+    public static double pairedTTest(final double[] sample1, final double[] sample2)
+        throws NullArgumentException, NoDataException, DimensionMismatchException,
+        NumberIsTooSmallException, MaxCountExceededException {
+        return T_TEST.pairedTTest(sample1, sample2);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.TTest#t(double, double[])
+     */
+    public static double t(final double mu, final double[] observed)
+        throws NullArgumentException, NumberIsTooSmallException {
+        return T_TEST.t(mu, observed);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.TTest#t(double, org.apache.commons.math3.stat.descriptive.StatisticalSummary)
+     */
+    public static double t(final double mu, final StatisticalSummary sampleStats)
+        throws NullArgumentException, NumberIsTooSmallException {
+        return T_TEST.t(mu, sampleStats);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.TTest#t(double[], double[])
+     */
+    public static double t(final double[] sample1, final double[] sample2)
+        throws NullArgumentException, NumberIsTooSmallException {
+        return T_TEST.t(sample1, sample2);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.TTest#t(org.apache.commons.math3.stat.descriptive.StatisticalSummary, org.apache.commons.math3.stat.descriptive.StatisticalSummary)
+     */
+    public static double t(final StatisticalSummary sampleStats1,
+                           final StatisticalSummary sampleStats2)
+        throws NullArgumentException, NumberIsTooSmallException {
+        return T_TEST.t(sampleStats1, sampleStats2);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.TTest#tTest(double, double[], double)
+     */
+    public static boolean tTest(final double mu, final double[] sample, final double alpha)
+        throws NullArgumentException, NumberIsTooSmallException,
+        OutOfRangeException, MaxCountExceededException {
+        return T_TEST.tTest(mu, sample, alpha);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.TTest#tTest(double, double[])
+     */
+    public static double tTest(final double mu, final double[] sample)
+        throws NullArgumentException, NumberIsTooSmallException,
+        MaxCountExceededException {
+        return T_TEST.tTest(mu, sample);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.TTest#tTest(double, org.apache.commons.math3.stat.descriptive.StatisticalSummary, double)
+     */
+    public static boolean tTest(final double mu, final StatisticalSummary sampleStats,
+                                final double alpha)
+        throws NullArgumentException, NumberIsTooSmallException,
+        OutOfRangeException, MaxCountExceededException {
+        return T_TEST.tTest(mu, sampleStats, alpha);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.TTest#tTest(double, org.apache.commons.math3.stat.descriptive.StatisticalSummary)
+     */
+    public static double tTest(final double mu, final StatisticalSummary sampleStats)
+        throws NullArgumentException, NumberIsTooSmallException,
+        MaxCountExceededException {
+        return T_TEST.tTest(mu, sampleStats);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.TTest#tTest(double[], double[], double)
+     */
+    public static boolean tTest(final double[] sample1, final double[] sample2,
+                                final double alpha)
+        throws NullArgumentException, NumberIsTooSmallException,
+        OutOfRangeException, MaxCountExceededException {
+        return T_TEST.tTest(sample1, sample2, alpha);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.TTest#tTest(double[], double[])
+     */
+    public static double tTest(final double[] sample1, final double[] sample2)
+        throws NullArgumentException, NumberIsTooSmallException,
+        MaxCountExceededException {
+        return T_TEST.tTest(sample1, sample2);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.TTest#tTest(org.apache.commons.math3.stat.descriptive.StatisticalSummary, org.apache.commons.math3.stat.descriptive.StatisticalSummary, double)
+     */
+    public static boolean tTest(final StatisticalSummary sampleStats1,
+                                final StatisticalSummary sampleStats2,
+                                final double alpha)
+        throws NullArgumentException, NumberIsTooSmallException,
+        OutOfRangeException, MaxCountExceededException {
+        return T_TEST.tTest(sampleStats1, sampleStats2, alpha);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.TTest#tTest(org.apache.commons.math3.stat.descriptive.StatisticalSummary, org.apache.commons.math3.stat.descriptive.StatisticalSummary)
+     */
+    public static double tTest(final StatisticalSummary sampleStats1,
+                               final StatisticalSummary sampleStats2)
+        throws NullArgumentException, NumberIsTooSmallException,
+        MaxCountExceededException {
+        return T_TEST.tTest(sampleStats1, sampleStats2);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.ChiSquareTest#chiSquare(double[], long[])
+     */
+    public static double chiSquare(final double[] expected, final long[] observed)
+        throws NotPositiveException, NotStrictlyPositiveException,
+        DimensionMismatchException {
+        return CHI_SQUARE_TEST.chiSquare(expected, observed);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.ChiSquareTest#chiSquare(long[][])
+     */
+    public static double chiSquare(final long[][] counts)
+        throws NullArgumentException, NotPositiveException,
+        DimensionMismatchException {
+        return CHI_SQUARE_TEST.chiSquare(counts);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.ChiSquareTest#chiSquareTest(double[], long[], double)
+     */
+    public static boolean chiSquareTest(final double[] expected, final long[] observed,
+                                        final double alpha)
+        throws NotPositiveException, NotStrictlyPositiveException,
+        DimensionMismatchException, OutOfRangeException, MaxCountExceededException {
+        return CHI_SQUARE_TEST.chiSquareTest(expected, observed, alpha);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.ChiSquareTest#chiSquareTest(double[], long[])
+     */
+    public static double chiSquareTest(final double[] expected, final long[] observed)
+        throws NotPositiveException, NotStrictlyPositiveException,
+        DimensionMismatchException, MaxCountExceededException {
+        return CHI_SQUARE_TEST.chiSquareTest(expected, observed);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.ChiSquareTest#chiSquareTest(long[][], double)
+     */
+    public static boolean chiSquareTest(final long[][] counts, final double alpha)
+        throws NullArgumentException, DimensionMismatchException,
+        NotPositiveException, OutOfRangeException, MaxCountExceededException {
+        return CHI_SQUARE_TEST.chiSquareTest(counts, alpha);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.ChiSquareTest#chiSquareTest(long[][])
+     */
+    public static double chiSquareTest(final long[][] counts)
+        throws NullArgumentException, DimensionMismatchException,
+        NotPositiveException, MaxCountExceededException {
+        return CHI_SQUARE_TEST.chiSquareTest(counts);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.ChiSquareTest#chiSquareDataSetsComparison(long[], long[])
+     *
+     * @since 1.2
+     */
+    public static double chiSquareDataSetsComparison(final long[] observed1,
+                                                     final long[] observed2)
+        throws DimensionMismatchException, NotPositiveException, ZeroException {
+        return CHI_SQUARE_TEST.chiSquareDataSetsComparison(observed1, observed2);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.ChiSquareTest#chiSquareTestDataSetsComparison(long[], long[])
+     *
+     * @since 1.2
+     */
+    public static double chiSquareTestDataSetsComparison(final long[] observed1,
+                                                         final long[] observed2)
+        throws DimensionMismatchException, NotPositiveException, ZeroException,
+        MaxCountExceededException {
+        return CHI_SQUARE_TEST.chiSquareTestDataSetsComparison(observed1, observed2);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.ChiSquareTest#chiSquareTestDataSetsComparison(long[], long[], double)
+     *
+     * @since 1.2
+     */
+    public static boolean chiSquareTestDataSetsComparison(final long[] observed1,
+                                                          final long[] observed2,
+                                                          final double alpha)
+        throws DimensionMismatchException, NotPositiveException,
+        ZeroException, OutOfRangeException, MaxCountExceededException {
+        return CHI_SQUARE_TEST.chiSquareTestDataSetsComparison(observed1, observed2, alpha);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.OneWayAnova#anovaFValue(Collection)
+     *
+     * @since 1.2
+     */
+    public static double oneWayAnovaFValue(final Collection<double[]> categoryData)
+        throws NullArgumentException, DimensionMismatchException {
+        return ONE_WAY_ANANOVA.anovaFValue(categoryData);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.OneWayAnova#anovaPValue(Collection)
+     *
+     * @since 1.2
+     */
+    public static double oneWayAnovaPValue(final Collection<double[]> categoryData)
+        throws NullArgumentException, DimensionMismatchException,
+        ConvergenceException, MaxCountExceededException {
+        return ONE_WAY_ANANOVA.anovaPValue(categoryData);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.OneWayAnova#anovaTest(Collection,double)
+     *
+     * @since 1.2
+     */
+    public static boolean oneWayAnovaTest(final Collection<double[]> categoryData,
+                                          final double alpha)
+        throws NullArgumentException, DimensionMismatchException,
+        OutOfRangeException, ConvergenceException, MaxCountExceededException {
+        return ONE_WAY_ANANOVA.anovaTest(categoryData, alpha);
+    }
+
+     /**
+     * @see org.apache.commons.math3.stat.inference.GTest#g(double[], long[])
+     * @since 3.1
+     */
+    public static double g(final double[] expected, final long[] observed)
+        throws NotPositiveException, NotStrictlyPositiveException,
+        DimensionMismatchException {
+        return G_TEST.g(expected, observed);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.GTest#gTest( double[],  long[] )
+     * @since 3.1
+     */
+    public static double gTest(final double[] expected, final long[] observed)
+        throws NotPositiveException, NotStrictlyPositiveException,
+        DimensionMismatchException, MaxCountExceededException {
+        return G_TEST.gTest(expected, observed);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.GTest#gTestIntrinsic(double[], long[] )
+     * @since 3.1
+     */
+    public static double gTestIntrinsic(final double[] expected, final long[] observed)
+        throws NotPositiveException, NotStrictlyPositiveException,
+        DimensionMismatchException, MaxCountExceededException {
+        return G_TEST.gTestIntrinsic(expected, observed);
+    }
+
+     /**
+     * @see org.apache.commons.math3.stat.inference.GTest#gTest( double[],long[],double)
+     * @since 3.1
+     */
+    public static boolean gTest(final double[] expected, final long[] observed,
+                                final double alpha)
+        throws NotPositiveException, NotStrictlyPositiveException,
+        DimensionMismatchException, OutOfRangeException, MaxCountExceededException {
+        return G_TEST.gTest(expected, observed, alpha);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.GTest#gDataSetsComparison(long[], long[])
+     * @since 3.1
+     */
+    public static double gDataSetsComparison(final long[] observed1,
+                                                  final long[] observed2)
+        throws DimensionMismatchException, NotPositiveException, ZeroException {
+        return G_TEST.gDataSetsComparison(observed1, observed2);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.GTest#rootLogLikelihoodRatio(long, long, long, long)
+     * @since 3.1
+     */
+    public static double rootLogLikelihoodRatio(final long k11, final long k12, final long k21, final long k22)
+        throws DimensionMismatchException, NotPositiveException, ZeroException {
+        return G_TEST.rootLogLikelihoodRatio(k11, k12, k21, k22);
+    }
+
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.GTest#gTestDataSetsComparison(long[], long[])
+     * @since 3.1
+     */
+    public static double gTestDataSetsComparison(final long[] observed1,
+                                                        final long[] observed2)
+        throws DimensionMismatchException, NotPositiveException, ZeroException,
+        MaxCountExceededException {
+        return G_TEST.gTestDataSetsComparison(observed1, observed2);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.GTest#gTestDataSetsComparison(long[],long[],double)
+     * @since 3.1
+     */
+    public static boolean gTestDataSetsComparison(final long[] observed1,
+                                                  final long[] observed2,
+                                                  final double alpha)
+        throws DimensionMismatchException, NotPositiveException,
+        ZeroException, OutOfRangeException, MaxCountExceededException {
+        return G_TEST.gTestDataSetsComparison(observed1, observed2, alpha);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.KolmogorovSmirnovTest#kolmogorovSmirnovStatistic(RealDistribution, double[])
+     * @since 3.3
+     */
+    public static double kolmogorovSmirnovStatistic(RealDistribution dist, double[] data)
+            throws InsufficientDataException, NullArgumentException {
+        return KS_TEST.kolmogorovSmirnovStatistic(dist, data);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.KolmogorovSmirnovTest#kolmogorovSmirnovTest(RealDistribution, double[])
+     * @since 3.3
+     */
+    public static double kolmogorovSmirnovTest(RealDistribution dist, double[] data)
+            throws InsufficientDataException, NullArgumentException {
+        return KS_TEST.kolmogorovSmirnovTest(dist, data);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.KolmogorovSmirnovTest#kolmogorovSmirnovTest(RealDistribution, double[], boolean)
+     * @since 3.3
+     */
+    public static double kolmogorovSmirnovTest(RealDistribution dist, double[] data, boolean strict)
+            throws InsufficientDataException, NullArgumentException {
+        return KS_TEST.kolmogorovSmirnovTest(dist, data, strict);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.KolmogorovSmirnovTest#kolmogorovSmirnovTest(RealDistribution, double[], double)
+     * @since 3.3
+     */
+    public static boolean kolmogorovSmirnovTest(RealDistribution dist, double[] data, double alpha)
+            throws InsufficientDataException, NullArgumentException {
+        return KS_TEST.kolmogorovSmirnovTest(dist, data, alpha);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.KolmogorovSmirnovTest#kolmogorovSmirnovStatistic(double[], double[])
+     * @since 3.3
+     */
+    public static double kolmogorovSmirnovStatistic(double[] x, double[] y)
+            throws InsufficientDataException, NullArgumentException {
+        return KS_TEST.kolmogorovSmirnovStatistic(x, y);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.KolmogorovSmirnovTest#kolmogorovSmirnovTest(double[], double[])
+     * @since 3.3
+     */
+    public static double kolmogorovSmirnovTest(double[] x, double[] y)
+            throws InsufficientDataException, NullArgumentException {
+        return KS_TEST.kolmogorovSmirnovTest(x, y);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.KolmogorovSmirnovTest#kolmogorovSmirnovTest(double[], double[], boolean)
+     * @since 3.3
+     */
+    public static double kolmogorovSmirnovTest(double[] x, double[] y, boolean strict)
+            throws InsufficientDataException, NullArgumentException  {
+        return KS_TEST.kolmogorovSmirnovTest(x, y, strict);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.KolmogorovSmirnovTest#exactP(double, int, int, boolean)
+     * @since 3.3
+     */
+    public static double exactP(double d, int m, int n, boolean strict) {
+        return KS_TEST.exactP(d, n, m, strict);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.KolmogorovSmirnovTest#approximateP(double, int, int)
+     * @since 3.3
+     */
+    public static double approximateP(double d, int n, int m) {
+        return KS_TEST.approximateP(d, n, m);
+    }
+
+    /**
+     * @see org.apache.commons.math3.stat.inference.KolmogorovSmirnovTest#monteCarloP(double, int, int, boolean, int)
+     * @since 3.3
+     */
+    public static double monteCarloP(double d, int n, int m, boolean strict, int iterations) {
+        return KS_TEST.monteCarloP(d, n, m, strict, iterations);
+    }
+
+
+    // CHECKSTYLE: resume JavadocMethodCheck
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/inference/WilcoxonSignedRankTest.java b/src/main/java/org/apache/commons/math3/stat/inference/WilcoxonSignedRankTest.java
new file mode 100644
index 0000000..bd4d7e2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/inference/WilcoxonSignedRankTest.java
@@ -0,0 +1,325 @@
+/*
+ * 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.math3.stat.inference;
+
+import org.apache.commons.math3.distribution.NormalDistribution;
+import org.apache.commons.math3.exception.ConvergenceException;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.stat.ranking.NaNStrategy;
+import org.apache.commons.math3.stat.ranking.NaturalRanking;
+import org.apache.commons.math3.stat.ranking.TiesStrategy;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * An implementation of the Wilcoxon signed-rank test.
+ *
+ */
+public class WilcoxonSignedRankTest {
+
+    /** Ranking algorithm. */
+    private NaturalRanking naturalRanking;
+
+    /**
+     * Create a test instance where NaN's are left in place and ties get
+     * the average of applicable ranks. Use this unless you are very sure
+     * of what you are doing.
+     */
+    public WilcoxonSignedRankTest() {
+        naturalRanking = new NaturalRanking(NaNStrategy.FIXED,
+                TiesStrategy.AVERAGE);
+    }
+
+    /**
+     * Create a test instance using the given strategies for NaN's and ties.
+     * Only use this if you are sure of what you are doing.
+     *
+     * @param nanStrategy
+     *            specifies the strategy that should be used for Double.NaN's
+     * @param tiesStrategy
+     *            specifies the strategy that should be used for ties
+     */
+    public WilcoxonSignedRankTest(final NaNStrategy nanStrategy,
+                                  final TiesStrategy tiesStrategy) {
+        naturalRanking = new NaturalRanking(nanStrategy, tiesStrategy);
+    }
+
+    /**
+     * Ensures that the provided arrays fulfills the assumptions.
+     *
+     * @param x first sample
+     * @param y second sample
+     * @throws NullArgumentException if {@code x} or {@code y} are {@code null}.
+     * @throws NoDataException if {@code x} or {@code y} are zero-length.
+     * @throws DimensionMismatchException if {@code x} and {@code y} do not
+     * have the same length.
+     */
+    private void ensureDataConformance(final double[] x, final double[] y)
+        throws NullArgumentException, NoDataException, DimensionMismatchException {
+
+        if (x == null ||
+            y == null) {
+                throw new NullArgumentException();
+        }
+        if (x.length == 0 ||
+            y.length == 0) {
+            throw new NoDataException();
+        }
+        if (y.length != x.length) {
+            throw new DimensionMismatchException(y.length, x.length);
+        }
+    }
+
+    /**
+     * Calculates y[i] - x[i] for all i
+     *
+     * @param x first sample
+     * @param y second sample
+     * @return z = y - x
+     */
+    private double[] calculateDifferences(final double[] x, final double[] y) {
+
+        final double[] z = new double[x.length];
+
+        for (int i = 0; i < x.length; ++i) {
+            z[i] = y[i] - x[i];
+        }
+
+        return z;
+    }
+
+    /**
+     * Calculates |z[i]| for all i
+     *
+     * @param z sample
+     * @return |z|
+     * @throws NullArgumentException if {@code z} is {@code null}
+     * @throws NoDataException if {@code z} is zero-length.
+     */
+    private double[] calculateAbsoluteDifferences(final double[] z)
+        throws NullArgumentException, NoDataException {
+
+        if (z == null) {
+            throw new NullArgumentException();
+        }
+
+        if (z.length == 0) {
+            throw new NoDataException();
+        }
+
+        final double[] zAbs = new double[z.length];
+
+        for (int i = 0; i < z.length; ++i) {
+            zAbs[i] = FastMath.abs(z[i]);
+        }
+
+        return zAbs;
+    }
+
+    /**
+     * Computes the <a
+     * href="http://en.wikipedia.org/wiki/Wilcoxon_signed-rank_test">
+     * Wilcoxon signed ranked statistic</a> comparing mean for two related
+     * samples or repeated measurements on a single sample.
+     * <p>
+     * This statistic can be used to perform a Wilcoxon signed ranked test
+     * evaluating the null hypothesis that the two related samples or repeated
+     * measurements on a single sample has equal mean.
+     * </p>
+     * <p>
+     * Let X<sub>i</sub> denote the i'th individual of the first sample and
+     * Y<sub>i</sub> the related i'th individual in the second sample. Let
+     * Z<sub>i</sub> = Y<sub>i</sub> - X<sub>i</sub>.
+     * </p>
+     * <p>
+     * <strong>Preconditions</strong>:
+     * <ul>
+     * <li>The differences Z<sub>i</sub> must be independent.</li>
+     * <li>Each Z<sub>i</sub> comes from a continuous population (they must be
+     * identical) and is symmetric about a common median.</li>
+     * <li>The values that X<sub>i</sub> and Y<sub>i</sub> represent are
+     * ordered, so the comparisons greater than, less than, and equal to are
+     * meaningful.</li>
+     * </ul>
+     * </p>
+     *
+     * @param x the first sample
+     * @param y the second sample
+     * @return wilcoxonSignedRank statistic (the larger of W+ and W-)
+     * @throws NullArgumentException if {@code x} or {@code y} are {@code null}.
+     * @throws NoDataException if {@code x} or {@code y} are zero-length.
+     * @throws DimensionMismatchException if {@code x} and {@code y} do not
+     * have the same length.
+     */
+    public double wilcoxonSignedRank(final double[] x, final double[] y)
+        throws NullArgumentException, NoDataException, DimensionMismatchException {
+
+        ensureDataConformance(x, y);
+
+        // throws IllegalArgumentException if x and y are not correctly
+        // specified
+        final double[] z = calculateDifferences(x, y);
+        final double[] zAbs = calculateAbsoluteDifferences(z);
+
+        final double[] ranks = naturalRanking.rank(zAbs);
+
+        double Wplus = 0;
+
+        for (int i = 0; i < z.length; ++i) {
+            if (z[i] > 0) {
+                Wplus += ranks[i];
+            }
+        }
+
+        final int N = x.length;
+        final double Wminus = (((double) (N * (N + 1))) / 2.0) - Wplus;
+
+        return FastMath.max(Wplus, Wminus);
+    }
+
+    /**
+     * Algorithm inspired by
+     * http://www.fon.hum.uva.nl/Service/Statistics/Signed_Rank_Algorihms.html#C
+     * by Rob van Son, Institute of Phonetic Sciences & IFOTT,
+     * University of Amsterdam
+     *
+     * @param Wmax largest Wilcoxon signed rank value
+     * @param N number of subjects (corresponding to x.length)
+     * @return two-sided exact p-value
+     */
+    private double calculateExactPValue(final double Wmax, final int N) {
+
+        // Total number of outcomes (equal to 2^N but a lot faster)
+        final int m = 1 << N;
+
+        int largerRankSums = 0;
+
+        for (int i = 0; i < m; ++i) {
+            int rankSum = 0;
+
+            // Generate all possible rank sums
+            for (int j = 0; j < N; ++j) {
+
+                // (i >> j) & 1 extract i's j-th bit from the right
+                if (((i >> j) & 1) == 1) {
+                    rankSum += j + 1;
+                }
+            }
+
+            if (rankSum >= Wmax) {
+                ++largerRankSums;
+            }
+        }
+
+        /*
+         * largerRankSums / m gives the one-sided p-value, so it's multiplied
+         * with 2 to get the two-sided p-value
+         */
+        return 2 * ((double) largerRankSums) / ((double) m);
+    }
+
+    /**
+     * @param Wmin smallest Wilcoxon signed rank value
+     * @param N number of subjects (corresponding to x.length)
+     * @return two-sided asymptotic p-value
+     */
+    private double calculateAsymptoticPValue(final double Wmin, final int N) {
+
+        final double ES = (double) (N * (N + 1)) / 4.0;
+
+        /* Same as (but saves computations):
+         * final double VarW = ((double) (N * (N + 1) * (2*N + 1))) / 24;
+         */
+        final double VarS = ES * ((double) (2 * N + 1) / 6.0);
+
+        // - 0.5 is a continuity correction
+        final double z = (Wmin - ES - 0.5) / FastMath.sqrt(VarS);
+
+        // No try-catch or advertised exception because args are valid
+        // pass a null rng to avoid unneeded overhead as we will not sample from this distribution
+        final NormalDistribution standardNormal = new NormalDistribution(null, 0, 1);
+
+        return 2*standardNormal.cumulativeProbability(z);
+    }
+
+    /**
+     * Returns the <i>observed significance level</i>, or <a href=
+     * "http://www.cas.lancs.ac.uk/glossary_v1.1/hyptest.html#pvalue">
+     * p-value</a>, associated with a <a
+     * href="http://en.wikipedia.org/wiki/Wilcoxon_signed-rank_test">
+     * Wilcoxon signed ranked statistic</a> comparing mean for two related
+     * samples or repeated measurements on a single sample.
+     * <p>
+     * Let X<sub>i</sub> denote the i'th individual of the first sample and
+     * Y<sub>i</sub> the related i'th individual in the second sample. Let
+     * Z<sub>i</sub> = Y<sub>i</sub> - X<sub>i</sub>.
+     * </p>
+     * <p>
+     * <strong>Preconditions</strong>:
+     * <ul>
+     * <li>The differences Z<sub>i</sub> must be independent.</li>
+     * <li>Each Z<sub>i</sub> comes from a continuous population (they must be
+     * identical) and is symmetric about a common median.</li>
+     * <li>The values that X<sub>i</sub> and Y<sub>i</sub> represent are
+     * ordered, so the comparisons greater than, less than, and equal to are
+     * meaningful.</li>
+     * </ul>
+     * </p>
+     *
+     * @param x the first sample
+     * @param y the second sample
+     * @param exactPValue
+     *            if the exact p-value is wanted (only works for x.length <= 30,
+     *            if true and x.length > 30, this is ignored because
+     *            calculations may take too long)
+     * @return p-value
+     * @throws NullArgumentException if {@code x} or {@code y} are {@code null}.
+     * @throws NoDataException if {@code x} or {@code y} are zero-length.
+     * @throws DimensionMismatchException if {@code x} and {@code y} do not
+     * have the same length.
+     * @throws NumberIsTooLargeException if {@code exactPValue} is {@code true}
+     * and {@code x.length} > 30
+     * @throws ConvergenceException if the p-value can not be computed due to
+     * a convergence error
+     * @throws MaxCountExceededException if the maximum number of iterations
+     * is exceeded
+     */
+    public double wilcoxonSignedRankTest(final double[] x, final double[] y,
+                                         final boolean exactPValue)
+        throws NullArgumentException, NoDataException, DimensionMismatchException,
+        NumberIsTooLargeException, ConvergenceException, MaxCountExceededException {
+
+        ensureDataConformance(x, y);
+
+        final int N = x.length;
+        final double Wmax = wilcoxonSignedRank(x, y);
+
+        if (exactPValue && N > 30) {
+            throw new NumberIsTooLargeException(N, 30, true);
+        }
+
+        if (exactPValue) {
+            return calculateExactPValue(Wmax, N);
+        } else {
+            final double Wmin = ( (double)(N*(N+1)) / 2.0 ) - Wmax;
+            return calculateAsymptoticPValue(Wmin, N);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/inference/package-info.java b/src/main/java/org/apache/commons/math3/stat/inference/package-info.java
new file mode 100644
index 0000000..a36a080
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/inference/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+/**
+ *
+ *      Classes providing hypothesis testing.
+ *
+ */
+package org.apache.commons.math3.stat.inference;
diff --git a/src/main/java/org/apache/commons/math3/stat/interval/AgrestiCoullInterval.java b/src/main/java/org/apache/commons/math3/stat/interval/AgrestiCoullInterval.java
new file mode 100644
index 0000000..b71c718
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/interval/AgrestiCoullInterval.java
@@ -0,0 +1,48 @@
+/*
+ * 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.math3.stat.interval;
+
+import org.apache.commons.math3.distribution.NormalDistribution;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Implements the Agresti-Coull method for creating a binomial proportion confidence interval.
+ *
+ * @see <a
+ *      href="http://en.wikipedia.org/wiki/Binomial_proportion_confidence_interval#Agresti-Coull_Interval">
+ *      Agresti-Coull interval (Wikipedia)</a>
+ * @since 3.3
+ */
+public class AgrestiCoullInterval implements BinomialConfidenceInterval {
+
+    /** {@inheritDoc} */
+    public ConfidenceInterval createInterval(int numberOfTrials, int numberOfSuccesses, double confidenceLevel) {
+        IntervalUtils.checkParameters(numberOfTrials, numberOfSuccesses, confidenceLevel);
+        final double alpha = (1.0 - confidenceLevel) / 2;
+        final NormalDistribution normalDistribution = new NormalDistribution();
+        final double z = normalDistribution.inverseCumulativeProbability(1 - alpha);
+        final double zSquared = FastMath.pow(z, 2);
+        final double modifiedNumberOfTrials = numberOfTrials + zSquared;
+        final double modifiedSuccessesRatio = (1.0 / modifiedNumberOfTrials) * (numberOfSuccesses + 0.5 * zSquared);
+        final double difference = z *
+                                  FastMath.sqrt(1.0 / modifiedNumberOfTrials * modifiedSuccessesRatio *
+                                                (1 - modifiedSuccessesRatio));
+        return new ConfidenceInterval(modifiedSuccessesRatio - difference, modifiedSuccessesRatio + difference,
+                                      confidenceLevel);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/interval/BinomialConfidenceInterval.java b/src/main/java/org/apache/commons/math3/stat/interval/BinomialConfidenceInterval.java
new file mode 100644
index 0000000..532679a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/interval/BinomialConfidenceInterval.java
@@ -0,0 +1,62 @@
+/*
+ * 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.math3.stat.interval;
+
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+
+/**
+ * Interface to generate confidence intervals for a binomial proportion.
+ *
+ * @see <a
+ *      href="http://en.wikipedia.org/wiki/Binomial_proportion_confidence_interval">Binomial
+ *      proportion confidence interval (Wikipedia)</a>
+ * @since 3.3
+ */
+public interface BinomialConfidenceInterval {
+
+    /**
+     * Create a confidence interval for the true probability of success
+     * of an unknown binomial distribution with the given observed number
+     * of trials, successes and confidence level.
+     * <p>
+     * Preconditions:
+     * <ul>
+     * <li>{@code numberOfTrials} must be positive</li>
+     * <li>{@code numberOfSuccesses} may not exceed {@code numberOfTrials}</li>
+     * <li>{@code confidenceLevel} must be strictly between 0 and 1 (exclusive)</li>
+     * </ul>
+     * </p>
+     *
+     * @param numberOfTrials number of trials
+     * @param numberOfSuccesses number of successes
+     * @param confidenceLevel desired probability that the true probability of
+     *        success falls within the returned interval
+     * @return Confidence interval containing the probability of success with
+     *         probability {@code confidenceLevel}
+     * @throws NotStrictlyPositiveException if {@code numberOfTrials <= 0}.
+     * @throws NotPositiveException if {@code numberOfSuccesses < 0}.
+     * @throws NumberIsTooLargeException if {@code numberOfSuccesses > numberOfTrials}.
+     * @throws OutOfRangeException if {@code confidenceLevel} is not in the interval {@code (0, 1)}.
+     */
+    ConfidenceInterval createInterval(int numberOfTrials, int numberOfSuccesses, double confidenceLevel)
+            throws NotStrictlyPositiveException, NotPositiveException,
+                   NumberIsTooLargeException, OutOfRangeException;
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/interval/ClopperPearsonInterval.java b/src/main/java/org/apache/commons/math3/stat/interval/ClopperPearsonInterval.java
new file mode 100644
index 0000000..34a0d57
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/interval/ClopperPearsonInterval.java
@@ -0,0 +1,58 @@
+/*
+ * 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.math3.stat.interval;
+
+import org.apache.commons.math3.distribution.FDistribution;
+
+/**
+ * Implements the Clopper-Pearson method for creating a binomial proportion confidence interval.
+ *
+ * @see <a
+ *      href="http://en.wikipedia.org/wiki/Binomial_proportion_confidence_interval#Clopper-Pearson_interval">
+ *      Clopper-Pearson interval (Wikipedia)</a>
+ * @since 3.3
+ */
+public class ClopperPearsonInterval implements BinomialConfidenceInterval {
+
+    /** {@inheritDoc} */
+    public ConfidenceInterval createInterval(int numberOfTrials, int numberOfSuccesses,
+                                             double confidenceLevel) {
+        IntervalUtils.checkParameters(numberOfTrials, numberOfSuccesses, confidenceLevel);
+        double lowerBound = 0;
+        double upperBound = 0;
+        final double alpha = (1.0 - confidenceLevel) / 2.0;
+
+        final FDistribution distributionLowerBound = new FDistribution(2 * (numberOfTrials - numberOfSuccesses + 1),
+                                                                       2 * numberOfSuccesses);
+        final double fValueLowerBound = distributionLowerBound.inverseCumulativeProbability(1 - alpha);
+        if (numberOfSuccesses > 0) {
+            lowerBound = numberOfSuccesses /
+                         (numberOfSuccesses + (numberOfTrials - numberOfSuccesses + 1) * fValueLowerBound);
+        }
+
+        final FDistribution distributionUpperBound = new FDistribution(2 * (numberOfSuccesses + 1),
+                                                                       2 * (numberOfTrials - numberOfSuccesses));
+        final double fValueUpperBound = distributionUpperBound.inverseCumulativeProbability(1 - alpha);
+        if (numberOfSuccesses > 0) {
+            upperBound = (numberOfSuccesses + 1) * fValueUpperBound /
+                         (numberOfTrials - numberOfSuccesses + (numberOfSuccesses + 1) * fValueUpperBound);
+        }
+
+        return new ConfidenceInterval(lowerBound, upperBound, confidenceLevel);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/interval/ConfidenceInterval.java b/src/main/java/org/apache/commons/math3/stat/interval/ConfidenceInterval.java
new file mode 100644
index 0000000..0147c8c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/interval/ConfidenceInterval.java
@@ -0,0 +1,109 @@
+/*
+ * 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.math3.stat.interval;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Represents an interval estimate of a population parameter.
+ *
+ * @since 3.3
+ */
+public class ConfidenceInterval {
+
+    /** Lower endpoint of the interval */
+    private double lowerBound;
+
+    /** Upper endpoint of the interval */
+    private double upperBound;
+
+    /**
+     * The asserted probability that the interval contains the population
+     * parameter
+     */
+    private double confidenceLevel;
+
+    /**
+     * Create a confidence interval with the given bounds and confidence level.
+     * <p>
+     * Preconditions:
+     * <ul>
+     * <li>{@code lower} must be strictly less than {@code upper}</li>
+     * <li>{@code confidenceLevel} must be strictly between 0 and 1 (exclusive)</li>
+     * </ul>
+     * </p>
+     *
+     * @param lowerBound lower endpoint of the interval
+     * @param upperBound upper endpoint of the interval
+     * @param confidenceLevel coverage probability
+     * @throws MathIllegalArgumentException if the preconditions are not met
+     */
+    public ConfidenceInterval(double lowerBound, double upperBound, double confidenceLevel) {
+        checkParameters(lowerBound, upperBound, confidenceLevel);
+        this.lowerBound = lowerBound;
+        this.upperBound = upperBound;
+        this.confidenceLevel = confidenceLevel;
+    }
+
+    /**
+     * @return the lower endpoint of the interval
+     */
+    public double getLowerBound() {
+        return lowerBound;
+    }
+
+    /**
+     * @return the upper endpoint of the interval
+     */
+    public double getUpperBound() {
+        return upperBound;
+    }
+
+    /**
+     * @return the asserted probability that the interval contains the
+     *         population parameter
+     */
+    public double getConfidenceLevel() {
+        return confidenceLevel;
+    }
+
+    /**
+     * @return String representation of the confidence interval
+     */
+    @Override
+    public String toString() {
+        return "[" + lowerBound + ";" + upperBound + "] (confidence level:" + confidenceLevel + ")";
+    }
+
+    /**
+     * Verifies that (lower, upper) is a valid non-empty interval and confidence
+     * is strictly between 0 and 1.
+     *
+     * @param lower lower endpoint
+     * @param upper upper endpoint
+     * @param confidence confidence level
+     */
+    private void checkParameters(double lower, double upper, double confidence) {
+        if (lower >= upper) {
+            throw new MathIllegalArgumentException(LocalizedFormats.LOWER_BOUND_NOT_BELOW_UPPER_BOUND, lower, upper);
+        }
+        if (confidence <= 0 || confidence >= 1) {
+            throw new MathIllegalArgumentException(LocalizedFormats.OUT_OF_BOUNDS_CONFIDENCE_LEVEL, confidence, 0, 1);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/interval/IntervalUtils.java b/src/main/java/org/apache/commons/math3/stat/interval/IntervalUtils.java
new file mode 100644
index 0000000..0613c99
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/interval/IntervalUtils.java
@@ -0,0 +1,174 @@
+/*
+ * 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.math3.stat.interval;
+
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Factory methods to generate confidence intervals for a binomial proportion.
+ * The supported methods are:
+ * <ul>
+ * <li>Agresti-Coull interval</li>
+ * <li>Clopper-Pearson method (exact method)</li>
+ * <li>Normal approximation (based on central limit theorem)</li>
+ * <li>Wilson score interval</li>
+ * </ul>
+ *
+ * @since 3.3
+ */
+public final class IntervalUtils {
+
+    /** Singleton Agresti-Coull instance. */
+    private static final BinomialConfidenceInterval AGRESTI_COULL = new AgrestiCoullInterval();
+
+    /** Singleton Clopper-Pearson instance. */
+    private static final BinomialConfidenceInterval CLOPPER_PEARSON = new ClopperPearsonInterval();
+
+    /** Singleton NormalApproximation instance. */
+    private static final BinomialConfidenceInterval NORMAL_APPROXIMATION = new NormalApproximationInterval();
+
+    /** Singleton Wilson score instance. */
+    private static final BinomialConfidenceInterval WILSON_SCORE = new WilsonScoreInterval();
+
+    /**
+     * Prevent instantiation.
+     */
+    private IntervalUtils() {
+    }
+
+    /**
+     * Create an Agresti-Coull binomial confidence interval for the true
+     * probability of success of an unknown binomial distribution with the given
+     * observed number of trials, successes and confidence level.
+     *
+     * @param numberOfTrials number of trials
+     * @param numberOfSuccesses number of successes
+     * @param confidenceLevel desired probability that the true probability of
+     *        success falls within the returned interval
+     * @return Confidence interval containing the probability of success with
+     *         probability {@code confidenceLevel}
+     * @throws NotStrictlyPositiveException if {@code numberOfTrials <= 0}.
+     * @throws NotPositiveException if {@code numberOfSuccesses < 0}.
+     * @throws NumberIsTooLargeException if {@code numberOfSuccesses > numberOfTrials}.
+     * @throws OutOfRangeException if {@code confidenceLevel} is not in the interval {@code (0, 1)}.
+     */
+    public static ConfidenceInterval getAgrestiCoullInterval(int numberOfTrials, int numberOfSuccesses,
+                                                             double confidenceLevel) {
+        return AGRESTI_COULL.createInterval(numberOfTrials, numberOfSuccesses, confidenceLevel);
+    }
+
+    /**
+     * Create a Clopper-Pearson binomial confidence interval for the true
+     * probability of success of an unknown binomial distribution with the given
+     * observed number of trials, successes and confidence level.
+     * <p>
+     * Preconditions:
+     * <ul>
+     * <li>{@code numberOfTrials} must be positive</li>
+     * <li>{@code numberOfSuccesses} may not exceed {@code numberOfTrials}</li>
+     * <li>{@code confidenceLevel} must be strictly between 0 and 1 (exclusive)</li>
+     * </ul>
+     * </p>
+     *
+     * @param numberOfTrials number of trials
+     * @param numberOfSuccesses number of successes
+     * @param confidenceLevel desired probability that the true probability of
+     *        success falls within the returned interval
+     * @return Confidence interval containing the probability of success with
+     *         probability {@code confidenceLevel}
+     * @throws NotStrictlyPositiveException if {@code numberOfTrials <= 0}.
+     * @throws NotPositiveException if {@code numberOfSuccesses < 0}.
+     * @throws NumberIsTooLargeException if {@code numberOfSuccesses > numberOfTrials}.
+     * @throws OutOfRangeException if {@code confidenceLevel} is not in the interval {@code (0, 1)}.
+     */
+    public static ConfidenceInterval getClopperPearsonInterval(int numberOfTrials, int numberOfSuccesses,
+                                                               double confidenceLevel) {
+        return CLOPPER_PEARSON.createInterval(numberOfTrials, numberOfSuccesses, confidenceLevel);
+    }
+
+    /**
+     * Create a binomial confidence interval for the true probability of success
+     * of an unknown binomial distribution with the given observed number of
+     * trials, successes and confidence level using the Normal approximation to
+     * the binomial distribution.
+     *
+     * @param numberOfTrials number of trials
+     * @param numberOfSuccesses number of successes
+     * @param confidenceLevel desired probability that the true probability of
+     *        success falls within the interval
+     * @return Confidence interval containing the probability of success with
+     *         probability {@code confidenceLevel}
+     */
+    public static ConfidenceInterval getNormalApproximationInterval(int numberOfTrials, int numberOfSuccesses,
+                                                                    double confidenceLevel) {
+        return NORMAL_APPROXIMATION.createInterval(numberOfTrials, numberOfSuccesses, confidenceLevel);
+    }
+
+    /**
+     * Create a Wilson score binomial confidence interval for the true
+     * probability of success of an unknown binomial distribution with the given
+     * observed number of trials, successes and confidence level.
+     *
+     * @param numberOfTrials number of trials
+     * @param numberOfSuccesses number of successes
+     * @param confidenceLevel desired probability that the true probability of
+     *        success falls within the returned interval
+     * @return Confidence interval containing the probability of success with
+     *         probability {@code confidenceLevel}
+     * @throws NotStrictlyPositiveException if {@code numberOfTrials <= 0}.
+     * @throws NotPositiveException if {@code numberOfSuccesses < 0}.
+     * @throws NumberIsTooLargeException if {@code numberOfSuccesses > numberOfTrials}.
+     * @throws OutOfRangeException if {@code confidenceLevel} is not in the interval {@code (0, 1)}.
+     */
+    public static ConfidenceInterval getWilsonScoreInterval(int numberOfTrials, int numberOfSuccesses,
+                                                            double confidenceLevel) {
+        return WILSON_SCORE.createInterval(numberOfTrials, numberOfSuccesses, confidenceLevel);
+    }
+
+    /**
+     * Verifies that parameters satisfy preconditions.
+     *
+     * @param numberOfTrials number of trials (must be positive)
+     * @param numberOfSuccesses number of successes (must not exceed numberOfTrials)
+     * @param confidenceLevel confidence level (must be strictly between 0 and 1)
+     * @throws NotStrictlyPositiveException if {@code numberOfTrials <= 0}.
+     * @throws NotPositiveException if {@code numberOfSuccesses < 0}.
+     * @throws NumberIsTooLargeException if {@code numberOfSuccesses > numberOfTrials}.
+     * @throws OutOfRangeException if {@code confidenceLevel} is not in the interval {@code (0, 1)}.
+     */
+    static void checkParameters(int numberOfTrials, int numberOfSuccesses, double confidenceLevel) {
+        if (numberOfTrials <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.NUMBER_OF_TRIALS, numberOfTrials);
+        }
+        if (numberOfSuccesses < 0) {
+            throw new NotPositiveException(LocalizedFormats.NEGATIVE_NUMBER_OF_SUCCESSES, numberOfSuccesses);
+        }
+        if (numberOfSuccesses > numberOfTrials) {
+            throw new NumberIsTooLargeException(LocalizedFormats.NUMBER_OF_SUCCESS_LARGER_THAN_POPULATION_SIZE,
+                                                numberOfSuccesses, numberOfTrials, true);
+        }
+        if (confidenceLevel <= 0 || confidenceLevel >= 1) {
+            throw new OutOfRangeException(LocalizedFormats.OUT_OF_BOUNDS_CONFIDENCE_LEVEL,
+                                          confidenceLevel, 0, 1);
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/interval/NormalApproximationInterval.java b/src/main/java/org/apache/commons/math3/stat/interval/NormalApproximationInterval.java
new file mode 100644
index 0000000..25a213a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/interval/NormalApproximationInterval.java
@@ -0,0 +1,44 @@
+/*
+ * 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.math3.stat.interval;
+
+import org.apache.commons.math3.distribution.NormalDistribution;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Implements the normal approximation method for creating a binomial proportion confidence interval.
+ *
+ * @see <a
+ *      href="http://en.wikipedia.org/wiki/Binomial_proportion_confidence_interval#Normal_approximation_interval">
+ *      Normal approximation interval (Wikipedia)</a>
+ * @since 3.3
+ */
+public class NormalApproximationInterval implements BinomialConfidenceInterval {
+
+    /** {@inheritDoc} */
+    public ConfidenceInterval createInterval(int numberOfTrials, int numberOfSuccesses,
+                                             double confidenceLevel) {
+        IntervalUtils.checkParameters(numberOfTrials, numberOfSuccesses, confidenceLevel);
+        final double mean = (double) numberOfSuccesses / (double) numberOfTrials;
+        final double alpha = (1.0 - confidenceLevel) / 2;
+        final NormalDistribution normalDistribution = new NormalDistribution();
+        final double difference = normalDistribution.inverseCumulativeProbability(1 - alpha) *
+                                  FastMath.sqrt(1.0 / numberOfTrials * mean * (1 - mean));
+        return new ConfidenceInterval(mean - difference, mean + difference, confidenceLevel);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/interval/WilsonScoreInterval.java b/src/main/java/org/apache/commons/math3/stat/interval/WilsonScoreInterval.java
new file mode 100644
index 0000000..9932835
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/interval/WilsonScoreInterval.java
@@ -0,0 +1,52 @@
+/*
+ * 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.math3.stat.interval;
+
+import org.apache.commons.math3.distribution.NormalDistribution;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Implements the Wilson score method for creating a binomial proportion confidence interval.
+ *
+ * @see <a
+ *      href="http://en.wikipedia.org/wiki/Binomial_proportion_confidence_interval#Wilson_score_interval">
+ *      Wilson score interval (Wikipedia)</a>
+ * @since 3.3
+ */
+public class WilsonScoreInterval implements BinomialConfidenceInterval {
+
+    /** {@inheritDoc} */
+    public ConfidenceInterval createInterval(int numberOfTrials, int numberOfSuccesses, double confidenceLevel) {
+        IntervalUtils.checkParameters(numberOfTrials, numberOfSuccesses, confidenceLevel);
+        final double alpha = (1.0 - confidenceLevel) / 2;
+        final NormalDistribution normalDistribution = new NormalDistribution();
+        final double z = normalDistribution.inverseCumulativeProbability(1 - alpha);
+        final double zSquared = FastMath.pow(z, 2);
+        final double mean = (double) numberOfSuccesses / (double) numberOfTrials;
+
+        final double factor = 1.0 / (1 + (1.0 / numberOfTrials) * zSquared);
+        final double modifiedSuccessRatio = mean + (1.0 / (2 * numberOfTrials)) * zSquared;
+        final double difference = z *
+                                  FastMath.sqrt(1.0 / numberOfTrials * mean * (1 - mean) +
+                                                (1.0 / (4 * FastMath.pow(numberOfTrials, 2)) * zSquared));
+
+        final double lowerBound = factor * (modifiedSuccessRatio - difference);
+        final double upperBound = factor * (modifiedSuccessRatio + difference);
+        return new ConfidenceInterval(lowerBound, upperBound, confidenceLevel);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/interval/package-info.java b/src/main/java/org/apache/commons/math3/stat/interval/package-info.java
new file mode 100644
index 0000000..34f43d9
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/interval/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+/**
+ *
+ *      Classes providing binomial proportion confidence interval construction.
+ *
+ */
+package org.apache.commons.math3.stat.interval;
diff --git a/src/main/java/org/apache/commons/math3/stat/package-info.java b/src/main/java/org/apache/commons/math3/stat/package-info.java
new file mode 100644
index 0000000..1df9698
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+/** Data storage, manipulation and summary routines. */
+package org.apache.commons.math3.stat;
diff --git a/src/main/java/org/apache/commons/math3/stat/ranking/NaNStrategy.java b/src/main/java/org/apache/commons/math3/stat/ranking/NaNStrategy.java
new file mode 100644
index 0000000..1a916ef
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/ranking/NaNStrategy.java
@@ -0,0 +1,55 @@
+/*
+ * 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.math3.stat.ranking;
+
+/**
+ * Strategies for handling NaN values in rank transformations.
+ * <ul>
+ * <li>MINIMAL - NaNs are treated as minimal in the ordering, equivalent to
+ * (that is, tied with) <code>Double.NEGATIVE_INFINITY</code>.</li>
+ * <li>MAXIMAL - NaNs are treated as maximal in the ordering, equivalent to
+ * <code>Double.POSITIVE_INFINITY</code></li>
+ * <li>REMOVED - NaNs are removed before the rank transform is applied</li>
+ * <li>FIXED - NaNs are left "in place," that is the rank transformation is
+ * applied to the other elements in the input array, but the NaN elements
+ * are returned unchanged.</li>
+ * <li>FAILED - If any NaN is encountered in the input array, an appropriate
+ * exception is thrown</li>
+ * </ul>
+ *
+ * @since 2.0
+ */
+public enum NaNStrategy {
+
+    /** NaNs are considered minimal in the ordering */
+    MINIMAL,
+
+    /** NaNs are considered maximal in the ordering */
+    MAXIMAL,
+
+    /** NaNs are removed before computing ranks */
+    REMOVED,
+
+    /** NaNs are left in place */
+    FIXED,
+
+    /** NaNs result in an exception
+     * @since 3.1
+     */
+    FAILED
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/ranking/NaturalRanking.java b/src/main/java/org/apache/commons/math3/stat/ranking/NaturalRanking.java
new file mode 100644
index 0000000..6107c46
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/ranking/NaturalRanking.java
@@ -0,0 +1,474 @@
+/*
+ * 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.math3.stat.ranking;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.commons.math3.exception.MathInternalError;
+import org.apache.commons.math3.exception.NotANumberException;
+import org.apache.commons.math3.random.RandomDataGenerator;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.util.FastMath;
+
+
+/**
+ * <p> Ranking based on the natural ordering on doubles.</p>
+ * <p>NaNs are treated according to the configured {@link NaNStrategy} and ties
+ * are handled using the selected {@link TiesStrategy}.
+ * Configuration settings are supplied in optional constructor arguments.
+ * Defaults are {@link NaNStrategy#FAILED} and {@link TiesStrategy#AVERAGE},
+ * respectively. When using {@link TiesStrategy#RANDOM}, a
+ * {@link RandomGenerator} may be supplied as a constructor argument.</p>
+ * <p>Examples:
+ * <table border="1" cellpadding="3">
+ * <tr><th colspan="3">
+ * Input data: (20, 17, 30, 42.3, 17, 50, Double.NaN, Double.NEGATIVE_INFINITY, 17)
+ * </th></tr>
+ * <tr><th>NaNStrategy</th><th>TiesStrategy</th>
+ * <th><code>rank(data)</code></th>
+ * <tr>
+ * <td>default (NaNs maximal)</td>
+ * <td>default (ties averaged)</td>
+ * <td>(5, 3, 6, 7, 3, 8, 9, 1, 3)</td></tr>
+ * <tr>
+ * <td>default (NaNs maximal)</td>
+ * <td>MINIMUM</td>
+ * <td>(5, 2, 6, 7, 2, 8, 9, 1, 2)</td></tr>
+ * <tr>
+ * <td>MINIMAL</td>
+ * <td>default (ties averaged)</td>
+ * <td>(6, 4, 7, 8, 4, 9, 1.5, 1.5, 4)</td></tr>
+ * <tr>
+ * <td>REMOVED</td>
+ * <td>SEQUENTIAL</td>
+ * <td>(5, 2, 6, 7, 3, 8, 1, 4)</td></tr>
+ * <tr>
+ * <td>MINIMAL</td>
+ * <td>MAXIMUM</td>
+ * <td>(6, 5, 7, 8, 5, 9, 2, 2, 5)</td></tr></table></p>
+ *
+ * @since 2.0
+ */
+public class NaturalRanking implements RankingAlgorithm {
+
+    /** default NaN strategy */
+    public static final NaNStrategy DEFAULT_NAN_STRATEGY = NaNStrategy.FAILED;
+
+    /** default ties strategy */
+    public static final TiesStrategy DEFAULT_TIES_STRATEGY = TiesStrategy.AVERAGE;
+
+    /** NaN strategy - defaults to NaNs maximal */
+    private final NaNStrategy nanStrategy;
+
+    /** Ties strategy - defaults to ties averaged */
+    private final TiesStrategy tiesStrategy;
+
+    /** Source of random data - used only when ties strategy is RANDOM */
+    private final RandomDataGenerator randomData;
+
+    /**
+     * Create a NaturalRanking with default strategies for handling ties and NaNs.
+     */
+    public NaturalRanking() {
+        super();
+        tiesStrategy = DEFAULT_TIES_STRATEGY;
+        nanStrategy = DEFAULT_NAN_STRATEGY;
+        randomData = null;
+    }
+
+    /**
+     * Create a NaturalRanking with the given TiesStrategy.
+     *
+     * @param tiesStrategy the TiesStrategy to use
+     */
+    public NaturalRanking(TiesStrategy tiesStrategy) {
+        super();
+        this.tiesStrategy = tiesStrategy;
+        nanStrategy = DEFAULT_NAN_STRATEGY;
+        randomData = new RandomDataGenerator();
+    }
+
+    /**
+     * Create a NaturalRanking with the given NaNStrategy.
+     *
+     * @param nanStrategy the NaNStrategy to use
+     */
+    public NaturalRanking(NaNStrategy nanStrategy) {
+        super();
+        this.nanStrategy = nanStrategy;
+        tiesStrategy = DEFAULT_TIES_STRATEGY;
+        randomData = null;
+    }
+
+    /**
+     * Create a NaturalRanking with the given NaNStrategy and TiesStrategy.
+     *
+     * @param nanStrategy NaNStrategy to use
+     * @param tiesStrategy TiesStrategy to use
+     */
+    public NaturalRanking(NaNStrategy nanStrategy, TiesStrategy tiesStrategy) {
+        super();
+        this.nanStrategy = nanStrategy;
+        this.tiesStrategy = tiesStrategy;
+        randomData = new RandomDataGenerator();
+    }
+
+    /**
+     * Create a NaturalRanking with TiesStrategy.RANDOM and the given
+     * RandomGenerator as the source of random data.
+     *
+     * @param randomGenerator source of random data
+     */
+    public NaturalRanking(RandomGenerator randomGenerator) {
+        super();
+        this.tiesStrategy = TiesStrategy.RANDOM;
+        nanStrategy = DEFAULT_NAN_STRATEGY;
+        randomData = new RandomDataGenerator(randomGenerator);
+    }
+
+
+    /**
+     * Create a NaturalRanking with the given NaNStrategy, TiesStrategy.RANDOM
+     * and the given source of random data.
+     *
+     * @param nanStrategy NaNStrategy to use
+     * @param randomGenerator source of random data
+     */
+    public NaturalRanking(NaNStrategy nanStrategy,
+            RandomGenerator randomGenerator) {
+        super();
+        this.nanStrategy = nanStrategy;
+        this.tiesStrategy = TiesStrategy.RANDOM;
+        randomData = new RandomDataGenerator(randomGenerator);
+    }
+
+    /**
+     * Return the NaNStrategy
+     *
+     * @return returns the NaNStrategy
+     */
+    public NaNStrategy getNanStrategy() {
+        return nanStrategy;
+    }
+
+    /**
+     * Return the TiesStrategy
+     *
+     * @return the TiesStrategy
+     */
+    public TiesStrategy getTiesStrategy() {
+        return tiesStrategy;
+    }
+
+    /**
+     * Rank <code>data</code> using the natural ordering on Doubles, with
+     * NaN values handled according to <code>nanStrategy</code> and ties
+     * resolved using <code>tiesStrategy.</code>
+     *
+     * @param data array to be ranked
+     * @return array of ranks
+     * @throws NotANumberException if the selected {@link NaNStrategy} is {@code FAILED}
+     * and a {@link Double#NaN} is encountered in the input data
+     */
+    public double[] rank(double[] data) {
+
+        // Array recording initial positions of data to be ranked
+        IntDoublePair[] ranks = new IntDoublePair[data.length];
+        for (int i = 0; i < data.length; i++) {
+            ranks[i] = new IntDoublePair(data[i], i);
+        }
+
+        // Recode, remove or record positions of NaNs
+        List<Integer> nanPositions = null;
+        switch (nanStrategy) {
+            case MAXIMAL: // Replace NaNs with +INFs
+                recodeNaNs(ranks, Double.POSITIVE_INFINITY);
+                break;
+            case MINIMAL: // Replace NaNs with -INFs
+                recodeNaNs(ranks, Double.NEGATIVE_INFINITY);
+                break;
+            case REMOVED: // Drop NaNs from data
+                ranks = removeNaNs(ranks);
+                break;
+            case FIXED:   // Record positions of NaNs
+                nanPositions = getNanPositions(ranks);
+                break;
+            case FAILED:
+                nanPositions = getNanPositions(ranks);
+                if (nanPositions.size() > 0) {
+                    throw new NotANumberException();
+                }
+                break;
+            default: // this should not happen unless NaNStrategy enum is changed
+                throw new MathInternalError();
+        }
+
+        // Sort the IntDoublePairs
+        Arrays.sort(ranks);
+
+        // Walk the sorted array, filling output array using sorted positions,
+        // resolving ties as we go
+        double[] out = new double[ranks.length];
+        int pos = 1;  // position in sorted array
+        out[ranks[0].getPosition()] = pos;
+        List<Integer> tiesTrace = new ArrayList<Integer>();
+        tiesTrace.add(ranks[0].getPosition());
+        for (int i = 1; i < ranks.length; i++) {
+            if (Double.compare(ranks[i].getValue(), ranks[i - 1].getValue()) > 0) {
+                // tie sequence has ended (or had length 1)
+                pos = i + 1;
+                if (tiesTrace.size() > 1) {  // if seq is nontrivial, resolve
+                    resolveTie(out, tiesTrace);
+                }
+                tiesTrace = new ArrayList<Integer>();
+                tiesTrace.add(ranks[i].getPosition());
+            } else {
+                // tie sequence continues
+                tiesTrace.add(ranks[i].getPosition());
+            }
+            out[ranks[i].getPosition()] = pos;
+        }
+        if (tiesTrace.size() > 1) {  // handle tie sequence at end
+            resolveTie(out, tiesTrace);
+        }
+        if (nanStrategy == NaNStrategy.FIXED) {
+            restoreNaNs(out, nanPositions);
+        }
+        return out;
+    }
+
+    /**
+     * Returns an array that is a copy of the input array with IntDoublePairs
+     * having NaN values removed.
+     *
+     * @param ranks input array
+     * @return array with NaN-valued entries removed
+     */
+    private IntDoublePair[] removeNaNs(IntDoublePair[] ranks) {
+        if (!containsNaNs(ranks)) {
+            return ranks;
+        }
+        IntDoublePair[] outRanks = new IntDoublePair[ranks.length];
+        int j = 0;
+        for (int i = 0; i < ranks.length; i++) {
+            if (Double.isNaN(ranks[i].getValue())) {
+                // drop, but adjust original ranks of later elements
+                for (int k = i + 1; k < ranks.length; k++) {
+                    ranks[k] = new IntDoublePair(
+                            ranks[k].getValue(), ranks[k].getPosition() - 1);
+                }
+            } else {
+                outRanks[j] = new IntDoublePair(
+                        ranks[i].getValue(), ranks[i].getPosition());
+                j++;
+            }
+        }
+        IntDoublePair[] returnRanks = new IntDoublePair[j];
+        System.arraycopy(outRanks, 0, returnRanks, 0, j);
+        return returnRanks;
+    }
+
+    /**
+     * Recodes NaN values to the given value.
+     *
+     * @param ranks array to recode
+     * @param value the value to replace NaNs with
+     */
+    private void recodeNaNs(IntDoublePair[] ranks, double value) {
+        for (int i = 0; i < ranks.length; i++) {
+            if (Double.isNaN(ranks[i].getValue())) {
+                ranks[i] = new IntDoublePair(
+                        value, ranks[i].getPosition());
+            }
+        }
+    }
+
+    /**
+     * Checks for presence of NaNs in <code>ranks.</code>
+     *
+     * @param ranks array to be searched for NaNs
+     * @return true iff ranks contains one or more NaNs
+     */
+    private boolean containsNaNs(IntDoublePair[] ranks) {
+        for (int i = 0; i < ranks.length; i++) {
+            if (Double.isNaN(ranks[i].getValue())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Resolve a sequence of ties, using the configured {@link TiesStrategy}.
+     * The input <code>ranks</code> array is expected to take the same value
+     * for all indices in <code>tiesTrace</code>.  The common value is recoded
+     * according to the tiesStrategy. For example, if ranks = <5,8,2,6,2,7,1,2>,
+     * tiesTrace = <2,4,7> and tiesStrategy is MINIMUM, ranks will be unchanged.
+     * The same array and trace with tiesStrategy AVERAGE will come out
+     * <5,8,3,6,3,7,1,3>.
+     *
+     * @param ranks array of ranks
+     * @param tiesTrace list of indices where <code>ranks</code> is constant
+     * -- that is, for any i and j in TiesTrace, <code> ranks[i] == ranks[j]
+     * </code>
+     */
+    private void resolveTie(double[] ranks, List<Integer> tiesTrace) {
+
+        // constant value of ranks over tiesTrace
+        final double c = ranks[tiesTrace.get(0)];
+
+        // length of sequence of tied ranks
+        final int length = tiesTrace.size();
+
+        switch (tiesStrategy) {
+            case  AVERAGE:  // Replace ranks with average
+                fill(ranks, tiesTrace, (2 * c + length - 1) / 2d);
+                break;
+            case MAXIMUM:   // Replace ranks with maximum values
+                fill(ranks, tiesTrace, c + length - 1);
+                break;
+            case MINIMUM:   // Replace ties with minimum
+                fill(ranks, tiesTrace, c);
+                break;
+            case RANDOM:    // Fill with random integral values in [c, c + length - 1]
+                Iterator<Integer> iterator = tiesTrace.iterator();
+                long f = FastMath.round(c);
+                while (iterator.hasNext()) {
+                    // No advertised exception because args are guaranteed valid
+                    ranks[iterator.next()] =
+                        randomData.nextLong(f, f + length - 1);
+                }
+                break;
+            case SEQUENTIAL:  // Fill sequentially from c to c + length - 1
+                // walk and fill
+                iterator = tiesTrace.iterator();
+                f = FastMath.round(c);
+                int i = 0;
+                while (iterator.hasNext()) {
+                    ranks[iterator.next()] = f + i++;
+                }
+                break;
+            default: // this should not happen unless TiesStrategy enum is changed
+                throw new MathInternalError();
+        }
+    }
+
+    /**
+     * Sets<code>data[i] = value</code> for each i in <code>tiesTrace.</code>
+     *
+     * @param data array to modify
+     * @param tiesTrace list of index values to set
+     * @param value value to set
+     */
+    private void fill(double[] data, List<Integer> tiesTrace, double value) {
+        Iterator<Integer> iterator = tiesTrace.iterator();
+        while (iterator.hasNext()) {
+            data[iterator.next()] = value;
+        }
+    }
+
+    /**
+     * Set <code>ranks[i] = Double.NaN</code> for each i in <code>nanPositions.</code>
+     *
+     * @param ranks array to modify
+     * @param nanPositions list of index values to set to <code>Double.NaN</code>
+     */
+    private void restoreNaNs(double[] ranks, List<Integer> nanPositions) {
+        if (nanPositions.size() == 0) {
+            return;
+        }
+        Iterator<Integer> iterator = nanPositions.iterator();
+        while (iterator.hasNext()) {
+            ranks[iterator.next().intValue()] = Double.NaN;
+        }
+
+    }
+
+    /**
+     * Returns a list of indexes where <code>ranks</code> is <code>NaN.</code>
+     *
+     * @param ranks array to search for <code>NaNs</code>
+     * @return list of indexes i such that <code>ranks[i] = NaN</code>
+     */
+    private List<Integer> getNanPositions(IntDoublePair[] ranks) {
+        ArrayList<Integer> out = new ArrayList<Integer>();
+        for (int i = 0; i < ranks.length; i++) {
+            if (Double.isNaN(ranks[i].getValue())) {
+                out.add(Integer.valueOf(i));
+            }
+        }
+        return out;
+    }
+
+    /**
+     * Represents the position of a double value in an ordering.
+     * Comparable interface is implemented so Arrays.sort can be used
+     * to sort an array of IntDoublePairs by value.  Note that the
+     * implicitly defined natural ordering is NOT consistent with equals.
+     */
+    private static class IntDoublePair implements Comparable<IntDoublePair>  {
+
+        /** Value of the pair */
+        private final double value;
+
+        /** Original position of the pair */
+        private final int position;
+
+        /**
+         * Construct an IntDoublePair with the given value and position.
+         * @param value the value of the pair
+         * @param position the original position
+         */
+        IntDoublePair(double value, int position) {
+            this.value = value;
+            this.position = position;
+        }
+
+        /**
+         * Compare this IntDoublePair to another pair.
+         * Only the <strong>values</strong> are compared.
+         *
+         * @param other the other pair to compare this to
+         * @return result of <code>Double.compare(value, other.value)</code>
+         */
+        public int compareTo(IntDoublePair other) {
+            return Double.compare(value, other.value);
+        }
+
+        // N.B. equals() and hashCode() are not implemented; see MATH-610 for discussion.
+
+        /**
+         * Returns the value of the pair.
+         * @return value
+         */
+        public double getValue() {
+            return value;
+        }
+
+        /**
+         * Returns the original position of the pair.
+         * @return position
+         */
+        public int getPosition() {
+            return position;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/ranking/RankingAlgorithm.java b/src/main/java/org/apache/commons/math3/stat/ranking/RankingAlgorithm.java
new file mode 100644
index 0000000..188bc99
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/ranking/RankingAlgorithm.java
@@ -0,0 +1,40 @@
+/*
+ * 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.math3.stat.ranking;
+
+/**
+ * Interface representing a rank transformation.
+ *
+ * @since 2.0
+ */
+public interface RankingAlgorithm {
+    /**
+     * <p>Performs a rank transformation on the input data, returning an array
+     * of ranks.</p>
+     *
+     * <p>Ranks should be 1-based - that is, the smallest value
+     * returned in an array of ranks should be greater than or equal to one,
+     * rather than 0. Ranks should in general take integer values, though
+     * implementations may return averages or other floating point values
+     * to resolve ties in the input data.</p>
+     *
+     * @param data array of data to be ranked
+     * @return an array of ranks corresponding to the elements of the input array
+     */
+    double[] rank (double[] data);
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/ranking/TiesStrategy.java b/src/main/java/org/apache/commons/math3/stat/ranking/TiesStrategy.java
new file mode 100644
index 0000000..08ab99a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/ranking/TiesStrategy.java
@@ -0,0 +1,54 @@
+/*
+ * 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.math3.stat.ranking;
+
+/**
+ * Strategies for handling tied values in rank transformations.
+ * <ul>
+ * <li>SEQUENTIAL - Ties are assigned ranks in order of occurrence in the original array,
+ * for example (1,3,4,3) is ranked as (1,2,4,3)</li>
+ * <li>MINIMUM - Tied values are assigned the minimum applicable rank, or the rank
+ * of the first occurrence. For example, (1,3,4,3) is ranked as (1,2,4,2)</li>
+ * <li>MAXIMUM - Tied values are assigned the maximum applicable rank, or the rank
+ * of the last occurrence. For example, (1,3,4,3) is ranked as (1,3,4,3)</li>
+ * <li>AVERAGE - Tied values are assigned the average of the applicable ranks.
+ * For example, (1,3,4,3) is ranked as (1,2.5,4,2.5)</li>
+ * <li>RANDOM - Tied values are assigned a random integer rank from among the
+ * applicable values. The assigned rank will always be an integer, (inclusively)
+ * between the values returned by the MINIMUM and MAXIMUM strategies.</li>
+ * </ul>
+ *
+ * @since 2.0
+ */
+public enum TiesStrategy {
+
+    /** Ties assigned sequential ranks in order of occurrence */
+    SEQUENTIAL,
+
+    /** Ties get the minimum applicable rank */
+    MINIMUM,
+
+    /** Ties get the maximum applicable rank */
+    MAXIMUM,
+
+    /** Ties get the average of applicable ranks */
+    AVERAGE,
+
+    /** Ties get a random integral value from among applicable ranks */
+    RANDOM
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/ranking/package-info.java b/src/main/java/org/apache/commons/math3/stat/ranking/package-info.java
new file mode 100644
index 0000000..b86575b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/ranking/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+/**
+ *
+ *      Classes providing rank transformations.
+ *
+ */
+package org.apache.commons.math3.stat.ranking;
diff --git a/src/main/java/org/apache/commons/math3/stat/regression/AbstractMultipleLinearRegression.java b/src/main/java/org/apache/commons/math3/stat/regression/AbstractMultipleLinearRegression.java
new file mode 100644
index 0000000..9b7c40a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/regression/AbstractMultipleLinearRegression.java
@@ -0,0 +1,383 @@
+/*
+ * 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.math3.stat.regression;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.InsufficientDataException;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.linear.NonSquareMatrixException;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.linear.Array2DRowRealMatrix;
+import org.apache.commons.math3.linear.RealVector;
+import org.apache.commons.math3.linear.ArrayRealVector;
+import org.apache.commons.math3.stat.descriptive.moment.Variance;
+import org.apache.commons.math3.util.FastMath;
+
+/**
+ * Abstract base class for implementations of MultipleLinearRegression.
+ * @since 2.0
+ */
+public abstract class AbstractMultipleLinearRegression implements
+        MultipleLinearRegression {
+
+    /** X sample data. */
+    private RealMatrix xMatrix;
+
+    /** Y sample data. */
+    private RealVector yVector;
+
+    /** Whether or not the regression model includes an intercept.  True means no intercept. */
+    private boolean noIntercept = false;
+
+    /**
+     * @return the X sample data.
+     */
+    protected RealMatrix getX() {
+        return xMatrix;
+    }
+
+    /**
+     * @return the Y sample data.
+     */
+    protected RealVector getY() {
+        return yVector;
+    }
+
+    /**
+     * @return true if the model has no intercept term; false otherwise
+     * @since 2.2
+     */
+    public boolean isNoIntercept() {
+        return noIntercept;
+    }
+
+    /**
+     * @param noIntercept true means the model is to be estimated without an intercept term
+     * @since 2.2
+     */
+    public void setNoIntercept(boolean noIntercept) {
+        this.noIntercept = noIntercept;
+    }
+
+    /**
+     * <p>Loads model x and y sample data from a flat input array, overriding any previous sample.
+     * </p>
+     * <p>Assumes that rows are concatenated with y values first in each row.  For example, an input
+     * <code>data</code> array containing the sequence of values (1, 2, 3, 4, 5, 6, 7, 8, 9) with
+     * <code>nobs = 3</code> and <code>nvars = 2</code> creates a regression dataset with two
+     * independent variables, as below:
+     * <pre>
+     *   y   x[0]  x[1]
+     *   --------------
+     *   1     2     3
+     *   4     5     6
+     *   7     8     9
+     * </pre>
+     * </p>
+     * <p>Note that there is no need to add an initial unitary column (column of 1's) when
+     * specifying a model including an intercept term.  If {@link #isNoIntercept()} is <code>true</code>,
+     * the X matrix will be created without an initial column of "1"s; otherwise this column will
+     * be added.
+     * </p>
+     * <p>Throws IllegalArgumentException if any of the following preconditions fail:
+     * <ul><li><code>data</code> cannot be null</li>
+     * <li><code>data.length = nobs * (nvars + 1)</li>
+     * <li><code>nobs > nvars</code></li></ul>
+     * </p>
+     *
+     * @param data input data array
+     * @param nobs number of observations (rows)
+     * @param nvars number of independent variables (columns, not counting y)
+     * @throws NullArgumentException if the data array is null
+     * @throws DimensionMismatchException if the length of the data array is not equal
+     * to <code>nobs * (nvars + 1)</code>
+     * @throws InsufficientDataException if <code>nobs</code> is less than
+     * <code>nvars + 1</code>
+     */
+    public void newSampleData(double[] data, int nobs, int nvars) {
+        if (data == null) {
+            throw new NullArgumentException();
+        }
+        if (data.length != nobs * (nvars + 1)) {
+            throw new DimensionMismatchException(data.length, nobs * (nvars + 1));
+        }
+        if (nobs <= nvars) {
+            throw new InsufficientDataException(LocalizedFormats.INSUFFICIENT_OBSERVED_POINTS_IN_SAMPLE, nobs, nvars + 1);
+        }
+        double[] y = new double[nobs];
+        final int cols = noIntercept ? nvars: nvars + 1;
+        double[][] x = new double[nobs][cols];
+        int pointer = 0;
+        for (int i = 0; i < nobs; i++) {
+            y[i] = data[pointer++];
+            if (!noIntercept) {
+                x[i][0] = 1.0d;
+            }
+            for (int j = noIntercept ? 0 : 1; j < cols; j++) {
+                x[i][j] = data[pointer++];
+            }
+        }
+        this.xMatrix = new Array2DRowRealMatrix(x);
+        this.yVector = new ArrayRealVector(y);
+    }
+
+    /**
+     * Loads new y sample data, overriding any previous data.
+     *
+     * @param y the array representing the y sample
+     * @throws NullArgumentException if y is null
+     * @throws NoDataException if y is empty
+     */
+    protected void newYSampleData(double[] y) {
+        if (y == null) {
+            throw new NullArgumentException();
+        }
+        if (y.length == 0) {
+            throw new NoDataException();
+        }
+        this.yVector = new ArrayRealVector(y);
+    }
+
+    /**
+     * <p>Loads new x sample data, overriding any previous data.
+     * </p>
+     * The input <code>x</code> array should have one row for each sample
+     * observation, with columns corresponding to independent variables.
+     * For example, if <pre>
+     * <code> x = new double[][] {{1, 2}, {3, 4}, {5, 6}} </code></pre>
+     * then <code>setXSampleData(x) </code> results in a model with two independent
+     * variables and 3 observations:
+     * <pre>
+     *   x[0]  x[1]
+     *   ----------
+     *     1    2
+     *     3    4
+     *     5    6
+     * </pre>
+     * </p>
+     * <p>Note that there is no need to add an initial unitary column (column of 1's) when
+     * specifying a model including an intercept term.
+     * </p>
+     * @param x the rectangular array representing the x sample
+     * @throws NullArgumentException if x is null
+     * @throws NoDataException if x is empty
+     * @throws DimensionMismatchException if x is not rectangular
+     */
+    protected void newXSampleData(double[][] x) {
+        if (x == null) {
+            throw new NullArgumentException();
+        }
+        if (x.length == 0) {
+            throw new NoDataException();
+        }
+        if (noIntercept) {
+            this.xMatrix = new Array2DRowRealMatrix(x, true);
+        } else { // Augment design matrix with initial unitary column
+            final int nVars = x[0].length;
+            final double[][] xAug = new double[x.length][nVars + 1];
+            for (int i = 0; i < x.length; i++) {
+                if (x[i].length != nVars) {
+                    throw new DimensionMismatchException(x[i].length, nVars);
+                }
+                xAug[i][0] = 1.0d;
+                System.arraycopy(x[i], 0, xAug[i], 1, nVars);
+            }
+            this.xMatrix = new Array2DRowRealMatrix(xAug, false);
+        }
+    }
+
+    /**
+     * Validates sample data.  Checks that
+     * <ul><li>Neither x nor y is null or empty;</li>
+     * <li>The length (i.e. number of rows) of x equals the length of y</li>
+     * <li>x has at least one more row than it has columns (i.e. there is
+     * sufficient data to estimate regression coefficients for each of the
+     * columns in x plus an intercept.</li>
+     * </ul>
+     *
+     * @param x the [n,k] array representing the x data
+     * @param y the [n,1] array representing the y data
+     * @throws NullArgumentException if {@code x} or {@code y} is null
+     * @throws DimensionMismatchException if {@code x} and {@code y} do not
+     * have the same length
+     * @throws NoDataException if {@code x} or {@code y} are zero-length
+     * @throws MathIllegalArgumentException if the number of rows of {@code x}
+     * is not larger than the number of columns + 1
+     */
+    protected void validateSampleData(double[][] x, double[] y) throws MathIllegalArgumentException {
+        if ((x == null) || (y == null)) {
+            throw new NullArgumentException();
+        }
+        if (x.length != y.length) {
+            throw new DimensionMismatchException(y.length, x.length);
+        }
+        if (x.length == 0) {  // Must be no y data either
+            throw new NoDataException();
+        }
+        if (x[0].length + 1 > x.length) {
+            throw new MathIllegalArgumentException(
+                    LocalizedFormats.NOT_ENOUGH_DATA_FOR_NUMBER_OF_PREDICTORS,
+                    x.length, x[0].length);
+        }
+    }
+
+    /**
+     * Validates that the x data and covariance matrix have the same
+     * number of rows and that the covariance matrix is square.
+     *
+     * @param x the [n,k] array representing the x sample
+     * @param covariance the [n,n] array representing the covariance matrix
+     * @throws DimensionMismatchException if the number of rows in x is not equal
+     * to the number of rows in covariance
+     * @throws NonSquareMatrixException if the covariance matrix is not square
+     */
+    protected void validateCovarianceData(double[][] x, double[][] covariance) {
+        if (x.length != covariance.length) {
+            throw new DimensionMismatchException(x.length, covariance.length);
+        }
+        if (covariance.length > 0 && covariance.length != covariance[0].length) {
+            throw new NonSquareMatrixException(covariance.length, covariance[0].length);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public double[] estimateRegressionParameters() {
+        RealVector b = calculateBeta();
+        return b.toArray();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public double[] estimateResiduals() {
+        RealVector b = calculateBeta();
+        RealVector e = yVector.subtract(xMatrix.operate(b));
+        return e.toArray();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public double[][] estimateRegressionParametersVariance() {
+        return calculateBetaVariance().getData();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public double[] estimateRegressionParametersStandardErrors() {
+        double[][] betaVariance = estimateRegressionParametersVariance();
+        double sigma = calculateErrorVariance();
+        int length = betaVariance[0].length;
+        double[] result = new double[length];
+        for (int i = 0; i < length; i++) {
+            result[i] = FastMath.sqrt(sigma * betaVariance[i][i]);
+        }
+        return result;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public double estimateRegressandVariance() {
+        return calculateYVariance();
+    }
+
+    /**
+     * Estimates the variance of the error.
+     *
+     * @return estimate of the error variance
+     * @since 2.2
+     */
+    public double estimateErrorVariance() {
+        return calculateErrorVariance();
+
+    }
+
+    /**
+     * Estimates the standard error of the regression.
+     *
+     * @return regression standard error
+     * @since 2.2
+     */
+    public double estimateRegressionStandardError() {
+        return FastMath.sqrt(estimateErrorVariance());
+    }
+
+    /**
+     * Calculates the beta of multiple linear regression in matrix notation.
+     *
+     * @return beta
+     */
+    protected abstract RealVector calculateBeta();
+
+    /**
+     * Calculates the beta variance of multiple linear regression in matrix
+     * notation.
+     *
+     * @return beta variance
+     */
+    protected abstract RealMatrix calculateBetaVariance();
+
+
+    /**
+     * Calculates the variance of the y values.
+     *
+     * @return Y variance
+     */
+    protected double calculateYVariance() {
+        return new Variance().evaluate(yVector.toArray());
+    }
+
+    /**
+     * <p>Calculates the variance of the error term.</p>
+     * Uses the formula <pre>
+     * var(u) = u &middot; u / (n - k)
+     * </pre>
+     * where n and k are the row and column dimensions of the design
+     * matrix X.
+     *
+     * @return error variance estimate
+     * @since 2.2
+     */
+    protected double calculateErrorVariance() {
+        RealVector residuals = calculateResiduals();
+        return residuals.dotProduct(residuals) /
+               (xMatrix.getRowDimension() - xMatrix.getColumnDimension());
+    }
+
+    /**
+     * Calculates the residuals of multiple linear regression in matrix
+     * notation.
+     *
+     * <pre>
+     * u = y - X * b
+     * </pre>
+     *
+     * @return The residuals [n,1] matrix
+     */
+    protected RealVector calculateResiduals() {
+        RealVector b = calculateBeta();
+        return yVector.subtract(xMatrix.operate(b));
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/regression/GLSMultipleLinearRegression.java b/src/main/java/org/apache/commons/math3/stat/regression/GLSMultipleLinearRegression.java
new file mode 100644
index 0000000..1644e6d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/regression/GLSMultipleLinearRegression.java
@@ -0,0 +1,135 @@
+/*
+ * 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.math3.stat.regression;
+
+import org.apache.commons.math3.linear.LUDecomposition;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.linear.Array2DRowRealMatrix;
+import org.apache.commons.math3.linear.RealVector;
+
+/**
+ * The GLS implementation of multiple linear regression.
+ *
+ * GLS assumes a general covariance matrix Omega of the error
+ * <pre>
+ * u ~ N(0, Omega)
+ * </pre>
+ *
+ * Estimated by GLS,
+ * <pre>
+ * b=(X' Omega^-1 X)^-1X'Omega^-1 y
+ * </pre>
+ * whose variance is
+ * <pre>
+ * Var(b)=(X' Omega^-1 X)^-1
+ * </pre>
+ * @since 2.0
+ */
+public class GLSMultipleLinearRegression extends AbstractMultipleLinearRegression {
+
+    /** Covariance matrix. */
+    private RealMatrix Omega;
+
+    /** Inverse of covariance matrix. */
+    private RealMatrix OmegaInverse;
+
+    /** Replace sample data, overriding any previous sample.
+     * @param y y values of the sample
+     * @param x x values of the sample
+     * @param covariance array representing the covariance matrix
+     */
+    public void newSampleData(double[] y, double[][] x, double[][] covariance) {
+        validateSampleData(x, y);
+        newYSampleData(y);
+        newXSampleData(x);
+        validateCovarianceData(x, covariance);
+        newCovarianceData(covariance);
+    }
+
+    /**
+     * Add the covariance data.
+     *
+     * @param omega the [n,n] array representing the covariance
+     */
+    protected void newCovarianceData(double[][] omega){
+        this.Omega = new Array2DRowRealMatrix(omega);
+        this.OmegaInverse = null;
+    }
+
+    /**
+     * Get the inverse of the covariance.
+     * <p>The inverse of the covariance matrix is lazily evaluated and cached.</p>
+     * @return inverse of the covariance
+     */
+    protected RealMatrix getOmegaInverse() {
+        if (OmegaInverse == null) {
+            OmegaInverse = new LUDecomposition(Omega).getSolver().getInverse();
+        }
+        return OmegaInverse;
+    }
+
+    /**
+     * Calculates beta by GLS.
+     * <pre>
+     *  b=(X' Omega^-1 X)^-1X'Omega^-1 y
+     * </pre>
+     * @return beta
+     */
+    @Override
+    protected RealVector calculateBeta() {
+        RealMatrix OI = getOmegaInverse();
+        RealMatrix XT = getX().transpose();
+        RealMatrix XTOIX = XT.multiply(OI).multiply(getX());
+        RealMatrix inverse = new LUDecomposition(XTOIX).getSolver().getInverse();
+        return inverse.multiply(XT).multiply(OI).operate(getY());
+    }
+
+    /**
+     * Calculates the variance on the beta.
+     * <pre>
+     *  Var(b)=(X' Omega^-1 X)^-1
+     * </pre>
+     * @return The beta variance matrix
+     */
+    @Override
+    protected RealMatrix calculateBetaVariance() {
+        RealMatrix OI = getOmegaInverse();
+        RealMatrix XTOIX = getX().transpose().multiply(OI).multiply(getX());
+        return new LUDecomposition(XTOIX).getSolver().getInverse();
+    }
+
+
+    /**
+     * Calculates the estimated variance of the error term using the formula
+     * <pre>
+     *  Var(u) = Tr(u' Omega^-1 u)/(n-k)
+     * </pre>
+     * where n and k are the row and column dimensions of the design
+     * matrix X.
+     *
+     * @return error variance
+     * @since 2.2
+     */
+    @Override
+    protected double calculateErrorVariance() {
+        RealVector residuals = calculateResiduals();
+        double t = residuals.dotProduct(getOmegaInverse().operate(residuals));
+        return t / (getX().getRowDimension() - getX().getColumnDimension());
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/regression/MillerUpdatingRegression.java b/src/main/java/org/apache/commons/math3/stat/regression/MillerUpdatingRegression.java
new file mode 100644
index 0000000..3fe3c03
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/regression/MillerUpdatingRegression.java
@@ -0,0 +1,1101 @@
+/*
+ * 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.math3.stat.regression;
+
+import java.util.Arrays;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.Precision;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * This class is a concrete implementation of the {@link UpdatingMultipleLinearRegression} interface.
+ *
+ * <p>The algorithm is described in: <pre>
+ * Algorithm AS 274: Least Squares Routines to Supplement Those of Gentleman
+ * Author(s): Alan J. Miller
+ * Source: Journal of the Royal Statistical Society.
+ * Series C (Applied Statistics), Vol. 41, No. 2
+ * (1992), pp. 458-478
+ * Published by: Blackwell Publishing for the Royal Statistical Society
+ * Stable URL: http://www.jstor.org/stable/2347583 </pre></p>
+ *
+ * <p>This method for multiple regression forms the solution to the OLS problem
+ * by updating the QR decomposition as described by Gentleman.</p>
+ *
+ * @since 3.0
+ */
+public class MillerUpdatingRegression implements UpdatingMultipleLinearRegression {
+
+    /** number of variables in regression */
+    private final int nvars;
+    /** diagonals of cross products matrix */
+    private final double[] d;
+    /** the elements of the R`Y */
+    private final double[] rhs;
+    /** the off diagonal portion of the R matrix */
+    private final double[] r;
+    /** the tolerance for each of the variables */
+    private final double[] tol;
+    /** residual sum of squares for all nested regressions */
+    private final double[] rss;
+    /** order of the regressors */
+    private final int[] vorder;
+    /** scratch space for tolerance calc */
+    private final double[] work_tolset;
+    /** number of observations entered */
+    private long nobs = 0;
+    /** sum of squared errors of largest regression */
+    private double sserr = 0.0;
+    /** has rss been called? */
+    private boolean rss_set = false;
+    /** has the tolerance setting method been called */
+    private boolean tol_set = false;
+    /** flags for variables with linear dependency problems */
+    private final boolean[] lindep;
+    /** singular x values */
+    private final double[] x_sing;
+    /** workspace for singularity method */
+    private final double[] work_sing;
+    /** summation of Y variable */
+    private double sumy = 0.0;
+    /** summation of squared Y values */
+    private double sumsqy = 0.0;
+    /** boolean flag whether a regression constant is added */
+    private boolean hasIntercept;
+    /** zero tolerance */
+    private final double epsilon;
+    /**
+     *  Set the default constructor to private access
+     *  to prevent inadvertent instantiation
+     */
+    @SuppressWarnings("unused")
+    private MillerUpdatingRegression() {
+        this(-1, false, Double.NaN);
+    }
+
+    /**
+     * This is the augmented constructor for the MillerUpdatingRegression class.
+     *
+     * @param numberOfVariables number of regressors to expect, not including constant
+     * @param includeConstant include a constant automatically
+     * @param errorTolerance  zero tolerance, how machine zero is determined
+     * @throws ModelSpecificationException if {@code numberOfVariables is less than 1}
+     */
+    public MillerUpdatingRegression(int numberOfVariables, boolean includeConstant, double errorTolerance)
+    throws ModelSpecificationException {
+        if (numberOfVariables < 1) {
+            throw new ModelSpecificationException(LocalizedFormats.NO_REGRESSORS);
+        }
+        if (includeConstant) {
+            this.nvars = numberOfVariables + 1;
+        } else {
+            this.nvars = numberOfVariables;
+        }
+        this.hasIntercept = includeConstant;
+        this.nobs = 0;
+        this.d = new double[this.nvars];
+        this.rhs = new double[this.nvars];
+        this.r = new double[this.nvars * (this.nvars - 1) / 2];
+        this.tol = new double[this.nvars];
+        this.rss = new double[this.nvars];
+        this.vorder = new int[this.nvars];
+        this.x_sing = new double[this.nvars];
+        this.work_sing = new double[this.nvars];
+        this.work_tolset = new double[this.nvars];
+        this.lindep = new boolean[this.nvars];
+        for (int i = 0; i < this.nvars; i++) {
+            vorder[i] = i;
+        }
+        if (errorTolerance > 0) {
+            this.epsilon = errorTolerance;
+        } else {
+            this.epsilon = -errorTolerance;
+        }
+    }
+
+    /**
+     * Primary constructor for the MillerUpdatingRegression.
+     *
+     * @param numberOfVariables maximum number of potential regressors
+     * @param includeConstant include a constant automatically
+     * @throws ModelSpecificationException if {@code numberOfVariables is less than 1}
+     */
+    public MillerUpdatingRegression(int numberOfVariables, boolean includeConstant)
+    throws ModelSpecificationException {
+        this(numberOfVariables, includeConstant, Precision.EPSILON);
+    }
+
+    /**
+     * A getter method which determines whether a constant is included.
+     * @return true regression has an intercept, false no intercept
+     */
+    public boolean hasIntercept() {
+        return this.hasIntercept;
+    }
+
+    /**
+     * Gets the number of observations added to the regression model.
+     * @return number of observations
+     */
+    public long getN() {
+        return this.nobs;
+    }
+
+    /**
+     * Adds an observation to the regression model.
+     * @param x the array with regressor values
+     * @param y  the value of dependent variable given these regressors
+     * @exception ModelSpecificationException if the length of {@code x} does not equal
+     * the number of independent variables in the model
+     */
+    public void addObservation(final double[] x, final double y)
+    throws ModelSpecificationException {
+
+        if ((!this.hasIntercept && x.length != nvars) ||
+               (this.hasIntercept && x.length + 1 != nvars)) {
+            throw new ModelSpecificationException(LocalizedFormats.INVALID_REGRESSION_OBSERVATION,
+                    x.length, nvars);
+        }
+        if (!this.hasIntercept) {
+            include(MathArrays.copyOf(x, x.length), 1.0, y);
+        } else {
+            final double[] tmp = new double[x.length + 1];
+            System.arraycopy(x, 0, tmp, 1, x.length);
+            tmp[0] = 1.0;
+            include(tmp, 1.0, y);
+        }
+        ++nobs;
+
+    }
+
+    /**
+     * Adds multiple observations to the model.
+     * @param x observations on the regressors
+     * @param y observations on the regressand
+     * @throws ModelSpecificationException if {@code x} is not rectangular, does not match
+     * the length of {@code y} or does not contain sufficient data to estimate the model
+     */
+    public void addObservations(double[][] x, double[] y) throws ModelSpecificationException {
+        if ((x == null) || (y == null) || (x.length != y.length)) {
+            throw new ModelSpecificationException(
+                  LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE,
+                  (x == null) ? 0 : x.length,
+                  (y == null) ? 0 : y.length);
+        }
+        if (x.length == 0) {  // Must be no y data either
+            throw new ModelSpecificationException(
+                    LocalizedFormats.NO_DATA);
+        }
+        if (x[0].length + 1 > x.length) {
+            throw new ModelSpecificationException(
+                  LocalizedFormats.NOT_ENOUGH_DATA_FOR_NUMBER_OF_PREDICTORS,
+                  x.length, x[0].length);
+        }
+        for (int i = 0; i < x.length; i++) {
+            addObservation(x[i], y[i]);
+        }
+    }
+
+    /**
+     * The include method is where the QR decomposition occurs. This statement forms all
+     * intermediate data which will be used for all derivative measures.
+     * According to the miller paper, note that in the original implementation the x vector
+     * is overwritten. In this implementation, the include method is passed a copy of the
+     * original data vector so that there is no contamination of the data. Additionally,
+     * this method differs slightly from Gentleman's method, in that the assumption is
+     * of dense design matrices, there is some advantage in using the original gentleman algorithm
+     * on sparse matrices.
+     *
+     * @param x observations on the regressors
+     * @param wi weight of the this observation (-1,1)
+     * @param yi observation on the regressand
+     */
+    private void include(final double[] x, final double wi, final double yi) {
+        int nextr = 0;
+        double w = wi;
+        double y = yi;
+        double xi;
+        double di;
+        double wxi;
+        double dpi;
+        double xk;
+        double _w;
+        this.rss_set = false;
+        sumy = smartAdd(yi, sumy);
+        sumsqy = smartAdd(sumsqy, yi * yi);
+        for (int i = 0; i < x.length; i++) {
+            if (w == 0.0) {
+                return;
+            }
+            xi = x[i];
+
+            if (xi == 0.0) {
+                nextr += nvars - i - 1;
+                continue;
+            }
+            di = d[i];
+            wxi = w * xi;
+            _w = w;
+            if (di != 0.0) {
+                dpi = smartAdd(di, wxi * xi);
+                final double tmp = wxi * xi / di;
+                if (FastMath.abs(tmp) > Precision.EPSILON) {
+                    w = (di * w) / dpi;
+                }
+            } else {
+                dpi = wxi * xi;
+                w = 0.0;
+            }
+            d[i] = dpi;
+            for (int k = i + 1; k < nvars; k++) {
+                xk = x[k];
+                x[k] = smartAdd(xk, -xi * r[nextr]);
+                if (di != 0.0) {
+                    r[nextr] = smartAdd(di * r[nextr], (_w * xi) * xk) / dpi;
+                } else {
+                    r[nextr] = xk / xi;
+                }
+                ++nextr;
+            }
+            xk = y;
+            y = smartAdd(xk, -xi * rhs[i]);
+            if (di != 0.0) {
+                rhs[i] = smartAdd(di * rhs[i], wxi * xk) / dpi;
+            } else {
+                rhs[i] = xk / xi;
+            }
+        }
+        sserr = smartAdd(sserr, w * y * y);
+    }
+
+    /**
+     * Adds to number a and b such that the contamination due to
+     * numerical smallness of one addend does not corrupt the sum.
+     * @param a - an addend
+     * @param b - an addend
+     * @return the sum of the a and b
+     */
+    private double smartAdd(double a, double b) {
+        final double _a = FastMath.abs(a);
+        final double _b = FastMath.abs(b);
+        if (_a > _b) {
+            final double eps = _a * Precision.EPSILON;
+            if (_b > eps) {
+                return a + b;
+            }
+            return a;
+        } else {
+            final double eps = _b * Precision.EPSILON;
+            if (_a > eps) {
+                return a + b;
+            }
+            return b;
+        }
+    }
+
+    /**
+     * As the name suggests,  clear wipes the internals and reorders everything in the
+     * canonical order.
+     */
+    public void clear() {
+        Arrays.fill(this.d, 0.0);
+        Arrays.fill(this.rhs, 0.0);
+        Arrays.fill(this.r, 0.0);
+        Arrays.fill(this.tol, 0.0);
+        Arrays.fill(this.rss, 0.0);
+        Arrays.fill(this.work_tolset, 0.0);
+        Arrays.fill(this.work_sing, 0.0);
+        Arrays.fill(this.x_sing, 0.0);
+        Arrays.fill(this.lindep, false);
+        for (int i = 0; i < nvars; i++) {
+            this.vorder[i] = i;
+        }
+        this.nobs = 0;
+        this.sserr = 0.0;
+        this.sumy = 0.0;
+        this.sumsqy = 0.0;
+        this.rss_set = false;
+        this.tol_set = false;
+    }
+
+    /**
+     * This sets up tolerances for singularity testing.
+     */
+    private void tolset() {
+        int pos;
+        double total;
+        final double eps = this.epsilon;
+        for (int i = 0; i < nvars; i++) {
+            this.work_tolset[i] = FastMath.sqrt(d[i]);
+        }
+        tol[0] = eps * this.work_tolset[0];
+        for (int col = 1; col < nvars; col++) {
+            pos = col - 1;
+            total = work_tolset[col];
+            for (int row = 0; row < col; row++) {
+                total += FastMath.abs(r[pos]) * work_tolset[row];
+                pos += nvars - row - 2;
+            }
+            tol[col] = eps * total;
+        }
+        tol_set = true;
+    }
+
+    /**
+     * The regcf method conducts the linear regression and extracts the
+     * parameter vector. Notice that the algorithm can do subset regression
+     * with no alteration.
+     *
+     * @param nreq how many of the regressors to include (either in canonical
+     * order, or in the current reordered state)
+     * @return an array with the estimated slope coefficients
+     * @throws ModelSpecificationException if {@code nreq} is less than 1
+     * or greater than the number of independent variables
+     */
+    private double[] regcf(int nreq) throws ModelSpecificationException {
+        int nextr;
+        if (nreq < 1) {
+            throw new ModelSpecificationException(LocalizedFormats.NO_REGRESSORS);
+        }
+        if (nreq > this.nvars) {
+            throw new ModelSpecificationException(
+                    LocalizedFormats.TOO_MANY_REGRESSORS, nreq, this.nvars);
+        }
+        if (!this.tol_set) {
+            tolset();
+        }
+        final double[] ret = new double[nreq];
+        boolean rankProblem = false;
+        for (int i = nreq - 1; i > -1; i--) {
+            if (FastMath.sqrt(d[i]) < tol[i]) {
+                ret[i] = 0.0;
+                d[i] = 0.0;
+                rankProblem = true;
+            } else {
+                ret[i] = rhs[i];
+                nextr = i * (nvars + nvars - i - 1) / 2;
+                for (int j = i + 1; j < nreq; j++) {
+                    ret[i] = smartAdd(ret[i], -r[nextr] * ret[j]);
+                    ++nextr;
+                }
+            }
+        }
+        if (rankProblem) {
+            for (int i = 0; i < nreq; i++) {
+                if (this.lindep[i]) {
+                    ret[i] = Double.NaN;
+                }
+            }
+        }
+        return ret;
+    }
+
+    /**
+     * The method which checks for singularities and then eliminates the offending
+     * columns.
+     */
+    private void singcheck() {
+        int pos;
+        for (int i = 0; i < nvars; i++) {
+            work_sing[i] = FastMath.sqrt(d[i]);
+        }
+        for (int col = 0; col < nvars; col++) {
+            // Set elements within R to zero if they are less than tol(col) in
+            // absolute value after being scaled by the square root of their row
+            // multiplier
+            final double temp = tol[col];
+            pos = col - 1;
+            for (int row = 0; row < col - 1; row++) {
+                if (FastMath.abs(r[pos]) * work_sing[row] < temp) {
+                    r[pos] = 0.0;
+                }
+                pos += nvars - row - 2;
+            }
+            // If diagonal element is near zero, set it to zero, set appropriate
+            // element of LINDEP, and use INCLUD to augment the projections in
+            // the lower rows of the orthogonalization.
+            lindep[col] = false;
+            if (work_sing[col] < temp) {
+                lindep[col] = true;
+                if (col < nvars - 1) {
+                    Arrays.fill(x_sing, 0.0);
+                    int _pi = col * (nvars + nvars - col - 1) / 2;
+                    for (int _xi = col + 1; _xi < nvars; _xi++, _pi++) {
+                        x_sing[_xi] = r[_pi];
+                        r[_pi] = 0.0;
+                    }
+                    final double y = rhs[col];
+                    final double weight = d[col];
+                    d[col] = 0.0;
+                    rhs[col] = 0.0;
+                    this.include(x_sing, weight, y);
+                } else {
+                    sserr += d[col] * rhs[col] * rhs[col];
+                }
+            }
+        }
+    }
+
+    /**
+     * Calculates the sum of squared errors for the full regression
+     * and all subsets in the following manner: <pre>
+     * rss[] ={
+     * ResidualSumOfSquares_allNvars,
+     * ResidualSumOfSquares_FirstNvars-1,
+     * ResidualSumOfSquares_FirstNvars-2,
+     * ..., ResidualSumOfSquares_FirstVariable} </pre>
+     */
+    private void ss() {
+        double total = sserr;
+        rss[nvars - 1] = sserr;
+        for (int i = nvars - 1; i > 0; i--) {
+            total += d[i] * rhs[i] * rhs[i];
+            rss[i - 1] = total;
+        }
+        rss_set = true;
+    }
+
+    /**
+     * Calculates the cov matrix assuming only the first nreq variables are
+     * included in the calculation. The returned array contains a symmetric
+     * matrix stored in lower triangular form. The matrix will have
+     * ( nreq + 1 ) * nreq / 2 elements. For illustration <pre>
+     * cov =
+     * {
+     *  cov_00,
+     *  cov_10, cov_11,
+     *  cov_20, cov_21, cov22,
+     *  ...
+     * } </pre>
+     *
+     * @param nreq how many of the regressors to include (either in canonical
+     * order, or in the current reordered state)
+     * @return an array with the variance covariance of the included
+     * regressors in lower triangular form
+     */
+    private double[] cov(int nreq) {
+        if (this.nobs <= nreq) {
+            return null;
+        }
+        double rnk = 0.0;
+        for (int i = 0; i < nreq; i++) {
+            if (!this.lindep[i]) {
+                rnk += 1.0;
+            }
+        }
+        final double var = rss[nreq - 1] / (nobs - rnk);
+        final double[] rinv = new double[nreq * (nreq - 1) / 2];
+        inverse(rinv, nreq);
+        final double[] covmat = new double[nreq * (nreq + 1) / 2];
+        Arrays.fill(covmat, Double.NaN);
+        int pos2;
+        int pos1;
+        int start = 0;
+        double total = 0;
+        for (int row = 0; row < nreq; row++) {
+            pos2 = start;
+            if (!this.lindep[row]) {
+                for (int col = row; col < nreq; col++) {
+                    if (!this.lindep[col]) {
+                        pos1 = start + col - row;
+                        if (row == col) {
+                            total = 1.0 / d[col];
+                        } else {
+                            total = rinv[pos1 - 1] / d[col];
+                        }
+                        for (int k = col + 1; k < nreq; k++) {
+                            if (!this.lindep[k]) {
+                                total += rinv[pos1] * rinv[pos2] / d[k];
+                            }
+                            ++pos1;
+                            ++pos2;
+                        }
+                        covmat[ (col + 1) * col / 2 + row] = total * var;
+                    } else {
+                        pos2 += nreq - col - 1;
+                    }
+                }
+            }
+            start += nreq - row - 1;
+        }
+        return covmat;
+    }
+
+    /**
+     * This internal method calculates the inverse of the upper-triangular portion
+     * of the R matrix.
+     * @param rinv  the storage for the inverse of r
+     * @param nreq how many of the regressors to include (either in canonical
+     * order, or in the current reordered state)
+     */
+    private void inverse(double[] rinv, int nreq) {
+        int pos = nreq * (nreq - 1) / 2 - 1;
+        int pos1 = -1;
+        int pos2 = -1;
+        double total = 0.0;
+        Arrays.fill(rinv, Double.NaN);
+        for (int row = nreq - 1; row > 0; --row) {
+            if (!this.lindep[row]) {
+                final int start = (row - 1) * (nvars + nvars - row) / 2;
+                for (int col = nreq; col > row; --col) {
+                    pos1 = start;
+                    pos2 = pos;
+                    total = 0.0;
+                    for (int k = row; k < col - 1; k++) {
+                        pos2 += nreq - k - 1;
+                        if (!this.lindep[k]) {
+                            total += -r[pos1] * rinv[pos2];
+                        }
+                        ++pos1;
+                    }
+                    rinv[pos] = total - r[pos1];
+                    --pos;
+                }
+            } else {
+                pos -= nreq - row;
+            }
+        }
+    }
+
+    /**
+     * In the original algorithm only the partial correlations of the regressors
+     * is returned to the user. In this implementation, we have <pre>
+     * corr =
+     * {
+     *   corrxx - lower triangular
+     *   corrxy - bottom row of the matrix
+     * }
+     * Replaces subroutines PCORR and COR of:
+     * ALGORITHM AS274  APPL. STATIST. (1992) VOL.41, NO. 2 </pre>
+     *
+     * <p>Calculate partial correlations after the variables in rows
+     * 1, 2, ..., IN have been forced into the regression.
+     * If IN = 1, and the first row of R represents a constant in the
+     * model, then the usual simple correlations are returned.</p>
+     *
+     * <p>If IN = 0, the value returned in array CORMAT for the correlation
+     * of variables Xi & Xj is: <pre>
+     * sum ( Xi.Xj ) / Sqrt ( sum (Xi^2) . sum (Xj^2) )</pre></p>
+     *
+     * <p>On return, array CORMAT contains the upper triangle of the matrix of
+     * partial correlations stored by rows, excluding the 1's on the diagonal.
+     * e.g. if IN = 2, the consecutive elements returned are:
+     * (3,4) (3,5) ... (3,ncol), (4,5) (4,6) ... (4,ncol), etc.
+     * Array YCORR stores the partial correlations with the Y-variable
+     * starting with YCORR(IN+1) = partial correlation with the variable in
+     * position (IN+1). </p>
+     *
+     * @param in how many of the regressors to include (either in canonical
+     * order, or in the current reordered state)
+     * @return an array with the partial correlations of the remainder of
+     * regressors with each other and the regressand, in lower triangular form
+     */
+    public double[] getPartialCorrelations(int in) {
+        final double[] output = new double[(nvars - in + 1) * (nvars - in) / 2];
+        int pos;
+        int pos1;
+        int pos2;
+        final int rms_off = -in;
+        final int wrk_off = -(in + 1);
+        final double[] rms = new double[nvars - in];
+        final double[] work = new double[nvars - in - 1];
+        double sumxx;
+        double sumxy;
+        double sumyy;
+        final int offXX = (nvars - in) * (nvars - in - 1) / 2;
+        if (in < -1 || in >= nvars) {
+            return null;
+        }
+        final int nvm = nvars - 1;
+        final int base_pos = r.length - (nvm - in) * (nvm - in + 1) / 2;
+        if (d[in] > 0.0) {
+            rms[in + rms_off] = 1.0 / FastMath.sqrt(d[in]);
+        }
+        for (int col = in + 1; col < nvars; col++) {
+            pos = base_pos + col - 1 - in;
+            sumxx = d[col];
+            for (int row = in; row < col; row++) {
+                sumxx += d[row] * r[pos] * r[pos];
+                pos += nvars - row - 2;
+            }
+            if (sumxx > 0.0) {
+                rms[col + rms_off] = 1.0 / FastMath.sqrt(sumxx);
+            } else {
+                rms[col + rms_off] = 0.0;
+            }
+        }
+        sumyy = sserr;
+        for (int row = in; row < nvars; row++) {
+            sumyy += d[row] * rhs[row] * rhs[row];
+        }
+        if (sumyy > 0.0) {
+            sumyy = 1.0 / FastMath.sqrt(sumyy);
+        }
+        pos = 0;
+        for (int col1 = in; col1 < nvars; col1++) {
+            sumxy = 0.0;
+            Arrays.fill(work, 0.0);
+            pos1 = base_pos + col1 - in - 1;
+            for (int row = in; row < col1; row++) {
+                pos2 = pos1 + 1;
+                for (int col2 = col1 + 1; col2 < nvars; col2++) {
+                    work[col2 + wrk_off] += d[row] * r[pos1] * r[pos2];
+                    pos2++;
+                }
+                sumxy += d[row] * r[pos1] * rhs[row];
+                pos1 += nvars - row - 2;
+            }
+            pos2 = pos1 + 1;
+            for (int col2 = col1 + 1; col2 < nvars; col2++) {
+                work[col2 + wrk_off] += d[col1] * r[pos2];
+                ++pos2;
+                output[ (col2 - 1 - in) * (col2 - in) / 2 + col1 - in] =
+                        work[col2 + wrk_off] * rms[col1 + rms_off] * rms[col2 + rms_off];
+                ++pos;
+            }
+            sumxy += d[col1] * rhs[col1];
+            output[col1 + rms_off + offXX] = sumxy * rms[col1 + rms_off] * sumyy;
+        }
+
+        return output;
+    }
+
+    /**
+     * ALGORITHM AS274 APPL. STATIST. (1992) VOL.41, NO. 2.
+     * Move variable from position FROM to position TO in an
+     * orthogonal reduction produced by AS75.1.
+     *
+     * @param from initial position
+     * @param to destination
+     */
+    private void vmove(int from, int to) {
+        double d1;
+        double d2;
+        double X;
+        double d1new;
+        double d2new;
+        double cbar;
+        double sbar;
+        double Y;
+        int first;
+        int inc;
+        int m1;
+        int m2;
+        int mp1;
+        int pos;
+        boolean bSkipTo40 = false;
+        if (from == to) {
+            return;
+        }
+        if (!this.rss_set) {
+            ss();
+        }
+        int count = 0;
+        if (from < to) {
+            first = from;
+            inc = 1;
+            count = to - from;
+        } else {
+            first = from - 1;
+            inc = -1;
+            count = from - to;
+        }
+
+        int m = first;
+        int idx = 0;
+        while (idx < count) {
+            m1 = m * (nvars + nvars - m - 1) / 2;
+            m2 = m1 + nvars - m - 1;
+            mp1 = m + 1;
+
+            d1 = d[m];
+            d2 = d[mp1];
+            // Special cases.
+            if (d1 > this.epsilon || d2 > this.epsilon) {
+                X = r[m1];
+                if (FastMath.abs(X) * FastMath.sqrt(d1) < tol[mp1]) {
+                    X = 0.0;
+                }
+                if (d1 < this.epsilon || FastMath.abs(X) < this.epsilon) {
+                    d[m] = d2;
+                    d[mp1] = d1;
+                    r[m1] = 0.0;
+                    for (int col = m + 2; col < nvars; col++) {
+                        ++m1;
+                        X = r[m1];
+                        r[m1] = r[m2];
+                        r[m2] = X;
+                        ++m2;
+                    }
+                    X = rhs[m];
+                    rhs[m] = rhs[mp1];
+                    rhs[mp1] = X;
+                    bSkipTo40 = true;
+                    //break;
+                } else if (d2 < this.epsilon) {
+                    d[m] = d1 * X * X;
+                    r[m1] = 1.0 / X;
+                    for (int _i = m1 + 1; _i < m1 + nvars - m - 1; _i++) {
+                        r[_i] /= X;
+                    }
+                    rhs[m] /= X;
+                    bSkipTo40 = true;
+                    //break;
+                }
+                if (!bSkipTo40) {
+                    d1new = d2 + d1 * X * X;
+                    cbar = d2 / d1new;
+                    sbar = X * d1 / d1new;
+                    d2new = d1 * cbar;
+                    d[m] = d1new;
+                    d[mp1] = d2new;
+                    r[m1] = sbar;
+                    for (int col = m + 2; col < nvars; col++) {
+                        ++m1;
+                        Y = r[m1];
+                        r[m1] = cbar * r[m2] + sbar * Y;
+                        r[m2] = Y - X * r[m2];
+                        ++m2;
+                    }
+                    Y = rhs[m];
+                    rhs[m] = cbar * rhs[mp1] + sbar * Y;
+                    rhs[mp1] = Y - X * rhs[mp1];
+                }
+            }
+            if (m > 0) {
+                pos = m;
+                for (int row = 0; row < m; row++) {
+                    X = r[pos];
+                    r[pos] = r[pos - 1];
+                    r[pos - 1] = X;
+                    pos += nvars - row - 2;
+                }
+            }
+            // Adjust variable order (VORDER), the tolerances (TOL) and
+            // the vector of residual sums of squares (RSS).
+            m1 = vorder[m];
+            vorder[m] = vorder[mp1];
+            vorder[mp1] = m1;
+            X = tol[m];
+            tol[m] = tol[mp1];
+            tol[mp1] = X;
+            rss[m] = rss[mp1] + d[mp1] * rhs[mp1] * rhs[mp1];
+
+            m += inc;
+            ++idx;
+        }
+    }
+
+    /**
+     * ALGORITHM AS274  APPL. STATIST. (1992) VOL.41, NO. 2
+     *
+     * <p> Re-order the variables in an orthogonal reduction produced by
+     * AS75.1 so that the N variables in LIST start at position POS1,
+     * though will not necessarily be in the same order as in LIST.
+     * Any variables in VORDER before position POS1 are not moved.
+     * Auxiliary routine called: VMOVE. </p>
+     *
+     * <p>This internal method reorders the regressors.</p>
+     *
+     * @param list the regressors to move
+     * @param pos1 where the list will be placed
+     * @return -1 error, 0 everything ok
+     */
+    private int reorderRegressors(int[] list, int pos1) {
+        int next;
+        int i;
+        int l;
+        if (list.length < 1 || list.length > nvars + 1 - pos1) {
+            return -1;
+        }
+        next = pos1;
+        i = pos1;
+        while (i < nvars) {
+            l = vorder[i];
+            for (int j = 0; j < list.length; j++) {
+                if (l == list[j] && i > next) {
+                    this.vmove(i, next);
+                    ++next;
+                    if (next >= list.length + pos1) {
+                        return 0;
+                    } else {
+                        break;
+                    }
+                }
+            }
+            ++i;
+        }
+        return 0;
+    }
+
+    /**
+     * Gets the diagonal of the Hat matrix also known as the leverage matrix.
+     *
+     * @param  row_data returns the diagonal of the hat matrix for this observation
+     * @return the diagonal element of the hatmatrix
+     */
+    public double getDiagonalOfHatMatrix(double[] row_data) {
+        double[] wk = new double[this.nvars];
+        int pos;
+        double total;
+
+        if (row_data.length > nvars) {
+            return Double.NaN;
+        }
+        double[] xrow;
+        if (this.hasIntercept) {
+            xrow = new double[row_data.length + 1];
+            xrow[0] = 1.0;
+            System.arraycopy(row_data, 0, xrow, 1, row_data.length);
+        } else {
+            xrow = row_data;
+        }
+        double hii = 0.0;
+        for (int col = 0; col < xrow.length; col++) {
+            if (FastMath.sqrt(d[col]) < tol[col]) {
+                wk[col] = 0.0;
+            } else {
+                pos = col - 1;
+                total = xrow[col];
+                for (int row = 0; row < col; row++) {
+                    total = smartAdd(total, -wk[row] * r[pos]);
+                    pos += nvars - row - 2;
+                }
+                wk[col] = total;
+                hii = smartAdd(hii, (total * total) / d[col]);
+            }
+        }
+        return hii;
+    }
+
+    /**
+     * Gets the order of the regressors, useful if some type of reordering
+     * has been called. Calling regress with int[]{} args will trigger
+     * a reordering.
+     *
+     * @return int[] with the current order of the regressors
+     */
+    public int[] getOrderOfRegressors(){
+        return MathArrays.copyOf(vorder);
+    }
+
+    /**
+     * Conducts a regression on the data in the model, using all regressors.
+     *
+     * @return RegressionResults the structure holding all regression results
+     * @exception  ModelSpecificationException - thrown if number of observations is
+     * less than the number of variables
+     */
+    public RegressionResults regress() throws ModelSpecificationException {
+        return regress(this.nvars);
+    }
+
+    /**
+     * Conducts a regression on the data in the model, using a subset of regressors.
+     *
+     * @param numberOfRegressors many of the regressors to include (either in canonical
+     * order, or in the current reordered state)
+     * @return RegressionResults the structure holding all regression results
+     * @exception  ModelSpecificationException - thrown if number of observations is
+     * less than the number of variables or number of regressors requested
+     * is greater than the regressors in the model
+     */
+    public RegressionResults regress(int numberOfRegressors) throws ModelSpecificationException {
+        if (this.nobs <= numberOfRegressors) {
+           throw new ModelSpecificationException(
+                   LocalizedFormats.NOT_ENOUGH_DATA_FOR_NUMBER_OF_PREDICTORS,
+                   this.nobs, numberOfRegressors);
+        }
+        if( numberOfRegressors > this.nvars ){
+            throw new ModelSpecificationException(
+                    LocalizedFormats.TOO_MANY_REGRESSORS, numberOfRegressors, this.nvars);
+        }
+
+        tolset();
+        singcheck();
+
+        double[] beta = this.regcf(numberOfRegressors);
+
+        ss();
+
+        double[] cov = this.cov(numberOfRegressors);
+
+        int rnk = 0;
+        for (int i = 0; i < this.lindep.length; i++) {
+            if (!this.lindep[i]) {
+                ++rnk;
+            }
+        }
+
+        boolean needsReorder = false;
+        for (int i = 0; i < numberOfRegressors; i++) {
+            if (this.vorder[i] != i) {
+                needsReorder = true;
+                break;
+            }
+        }
+        if (!needsReorder) {
+            return new RegressionResults(
+                    beta, new double[][]{cov}, true, this.nobs, rnk,
+                    this.sumy, this.sumsqy, this.sserr, this.hasIntercept, false);
+        } else {
+            double[] betaNew = new double[beta.length];
+            double[] covNew = new double[cov.length];
+
+            int[] newIndices = new int[beta.length];
+            for (int i = 0; i < nvars; i++) {
+                for (int j = 0; j < numberOfRegressors; j++) {
+                    if (this.vorder[j] == i) {
+                        betaNew[i] = beta[ j];
+                        newIndices[i] = j;
+                    }
+                }
+            }
+
+            int idx1 = 0;
+            int idx2;
+            int _i;
+            int _j;
+            for (int i = 0; i < beta.length; i++) {
+                _i = newIndices[i];
+                for (int j = 0; j <= i; j++, idx1++) {
+                    _j = newIndices[j];
+                    if (_i > _j) {
+                        idx2 = _i * (_i + 1) / 2 + _j;
+                    } else {
+                        idx2 = _j * (_j + 1) / 2 + _i;
+                    }
+                    covNew[idx1] = cov[idx2];
+                }
+            }
+            return new RegressionResults(
+                    betaNew, new double[][]{covNew}, true, this.nobs, rnk,
+                    this.sumy, this.sumsqy, this.sserr, this.hasIntercept, false);
+        }
+    }
+
+    /**
+     * Conducts a regression on the data in the model, using regressors in array
+     * Calling this method will change the internal order of the regressors
+     * and care is required in interpreting the hatmatrix.
+     *
+     * @param  variablesToInclude array of variables to include in regression
+     * @return RegressionResults the structure holding all regression results
+     * @exception  ModelSpecificationException - thrown if number of observations is
+     * less than the number of variables, the number of regressors requested
+     * is greater than the regressors in the model or a regressor index in
+     * regressor array does not exist
+     */
+    public RegressionResults regress(int[] variablesToInclude) throws ModelSpecificationException {
+        if (variablesToInclude.length > this.nvars) {
+            throw new ModelSpecificationException(
+                    LocalizedFormats.TOO_MANY_REGRESSORS, variablesToInclude.length, this.nvars);
+        }
+        if (this.nobs <= this.nvars) {
+            throw new ModelSpecificationException(
+                    LocalizedFormats.NOT_ENOUGH_DATA_FOR_NUMBER_OF_PREDICTORS,
+                    this.nobs, this.nvars);
+        }
+        Arrays.sort(variablesToInclude);
+        int iExclude = 0;
+        for (int i = 0; i < variablesToInclude.length; i++) {
+            if (i >= this.nvars) {
+                throw new ModelSpecificationException(
+                        LocalizedFormats.INDEX_LARGER_THAN_MAX, i, this.nvars);
+            }
+            if (i > 0 && variablesToInclude[i] == variablesToInclude[i - 1]) {
+                variablesToInclude[i] = -1;
+                ++iExclude;
+            }
+        }
+        int[] series;
+        if (iExclude > 0) {
+            int j = 0;
+            series = new int[variablesToInclude.length - iExclude];
+            for (int i = 0; i < variablesToInclude.length; i++) {
+                if (variablesToInclude[i] > -1) {
+                    series[j] = variablesToInclude[i];
+                    ++j;
+                }
+            }
+        } else {
+            series = variablesToInclude;
+        }
+
+        reorderRegressors(series, 0);
+        tolset();
+        singcheck();
+
+        double[] beta = this.regcf(series.length);
+
+        ss();
+
+        double[] cov = this.cov(series.length);
+
+        int rnk = 0;
+        for (int i = 0; i < this.lindep.length; i++) {
+            if (!this.lindep[i]) {
+                ++rnk;
+            }
+        }
+
+        boolean needsReorder = false;
+        for (int i = 0; i < this.nvars; i++) {
+            if (this.vorder[i] != series[i]) {
+                needsReorder = true;
+                break;
+            }
+        }
+        if (!needsReorder) {
+            return new RegressionResults(
+                    beta, new double[][]{cov}, true, this.nobs, rnk,
+                    this.sumy, this.sumsqy, this.sserr, this.hasIntercept, false);
+        } else {
+            double[] betaNew = new double[beta.length];
+            int[] newIndices = new int[beta.length];
+            for (int i = 0; i < series.length; i++) {
+                for (int j = 0; j < this.vorder.length; j++) {
+                    if (this.vorder[j] == series[i]) {
+                        betaNew[i] = beta[ j];
+                        newIndices[i] = j;
+                    }
+                }
+            }
+            double[] covNew = new double[cov.length];
+            int idx1 = 0;
+            int idx2;
+            int _i;
+            int _j;
+            for (int i = 0; i < beta.length; i++) {
+                _i = newIndices[i];
+                for (int j = 0; j <= i; j++, idx1++) {
+                    _j = newIndices[j];
+                    if (_i > _j) {
+                        idx2 = _i * (_i + 1) / 2 + _j;
+                    } else {
+                        idx2 = _j * (_j + 1) / 2 + _i;
+                    }
+                    covNew[idx1] = cov[idx2];
+                }
+            }
+            return new RegressionResults(
+                    betaNew, new double[][]{covNew}, true, this.nobs, rnk,
+                    this.sumy, this.sumsqy, this.sserr, this.hasIntercept, false);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/regression/ModelSpecificationException.java b/src/main/java/org/apache/commons/math3/stat/regression/ModelSpecificationException.java
new file mode 100644
index 0000000..f3804db
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/regression/ModelSpecificationException.java
@@ -0,0 +1,40 @@
+/*
+ * 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.math3.stat.regression;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.Localizable;
+
+/**
+ * Exception thrown when a regression model is not correctly specified.
+ *
+ * @since 3.0
+ */
+public class ModelSpecificationException extends MathIllegalArgumentException {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = 4206514456095401070L;
+
+    /**
+     * @param pattern message pattern describing the specification error.
+     *
+     * @param args arguments.
+     */
+    public ModelSpecificationException(Localizable pattern,
+                                        Object ... args) {
+        super(pattern, args);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/regression/MultipleLinearRegression.java b/src/main/java/org/apache/commons/math3/stat/regression/MultipleLinearRegression.java
new file mode 100644
index 0000000..866214f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/regression/MultipleLinearRegression.java
@@ -0,0 +1,69 @@
+/*
+ * 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.math3.stat.regression;
+
+/**
+ * The multiple linear regression can be represented in matrix-notation.
+ * <pre>
+ *  y=X*b+u
+ * </pre>
+ * where y is an <code>n-vector</code> <b>regressand</b>, X is a <code>[n,k]</code> matrix whose <code>k</code> columns are called
+ * <b>regressors</b>, b is <code>k-vector</code> of <b>regression parameters</b> and <code>u</code> is an <code>n-vector</code>
+ * of <b>error terms</b> or <b>residuals</b>.
+ *
+ * The notation is quite standard in literature,
+ * cf eg <a href="http://www.econ.queensu.ca/ETM">Davidson and MacKinnon, Econometrics Theory and Methods, 2004</a>.
+ * @since 2.0
+ */
+public interface MultipleLinearRegression {
+
+    /**
+     * Estimates the regression parameters b.
+     *
+     * @return The [k,1] array representing b
+     */
+    double[] estimateRegressionParameters();
+
+    /**
+     * Estimates the variance of the regression parameters, ie Var(b).
+     *
+     * @return The [k,k] array representing the variance of b
+     */
+    double[][] estimateRegressionParametersVariance();
+
+    /**
+     * Estimates the residuals, ie u = y - X*b.
+     *
+     * @return The [n,1] array representing the residuals
+     */
+    double[] estimateResiduals();
+
+    /**
+     * Returns the variance of the regressand, ie Var(y).
+     *
+     * @return The double representing the variance of y
+     */
+    double estimateRegressandVariance();
+
+    /**
+     * Returns the standard errors of the regression parameters.
+     *
+     * @return standard errors of estimated regression parameters
+     */
+     double[] estimateRegressionParametersStandardErrors();
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/regression/OLSMultipleLinearRegression.java b/src/main/java/org/apache/commons/math3/stat/regression/OLSMultipleLinearRegression.java
new file mode 100644
index 0000000..7fff940
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/regression/OLSMultipleLinearRegression.java
@@ -0,0 +1,285 @@
+/*
+ * 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.math3.stat.regression;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.linear.Array2DRowRealMatrix;
+import org.apache.commons.math3.linear.LUDecomposition;
+import org.apache.commons.math3.linear.QRDecomposition;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.linear.RealVector;
+import org.apache.commons.math3.stat.StatUtils;
+import org.apache.commons.math3.stat.descriptive.moment.SecondMoment;
+
+/**
+ * <p>Implements ordinary least squares (OLS) to estimate the parameters of a
+ * multiple linear regression model.</p>
+ *
+ * <p>The regression coefficients, <code>b</code>, satisfy the normal equations:
+ * <pre><code> X<sup>T</sup> X b = X<sup>T</sup> y </code></pre></p>
+ *
+ * <p>To solve the normal equations, this implementation uses QR decomposition
+ * of the <code>X</code> matrix. (See {@link QRDecomposition} for details on the
+ * decomposition algorithm.) The <code>X</code> matrix, also known as the <i>design matrix,</i>
+ * has rows corresponding to sample observations and columns corresponding to independent
+ * variables.  When the model is estimated using an intercept term (i.e. when
+ * {@link #isNoIntercept() isNoIntercept} is false as it is by default), the <code>X</code>
+ * matrix includes an initial column identically equal to 1.  We solve the normal equations
+ * as follows:
+ * <pre><code> X<sup>T</sup>X b = X<sup>T</sup> y
+ * (QR)<sup>T</sup> (QR) b = (QR)<sup>T</sup>y
+ * R<sup>T</sup> (Q<sup>T</sup>Q) R b = R<sup>T</sup> Q<sup>T</sup> y
+ * R<sup>T</sup> R b = R<sup>T</sup> Q<sup>T</sup> y
+ * (R<sup>T</sup>)<sup>-1</sup> R<sup>T</sup> R b = (R<sup>T</sup>)<sup>-1</sup> R<sup>T</sup> Q<sup>T</sup> y
+ * R b = Q<sup>T</sup> y </code></pre></p>
+ *
+ * <p>Given <code>Q</code> and <code>R</code>, the last equation is solved by back-substitution.</p>
+ *
+ * @since 2.0
+ */
+public class OLSMultipleLinearRegression extends AbstractMultipleLinearRegression {
+
+    /** Cached QR decomposition of X matrix */
+    private QRDecomposition qr = null;
+
+    /** Singularity threshold for QR decomposition */
+    private final double threshold;
+
+    /**
+     * Create an empty OLSMultipleLinearRegression instance.
+     */
+    public OLSMultipleLinearRegression() {
+        this(0d);
+    }
+
+    /**
+     * Create an empty OLSMultipleLinearRegression instance, using the given
+     * singularity threshold for the QR decomposition.
+     *
+     * @param threshold the singularity threshold
+     * @since 3.3
+     */
+    public OLSMultipleLinearRegression(final double threshold) {
+        this.threshold = threshold;
+    }
+
+    /**
+     * Loads model x and y sample data, overriding any previous sample.
+     *
+     * Computes and caches QR decomposition of the X matrix.
+     * @param y the [n,1] array representing the y sample
+     * @param x the [n,k] array representing the x sample
+     * @throws MathIllegalArgumentException if the x and y array data are not
+     *             compatible for the regression
+     */
+    public void newSampleData(double[] y, double[][] x) throws MathIllegalArgumentException {
+        validateSampleData(x, y);
+        newYSampleData(y);
+        newXSampleData(x);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>This implementation computes and caches the QR decomposition of the X matrix.</p>
+     */
+    @Override
+    public void newSampleData(double[] data, int nobs, int nvars) {
+        super.newSampleData(data, nobs, nvars);
+        qr = new QRDecomposition(getX(), threshold);
+    }
+
+    /**
+     * <p>Compute the "hat" matrix.
+     * </p>
+     * <p>The hat matrix is defined in terms of the design matrix X
+     *  by X(X<sup>T</sup>X)<sup>-1</sup>X<sup>T</sup>
+     * </p>
+     * <p>The implementation here uses the QR decomposition to compute the
+     * hat matrix as Q I<sub>p</sub>Q<sup>T</sup> where I<sub>p</sub> is the
+     * p-dimensional identity matrix augmented by 0's.  This computational
+     * formula is from "The Hat Matrix in Regression and ANOVA",
+     * David C. Hoaglin and Roy E. Welsch,
+     * <i>The American Statistician</i>, Vol. 32, No. 1 (Feb., 1978), pp. 17-22.
+     * </p>
+     * <p>Data for the model must have been successfully loaded using one of
+     * the {@code newSampleData} methods before invoking this method; otherwise
+     * a {@code NullPointerException} will be thrown.</p>
+     *
+     * @return the hat matrix
+     * @throws NullPointerException unless method {@code newSampleData} has been
+     * called beforehand.
+     */
+    public RealMatrix calculateHat() {
+        // Create augmented identity matrix
+        RealMatrix Q = qr.getQ();
+        final int p = qr.getR().getColumnDimension();
+        final int n = Q.getColumnDimension();
+        // No try-catch or advertised NotStrictlyPositiveException - NPE above if n < 3
+        Array2DRowRealMatrix augI = new Array2DRowRealMatrix(n, n);
+        double[][] augIData = augI.getDataRef();
+        for (int i = 0; i < n; i++) {
+            for (int j =0; j < n; j++) {
+                if (i == j && i < p) {
+                    augIData[i][j] = 1d;
+                } else {
+                    augIData[i][j] = 0d;
+                }
+            }
+        }
+
+        // Compute and return Hat matrix
+        // No DME advertised - args valid if we get here
+        return Q.multiply(augI).multiply(Q.transpose());
+    }
+
+    /**
+     * <p>Returns the sum of squared deviations of Y from its mean.</p>
+     *
+     * <p>If the model has no intercept term, <code>0</code> is used for the
+     * mean of Y - i.e., what is returned is the sum of the squared Y values.</p>
+     *
+     * <p>The value returned by this method is the SSTO value used in
+     * the {@link #calculateRSquared() R-squared} computation.</p>
+     *
+     * @return SSTO - the total sum of squares
+     * @throws NullPointerException if the sample has not been set
+     * @see #isNoIntercept()
+     * @since 2.2
+     */
+    public double calculateTotalSumOfSquares() {
+        if (isNoIntercept()) {
+            return StatUtils.sumSq(getY().toArray());
+        } else {
+            return new SecondMoment().evaluate(getY().toArray());
+        }
+    }
+
+    /**
+     * Returns the sum of squared residuals.
+     *
+     * @return residual sum of squares
+     * @since 2.2
+     * @throws org.apache.commons.math3.linear.SingularMatrixException if the design matrix is singular
+     * @throws NullPointerException if the data for the model have not been loaded
+     */
+    public double calculateResidualSumOfSquares() {
+        final RealVector residuals = calculateResiduals();
+        // No advertised DME, args are valid
+        return residuals.dotProduct(residuals);
+    }
+
+    /**
+     * Returns the R-Squared statistic, defined by the formula <pre>
+     * R<sup>2</sup> = 1 - SSR / SSTO
+     * </pre>
+     * where SSR is the {@link #calculateResidualSumOfSquares() sum of squared residuals}
+     * and SSTO is the {@link #calculateTotalSumOfSquares() total sum of squares}
+     *
+     * <p>If there is no variance in y, i.e., SSTO = 0, NaN is returned.</p>
+     *
+     * @return R-square statistic
+     * @throws NullPointerException if the sample has not been set
+     * @throws org.apache.commons.math3.linear.SingularMatrixException if the design matrix is singular
+     * @since 2.2
+     */
+    public double calculateRSquared() {
+        return 1 - calculateResidualSumOfSquares() / calculateTotalSumOfSquares();
+    }
+
+    /**
+     * <p>Returns the adjusted R-squared statistic, defined by the formula <pre>
+     * R<sup>2</sup><sub>adj</sub> = 1 - [SSR (n - 1)] / [SSTO (n - p)]
+     * </pre>
+     * where SSR is the {@link #calculateResidualSumOfSquares() sum of squared residuals},
+     * SSTO is the {@link #calculateTotalSumOfSquares() total sum of squares}, n is the number
+     * of observations and p is the number of parameters estimated (including the intercept).</p>
+     *
+     * <p>If the regression is estimated without an intercept term, what is returned is <pre>
+     * <code> 1 - (1 - {@link #calculateRSquared()}) * (n / (n - p)) </code>
+     * </pre></p>
+     *
+     * <p>If there is no variance in y, i.e., SSTO = 0, NaN is returned.</p>
+     *
+     * @return adjusted R-Squared statistic
+     * @throws NullPointerException if the sample has not been set
+     * @throws org.apache.commons.math3.linear.SingularMatrixException if the design matrix is singular
+     * @see #isNoIntercept()
+     * @since 2.2
+     */
+    public double calculateAdjustedRSquared() {
+        final double n = getX().getRowDimension();
+        if (isNoIntercept()) {
+            return 1 - (1 - calculateRSquared()) * (n / (n - getX().getColumnDimension()));
+        } else {
+            return 1 - (calculateResidualSumOfSquares() * (n - 1)) /
+                (calculateTotalSumOfSquares() * (n - getX().getColumnDimension()));
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>This implementation computes and caches the QR decomposition of the X matrix
+     * once it is successfully loaded.</p>
+     */
+    @Override
+    protected void newXSampleData(double[][] x) {
+        super.newXSampleData(x);
+        qr = new QRDecomposition(getX(), threshold);
+    }
+
+    /**
+     * Calculates the regression coefficients using OLS.
+     *
+     * <p>Data for the model must have been successfully loaded using one of
+     * the {@code newSampleData} methods before invoking this method; otherwise
+     * a {@code NullPointerException} will be thrown.</p>
+     *
+     * @return beta
+     * @throws org.apache.commons.math3.linear.SingularMatrixException if the design matrix is singular
+     * @throws NullPointerException if the data for the model have not been loaded
+     */
+    @Override
+    protected RealVector calculateBeta() {
+        return qr.getSolver().solve(getY());
+    }
+
+    /**
+     * <p>Calculates the variance-covariance matrix of the regression parameters.
+     * </p>
+     * <p>Var(b) = (X<sup>T</sup>X)<sup>-1</sup>
+     * </p>
+     * <p>Uses QR decomposition to reduce (X<sup>T</sup>X)<sup>-1</sup>
+     * to (R<sup>T</sup>R)<sup>-1</sup>, with only the top p rows of
+     * R included, where p = the length of the beta vector.</p>
+     *
+     * <p>Data for the model must have been successfully loaded using one of
+     * the {@code newSampleData} methods before invoking this method; otherwise
+     * a {@code NullPointerException} will be thrown.</p>
+     *
+     * @return The beta variance-covariance matrix
+     * @throws org.apache.commons.math3.linear.SingularMatrixException if the design matrix is singular
+     * @throws NullPointerException if the data for the model have not been loaded
+     */
+    @Override
+    protected RealMatrix calculateBetaVariance() {
+        int p = getX().getColumnDimension();
+        RealMatrix Raug = qr.getR().getSubMatrix(0, p - 1 , 0, p - 1);
+        RealMatrix Rinv = new LUDecomposition(Raug).getSolver().getInverse();
+        return Rinv.multiply(Rinv.transpose());
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/regression/RegressionResults.java b/src/main/java/org/apache/commons/math3/stat/regression/RegressionResults.java
new file mode 100644
index 0000000..70faeac
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/regression/RegressionResults.java
@@ -0,0 +1,421 @@
+/*
+ * 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.math3.stat.regression;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathArrays;
+import org.apache.commons.math3.exception.OutOfRangeException;
+
+/**
+ * Results of a Multiple Linear Regression model fit.
+ *
+ * @since 3.0
+ */
+public class RegressionResults implements Serializable {
+
+    /** INDEX of Sum of Squared Errors */
+    private static final int SSE_IDX = 0;
+    /** INDEX of Sum of Squares of Model */
+    private static final int SST_IDX = 1;
+    /** INDEX of R-Squared of regression */
+    private static final int RSQ_IDX = 2;
+    /** INDEX of Mean Squared Error */
+    private static final int MSE_IDX = 3;
+    /** INDEX of Adjusted R Squared */
+    private static final int ADJRSQ_IDX = 4;
+    /** UID */
+    private static final long serialVersionUID = 1l;
+    /** regression slope parameters */
+    private final double[] parameters;
+    /** variance covariance matrix of parameters */
+    private final double[][] varCovData;
+    /** boolean flag for variance covariance matrix in symm compressed storage */
+    private final boolean isSymmetricVCD;
+    /** rank of the solution */
+    @SuppressWarnings("unused")
+    private final int rank;
+    /** number of observations on which results are based */
+    private final long nobs;
+    /** boolean flag indicator of whether a constant was included*/
+    private final boolean containsConstant;
+    /** array storing global results, SSE, MSE, RSQ, adjRSQ */
+    private final double[] globalFitInfo;
+
+    /**
+     *  Set the default constructor to private access
+     *  to prevent inadvertent instantiation
+     */
+    @SuppressWarnings("unused")
+    private RegressionResults() {
+        this.parameters = null;
+        this.varCovData = null;
+        this.rank = -1;
+        this.nobs = -1;
+        this.containsConstant = false;
+        this.isSymmetricVCD = false;
+        this.globalFitInfo = null;
+    }
+
+    /**
+     * Constructor for Regression Results.
+     *
+     * @param parameters a double array with the regression slope estimates
+     * @param varcov the variance covariance matrix, stored either in a square matrix
+     * or as a compressed
+     * @param isSymmetricCompressed a flag which denotes that the variance covariance
+     * matrix is in symmetric compressed format
+     * @param nobs the number of observations of the regression estimation
+     * @param rank the number of independent variables in the regression
+     * @param sumy the sum of the independent variable
+     * @param sumysq the sum of the squared independent variable
+     * @param sse sum of squared errors
+     * @param containsConstant true model has constant,  false model does not have constant
+     * @param copyData if true a deep copy of all input data is made, if false only references
+     * are copied and the RegressionResults become mutable
+     */
+    public RegressionResults(
+            final double[] parameters, final double[][] varcov,
+            final boolean isSymmetricCompressed,
+            final long nobs, final int rank,
+            final double sumy, final double sumysq, final double sse,
+            final boolean containsConstant,
+            final boolean copyData) {
+        if (copyData) {
+            this.parameters = MathArrays.copyOf(parameters);
+            this.varCovData = new double[varcov.length][];
+            for (int i = 0; i < varcov.length; i++) {
+                this.varCovData[i] = MathArrays.copyOf(varcov[i]);
+            }
+        } else {
+            this.parameters = parameters;
+            this.varCovData = varcov;
+        }
+        this.isSymmetricVCD = isSymmetricCompressed;
+        this.nobs = nobs;
+        this.rank = rank;
+        this.containsConstant = containsConstant;
+        this.globalFitInfo = new double[5];
+        Arrays.fill(this.globalFitInfo, Double.NaN);
+
+        if (rank > 0) {
+            this.globalFitInfo[SST_IDX] = containsConstant ?
+                    (sumysq - sumy * sumy / nobs) : sumysq;
+        }
+
+        this.globalFitInfo[SSE_IDX] = sse;
+        this.globalFitInfo[MSE_IDX] = this.globalFitInfo[SSE_IDX] /
+                (nobs - rank);
+        this.globalFitInfo[RSQ_IDX] = 1.0 -
+                this.globalFitInfo[SSE_IDX] /
+                this.globalFitInfo[SST_IDX];
+
+        if (!containsConstant) {
+            this.globalFitInfo[ADJRSQ_IDX] = 1.0-
+                    (1.0 - this.globalFitInfo[RSQ_IDX]) *
+                    ( (double) nobs / ( (double) (nobs - rank)));
+        } else {
+            this.globalFitInfo[ADJRSQ_IDX] = 1.0 - (sse * (nobs - 1.0)) /
+                    (globalFitInfo[SST_IDX] * (nobs - rank));
+        }
+    }
+
+    /**
+     * <p>Returns the parameter estimate for the regressor at the given index.</p>
+     *
+     * <p>A redundant regressor will have its redundancy flag set, as well as
+     *  a parameters estimated equal to {@code Double.NaN}</p>
+     *
+     * @param index Index.
+     * @return the parameters estimated for regressor at index.
+     * @throws OutOfRangeException if {@code index} is not in the interval
+     * {@code [0, number of parameters)}.
+     */
+    public double getParameterEstimate(int index) throws OutOfRangeException {
+        if (parameters == null) {
+            return Double.NaN;
+        }
+        if (index < 0 || index >= this.parameters.length) {
+            throw new OutOfRangeException(index, 0, this.parameters.length - 1);
+        }
+        return this.parameters[index];
+    }
+
+    /**
+     * <p>Returns a copy of the regression parameters estimates.</p>
+     *
+     * <p>The parameter estimates are returned in the natural order of the data.</p>
+     *
+     * <p>A redundant regressor will have its redundancy flag set, as will
+     *  a parameter estimate equal to {@code Double.NaN}.</p>
+     *
+     * @return array of parameter estimates, null if no estimation occurred
+     */
+    public double[] getParameterEstimates() {
+        if (this.parameters == null) {
+            return null;
+        }
+        return MathArrays.copyOf(parameters);
+    }
+
+    /**
+     * Returns the <a href="http://www.xycoon.com/standerrorb(1).htm">standard
+     * error of the parameter estimate at index</a>,
+     * usually denoted s(b<sub>index</sub>).
+     *
+     * @param index Index.
+     * @return the standard errors associated with parameters estimated at index.
+     * @throws OutOfRangeException if {@code index} is not in the interval
+     * {@code [0, number of parameters)}.
+     */
+    public double getStdErrorOfEstimate(int index) throws OutOfRangeException {
+        if (parameters == null) {
+            return Double.NaN;
+        }
+        if (index < 0 || index >= this.parameters.length) {
+            throw new OutOfRangeException(index, 0, this.parameters.length - 1);
+        }
+        double var = this.getVcvElement(index, index);
+        if (!Double.isNaN(var) && var > Double.MIN_VALUE) {
+            return FastMath.sqrt(var);
+        }
+        return Double.NaN;
+    }
+
+    /**
+     * <p>Returns the <a href="http://www.xycoon.com/standerrorb(1).htm">standard
+     * error of the parameter estimates</a>,
+     * usually denoted s(b<sub>i</sub>).</p>
+     *
+     * <p>If there are problems with an ill conditioned design matrix then the regressor
+     * which is redundant will be assigned <code>Double.NaN</code>. </p>
+     *
+     * @return an array standard errors associated with parameters estimates,
+     *  null if no estimation occurred
+     */
+    public double[] getStdErrorOfEstimates() {
+        if (parameters == null) {
+            return null;
+        }
+        double[] se = new double[this.parameters.length];
+        for (int i = 0; i < this.parameters.length; i++) {
+            double var = this.getVcvElement(i, i);
+            if (!Double.isNaN(var) && var > Double.MIN_VALUE) {
+                se[i] = FastMath.sqrt(var);
+                continue;
+            }
+            se[i] = Double.NaN;
+        }
+        return se;
+    }
+
+    /**
+     * <p>Returns the covariance between regression parameters i and j.</p>
+     *
+     * <p>If there are problems with an ill conditioned design matrix then the covariance
+     * which involves redundant columns will be assigned {@code Double.NaN}. </p>
+     *
+     * @param i {@code i}th regression parameter.
+     * @param j {@code j}th regression parameter.
+     * @return the covariance of the parameter estimates.
+     * @throws OutOfRangeException if {@code i} or {@code j} is not in the
+     * interval {@code [0, number of parameters)}.
+     */
+    public double getCovarianceOfParameters(int i, int j) throws OutOfRangeException {
+        if (parameters == null) {
+            return Double.NaN;
+        }
+        if (i < 0 || i >= this.parameters.length) {
+            throw new OutOfRangeException(i, 0, this.parameters.length - 1);
+        }
+        if (j < 0 || j >= this.parameters.length) {
+            throw new OutOfRangeException(j, 0, this.parameters.length - 1);
+        }
+        return this.getVcvElement(i, j);
+    }
+
+    /**
+     * <p>Returns the number of parameters estimated in the model.</p>
+     *
+     * <p>This is the maximum number of regressors, some techniques may drop
+     * redundant parameters</p>
+     *
+     * @return number of regressors, -1 if not estimated
+     */
+    public int getNumberOfParameters() {
+        if (this.parameters == null) {
+            return -1;
+        }
+        return this.parameters.length;
+    }
+
+    /**
+     * Returns the number of observations added to the regression model.
+     *
+     * @return Number of observations, -1 if an error condition prevents estimation
+     */
+    public long getN() {
+        return this.nobs;
+    }
+
+    /**
+     * <p>Returns the sum of squared deviations of the y values about their mean.</p>
+     *
+     * <p>This is defined as SSTO
+     * <a href="http://www.xycoon.com/SumOfSquares.htm">here</a>.</p>
+     *
+     * <p>If {@code n < 2}, this returns {@code Double.NaN}.</p>
+     *
+     * @return sum of squared deviations of y values
+     */
+    public double getTotalSumSquares() {
+        return this.globalFitInfo[SST_IDX];
+    }
+
+    /**
+     * <p>Returns the sum of squared deviations of the predicted y values about
+     * their mean (which equals the mean of y).</p>
+     *
+     * <p>This is usually abbreviated SSR or SSM.  It is defined as SSM
+     * <a href="http://www.xycoon.com/SumOfSquares.htm">here</a></p>
+     *
+     * <p><strong>Preconditions</strong>: <ul>
+     * <li>At least two observations (with at least two different x values)
+     * must have been added before invoking this method. If this method is
+     * invoked before a model can be estimated, <code>Double.NaN</code> is
+     * returned.
+     * </li></ul></p>
+     *
+     * @return sum of squared deviations of predicted y values
+     */
+    public double getRegressionSumSquares() {
+        return this.globalFitInfo[SST_IDX] - this.globalFitInfo[SSE_IDX];
+    }
+
+    /**
+     * <p>Returns the <a href="http://www.xycoon.com/SumOfSquares.htm">
+     * sum of squared errors</a> (SSE) associated with the regression
+     * model.</p>
+     *
+     * <p>The return value is constrained to be non-negative - i.e., if due to
+     * rounding errors the computational formula returns a negative result,
+     * 0 is returned.</p>
+     *
+     * <p><strong>Preconditions</strong>: <ul>
+     * <li>numberOfParameters data pairs
+     * must have been added before invoking this method. If this method is
+     * invoked before a model can be estimated, <code>Double,NaN</code> is
+     * returned.
+     * </li></ul></p>
+     *
+     * @return sum of squared errors associated with the regression model
+     */
+    public double getErrorSumSquares() {
+        return this.globalFitInfo[ SSE_IDX];
+    }
+
+    /**
+     * <p>Returns the sum of squared errors divided by the degrees of freedom,
+     * usually abbreviated MSE.</p>
+     *
+     * <p>If there are fewer than <strong>numberOfParameters + 1</strong> data pairs in the model,
+     * or if there is no variation in <code>x</code>, this returns
+     * <code>Double.NaN</code>.</p>
+     *
+     * @return sum of squared deviations of y values
+     */
+    public double getMeanSquareError() {
+        return this.globalFitInfo[ MSE_IDX];
+    }
+
+    /**
+     * <p>Returns the <a href="http://www.xycoon.com/coefficient1.htm">
+     * coefficient of multiple determination</a>,
+     * usually denoted r-square.</p>
+     *
+     * <p><strong>Preconditions</strong>: <ul>
+     * <li>At least numberOfParameters observations (with at least numberOfParameters different x values)
+     * must have been added before invoking this method. If this method is
+     * invoked before a model can be estimated, {@code Double,NaN} is
+     * returned.
+     * </li></ul></p>
+     *
+     * @return r-square, a double in the interval [0, 1]
+     */
+    public double getRSquared() {
+        return this.globalFitInfo[ RSQ_IDX];
+    }
+
+    /**
+     * <p>Returns the adjusted R-squared statistic, defined by the formula <pre>
+     * R<sup>2</sup><sub>adj</sub> = 1 - [SSR (n - 1)] / [SSTO (n - p)]
+     * </pre>
+     * where SSR is the sum of squared residuals},
+     * SSTO is the total sum of squares}, n is the number
+     * of observations and p is the number of parameters estimated (including the intercept).</p>
+     *
+     * <p>If the regression is estimated without an intercept term, what is returned is <pre>
+     * <code> 1 - (1 - {@link #getRSquared()} ) * (n / (n - p)) </code>
+     * </pre></p>
+     *
+     * @return adjusted R-Squared statistic
+     */
+    public double getAdjustedRSquared() {
+        return this.globalFitInfo[ ADJRSQ_IDX];
+    }
+
+    /**
+     * Returns true if the regression model has been computed including an intercept.
+     * In this case, the coefficient of the intercept is the first element of the
+     * {@link #getParameterEstimates() parameter estimates}.
+     * @return true if the model has an intercept term
+     */
+    public boolean hasIntercept() {
+        return this.containsConstant;
+    }
+
+    /**
+     * Gets the i-jth element of the variance-covariance matrix.
+     *
+     * @param i first variable index
+     * @param j second variable index
+     * @return the requested variance-covariance matrix entry
+     */
+    private double getVcvElement(int i, int j) {
+        if (this.isSymmetricVCD) {
+            if (this.varCovData.length > 1) {
+                //could be stored in upper or lower triangular
+                if (i == j) {
+                    return varCovData[i][i];
+                } else if (i >= varCovData[j].length) {
+                    return varCovData[i][j];
+                } else {
+                    return varCovData[j][i];
+                }
+            } else {//could be in single array
+                if (i > j) {
+                    return varCovData[0][(i + 1) * i / 2 + j];
+                } else {
+                    return varCovData[0][(j + 1) * j / 2 + i];
+                }
+            }
+        } else {
+            return this.varCovData[i][j];
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/regression/SimpleRegression.java b/src/main/java/org/apache/commons/math3/stat/regression/SimpleRegression.java
new file mode 100644
index 0000000..02bf8f4
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/regression/SimpleRegression.java
@@ -0,0 +1,881 @@
+/*
+ * 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.math3.stat.regression;
+import java.io.Serializable;
+
+import org.apache.commons.math3.distribution.TDistribution;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.Precision;
+
+/**
+ * Estimates an ordinary least squares regression model
+ * with one independent variable.
+ * <p>
+ * <code> y = intercept + slope * x  </code></p>
+ * <p>
+ * Standard errors for <code>intercept</code> and <code>slope</code> are
+ * available as well as ANOVA, r-square and Pearson's r statistics.</p>
+ * <p>
+ * Observations (x,y pairs) can be added to the model one at a time or they
+ * can be provided in a 2-dimensional array.  The observations are not stored
+ * in memory, so there is no limit to the number of observations that can be
+ * added to the model.</p>
+ * <p>
+ * <strong>Usage Notes</strong>: <ul>
+ * <li> When there are fewer than two observations in the model, or when
+ * there is no variation in the x values (i.e. all x values are the same)
+ * all statistics return <code>NaN</code>. At least two observations with
+ * different x coordinates are required to estimate a bivariate regression
+ * model.
+ * </li>
+ * <li> Getters for the statistics always compute values based on the current
+ * set of observations -- i.e., you can get statistics, then add more data
+ * and get updated statistics without using a new instance.  There is no
+ * "compute" method that updates all statistics.  Each of the getters performs
+ * the necessary computations to return the requested statistic.
+ * </li>
+ * <li> The intercept term may be suppressed by passing {@code false} to
+ * the {@link #SimpleRegression(boolean)} constructor.  When the
+ * {@code hasIntercept} property is false, the model is estimated without a
+ * constant term and {@link #getIntercept()} returns {@code 0}.</li>
+ * </ul></p>
+ *
+ */
+public class SimpleRegression implements Serializable, UpdatingMultipleLinearRegression {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -3004689053607543335L;
+
+    /** sum of x values */
+    private double sumX = 0d;
+
+    /** total variation in x (sum of squared deviations from xbar) */
+    private double sumXX = 0d;
+
+    /** sum of y values */
+    private double sumY = 0d;
+
+    /** total variation in y (sum of squared deviations from ybar) */
+    private double sumYY = 0d;
+
+    /** sum of products */
+    private double sumXY = 0d;
+
+    /** number of observations */
+    private long n = 0;
+
+    /** mean of accumulated x values, used in updating formulas */
+    private double xbar = 0;
+
+    /** mean of accumulated y values, used in updating formulas */
+    private double ybar = 0;
+
+    /** include an intercept or not */
+    private final boolean hasIntercept;
+    // ---------------------Public methods--------------------------------------
+
+    /**
+     * Create an empty SimpleRegression instance
+     */
+    public SimpleRegression() {
+        this(true);
+    }
+    /**
+    * Create a SimpleRegression instance, specifying whether or not to estimate
+    * an intercept.
+    *
+    * <p>Use {@code false} to estimate a model with no intercept.  When the
+    * {@code hasIntercept} property is false, the model is estimated without a
+    * constant term and {@link #getIntercept()} returns {@code 0}.</p>
+    *
+    * @param includeIntercept whether or not to include an intercept term in
+    * the regression model
+    */
+    public SimpleRegression(boolean includeIntercept) {
+        super();
+        hasIntercept = includeIntercept;
+    }
+
+    /**
+     * Adds the observation (x,y) to the regression data set.
+     * <p>
+     * Uses updating formulas for means and sums of squares defined in
+     * "Algorithms for Computing the Sample Variance: Analysis and
+     * Recommendations", Chan, T.F., Golub, G.H., and LeVeque, R.J.
+     * 1983, American Statistician, vol. 37, pp. 242-247, referenced in
+     * Weisberg, S. "Applied Linear Regression". 2nd Ed. 1985.</p>
+     *
+     *
+     * @param x independent variable value
+     * @param y dependent variable value
+     */
+    public void addData(final double x,final double y) {
+        if (n == 0) {
+            xbar = x;
+            ybar = y;
+        } else {
+            if( hasIntercept ){
+                final double fact1 = 1.0 + n;
+                final double fact2 = n / (1.0 + n);
+                final double dx = x - xbar;
+                final double dy = y - ybar;
+                sumXX += dx * dx * fact2;
+                sumYY += dy * dy * fact2;
+                sumXY += dx * dy * fact2;
+                xbar += dx / fact1;
+                ybar += dy / fact1;
+            }
+         }
+        if( !hasIntercept ){
+            sumXX += x * x ;
+            sumYY += y * y ;
+            sumXY += x * y ;
+        }
+        sumX += x;
+        sumY += y;
+        n++;
+    }
+
+    /**
+     * Appends data from another regression calculation to this one.
+     *
+     * <p>The mean update formulae are based on a paper written by Philippe
+     * P&eacute;bay:
+     * <a
+     * href="http://prod.sandia.gov/techlib/access-control.cgi/2008/086212.pdf">
+     * Formulas for Robust, One-Pass Parallel Computation of Covariances and
+     * Arbitrary-Order Statistical Moments</a>, 2008, Technical Report
+     * SAND2008-6212, Sandia National Laboratories.</p>
+     *
+     * @param reg model to append data from
+     * @since 3.3
+     */
+    public void append(SimpleRegression reg) {
+        if (n == 0) {
+            xbar = reg.xbar;
+            ybar = reg.ybar;
+            sumXX = reg.sumXX;
+            sumYY = reg.sumYY;
+            sumXY = reg.sumXY;
+        } else {
+            if (hasIntercept) {
+                final double fact1 = reg.n / (double) (reg.n + n);
+                final double fact2 = n * reg.n / (double) (reg.n + n);
+                final double dx = reg.xbar - xbar;
+                final double dy = reg.ybar - ybar;
+                sumXX += reg.sumXX + dx * dx * fact2;
+                sumYY += reg.sumYY + dy * dy * fact2;
+                sumXY += reg.sumXY + dx * dy * fact2;
+                xbar += dx * fact1;
+                ybar += dy * fact1;
+            }else{
+                sumXX += reg.sumXX;
+                sumYY += reg.sumYY;
+                sumXY += reg.sumXY;
+            }
+        }
+        sumX += reg.sumX;
+        sumY += reg.sumY;
+        n += reg.n;
+    }
+
+    /**
+     * Removes the observation (x,y) from the regression data set.
+     * <p>
+     * Mirrors the addData method.  This method permits the use of
+     * SimpleRegression instances in streaming mode where the regression
+     * is applied to a sliding "window" of observations, however the caller is
+     * responsible for maintaining the set of observations in the window.</p>
+     *
+     * The method has no effect if there are no points of data (i.e. n=0)
+     *
+     * @param x independent variable value
+     * @param y dependent variable value
+     */
+    public void removeData(final double x,final double y) {
+        if (n > 0) {
+            if (hasIntercept) {
+                final double fact1 = n - 1.0;
+                final double fact2 = n / (n - 1.0);
+                final double dx = x - xbar;
+                final double dy = y - ybar;
+                sumXX -= dx * dx * fact2;
+                sumYY -= dy * dy * fact2;
+                sumXY -= dx * dy * fact2;
+                xbar -= dx / fact1;
+                ybar -= dy / fact1;
+            } else {
+                final double fact1 = n - 1.0;
+                sumXX -= x * x;
+                sumYY -= y * y;
+                sumXY -= x * y;
+                xbar -= x / fact1;
+                ybar -= y / fact1;
+            }
+             sumX -= x;
+             sumY -= y;
+             n--;
+        }
+    }
+
+    /**
+     * Adds the observations represented by the elements in
+     * <code>data</code>.
+     * <p>
+     * <code>(data[0][0],data[0][1])</code> will be the first observation, then
+     * <code>(data[1][0],data[1][1])</code>, etc.</p>
+     * <p>
+     * This method does not replace data that has already been added.  The
+     * observations represented by <code>data</code> are added to the existing
+     * dataset.</p>
+     * <p>
+     * To replace all data, use <code>clear()</code> before adding the new
+     * data.</p>
+     *
+     * @param data array of observations to be added
+     * @throws ModelSpecificationException if the length of {@code data[i]} is not
+     * greater than or equal to 2
+     */
+    public void addData(final double[][] data) throws ModelSpecificationException {
+        for (int i = 0; i < data.length; i++) {
+            if( data[i].length < 2 ){
+               throw new ModelSpecificationException(LocalizedFormats.INVALID_REGRESSION_OBSERVATION,
+                    data[i].length, 2);
+            }
+            addData(data[i][0], data[i][1]);
+        }
+    }
+
+    /**
+     * Adds one observation to the regression model.
+     *
+     * @param x the independent variables which form the design matrix
+     * @param y the dependent or response variable
+     * @throws ModelSpecificationException if the length of {@code x} does not equal
+     * the number of independent variables in the model
+     */
+    public void addObservation(final double[] x,final double y)
+    throws ModelSpecificationException {
+        if( x == null || x.length == 0 ){
+            throw new ModelSpecificationException(LocalizedFormats.INVALID_REGRESSION_OBSERVATION,x!=null?x.length:0, 1);
+        }
+        addData( x[0], y );
+    }
+
+    /**
+     * Adds a series of observations to the regression model. The lengths of
+     * x and y must be the same and x must be rectangular.
+     *
+     * @param x a series of observations on the independent variables
+     * @param y a series of observations on the dependent variable
+     * The length of x and y must be the same
+     * @throws ModelSpecificationException if {@code x} is not rectangular, does not match
+     * the length of {@code y} or does not contain sufficient data to estimate the model
+     */
+    public void addObservations(final double[][] x,final double[] y) throws ModelSpecificationException {
+        if ((x == null) || (y == null) || (x.length != y.length)) {
+            throw new ModelSpecificationException(
+                  LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE,
+                  (x == null) ? 0 : x.length,
+                  (y == null) ? 0 : y.length);
+        }
+        boolean obsOk=true;
+        for( int i = 0 ; i < x.length; i++){
+            if( x[i] == null || x[i].length == 0 ){
+                obsOk = false;
+            }
+        }
+        if( !obsOk ){
+            throw new ModelSpecificationException(
+                  LocalizedFormats.NOT_ENOUGH_DATA_FOR_NUMBER_OF_PREDICTORS,
+                  0, 1);
+        }
+        for( int i = 0 ; i < x.length ; i++){
+            addData( x[i][0], y[i] );
+        }
+    }
+
+    /**
+     * Removes observations represented by the elements in <code>data</code>.
+      * <p>
+     * If the array is larger than the current n, only the first n elements are
+     * processed.  This method permits the use of SimpleRegression instances in
+     * streaming mode where the regression is applied to a sliding "window" of
+     * observations, however the caller is responsible for maintaining the set
+     * of observations in the window.</p>
+     * <p>
+     * To remove all data, use <code>clear()</code>.</p>
+     *
+     * @param data array of observations to be removed
+     */
+    public void removeData(double[][] data) {
+        for (int i = 0; i < data.length && n > 0; i++) {
+            removeData(data[i][0], data[i][1]);
+        }
+    }
+
+    /**
+     * Clears all data from the model.
+     */
+    public void clear() {
+        sumX = 0d;
+        sumXX = 0d;
+        sumY = 0d;
+        sumYY = 0d;
+        sumXY = 0d;
+        n = 0;
+    }
+
+    /**
+     * Returns the number of observations that have been added to the model.
+     *
+     * @return n number of observations that have been added.
+     */
+    public long getN() {
+        return n;
+    }
+
+    /**
+     * Returns the "predicted" <code>y</code> value associated with the
+     * supplied <code>x</code> value,  based on the data that has been
+     * added to the model when this method is activated.
+     * <p>
+     * <code> predict(x) = intercept + slope * x </code></p>
+     * <p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>At least two observations (with at least two different x values)
+     * must have been added before invoking this method. If this method is
+     * invoked before a model can be estimated, <code>Double,NaN</code> is
+     * returned.
+     * </li></ul></p>
+     *
+     * @param x input <code>x</code> value
+     * @return predicted <code>y</code> value
+     */
+    public double predict(final double x) {
+        final double b1 = getSlope();
+        if (hasIntercept) {
+            return getIntercept(b1) + b1 * x;
+        }
+        return b1 * x;
+    }
+
+    /**
+     * Returns the intercept of the estimated regression line, if
+     * {@link #hasIntercept()} is true; otherwise 0.
+     * <p>
+     * The least squares estimate of the intercept is computed using the
+     * <a href="http://www.xycoon.com/estimation4.htm">normal equations</a>.
+     * The intercept is sometimes denoted b0.</p>
+     * <p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>At least two observations (with at least two different x values)
+     * must have been added before invoking this method. If this method is
+     * invoked before a model can be estimated, <code>Double,NaN</code> is
+     * returned.
+     * </li></ul></p>
+     *
+     * @return the intercept of the regression line if the model includes an
+     * intercept; 0 otherwise
+     * @see #SimpleRegression(boolean)
+     */
+    public double getIntercept() {
+        return hasIntercept ? getIntercept(getSlope()) : 0.0;
+    }
+
+    /**
+     * Returns true if the model includes an intercept term.
+     *
+     * @return true if the regression includes an intercept; false otherwise
+     * @see #SimpleRegression(boolean)
+     */
+    public boolean hasIntercept() {
+        return hasIntercept;
+    }
+
+    /**
+    * Returns the slope of the estimated regression line.
+    * <p>
+    * The least squares estimate of the slope is computed using the
+    * <a href="http://www.xycoon.com/estimation4.htm">normal equations</a>.
+    * The slope is sometimes denoted b1.</p>
+    * <p>
+    * <strong>Preconditions</strong>: <ul>
+    * <li>At least two observations (with at least two different x values)
+    * must have been added before invoking this method. If this method is
+    * invoked before a model can be estimated, <code>Double.NaN</code> is
+    * returned.
+    * </li></ul></p>
+    *
+    * @return the slope of the regression line
+    */
+    public double getSlope() {
+        if (n < 2) {
+            return Double.NaN; //not enough data
+        }
+        if (FastMath.abs(sumXX) < 10 * Double.MIN_VALUE) {
+            return Double.NaN; //not enough variation in x
+        }
+        return sumXY / sumXX;
+    }
+
+    /**
+     * Returns the <a href="http://www.xycoon.com/SumOfSquares.htm">
+     * sum of squared errors</a> (SSE) associated with the regression
+     * model.
+     * <p>
+     * The sum is computed using the computational formula</p>
+     * <p>
+     * <code>SSE = SYY - (SXY * SXY / SXX)</code></p>
+     * <p>
+     * where <code>SYY</code> is the sum of the squared deviations of the y
+     * values about their mean, <code>SXX</code> is similarly defined and
+     * <code>SXY</code> is the sum of the products of x and y mean deviations.
+     * </p><p>
+     * The sums are accumulated using the updating algorithm referenced in
+     * {@link #addData}.</p>
+     * <p>
+     * The return value is constrained to be non-negative - i.e., if due to
+     * rounding errors the computational formula returns a negative result,
+     * 0 is returned.</p>
+     * <p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>At least two observations (with at least two different x values)
+     * must have been added before invoking this method. If this method is
+     * invoked before a model can be estimated, <code>Double,NaN</code> is
+     * returned.
+     * </li></ul></p>
+     *
+     * @return sum of squared errors associated with the regression model
+     */
+    public double getSumSquaredErrors() {
+        return FastMath.max(0d, sumYY - sumXY * sumXY / sumXX);
+    }
+
+    /**
+     * Returns the sum of squared deviations of the y values about their mean.
+     * <p>
+     * This is defined as SSTO
+     * <a href="http://www.xycoon.com/SumOfSquares.htm">here</a>.</p>
+     * <p>
+     * If <code>n < 2</code>, this returns <code>Double.NaN</code>.</p>
+     *
+     * @return sum of squared deviations of y values
+     */
+    public double getTotalSumSquares() {
+        if (n < 2) {
+            return Double.NaN;
+        }
+        return sumYY;
+    }
+
+    /**
+     * Returns the sum of squared deviations of the x values about their mean.
+     *
+     * If <code>n < 2</code>, this returns <code>Double.NaN</code>.</p>
+     *
+     * @return sum of squared deviations of x values
+     */
+    public double getXSumSquares() {
+        if (n < 2) {
+            return Double.NaN;
+        }
+        return sumXX;
+    }
+
+    /**
+     * Returns the sum of crossproducts, x<sub>i</sub>*y<sub>i</sub>.
+     *
+     * @return sum of cross products
+     */
+    public double getSumOfCrossProducts() {
+        return sumXY;
+    }
+
+    /**
+     * Returns the sum of squared deviations of the predicted y values about
+     * their mean (which equals the mean of y).
+     * <p>
+     * This is usually abbreviated SSR or SSM.  It is defined as SSM
+     * <a href="http://www.xycoon.com/SumOfSquares.htm">here</a></p>
+     * <p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>At least two observations (with at least two different x values)
+     * must have been added before invoking this method. If this method is
+     * invoked before a model can be estimated, <code>Double.NaN</code> is
+     * returned.
+     * </li></ul></p>
+     *
+     * @return sum of squared deviations of predicted y values
+     */
+    public double getRegressionSumSquares() {
+        return getRegressionSumSquares(getSlope());
+    }
+
+    /**
+     * Returns the sum of squared errors divided by the degrees of freedom,
+     * usually abbreviated MSE.
+     * <p>
+     * If there are fewer than <strong>three</strong> data pairs in the model,
+     * or if there is no variation in <code>x</code>, this returns
+     * <code>Double.NaN</code>.</p>
+     *
+     * @return sum of squared deviations of y values
+     */
+    public double getMeanSquareError() {
+        if (n < 3) {
+            return Double.NaN;
+        }
+        return hasIntercept ? (getSumSquaredErrors() / (n - 2)) : (getSumSquaredErrors() / (n - 1));
+    }
+
+    /**
+     * Returns <a href="http://mathworld.wolfram.com/CorrelationCoefficient.html">
+     * Pearson's product moment correlation coefficient</a>,
+     * usually denoted r.
+     * <p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>At least two observations (with at least two different x values)
+     * must have been added before invoking this method. If this method is
+     * invoked before a model can be estimated, <code>Double,NaN</code> is
+     * returned.
+     * </li></ul></p>
+     *
+     * @return Pearson's r
+     */
+    public double getR() {
+        double b1 = getSlope();
+        double result = FastMath.sqrt(getRSquare());
+        if (b1 < 0) {
+            result = -result;
+        }
+        return result;
+    }
+
+    /**
+     * Returns the <a href="http://www.xycoon.com/coefficient1.htm">
+     * coefficient of determination</a>,
+     * usually denoted r-square.
+     * <p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>At least two observations (with at least two different x values)
+     * must have been added before invoking this method. If this method is
+     * invoked before a model can be estimated, <code>Double,NaN</code> is
+     * returned.
+     * </li></ul></p>
+     *
+     * @return r-square
+     */
+    public double getRSquare() {
+        double ssto = getTotalSumSquares();
+        return (ssto - getSumSquaredErrors()) / ssto;
+    }
+
+    /**
+     * Returns the <a href="http://www.xycoon.com/standarderrorb0.htm">
+     * standard error of the intercept estimate</a>,
+     * usually denoted s(b0).
+     * <p>
+     * If there are fewer that <strong>three</strong> observations in the
+     * model, or if there is no variation in x, this returns
+     * <code>Double.NaN</code>.</p> Additionally, a <code>Double.NaN</code> is
+     * returned when the intercept is constrained to be zero
+     *
+     * @return standard error associated with intercept estimate
+     */
+    public double getInterceptStdErr() {
+        if( !hasIntercept ){
+            return Double.NaN;
+        }
+        return FastMath.sqrt(
+            getMeanSquareError() * ((1d / n) + (xbar * xbar) / sumXX));
+    }
+
+    /**
+     * Returns the <a href="http://www.xycoon.com/standerrorb(1).htm">standard
+     * error of the slope estimate</a>,
+     * usually denoted s(b1).
+     * <p>
+     * If there are fewer that <strong>three</strong> data pairs in the model,
+     * or if there is no variation in x, this returns <code>Double.NaN</code>.
+     * </p>
+     *
+     * @return standard error associated with slope estimate
+     */
+    public double getSlopeStdErr() {
+        return FastMath.sqrt(getMeanSquareError() / sumXX);
+    }
+
+    /**
+     * Returns the half-width of a 95% confidence interval for the slope
+     * estimate.
+     * <p>
+     * The 95% confidence interval is</p>
+     * <p>
+     * <code>(getSlope() - getSlopeConfidenceInterval(),
+     * getSlope() + getSlopeConfidenceInterval())</code></p>
+     * <p>
+     * If there are fewer that <strong>three</strong> observations in the
+     * model, or if there is no variation in x, this returns
+     * <code>Double.NaN</code>.</p>
+     * <p>
+     * <strong>Usage Note</strong>:<br>
+     * The validity of this statistic depends on the assumption that the
+     * observations included in the model are drawn from a
+     * <a href="http://mathworld.wolfram.com/BivariateNormalDistribution.html">
+     * Bivariate Normal Distribution</a>.</p>
+     *
+     * @return half-width of 95% confidence interval for the slope estimate
+     * @throws OutOfRangeException if the confidence interval can not be computed.
+     */
+    public double getSlopeConfidenceInterval() throws OutOfRangeException {
+        return getSlopeConfidenceInterval(0.05d);
+    }
+
+    /**
+     * Returns the half-width of a (100-100*alpha)% confidence interval for
+     * the slope estimate.
+     * <p>
+     * The (100-100*alpha)% confidence interval is </p>
+     * <p>
+     * <code>(getSlope() - getSlopeConfidenceInterval(),
+     * getSlope() + getSlopeConfidenceInterval())</code></p>
+     * <p>
+     * To request, for example, a 99% confidence interval, use
+     * <code>alpha = .01</code></p>
+     * <p>
+     * <strong>Usage Note</strong>:<br>
+     * The validity of this statistic depends on the assumption that the
+     * observations included in the model are drawn from a
+     * <a href="http://mathworld.wolfram.com/BivariateNormalDistribution.html">
+     * Bivariate Normal Distribution</a>.</p>
+     * <p>
+     * <strong> Preconditions:</strong><ul>
+     * <li>If there are fewer that <strong>three</strong> observations in the
+     * model, or if there is no variation in x, this returns
+     * <code>Double.NaN</code>.
+     * </li>
+     * <li><code>(0 < alpha < 1)</code>; otherwise an
+     * <code>OutOfRangeException</code> is thrown.
+     * </li></ul></p>
+     *
+     * @param alpha the desired significance level
+     * @return half-width of 95% confidence interval for the slope estimate
+     * @throws OutOfRangeException if the confidence interval can not be computed.
+     */
+    public double getSlopeConfidenceInterval(final double alpha)
+    throws OutOfRangeException {
+        if (n < 3) {
+            return Double.NaN;
+        }
+        if (alpha >= 1 || alpha <= 0) {
+            throw new OutOfRangeException(LocalizedFormats.SIGNIFICANCE_LEVEL,
+                                          alpha, 0, 1);
+        }
+        // No advertised NotStrictlyPositiveException here - will return NaN above
+        TDistribution distribution = new TDistribution(n - 2);
+        return getSlopeStdErr() *
+            distribution.inverseCumulativeProbability(1d - alpha / 2d);
+    }
+
+    /**
+     * Returns the significance level of the slope (equiv) correlation.
+     * <p>
+     * Specifically, the returned value is the smallest <code>alpha</code>
+     * such that the slope confidence interval with significance level
+     * equal to <code>alpha</code> does not include <code>0</code>.
+     * On regression output, this is often denoted <code>Prob(|t| > 0)</code>
+     * </p><p>
+     * <strong>Usage Note</strong>:<br>
+     * The validity of this statistic depends on the assumption that the
+     * observations included in the model are drawn from a
+     * <a href="http://mathworld.wolfram.com/BivariateNormalDistribution.html">
+     * Bivariate Normal Distribution</a>.</p>
+     * <p>
+     * If there are fewer that <strong>three</strong> observations in the
+     * model, or if there is no variation in x, this returns
+     * <code>Double.NaN</code>.</p>
+     *
+     * @return significance level for slope/correlation
+     * @throws org.apache.commons.math3.exception.MaxCountExceededException
+     * if the significance level can not be computed.
+     */
+    public double getSignificance() {
+        if (n < 3) {
+            return Double.NaN;
+        }
+        // No advertised NotStrictlyPositiveException here - will return NaN above
+        TDistribution distribution = new TDistribution(n - 2);
+        return 2d * (1.0 - distribution.cumulativeProbability(
+                    FastMath.abs(getSlope()) / getSlopeStdErr()));
+    }
+
+    // ---------------------Private methods-----------------------------------
+
+    /**
+    * Returns the intercept of the estimated regression line, given the slope.
+    * <p>
+    * Will return <code>NaN</code> if slope is <code>NaN</code>.</p>
+    *
+    * @param slope current slope
+    * @return the intercept of the regression line
+    */
+    private double getIntercept(final double slope) {
+      if( hasIntercept){
+        return (sumY - slope * sumX) / n;
+      }
+      return 0.0;
+    }
+
+    /**
+     * Computes SSR from b1.
+     *
+     * @param slope regression slope estimate
+     * @return sum of squared deviations of predicted y values
+     */
+    private double getRegressionSumSquares(final double slope) {
+        return slope * slope * sumXX;
+    }
+
+    /**
+     * Performs a regression on data present in buffers and outputs a RegressionResults object.
+     *
+     * <p>If there are fewer than 3 observations in the model and {@code hasIntercept} is true
+     * a {@code NoDataException} is thrown.  If there is no intercept term, the model must
+     * contain at least 2 observations.</p>
+     *
+     * @return RegressionResults acts as a container of regression output
+     * @throws ModelSpecificationException if the model is not correctly specified
+     * @throws NoDataException if there is not sufficient data in the model to
+     * estimate the regression parameters
+     */
+    public RegressionResults regress() throws ModelSpecificationException, NoDataException {
+        if (hasIntercept) {
+            if (n < 3) {
+                throw new NoDataException(LocalizedFormats.NOT_ENOUGH_DATA_REGRESSION);
+            }
+            if (FastMath.abs(sumXX) > Precision.SAFE_MIN) {
+                final double[] params = new double[] { getIntercept(), getSlope() };
+                final double mse = getMeanSquareError();
+                final double _syy = sumYY + sumY * sumY / n;
+                final double[] vcv = new double[] { mse * (xbar * xbar / sumXX + 1.0 / n), -xbar * mse / sumXX, mse / sumXX };
+                return new RegressionResults(params, new double[][] { vcv }, true, n, 2, sumY, _syy, getSumSquaredErrors(), true,
+                        false);
+            } else {
+                final double[] params = new double[] { sumY / n, Double.NaN };
+                // final double mse = getMeanSquareError();
+                final double[] vcv = new double[] { ybar / (n - 1.0), Double.NaN, Double.NaN };
+                return new RegressionResults(params, new double[][] { vcv }, true, n, 1, sumY, sumYY, getSumSquaredErrors(), true,
+                        false);
+            }
+        } else {
+            if (n < 2) {
+                throw new NoDataException(LocalizedFormats.NOT_ENOUGH_DATA_REGRESSION);
+            }
+            if (!Double.isNaN(sumXX)) {
+                final double[] vcv = new double[] { getMeanSquareError() / sumXX };
+                final double[] params = new double[] { sumXY / sumXX };
+                return new RegressionResults(params, new double[][] { vcv }, true, n, 1, sumY, sumYY, getSumSquaredErrors(), false,
+                        false);
+            } else {
+                final double[] vcv = new double[] { Double.NaN };
+                final double[] params = new double[] { Double.NaN };
+                return new RegressionResults(params, new double[][] { vcv }, true, n, 1, Double.NaN, Double.NaN, Double.NaN, false,
+                        false);
+            }
+        }
+    }
+
+    /**
+     * Performs a regression on data present in buffers including only regressors
+     * indexed in variablesToInclude and outputs a RegressionResults object
+     * @param variablesToInclude an array of indices of regressors to include
+     * @return RegressionResults acts as a container of regression output
+     * @throws MathIllegalArgumentException if the variablesToInclude array is null or zero length
+     * @throws OutOfRangeException if a requested variable is not present in model
+     */
+    public RegressionResults regress(int[] variablesToInclude) throws MathIllegalArgumentException{
+        if( variablesToInclude == null || variablesToInclude.length == 0){
+          throw new MathIllegalArgumentException(LocalizedFormats.ARRAY_ZERO_LENGTH_OR_NULL_NOT_ALLOWED);
+        }
+        if( variablesToInclude.length > 2 || (variablesToInclude.length > 1 && !hasIntercept) ){
+            throw new ModelSpecificationException(
+                    LocalizedFormats.ARRAY_SIZE_EXCEEDS_MAX_VARIABLES,
+                    (variablesToInclude.length > 1 && !hasIntercept) ? 1 : 2);
+        }
+
+        if( hasIntercept ){
+            if( variablesToInclude.length == 2 ){
+                if( variablesToInclude[0] == 1 ){
+                    throw new ModelSpecificationException(LocalizedFormats.NOT_INCREASING_SEQUENCE);
+                }else if( variablesToInclude[0] != 0 ){
+                    throw new OutOfRangeException( variablesToInclude[0], 0,1 );
+                }
+                if( variablesToInclude[1] != 1){
+                     throw new OutOfRangeException( variablesToInclude[0], 0,1 );
+                }
+                return regress();
+            }else{
+                if( variablesToInclude[0] != 1 && variablesToInclude[0] != 0 ){
+                     throw new OutOfRangeException( variablesToInclude[0],0,1 );
+                }
+                final double _mean = sumY * sumY / n;
+                final double _syy = sumYY + _mean;
+                if( variablesToInclude[0] == 0 ){
+                    //just the mean
+                    final double[] vcv = new double[]{ sumYY/(((n-1)*n)) };
+                    final double[] params = new double[]{ ybar };
+                    return new RegressionResults(
+                      params, new double[][]{vcv}, true, n, 1,
+                      sumY, _syy+_mean, sumYY,true,false);
+
+                }else if( variablesToInclude[0] == 1){
+                    //final double _syy = sumYY + sumY * sumY / ((double) n);
+                    final double _sxx = sumXX + sumX * sumX / n;
+                    final double _sxy = sumXY + sumX * sumY / n;
+                    final double _sse = FastMath.max(0d, _syy - _sxy * _sxy / _sxx);
+                    final double _mse = _sse/((n-1));
+                    if( !Double.isNaN(_sxx) ){
+                        final double[] vcv = new double[]{ _mse / _sxx };
+                        final double[] params = new double[]{ _sxy/_sxx };
+                        return new RegressionResults(
+                                    params, new double[][]{vcv}, true, n, 1,
+                                    sumY, _syy, _sse,false,false);
+                    }else{
+                        final double[] vcv = new double[]{Double.NaN };
+                        final double[] params = new double[]{ Double.NaN };
+                        return new RegressionResults(
+                                    params, new double[][]{vcv}, true, n, 1,
+                                    Double.NaN, Double.NaN, Double.NaN,false,false);
+                    }
+                }
+            }
+        }else{
+            if( variablesToInclude[0] != 0 ){
+                throw new OutOfRangeException(variablesToInclude[0],0,0);
+            }
+            return regress();
+        }
+
+        return null;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/regression/UpdatingMultipleLinearRegression.java b/src/main/java/org/apache/commons/math3/stat/regression/UpdatingMultipleLinearRegression.java
new file mode 100644
index 0000000..ebefc31
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/regression/UpdatingMultipleLinearRegression.java
@@ -0,0 +1,93 @@
+/*
+ * 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.math3.stat.regression;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NoDataException;
+
+/**
+ * An interface for regression models allowing for dynamic updating of the data.
+ * That is, the entire data set need not be loaded into memory. As observations
+ * become available, they can be added to the regression  model and an updated
+ * estimate regression statistics can be calculated.
+ *
+ * @since 3.0
+ */
+public interface UpdatingMultipleLinearRegression {
+
+    /**
+     * Returns true if a constant has been included false otherwise.
+     *
+     * @return true if constant exists, false otherwise
+     */
+    boolean hasIntercept();
+
+    /**
+     * Returns the number of observations added to the regression model.
+     *
+     * @return Number of observations
+     */
+    long getN();
+
+    /**
+     * Adds one observation to the regression model.
+     *
+     * @param x the independent variables which form the design matrix
+     * @param y the dependent or response variable
+     * @throws ModelSpecificationException if the length of {@code x} does not equal
+     * the number of independent variables in the model
+     */
+    void addObservation(double[] x, double y) throws ModelSpecificationException;
+
+    /**
+     * Adds a series of observations to the regression model. The lengths of
+     * x and y must be the same and x must be rectangular.
+     *
+     * @param x a series of observations on the independent variables
+     * @param y a series of observations on the dependent variable
+     * The length of x and y must be the same
+     * @throws ModelSpecificationException if {@code x} is not rectangular, does not match
+     * the length of {@code y} or does not contain sufficient data to estimate the model
+     */
+    void addObservations(double[][] x, double[] y) throws ModelSpecificationException;
+
+    /**
+     * Clears internal buffers and resets the regression model. This means all
+     * data and derived values are initialized
+     */
+    void clear();
+
+
+    /**
+     * Performs a regression on data present in buffers and outputs a RegressionResults object
+     * @return RegressionResults acts as a container of regression output
+     * @throws ModelSpecificationException if the model is not correctly specified
+     * @throws NoDataException if there is not sufficient data in the model to
+     * estimate the regression parameters
+     */
+    RegressionResults regress() throws ModelSpecificationException, NoDataException;
+
+    /**
+     * Performs a regression on data present in buffers including only regressors
+     * indexed in variablesToInclude and outputs a RegressionResults object
+     * @param variablesToInclude an array of indices of regressors to include
+     * @return RegressionResults acts as a container of regression output
+     * @throws ModelSpecificationException if the model is not correctly specified
+     * @throws MathIllegalArgumentException if the variablesToInclude array is null or zero length
+     */
+    RegressionResults regress(int[] variablesToInclude) throws ModelSpecificationException, MathIllegalArgumentException;
+}
diff --git a/src/main/java/org/apache/commons/math3/stat/regression/package-info.java b/src/main/java/org/apache/commons/math3/stat/regression/package-info.java
new file mode 100644
index 0000000..fbc0e12
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/stat/regression/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+/**
+ *
+ *      Statistical routines involving multivariate data.
+ *
+ */
+package org.apache.commons.math3.stat.regression;
diff --git a/src/main/java/org/apache/commons/math3/transform/DctNormalization.java b/src/main/java/org/apache/commons/math3/transform/DctNormalization.java
new file mode 100644
index 0000000..2f38d04
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/transform/DctNormalization.java
@@ -0,0 +1,62 @@
+/*
+ * 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.math3.transform;
+
+/**
+ * This enumeration defines the various types of normalizations that can be applied to discrete
+ * cosine transforms (DCT). The exact definition of these normalizations is detailed below.
+ *
+ * @see FastCosineTransformer
+ * @since 3.0
+ */
+public enum DctNormalization {
+    /**
+     * Should be passed to the constructor of {@link FastCosineTransformer} to use the
+     * <em>standard</em> normalization convention. The standard DCT-I normalization convention is
+     * defined as follows
+     *
+     * <ul>
+     *   <li>forward transform: y<sub>n</sub> = (1/2) [x<sub>0</sub> +
+     *       (-1)<sup>n</sup>x<sub>N-1</sub>] + &sum;<sub>k=1</sub><sup>N-2</sup> x<sub>k</sub>
+     *       cos[&pi; nk / (N - 1)],
+     *   <li>inverse transform: x<sub>k</sub> = [1 / (N - 1)] [y<sub>0</sub> +
+     *       (-1)<sup>k</sup>y<sub>N-1</sub>] + [2 / (N - 1)] &sum;<sub>n=1</sub><sup>N-2</sup>
+     *       y<sub>n</sub> cos[&pi; nk / (N - 1)],
+     * </ul>
+     *
+     * where N is the size of the data sample.
+     */
+    STANDARD_DCT_I,
+
+    /**
+     * Should be passed to the constructor of {@link FastCosineTransformer} to use the
+     * <em>orthogonal</em> normalization convention. The orthogonal DCT-I normalization convention
+     * is defined as follows
+     *
+     * <ul>
+     *   <li>forward transform: y<sub>n</sub> = [2(N - 1)]<sup>-1/2</sup> [x<sub>0</sub> +
+     *       (-1)<sup>n</sup>x<sub>N-1</sub>] + [2 / (N - 1)]<sup>1/2</sup>
+     *       &sum;<sub>k=1</sub><sup>N-2</sup> x<sub>k</sub> cos[&pi; nk / (N - 1)],
+     *   <li>inverse transform: x<sub>k</sub> = [2(N - 1)]<sup>-1/2</sup> [y<sub>0</sub> +
+     *       (-1)<sup>k</sup>y<sub>N-1</sub>] + [2 / (N - 1)]<sup>1/2</sup>
+     *       &sum;<sub>n=1</sub><sup>N-2</sup> y<sub>n</sub> cos[&pi; nk / (N - 1)],
+     * </ul>
+     *
+     * which makes the transform orthogonal. N is the size of the data sample.
+     */
+    ORTHOGONAL_DCT_I;
+}
diff --git a/src/main/java/org/apache/commons/math3/transform/DftNormalization.java b/src/main/java/org/apache/commons/math3/transform/DftNormalization.java
new file mode 100644
index 0000000..db0eddb
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/transform/DftNormalization.java
@@ -0,0 +1,58 @@
+/*
+ * 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.math3.transform;
+
+/**
+ * This enumeration defines the various types of normalizations that can be applied to discrete
+ * Fourier transforms (DFT). The exact definition of these normalizations is detailed below.
+ *
+ * @see FastFourierTransformer
+ * @since 3.0
+ */
+public enum DftNormalization {
+    /**
+     * Should be passed to the constructor of {@link FastFourierTransformer} to use the
+     * <em>standard</em> normalization convention. This normalization convention is defined as
+     * follows
+     *
+     * <ul>
+     *   <li>forward transform: y<sub>n</sub> = &sum;<sub>k=0</sub><sup>N-1</sup> x<sub>k</sub>
+     *       exp(-2&pi;i n k / N),
+     *   <li>inverse transform: x<sub>k</sub> = N<sup>-1</sup> &sum;<sub>n=0</sub><sup>N-1</sup>
+     *       y<sub>n</sub> exp(2&pi;i n k / N),
+     * </ul>
+     *
+     * where N is the size of the data sample.
+     */
+    STANDARD,
+
+    /**
+     * Should be passed to the constructor of {@link FastFourierTransformer} to use the
+     * <em>unitary</em> normalization convention. This normalization convention is defined as
+     * follows
+     *
+     * <ul>
+     *   <li>forward transform: y<sub>n</sub> = (1 / &radic;N) &sum;<sub>k=0</sub><sup>N-1</sup>
+     *       x<sub>k</sub> exp(-2&pi;i n k / N),
+     *   <li>inverse transform: x<sub>k</sub> = (1 / &radic;N) &sum;<sub>n=0</sub><sup>N-1</sup>
+     *       y<sub>n</sub> exp(2&pi;i n k / N),
+     * </ul>
+     *
+     * which makes the transform unitary. N is the size of the data sample.
+     */
+    UNITARY;
+}
diff --git a/src/main/java/org/apache/commons/math3/transform/DstNormalization.java b/src/main/java/org/apache/commons/math3/transform/DstNormalization.java
new file mode 100644
index 0000000..0aba09e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/transform/DstNormalization.java
@@ -0,0 +1,59 @@
+/*
+ * 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.math3.transform;
+
+/**
+ * This enumeration defines the various types of normalizations that can be applied to discrete sine
+ * transforms (DST). The exact definition of these normalizations is detailed below.
+ *
+ * @see FastSineTransformer
+ * @since 3.0
+ */
+public enum DstNormalization {
+    /**
+     * Should be passed to the constructor of {@link FastSineTransformer} to use the
+     * <em>standard</em> normalization convention. The standard DST-I normalization convention is
+     * defined as follows
+     *
+     * <ul>
+     *   <li>forward transform: y<sub>n</sub> = &sum;<sub>k=0</sub><sup>N-1</sup> x<sub>k</sub>
+     *       sin(&pi; nk / N),
+     *   <li>inverse transform: x<sub>k</sub> = (2 / N) &sum;<sub>n=0</sub><sup>N-1</sup>
+     *       y<sub>n</sub> sin(&pi; nk / N),
+     * </ul>
+     *
+     * where N is the size of the data sample, and x<sub>0</sub> = 0.
+     */
+    STANDARD_DST_I,
+
+    /**
+     * Should be passed to the constructor of {@link FastSineTransformer} to use the
+     * <em>orthogonal</em> normalization convention. The orthogonal DCT-I normalization convention
+     * is defined as follows
+     *
+     * <ul>
+     *   <li>Forward transform: y<sub>n</sub> = &radic;(2 / N) &sum;<sub>k=0</sub><sup>N-1</sup>
+     *       x<sub>k</sub> sin(&pi; nk / N),
+     *   <li>Inverse transform: x<sub>k</sub> = &radic;(2 / N) &sum;<sub>n=0</sub><sup>N-1</sup>
+     *       y<sub>n</sub> sin(&pi; nk / N),
+     * </ul>
+     *
+     * which makes the transform orthogonal. N is the size of the data sample, and x<sub>0</sub> =
+     * 0.
+     */
+    ORTHOGONAL_DST_I
+}
diff --git a/src/main/java/org/apache/commons/math3/transform/FastCosineTransformer.java b/src/main/java/org/apache/commons/math3/transform/FastCosineTransformer.java
new file mode 100644
index 0000000..1e73187
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/transform/FastCosineTransformer.java
@@ -0,0 +1,177 @@
+/*
+ * 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.math3.transform;
+
+import org.apache.commons.math3.analysis.FunctionUtils;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.complex.Complex;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.ArithmeticUtils;
+import org.apache.commons.math3.util.FastMath;
+
+import java.io.Serializable;
+
+/**
+ * Implements the Fast Cosine Transform for transformation of one-dimensional real data sets. For
+ * reference, see James S. Walker, <em>Fast Fourier Transforms</em>, chapter 3 (ISBN 0849371635).
+ *
+ * <p>There are several variants of the discrete cosine transform. The present implementation
+ * corresponds to DCT-I, with various normalization conventions, which are specified by the
+ * parameter {@link DctNormalization}.
+ *
+ * <p>DCT-I is equivalent to DFT of an <em>even extension</em> of the data series. More precisely,
+ * if x<sub>0</sub>, &hellip;, x<sub>N-1</sub> is the data set to be cosine transformed, the
+ * extended data set x<sub>0</sub><sup>&#35;</sup>, &hellip;, x<sub>2N-3</sub><sup>&#35;</sup> is
+ * defined as follows
+ *
+ * <ul>
+ *   <li>x<sub>k</sub><sup>&#35;</sup> = x<sub>k</sub> if 0 &le; k &lt; N,
+ *   <li>x<sub>k</sub><sup>&#35;</sup> = x<sub>2N-2-k</sub> if N &le; k &lt; 2N - 2.
+ * </ul>
+ *
+ * <p>Then, the standard DCT-I y<sub>0</sub>, &hellip;, y<sub>N-1</sub> of the real data set
+ * x<sub>0</sub>, &hellip;, x<sub>N-1</sub> is equal to <em>half</em> of the N first elements of the
+ * DFT of the extended data set x<sub>0</sub><sup>&#35;</sup>, &hellip;,
+ * x<sub>2N-3</sub><sup>&#35;</sup> <br>
+ * y<sub>n</sub> = (1 / 2) &sum;<sub>k=0</sub><sup>2N-3</sup> x<sub>k</sub><sup>&#35;</sup>
+ * exp[-2&pi;i nk / (2N - 2)] &nbsp;&nbsp;&nbsp;&nbsp;k = 0, &hellip;, N-1.
+ *
+ * <p>The present implementation of the discrete cosine transform as a fast cosine transform
+ * requires the length of the data set to be a power of two plus one
+ * (N&nbsp;=&nbsp;2<sup>n</sup>&nbsp;+&nbsp;1). Besides, it implicitly assumes that the sampled
+ * function is even.
+ *
+ * @since 1.2
+ */
+public class FastCosineTransformer implements RealTransformer, Serializable {
+
+    /** Serializable version identifier. */
+    static final long serialVersionUID = 20120212L;
+
+    /** The type of DCT to be performed. */
+    private final DctNormalization normalization;
+
+    /**
+     * Creates a new instance of this class, with various normalization conventions.
+     *
+     * @param normalization the type of normalization to be applied to the transformed data
+     */
+    public FastCosineTransformer(final DctNormalization normalization) {
+        this.normalization = normalization;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws MathIllegalArgumentException if the length of the data array is not a power of two
+     *     plus one
+     */
+    public double[] transform(final double[] f, final TransformType type)
+            throws MathIllegalArgumentException {
+        if (type == TransformType.FORWARD) {
+            if (normalization == DctNormalization.ORTHOGONAL_DCT_I) {
+                final double s = FastMath.sqrt(2.0 / (f.length - 1));
+                return TransformUtils.scaleArray(fct(f), s);
+            }
+            return fct(f);
+        }
+        final double s2 = 2.0 / (f.length - 1);
+        final double s1;
+        if (normalization == DctNormalization.ORTHOGONAL_DCT_I) {
+            s1 = FastMath.sqrt(s2);
+        } else {
+            s1 = s2;
+        }
+        return TransformUtils.scaleArray(fct(f), s1);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws org.apache.commons.math3.exception.NonMonotonicSequenceException if the lower bound
+     *     is greater than, or equal to the upper bound
+     * @throws org.apache.commons.math3.exception.NotStrictlyPositiveException if the number of
+     *     sample points is negative
+     * @throws MathIllegalArgumentException if the number of sample points is not a power of two
+     *     plus one
+     */
+    public double[] transform(
+            final UnivariateFunction f,
+            final double min,
+            final double max,
+            final int n,
+            final TransformType type)
+            throws MathIllegalArgumentException {
+
+        final double[] data = FunctionUtils.sample(f, min, max, n);
+        return transform(data, type);
+    }
+
+    /**
+     * Perform the FCT algorithm (including inverse).
+     *
+     * @param f the real data array to be transformed
+     * @return the real transformed array
+     * @throws MathIllegalArgumentException if the length of the data array is not a power of two
+     *     plus one
+     */
+    protected double[] fct(double[] f) throws MathIllegalArgumentException {
+
+        final double[] transformed = new double[f.length];
+
+        final int n = f.length - 1;
+        if (!ArithmeticUtils.isPowerOfTwo(n)) {
+            throw new MathIllegalArgumentException(
+                    LocalizedFormats.NOT_POWER_OF_TWO_PLUS_ONE, Integer.valueOf(f.length));
+        }
+        if (n == 1) { // trivial case
+            transformed[0] = 0.5 * (f[0] + f[1]);
+            transformed[1] = 0.5 * (f[0] - f[1]);
+            return transformed;
+        }
+
+        // construct a new array and perform FFT on it
+        final double[] x = new double[n];
+        x[0] = 0.5 * (f[0] + f[n]);
+        x[n >> 1] = f[n >> 1];
+        // temporary variable for transformed[1]
+        double t1 = 0.5 * (f[0] - f[n]);
+        for (int i = 1; i < (n >> 1); i++) {
+            final double a = 0.5 * (f[i] + f[n - i]);
+            final double b = FastMath.sin(i * FastMath.PI / n) * (f[i] - f[n - i]);
+            final double c = FastMath.cos(i * FastMath.PI / n) * (f[i] - f[n - i]);
+            x[i] = a - b;
+            x[n - i] = a + b;
+            t1 += c;
+        }
+        FastFourierTransformer transformer;
+        transformer = new FastFourierTransformer(DftNormalization.STANDARD);
+        Complex[] y = transformer.transform(x, TransformType.FORWARD);
+
+        // reconstruct the FCT result for the original array
+        transformed[0] = y[0].getReal();
+        transformed[1] = t1;
+        for (int i = 1; i < (n >> 1); i++) {
+            transformed[2 * i] = y[i].getReal();
+            transformed[2 * i + 1] = transformed[2 * i - 1] - y[i].getImaginary();
+        }
+        transformed[n] = y[n >> 1].getReal();
+
+        return transformed;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/transform/FastFourierTransformer.java b/src/main/java/org/apache/commons/math3/transform/FastFourierTransformer.java
new file mode 100644
index 0000000..116b21d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/transform/FastFourierTransformer.java
@@ -0,0 +1,749 @@
+/*
+ * 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.math3.transform;
+
+import org.apache.commons.math3.analysis.FunctionUtils;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.complex.Complex;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.ArithmeticUtils;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.util.MathArrays;
+
+import java.io.Serializable;
+import java.lang.reflect.Array;
+
+/**
+ * Implements the Fast Fourier Transform for transformation of one-dimensional real or complex data
+ * sets. For reference, see <em>Applied Numerical Linear Algebra</em>, ISBN 0898713897, chapter 6.
+ *
+ * <p>There are several variants of the discrete Fourier transform, with various normalization
+ * conventions, which are specified by the parameter {@link DftNormalization}.
+ *
+ * <p>The current implementation of the discrete Fourier transform as a fast Fourier transform
+ * requires the length of the data set to be a power of 2. This greatly simplifies and speeds up the
+ * code. Users can pad the data with zeros to meet this requirement. There are other flavors of FFT,
+ * for reference, see S. Winograd, <i>On computing the discrete Fourier transform</i>, Mathematics
+ * of Computation, 32 (1978), 175 - 199.
+ *
+ * @see DftNormalization
+ * @since 1.2
+ */
+public class FastFourierTransformer implements Serializable {
+
+    /** Serializable version identifier. */
+    static final long serialVersionUID = 20120210L;
+
+    /**
+     * {@code W_SUB_N_R[i]} is the real part of {@code exp(- 2 * i * pi / n)}: {@code W_SUB_N_R[i] =
+     * cos(2 * pi/ n)}, where {@code n = 2^i}.
+     */
+    private static final double[] W_SUB_N_R = {
+        0x1.0p0,
+        -0x1.0p0,
+        0x1.1a62633145c07p-54,
+        0x1.6a09e667f3bcdp-1,
+        0x1.d906bcf328d46p-1,
+        0x1.f6297cff75cbp-1,
+        0x1.fd88da3d12526p-1,
+        0x1.ff621e3796d7ep-1,
+        0x1.ffd886084cd0dp-1,
+        0x1.fff62169b92dbp-1,
+        0x1.fffd8858e8a92p-1,
+        0x1.ffff621621d02p-1,
+        0x1.ffffd88586ee6p-1,
+        0x1.fffff62161a34p-1,
+        0x1.fffffd8858675p-1,
+        0x1.ffffff621619cp-1,
+        0x1.ffffffd885867p-1,
+        0x1.fffffff62161ap-1,
+        0x1.fffffffd88586p-1,
+        0x1.ffffffff62162p-1,
+        0x1.ffffffffd8858p-1,
+        0x1.fffffffff6216p-1,
+        0x1.fffffffffd886p-1,
+        0x1.ffffffffff621p-1,
+        0x1.ffffffffffd88p-1,
+        0x1.fffffffffff62p-1,
+        0x1.fffffffffffd9p-1,
+        0x1.ffffffffffff6p-1,
+        0x1.ffffffffffffep-1,
+        0x1.fffffffffffffp-1,
+        0x1.0p0,
+        0x1.0p0,
+        0x1.0p0,
+        0x1.0p0,
+        0x1.0p0,
+        0x1.0p0,
+        0x1.0p0,
+        0x1.0p0,
+        0x1.0p0,
+        0x1.0p0,
+        0x1.0p0,
+        0x1.0p0,
+        0x1.0p0,
+        0x1.0p0,
+        0x1.0p0,
+        0x1.0p0,
+        0x1.0p0,
+        0x1.0p0,
+        0x1.0p0,
+        0x1.0p0,
+        0x1.0p0,
+        0x1.0p0,
+        0x1.0p0,
+        0x1.0p0,
+        0x1.0p0,
+        0x1.0p0,
+        0x1.0p0,
+        0x1.0p0,
+        0x1.0p0,
+        0x1.0p0,
+        0x1.0p0,
+        0x1.0p0,
+        0x1.0p0
+    };
+
+    /**
+     * {@code W_SUB_N_I[i]} is the imaginary part of {@code exp(- 2 * i * pi / n)}: {@code
+     * W_SUB_N_I[i] = -sin(2 * pi/ n)}, where {@code n = 2^i}.
+     */
+    private static final double[] W_SUB_N_I = {
+        0x1.1a62633145c07p-52,
+        -0x1.1a62633145c07p-53,
+        -0x1.0p0,
+        -0x1.6a09e667f3bccp-1,
+        -0x1.87de2a6aea963p-2,
+        -0x1.8f8b83c69a60ap-3,
+        -0x1.917a6bc29b42cp-4,
+        -0x1.91f65f10dd814p-5,
+        -0x1.92155f7a3667ep-6,
+        -0x1.921d1fcdec784p-7,
+        -0x1.921f0fe670071p-8,
+        -0x1.921f8becca4bap-9,
+        -0x1.921faaee6472dp-10,
+        -0x1.921fb2aecb36p-11,
+        -0x1.921fb49ee4ea6p-12,
+        -0x1.921fb51aeb57bp-13,
+        -0x1.921fb539ecf31p-14,
+        -0x1.921fb541ad59ep-15,
+        -0x1.921fb5439d73ap-16,
+        -0x1.921fb544197ap-17,
+        -0x1.921fb544387bap-18,
+        -0x1.921fb544403c1p-19,
+        -0x1.921fb544422c2p-20,
+        -0x1.921fb54442a83p-21,
+        -0x1.921fb54442c73p-22,
+        -0x1.921fb54442cefp-23,
+        -0x1.921fb54442d0ep-24,
+        -0x1.921fb54442d15p-25,
+        -0x1.921fb54442d17p-26,
+        -0x1.921fb54442d18p-27,
+        -0x1.921fb54442d18p-28,
+        -0x1.921fb54442d18p-29,
+        -0x1.921fb54442d18p-30,
+        -0x1.921fb54442d18p-31,
+        -0x1.921fb54442d18p-32,
+        -0x1.921fb54442d18p-33,
+        -0x1.921fb54442d18p-34,
+        -0x1.921fb54442d18p-35,
+        -0x1.921fb54442d18p-36,
+        -0x1.921fb54442d18p-37,
+        -0x1.921fb54442d18p-38,
+        -0x1.921fb54442d18p-39,
+        -0x1.921fb54442d18p-40,
+        -0x1.921fb54442d18p-41,
+        -0x1.921fb54442d18p-42,
+        -0x1.921fb54442d18p-43,
+        -0x1.921fb54442d18p-44,
+        -0x1.921fb54442d18p-45,
+        -0x1.921fb54442d18p-46,
+        -0x1.921fb54442d18p-47,
+        -0x1.921fb54442d18p-48,
+        -0x1.921fb54442d18p-49,
+        -0x1.921fb54442d18p-50,
+        -0x1.921fb54442d18p-51,
+        -0x1.921fb54442d18p-52,
+        -0x1.921fb54442d18p-53,
+        -0x1.921fb54442d18p-54,
+        -0x1.921fb54442d18p-55,
+        -0x1.921fb54442d18p-56,
+        -0x1.921fb54442d18p-57,
+        -0x1.921fb54442d18p-58,
+        -0x1.921fb54442d18p-59,
+        -0x1.921fb54442d18p-60
+    };
+
+    /** The type of DFT to be performed. */
+    private final DftNormalization normalization;
+
+    /**
+     * Creates a new instance of this class, with various normalization conventions.
+     *
+     * @param normalization the type of normalization to be applied to the transformed data
+     */
+    public FastFourierTransformer(final DftNormalization normalization) {
+        this.normalization = normalization;
+    }
+
+    /**
+     * Performs identical index bit reversal shuffles on two arrays of identical size. Each element
+     * in the array is swapped with another element based on the bit-reversal of the index. For
+     * example, in an array with length 16, item at binary index 0011 (decimal 3) would be swapped
+     * with the item at binary index 1100 (decimal 12).
+     *
+     * @param a the first array to be shuffled
+     * @param b the second array to be shuffled
+     */
+    private static void bitReversalShuffle2(double[] a, double[] b) {
+        final int n = a.length;
+        assert b.length == n;
+        final int halfOfN = n >> 1;
+
+        int j = 0;
+        for (int i = 0; i < n; i++) {
+            if (i < j) {
+                // swap indices i & j
+                double temp = a[i];
+                a[i] = a[j];
+                a[j] = temp;
+
+                temp = b[i];
+                b[i] = b[j];
+                b[j] = temp;
+            }
+
+            int k = halfOfN;
+            while (k <= j && k > 0) {
+                j -= k;
+                k >>= 1;
+            }
+            j += k;
+        }
+    }
+
+    /**
+     * Applies the proper normalization to the specified transformed data.
+     *
+     * @param dataRI the unscaled transformed data
+     * @param normalization the normalization to be applied
+     * @param type the type of transform (forward, inverse) which resulted in the specified data
+     */
+    private static void normalizeTransformedData(
+            final double[][] dataRI,
+            final DftNormalization normalization,
+            final TransformType type) {
+
+        final double[] dataR = dataRI[0];
+        final double[] dataI = dataRI[1];
+        final int n = dataR.length;
+        assert dataI.length == n;
+
+        switch (normalization) {
+            case STANDARD:
+                if (type == TransformType.INVERSE) {
+                    final double scaleFactor = 1.0 / ((double) n);
+                    for (int i = 0; i < n; i++) {
+                        dataR[i] *= scaleFactor;
+                        dataI[i] *= scaleFactor;
+                    }
+                }
+                break;
+            case UNITARY:
+                final double scaleFactor = 1.0 / FastMath.sqrt(n);
+                for (int i = 0; i < n; i++) {
+                    dataR[i] *= scaleFactor;
+                    dataI[i] *= scaleFactor;
+                }
+                break;
+            default:
+                /*
+                 * This should never occur in normal conditions. However this
+                 * clause has been added as a safeguard if other types of
+                 * normalizations are ever implemented, and the corresponding
+                 * test is forgotten in the present switch.
+                 */
+                throw new MathIllegalStateException();
+        }
+    }
+
+    /**
+     * Computes the standard transform of the specified complex data. The computation is done in
+     * place. The input data is laid out as follows
+     *
+     * <ul>
+     *   <li>{@code dataRI[0][i]} is the real part of the {@code i}-th data point,
+     *   <li>{@code dataRI[1][i]} is the imaginary part of the {@code i}-th data point.
+     * </ul>
+     *
+     * @param dataRI the two dimensional array of real and imaginary parts of the data
+     * @param normalization the normalization to be applied to the transformed data
+     * @param type the type of transform (forward, inverse) to be performed
+     * @throws DimensionMismatchException if the number of rows of the specified array is not two,
+     *     or the array is not rectangular
+     * @throws MathIllegalArgumentException if the number of data points is not a power of two
+     */
+    public static void transformInPlace(
+            final double[][] dataRI,
+            final DftNormalization normalization,
+            final TransformType type) {
+
+        if (dataRI.length != 2) {
+            throw new DimensionMismatchException(dataRI.length, 2);
+        }
+        final double[] dataR = dataRI[0];
+        final double[] dataI = dataRI[1];
+        if (dataR.length != dataI.length) {
+            throw new DimensionMismatchException(dataI.length, dataR.length);
+        }
+
+        final int n = dataR.length;
+        if (!ArithmeticUtils.isPowerOfTwo(n)) {
+            throw new MathIllegalArgumentException(
+                    LocalizedFormats.NOT_POWER_OF_TWO_CONSIDER_PADDING, Integer.valueOf(n));
+        }
+
+        if (n == 1) {
+            return;
+        } else if (n == 2) {
+            final double srcR0 = dataR[0];
+            final double srcI0 = dataI[0];
+            final double srcR1 = dataR[1];
+            final double srcI1 = dataI[1];
+
+            // X_0 = x_0 + x_1
+            dataR[0] = srcR0 + srcR1;
+            dataI[0] = srcI0 + srcI1;
+            // X_1 = x_0 - x_1
+            dataR[1] = srcR0 - srcR1;
+            dataI[1] = srcI0 - srcI1;
+
+            normalizeTransformedData(dataRI, normalization, type);
+            return;
+        }
+
+        bitReversalShuffle2(dataR, dataI);
+
+        // Do 4-term DFT.
+        if (type == TransformType.INVERSE) {
+            for (int i0 = 0; i0 < n; i0 += 4) {
+                final int i1 = i0 + 1;
+                final int i2 = i0 + 2;
+                final int i3 = i0 + 3;
+
+                final double srcR0 = dataR[i0];
+                final double srcI0 = dataI[i0];
+                final double srcR1 = dataR[i2];
+                final double srcI1 = dataI[i2];
+                final double srcR2 = dataR[i1];
+                final double srcI2 = dataI[i1];
+                final double srcR3 = dataR[i3];
+                final double srcI3 = dataI[i3];
+
+                // 4-term DFT
+                // X_0 = x_0 + x_1 + x_2 + x_3
+                dataR[i0] = srcR0 + srcR1 + srcR2 + srcR3;
+                dataI[i0] = srcI0 + srcI1 + srcI2 + srcI3;
+                // X_1 = x_0 - x_2 + j * (x_3 - x_1)
+                dataR[i1] = srcR0 - srcR2 + (srcI3 - srcI1);
+                dataI[i1] = srcI0 - srcI2 + (srcR1 - srcR3);
+                // X_2 = x_0 - x_1 + x_2 - x_3
+                dataR[i2] = srcR0 - srcR1 + srcR2 - srcR3;
+                dataI[i2] = srcI0 - srcI1 + srcI2 - srcI3;
+                // X_3 = x_0 - x_2 + j * (x_1 - x_3)
+                dataR[i3] = srcR0 - srcR2 + (srcI1 - srcI3);
+                dataI[i3] = srcI0 - srcI2 + (srcR3 - srcR1);
+            }
+        } else {
+            for (int i0 = 0; i0 < n; i0 += 4) {
+                final int i1 = i0 + 1;
+                final int i2 = i0 + 2;
+                final int i3 = i0 + 3;
+
+                final double srcR0 = dataR[i0];
+                final double srcI0 = dataI[i0];
+                final double srcR1 = dataR[i2];
+                final double srcI1 = dataI[i2];
+                final double srcR2 = dataR[i1];
+                final double srcI2 = dataI[i1];
+                final double srcR3 = dataR[i3];
+                final double srcI3 = dataI[i3];
+
+                // 4-term DFT
+                // X_0 = x_0 + x_1 + x_2 + x_3
+                dataR[i0] = srcR0 + srcR1 + srcR2 + srcR3;
+                dataI[i0] = srcI0 + srcI1 + srcI2 + srcI3;
+                // X_1 = x_0 - x_2 + j * (x_3 - x_1)
+                dataR[i1] = srcR0 - srcR2 + (srcI1 - srcI3);
+                dataI[i1] = srcI0 - srcI2 + (srcR3 - srcR1);
+                // X_2 = x_0 - x_1 + x_2 - x_3
+                dataR[i2] = srcR0 - srcR1 + srcR2 - srcR3;
+                dataI[i2] = srcI0 - srcI1 + srcI2 - srcI3;
+                // X_3 = x_0 - x_2 + j * (x_1 - x_3)
+                dataR[i3] = srcR0 - srcR2 + (srcI3 - srcI1);
+                dataI[i3] = srcI0 - srcI2 + (srcR1 - srcR3);
+            }
+        }
+
+        int lastN0 = 4;
+        int lastLogN0 = 2;
+        while (lastN0 < n) {
+            int n0 = lastN0 << 1;
+            int logN0 = lastLogN0 + 1;
+            double wSubN0R = W_SUB_N_R[logN0];
+            double wSubN0I = W_SUB_N_I[logN0];
+            if (type == TransformType.INVERSE) {
+                wSubN0I = -wSubN0I;
+            }
+
+            // Combine even/odd transforms of size lastN0 into a transform of size N0 (lastN0 * 2).
+            for (int destEvenStartIndex = 0; destEvenStartIndex < n; destEvenStartIndex += n0) {
+                int destOddStartIndex = destEvenStartIndex + lastN0;
+
+                double wSubN0ToRR = 1;
+                double wSubN0ToRI = 0;
+
+                for (int r = 0; r < lastN0; r++) {
+                    double grR = dataR[destEvenStartIndex + r];
+                    double grI = dataI[destEvenStartIndex + r];
+                    double hrR = dataR[destOddStartIndex + r];
+                    double hrI = dataI[destOddStartIndex + r];
+
+                    // dest[destEvenStartIndex + r] = Gr + WsubN0ToR * Hr
+                    dataR[destEvenStartIndex + r] = grR + wSubN0ToRR * hrR - wSubN0ToRI * hrI;
+                    dataI[destEvenStartIndex + r] = grI + wSubN0ToRR * hrI + wSubN0ToRI * hrR;
+                    // dest[destOddStartIndex + r] = Gr - WsubN0ToR * Hr
+                    dataR[destOddStartIndex + r] = grR - (wSubN0ToRR * hrR - wSubN0ToRI * hrI);
+                    dataI[destOddStartIndex + r] = grI - (wSubN0ToRR * hrI + wSubN0ToRI * hrR);
+
+                    // WsubN0ToR *= WsubN0R
+                    double nextWsubN0ToRR = wSubN0ToRR * wSubN0R - wSubN0ToRI * wSubN0I;
+                    double nextWsubN0ToRI = wSubN0ToRR * wSubN0I + wSubN0ToRI * wSubN0R;
+                    wSubN0ToRR = nextWsubN0ToRR;
+                    wSubN0ToRI = nextWsubN0ToRI;
+                }
+            }
+
+            lastN0 = n0;
+            lastLogN0 = logN0;
+        }
+
+        normalizeTransformedData(dataRI, normalization, type);
+    }
+
+    /**
+     * Returns the (forward, inverse) transform of the specified real data set.
+     *
+     * @param f the real data array to be transformed
+     * @param type the type of transform (forward, inverse) to be performed
+     * @return the complex transformed array
+     * @throws MathIllegalArgumentException if the length of the data array is not a power of two
+     */
+    public Complex[] transform(final double[] f, final TransformType type) {
+        final double[][] dataRI =
+                new double[][] {MathArrays.copyOf(f, f.length), new double[f.length]};
+
+        transformInPlace(dataRI, normalization, type);
+
+        return TransformUtils.createComplexArray(dataRI);
+    }
+
+    /**
+     * Returns the (forward, inverse) transform of the specified real function, sampled on the
+     * specified interval.
+     *
+     * @param f the function to be sampled and transformed
+     * @param min the (inclusive) lower bound for the interval
+     * @param max the (exclusive) upper bound for the interval
+     * @param n the number of sample points
+     * @param type the type of transform (forward, inverse) to be performed
+     * @return the complex transformed array
+     * @throws org.apache.commons.math3.exception.NumberIsTooLargeException if the lower bound is
+     *     greater than, or equal to the upper bound
+     * @throws org.apache.commons.math3.exception.NotStrictlyPositiveException if the number of
+     *     sample points {@code n} is negative
+     * @throws MathIllegalArgumentException if the number of sample points {@code n} is not a power
+     *     of two
+     */
+    public Complex[] transform(
+            final UnivariateFunction f,
+            final double min,
+            final double max,
+            final int n,
+            final TransformType type) {
+
+        final double[] data = FunctionUtils.sample(f, min, max, n);
+        return transform(data, type);
+    }
+
+    /**
+     * Returns the (forward, inverse) transform of the specified complex data set.
+     *
+     * @param f the complex data array to be transformed
+     * @param type the type of transform (forward, inverse) to be performed
+     * @return the complex transformed array
+     * @throws MathIllegalArgumentException if the length of the data array is not a power of two
+     */
+    public Complex[] transform(final Complex[] f, final TransformType type) {
+        final double[][] dataRI = TransformUtils.createRealImaginaryArray(f);
+
+        transformInPlace(dataRI, normalization, type);
+
+        return TransformUtils.createComplexArray(dataRI);
+    }
+
+    /**
+     * Performs a multi-dimensional Fourier transform on a given array. Use {@link
+     * #transform(Complex[], TransformType)} in a row-column implementation in any number of
+     * dimensions with O(N&times;log(N)) complexity with N = n<sub>1</sub> &times; n<sub>2</sub>
+     * &times;n<sub>3</sub> &times; ... &times; n<sub>d</sub>, where n<sub>k</sub> is the number of
+     * elements in dimension k, and d is the total number of dimensions.
+     *
+     * @param mdca Multi-Dimensional Complex Array, i.e. {@code Complex[][][][]}
+     * @param type the type of transform (forward, inverse) to be performed
+     * @return transform of {@code mdca} as a Multi-Dimensional Complex Array, i.e. {@code
+     *     Complex[][][][]}
+     * @throws IllegalArgumentException if any dimension is not a power of two
+     * @deprecated see MATH-736
+     */
+    @Deprecated
+    public Object mdfft(Object mdca, TransformType type) {
+        MultiDimensionalComplexMatrix mdcm =
+                (MultiDimensionalComplexMatrix) new MultiDimensionalComplexMatrix(mdca).clone();
+        int[] dimensionSize = mdcm.getDimensionSizes();
+        // cycle through each dimension
+        for (int i = 0; i < dimensionSize.length; i++) {
+            mdfft(mdcm, type, i, new int[0]);
+        }
+        return mdcm.getArray();
+    }
+
+    /**
+     * Performs one dimension of a multi-dimensional Fourier transform.
+     *
+     * @param mdcm input matrix
+     * @param type the type of transform (forward, inverse) to be performed
+     * @param d index of the dimension to process
+     * @param subVector recursion subvector
+     * @throws IllegalArgumentException if any dimension is not a power of two
+     * @deprecated see MATH-736
+     */
+    @Deprecated
+    private void mdfft(
+            MultiDimensionalComplexMatrix mdcm, TransformType type, int d, int[] subVector) {
+
+        int[] dimensionSize = mdcm.getDimensionSizes();
+        // if done
+        if (subVector.length == dimensionSize.length) {
+            Complex[] temp = new Complex[dimensionSize[d]];
+            for (int i = 0; i < dimensionSize[d]; i++) {
+                // fft along dimension d
+                subVector[d] = i;
+                temp[i] = mdcm.get(subVector);
+            }
+
+            temp = transform(temp, type);
+
+            for (int i = 0; i < dimensionSize[d]; i++) {
+                subVector[d] = i;
+                mdcm.set(temp[i], subVector);
+            }
+        } else {
+            int[] vector = new int[subVector.length + 1];
+            System.arraycopy(subVector, 0, vector, 0, subVector.length);
+            if (subVector.length == d) {
+                // value is not important once the recursion is done.
+                // then an fft will be applied along the dimension d.
+                vector[d] = 0;
+                mdfft(mdcm, type, d, vector);
+            } else {
+                for (int i = 0; i < dimensionSize[subVector.length]; i++) {
+                    vector[subVector.length] = i;
+                    // further split along the next dimension
+                    mdfft(mdcm, type, d, vector);
+                }
+            }
+        }
+    }
+
+    /**
+     * Complex matrix implementation. Not designed for synchronized access may eventually be
+     * replaced by jsr-83 of the java community process http://jcp.org/en/jsr/detail?id=83 may
+     * require additional exception throws for other basic requirements.
+     *
+     * @deprecated see MATH-736
+     */
+    @Deprecated
+    private static class MultiDimensionalComplexMatrix implements Cloneable {
+
+        /** Size in all dimensions. */
+        protected int[] dimensionSize;
+
+        /** Storage array. */
+        protected Object multiDimensionalComplexArray;
+
+        /**
+         * Simple constructor.
+         *
+         * @param multiDimensionalComplexArray array containing the matrix elements
+         */
+        MultiDimensionalComplexMatrix(Object multiDimensionalComplexArray) {
+
+            this.multiDimensionalComplexArray = multiDimensionalComplexArray;
+
+            // count dimensions
+            int numOfDimensions = 0;
+            for (Object lastDimension = multiDimensionalComplexArray;
+                    lastDimension instanceof Object[]; ) {
+                final Object[] array = (Object[]) lastDimension;
+                numOfDimensions++;
+                lastDimension = array[0];
+            }
+
+            // allocate array with exact count
+            dimensionSize = new int[numOfDimensions];
+
+            // fill array
+            numOfDimensions = 0;
+            for (Object lastDimension = multiDimensionalComplexArray;
+                    lastDimension instanceof Object[]; ) {
+                final Object[] array = (Object[]) lastDimension;
+                dimensionSize[numOfDimensions++] = array.length;
+                lastDimension = array[0];
+            }
+        }
+
+        /**
+         * Get a matrix element.
+         *
+         * @param vector indices of the element
+         * @return matrix element
+         * @exception DimensionMismatchException if dimensions do not match
+         */
+        public Complex get(int... vector) throws DimensionMismatchException {
+
+            if (vector == null) {
+                if (dimensionSize.length > 0) {
+                    throw new DimensionMismatchException(0, dimensionSize.length);
+                }
+                return null;
+            }
+            if (vector.length != dimensionSize.length) {
+                throw new DimensionMismatchException(vector.length, dimensionSize.length);
+            }
+
+            Object lastDimension = multiDimensionalComplexArray;
+
+            for (int i = 0; i < dimensionSize.length; i++) {
+                lastDimension = ((Object[]) lastDimension)[vector[i]];
+            }
+            return (Complex) lastDimension;
+        }
+
+        /**
+         * Set a matrix element.
+         *
+         * @param magnitude magnitude of the element
+         * @param vector indices of the element
+         * @return the previous value
+         * @exception DimensionMismatchException if dimensions do not match
+         */
+        public Complex set(Complex magnitude, int... vector) throws DimensionMismatchException {
+
+            if (vector == null) {
+                if (dimensionSize.length > 0) {
+                    throw new DimensionMismatchException(0, dimensionSize.length);
+                }
+                return null;
+            }
+            if (vector.length != dimensionSize.length) {
+                throw new DimensionMismatchException(vector.length, dimensionSize.length);
+            }
+
+            Object[] lastDimension = (Object[]) multiDimensionalComplexArray;
+            for (int i = 0; i < dimensionSize.length - 1; i++) {
+                lastDimension = (Object[]) lastDimension[vector[i]];
+            }
+
+            Complex lastValue = (Complex) lastDimension[vector[dimensionSize.length - 1]];
+            lastDimension[vector[dimensionSize.length - 1]] = magnitude;
+
+            return lastValue;
+        }
+
+        /**
+         * Get the size in all dimensions.
+         *
+         * @return size in all dimensions
+         */
+        public int[] getDimensionSizes() {
+            return dimensionSize.clone();
+        }
+
+        /**
+         * Get the underlying storage array.
+         *
+         * @return underlying storage array
+         */
+        public Object getArray() {
+            return multiDimensionalComplexArray;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public Object clone() {
+            MultiDimensionalComplexMatrix mdcm =
+                    new MultiDimensionalComplexMatrix(
+                            Array.newInstance(Complex.class, dimensionSize));
+            clone(mdcm);
+            return mdcm;
+        }
+
+        /**
+         * Copy contents of current array into mdcm.
+         *
+         * @param mdcm array where to copy data
+         */
+        private void clone(MultiDimensionalComplexMatrix mdcm) {
+
+            int[] vector = new int[dimensionSize.length];
+            int size = 1;
+            for (int i = 0; i < dimensionSize.length; i++) {
+                size *= dimensionSize[i];
+            }
+            int[][] vectorList = new int[size][dimensionSize.length];
+            for (int[] nextVector : vectorList) {
+                System.arraycopy(vector, 0, nextVector, 0, dimensionSize.length);
+                for (int i = 0; i < dimensionSize.length; i++) {
+                    vector[i]++;
+                    if (vector[i] < dimensionSize[i]) {
+                        break;
+                    } else {
+                        vector[i] = 0;
+                    }
+                }
+            }
+
+            for (int[] nextVector : vectorList) {
+                mdcm.set(get(nextVector), nextVector);
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/transform/FastHadamardTransformer.java b/src/main/java/org/apache/commons/math3/transform/FastHadamardTransformer.java
new file mode 100644
index 0000000..5f7e6a2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/transform/FastHadamardTransformer.java
@@ -0,0 +1,316 @@
+/*
+ * 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.math3.transform;
+
+import org.apache.commons.math3.analysis.FunctionUtils;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.ArithmeticUtils;
+
+import java.io.Serializable;
+
+/**
+ * Implements the <a href="http://www.archive.chipcenter.com/dsp/DSP000517F1.html">Fast Hadamard
+ * Transform</a> (FHT). Transformation of an input vector x to the output vector y.
+ *
+ * <p>In addition to transformation of real vectors, the Hadamard transform can transform integer
+ * vectors into integer vectors. However, this integer transform cannot be inverted directly. Due to
+ * a scaling factor it may lead to rational results. As an example, the inverse transform of integer
+ * vector (0, 1, 0, 1) is rational vector (1/2, -1/2, 0, 0).
+ *
+ * @since 2.0
+ */
+public class FastHadamardTransformer implements RealTransformer, Serializable {
+
+    /** Serializable version identifier. */
+    static final long serialVersionUID = 20120211L;
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws MathIllegalArgumentException if the length of the data array is not a power of two
+     */
+    public double[] transform(final double[] f, final TransformType type) {
+        if (type == TransformType.FORWARD) {
+            return fht(f);
+        }
+        return TransformUtils.scaleArray(fht(f), 1.0 / f.length);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws org.apache.commons.math3.exception.NonMonotonicSequenceException if the lower bound
+     *     is greater than, or equal to the upper bound
+     * @throws org.apache.commons.math3.exception.NotStrictlyPositiveException if the number of
+     *     sample points is negative
+     * @throws MathIllegalArgumentException if the number of sample points is not a power of two
+     */
+    public double[] transform(
+            final UnivariateFunction f,
+            final double min,
+            final double max,
+            final int n,
+            final TransformType type) {
+
+        return transform(FunctionUtils.sample(f, min, max, n), type);
+    }
+
+    /**
+     * Returns the forward transform of the specified integer data set.The integer transform cannot
+     * be inverted directly, due to a scaling factor which may lead to double results.
+     *
+     * @param f the integer data array to be transformed (signal)
+     * @return the integer transformed array (spectrum)
+     * @throws MathIllegalArgumentException if the length of the data array is not a power of two
+     */
+    public int[] transform(final int[] f) {
+        return fht(f);
+    }
+
+    /**
+     * The FHT (Fast Hadamard Transformation) which uses only subtraction and addition. Requires
+     * {@code N * log2(N) = n * 2^n} additions.
+     *
+     * <h3>Short Table of manual calculation for N=8</h3>
+     *
+     * <ol>
+     *   <li><b>x</b> is the input vector to be transformed,
+     *   <li><b>y</b> is the output vector (Fast Hadamard transform of <b>x</b>),
+     *   <li>a and b are helper rows.
+     * </ol>
+     *
+     * <table align="center" border="1" cellpadding="3">
+     * <tbody align="center">
+     * <tr>
+     *     <th>x</th>
+     *     <th>a</th>
+     *     <th>b</th>
+     *     <th>y</th>
+     * </tr>
+     * <tr>
+     *     <th>x<sub>0</sub></th>
+     *     <td>a<sub>0</sub> = x<sub>0</sub> + x<sub>1</sub></td>
+     *     <td>b<sub>0</sub> = a<sub>0</sub> + a<sub>1</sub></td>
+     *     <td>y<sub>0</sub> = b<sub>0</sub >+ b<sub>1</sub></td>
+     * </tr>
+     * <tr>
+     *     <th>x<sub>1</sub></th>
+     *     <td>a<sub>1</sub> = x<sub>2</sub> + x<sub>3</sub></td>
+     *     <td>b<sub>0</sub> = a<sub>2</sub> + a<sub>3</sub></td>
+     *     <td>y<sub>0</sub> = b<sub>2</sub> + b<sub>3</sub></td>
+     * </tr>
+     * <tr>
+     *     <th>x<sub>2</sub></th>
+     *     <td>a<sub>2</sub> = x<sub>4</sub> + x<sub>5</sub></td>
+     *     <td>b<sub>0</sub> = a<sub>4</sub> + a<sub>5</sub></td>
+     *     <td>y<sub>0</sub> = b<sub>4</sub> + b<sub>5</sub></td>
+     * </tr>
+     * <tr>
+     *     <th>x<sub>3</sub></th>
+     *     <td>a<sub>3</sub> = x<sub>6</sub> + x<sub>7</sub></td>
+     *     <td>b<sub>0</sub> = a<sub>6</sub> + a<sub>7</sub></td>
+     *     <td>y<sub>0</sub> = b<sub>6</sub> + b<sub>7</sub></td>
+     * </tr>
+     * <tr>
+     *     <th>x<sub>4</sub></th>
+     *     <td>a<sub>0</sub> = x<sub>0</sub> - x<sub>1</sub></td>
+     *     <td>b<sub>0</sub> = a<sub>0</sub> - a<sub>1</sub></td>
+     *     <td>y<sub>0</sub> = b<sub>0</sub> - b<sub>1</sub></td>
+     * </tr>
+     * <tr>
+     *     <th>x<sub>5</sub></th>
+     *     <td>a<sub>1</sub> = x<sub>2</sub> - x<sub>3</sub></td>
+     *     <td>b<sub>0</sub> = a<sub>2</sub> - a<sub>3</sub></td>
+     *     <td>y<sub>0</sub> = b<sub>2</sub> - b<sub>3</sub></td>
+     * </tr>
+     * <tr>
+     *     <th>x<sub>6</sub></th>
+     *     <td>a<sub>2</sub> = x<sub>4</sub> - x<sub>5</sub></td>
+     *     <td>b<sub>0</sub> = a<sub>4</sub> - a<sub>5</sub></td>
+     *     <td>y<sub>0</sub> = b<sub>4</sub> - b<sub>5</sub></td>
+     * </tr>
+     * <tr>
+     *     <th>x<sub>7</sub></th>
+     *     <td>a<sub>3</sub> = x<sub>6</sub> - x<sub>7</sub></td>
+     *     <td>b<sub>0</sub> = a<sub>6</sub> - a<sub>7</sub></td>
+     *     <td>y<sub>0</sub> = b<sub>6</sub> - b<sub>7</sub></td>
+     * </tr>
+     * </tbody>
+     * </table>
+     *
+     * <h3>How it works</h3>
+     *
+     * <ol>
+     *   <li>Construct a matrix with {@code N} rows and {@code n + 1} columns, {@code hadm[n+1][N]}.
+     *       <br>
+     *       <em>(If I use [x][y] it always means [row-offset][column-offset] of a Matrix with n
+     *       rows and m columns. Its entries go from M[0][0] to M[n][N])</em>
+     *   <li>Place the input vector {@code x[N]} in the first column of the matrix {@code hadm}.
+     *   <li>The entries of the submatrix {@code D_top} are calculated as follows
+     *       <ul>
+     *         <li>{@code D_top} goes from entry {@code [0][1]} to {@code [N / 2 - 1][n + 1]},
+     *         <li>the columns of {@code D_top} are the pairwise mutually exclusive sums of the
+     *             previous column.
+     *       </ul>
+     *   <li>The entries of the submatrix {@code D_bottom} are calculated as follows
+     *       <ul>
+     *         <li>{@code D_bottom} goes from entry {@code [N / 2][1]} to {@code [N][n + 1]},
+     *         <li>the columns of {@code D_bottom} are the pairwise differences of the previous
+     *             column.
+     *       </ul>
+     *   <li>The consputation of {@code D_top} and {@code D_bottom} are best understood with the
+     *       above example (for {@code N = 8}).
+     *   <li>The output vector {@code y} is now in the last column of {@code hadm}.
+     *   <li><em>Algorithm from <a
+     *       href="http://www.archive.chipcenter.com/dsp/DSP000517F1.html">chipcenter</a>.</em>
+     * </ol>
+     *
+     * <h3>Visually</h3>
+     *
+     * <table border="1" align="center" cellpadding="3">
+     * <tbody align="center">
+     * <tr>
+     *     <td></td><th>0</th><th>1</th><th>2</th><th>3</th>
+     *     <th>&hellip;</th>
+     *     <th>n + 1</th>
+     * </tr>
+     * <tr>
+     *     <th>0</th>
+     *     <td>x<sub>0</sub></td>
+     *     <td colspan="5" rowspan="5" align="center" valign="middle">
+     *         &uarr;<br/>
+     *         &larr; D<sub>top</sub> &rarr;<br/>
+     *         &darr;
+     *     </td>
+     * </tr>
+     * <tr><th>1</th><td>x<sub>1</sub></td></tr>
+     * <tr><th>2</th><td>x<sub>2</sub></td></tr>
+     * <tr><th>&hellip;</th><td>&hellip;</td></tr>
+     * <tr><th>N / 2 - 1</th><td>x<sub>N/2-1</sub></td></tr>
+     * <tr>
+     *     <th>N / 2</th>
+     *     <td>x<sub>N/2</sub></td>
+     *     <td colspan="5" rowspan="5" align="center" valign="middle">
+     *         &uarr;<br/>
+     *         &larr; D<sub>bottom</sub> &rarr;<br/>
+     *         &darr;
+     *     </td>
+     * </tr>
+     * <tr><th>N / 2 + 1</th><td>x<sub>N/2+1</sub></td></tr>
+     * <tr><th>N / 2 + 2</th><td>x<sub>N/2+2</sub></td></tr>
+     * <tr><th>&hellip;</th><td>&hellip;</td></tr>
+     * <tr><th>N</th><td>x<sub>N</sub></td></tr>
+     * </tbody>
+     * </table>
+     *
+     * @param x the real data array to be transformed
+     * @return the real transformed array, {@code y}
+     * @throws MathIllegalArgumentException if the length of the data array is not a power of two
+     */
+    protected double[] fht(double[] x) throws MathIllegalArgumentException {
+
+        final int n = x.length;
+        final int halfN = n / 2;
+
+        if (!ArithmeticUtils.isPowerOfTwo(n)) {
+            throw new MathIllegalArgumentException(
+                    LocalizedFormats.NOT_POWER_OF_TWO, Integer.valueOf(n));
+        }
+
+        /*
+         * Instead of creating a matrix with p+1 columns and n rows, we use two
+         * one dimension arrays which we are used in an alternating way.
+         */
+        double[] yPrevious = new double[n];
+        double[] yCurrent = x.clone();
+
+        // iterate from left to right (column)
+        for (int j = 1; j < n; j <<= 1) {
+
+            // switch columns
+            final double[] yTmp = yCurrent;
+            yCurrent = yPrevious;
+            yPrevious = yTmp;
+
+            // iterate from top to bottom (row)
+            for (int i = 0; i < halfN; ++i) {
+                // Dtop: the top part works with addition
+                final int twoI = 2 * i;
+                yCurrent[i] = yPrevious[twoI] + yPrevious[twoI + 1];
+            }
+            for (int i = halfN; i < n; ++i) {
+                // Dbottom: the bottom part works with subtraction
+                final int twoI = 2 * i;
+                yCurrent[i] = yPrevious[twoI - n] - yPrevious[twoI - n + 1];
+            }
+        }
+
+        return yCurrent;
+    }
+
+    /**
+     * Returns the forward transform of the specified integer data set. The FHT (Fast Hadamard
+     * Transform) uses only subtraction and addition.
+     *
+     * @param x the integer data array to be transformed
+     * @return the integer transformed array, {@code y}
+     * @throws MathIllegalArgumentException if the length of the data array is not a power of two
+     */
+    protected int[] fht(int[] x) throws MathIllegalArgumentException {
+
+        final int n = x.length;
+        final int halfN = n / 2;
+
+        if (!ArithmeticUtils.isPowerOfTwo(n)) {
+            throw new MathIllegalArgumentException(
+                    LocalizedFormats.NOT_POWER_OF_TWO, Integer.valueOf(n));
+        }
+
+        /*
+         * Instead of creating a matrix with p+1 columns and n rows, we use two
+         * one dimension arrays which we are used in an alternating way.
+         */
+        int[] yPrevious = new int[n];
+        int[] yCurrent = x.clone();
+
+        // iterate from left to right (column)
+        for (int j = 1; j < n; j <<= 1) {
+
+            // switch columns
+            final int[] yTmp = yCurrent;
+            yCurrent = yPrevious;
+            yPrevious = yTmp;
+
+            // iterate from top to bottom (row)
+            for (int i = 0; i < halfN; ++i) {
+                // Dtop: the top part works with addition
+                final int twoI = 2 * i;
+                yCurrent[i] = yPrevious[twoI] + yPrevious[twoI + 1];
+            }
+            for (int i = halfN; i < n; ++i) {
+                // Dbottom: the bottom part works with subtraction
+                final int twoI = 2 * i;
+                yCurrent[i] = yPrevious[twoI - n] - yPrevious[twoI - n + 1];
+            }
+        }
+
+        // return the last computed output vector y
+        return yCurrent;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/transform/FastSineTransformer.java b/src/main/java/org/apache/commons/math3/transform/FastSineTransformer.java
new file mode 100644
index 0000000..b33b226
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/transform/FastSineTransformer.java
@@ -0,0 +1,175 @@
+/*
+ * 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.math3.transform;
+
+import org.apache.commons.math3.analysis.FunctionUtils;
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.complex.Complex;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.util.ArithmeticUtils;
+import org.apache.commons.math3.util.FastMath;
+
+import java.io.Serializable;
+
+/**
+ * Implements the Fast Sine Transform for transformation of one-dimensional real data sets. For
+ * reference, see James S. Walker, <em>Fast Fourier Transforms</em>, chapter 3 (ISBN 0849371635).
+ *
+ * <p>There are several variants of the discrete sine transform. The present implementation
+ * corresponds to DST-I, with various normalization conventions, which are specified by the
+ * parameter {@link DstNormalization}. <strong>It should be noted that regardless to the convention,
+ * the first element of the dataset to be transformed must be zero.</strong>
+ *
+ * <p>DST-I is equivalent to DFT of an <em>odd extension</em> of the data series. More precisely, if
+ * x<sub>0</sub>, &hellip;, x<sub>N-1</sub> is the data set to be sine transformed, the extended
+ * data set x<sub>0</sub><sup>&#35;</sup>, &hellip;, x<sub>2N-1</sub><sup>&#35;</sup> is defined as
+ * follows
+ *
+ * <ul>
+ *   <li>x<sub>0</sub><sup>&#35;</sup> = x<sub>0</sub> = 0,
+ *   <li>x<sub>k</sub><sup>&#35;</sup> = x<sub>k</sub> if 1 &le; k &lt; N,
+ *   <li>x<sub>N</sub><sup>&#35;</sup> = 0,
+ *   <li>x<sub>k</sub><sup>&#35;</sup> = -x<sub>2N-k</sub> if N + 1 &le; k &lt; 2N.
+ * </ul>
+ *
+ * <p>Then, the standard DST-I y<sub>0</sub>, &hellip;, y<sub>N-1</sub> of the real data set
+ * x<sub>0</sub>, &hellip;, x<sub>N-1</sub> is equal to <em>half</em> of i (the pure imaginary
+ * number) times the N first elements of the DFT of the extended data set
+ * x<sub>0</sub><sup>&#35;</sup>, &hellip;, x<sub>2N-1</sub><sup>&#35;</sup> <br>
+ * y<sub>n</sub> = (i / 2) &sum;<sub>k=0</sub><sup>2N-1</sup> x<sub>k</sub><sup>&#35;</sup>
+ * exp[-2&pi;i nk / (2N)] &nbsp;&nbsp;&nbsp;&nbsp;k = 0, &hellip;, N-1.
+ *
+ * <p>The present implementation of the discrete sine transform as a fast sine transform requires
+ * the length of the data to be a power of two. Besides, it implicitly assumes that the sampled
+ * function is odd. In particular, the first element of the data set must be 0, which is enforced in
+ * {@link #transform(UnivariateFunction, double, double, int, TransformType)}, after sampling.
+ *
+ * @since 1.2
+ */
+public class FastSineTransformer implements RealTransformer, Serializable {
+
+    /** Serializable version identifier. */
+    static final long serialVersionUID = 20120211L;
+
+    /** The type of DST to be performed. */
+    private final DstNormalization normalization;
+
+    /**
+     * Creates a new instance of this class, with various normalization conventions.
+     *
+     * @param normalization the type of normalization to be applied to the transformed data
+     */
+    public FastSineTransformer(final DstNormalization normalization) {
+        this.normalization = normalization;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The first element of the specified data set is required to be {@code 0}.
+     *
+     * @throws MathIllegalArgumentException if the length of the data array is not a power of two,
+     *     or the first element of the data array is not zero
+     */
+    public double[] transform(final double[] f, final TransformType type) {
+        if (normalization == DstNormalization.ORTHOGONAL_DST_I) {
+            final double s = FastMath.sqrt(2.0 / f.length);
+            return TransformUtils.scaleArray(fst(f), s);
+        }
+        if (type == TransformType.FORWARD) {
+            return fst(f);
+        }
+        final double s = 2.0 / f.length;
+        return TransformUtils.scaleArray(fst(f), s);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>This implementation enforces {@code f(x) = 0.0} at {@code x = 0.0}.
+     *
+     * @throws org.apache.commons.math3.exception.NonMonotonicSequenceException if the lower bound
+     *     is greater than, or equal to the upper bound
+     * @throws org.apache.commons.math3.exception.NotStrictlyPositiveException if the number of
+     *     sample points is negative
+     * @throws MathIllegalArgumentException if the number of sample points is not a power of two
+     */
+    public double[] transform(
+            final UnivariateFunction f,
+            final double min,
+            final double max,
+            final int n,
+            final TransformType type) {
+
+        final double[] data = FunctionUtils.sample(f, min, max, n);
+        data[0] = 0.0;
+        return transform(data, type);
+    }
+
+    /**
+     * Perform the FST algorithm (including inverse). The first element of the data set is required
+     * to be {@code 0}.
+     *
+     * @param f the real data array to be transformed
+     * @return the real transformed array
+     * @throws MathIllegalArgumentException if the length of the data array is not a power of two,
+     *     or the first element of the data array is not zero
+     */
+    protected double[] fst(double[] f) throws MathIllegalArgumentException {
+
+        final double[] transformed = new double[f.length];
+
+        if (!ArithmeticUtils.isPowerOfTwo(f.length)) {
+            throw new MathIllegalArgumentException(
+                    LocalizedFormats.NOT_POWER_OF_TWO_CONSIDER_PADDING, Integer.valueOf(f.length));
+        }
+        if (f[0] != 0.0) {
+            throw new MathIllegalArgumentException(
+                    LocalizedFormats.FIRST_ELEMENT_NOT_ZERO, Double.valueOf(f[0]));
+        }
+        final int n = f.length;
+        if (n == 1) { // trivial case
+            transformed[0] = 0.0;
+            return transformed;
+        }
+
+        // construct a new array and perform FFT on it
+        final double[] x = new double[n];
+        x[0] = 0.0;
+        x[n >> 1] = 2.0 * f[n >> 1];
+        for (int i = 1; i < (n >> 1); i++) {
+            final double a = FastMath.sin(i * FastMath.PI / n) * (f[i] + f[n - i]);
+            final double b = 0.5 * (f[i] - f[n - i]);
+            x[i] = a + b;
+            x[n - i] = a - b;
+        }
+        FastFourierTransformer transformer;
+        transformer = new FastFourierTransformer(DftNormalization.STANDARD);
+        Complex[] y = transformer.transform(x, TransformType.FORWARD);
+
+        // reconstruct the FST result for the original array
+        transformed[0] = 0.0;
+        transformed[1] = 0.5 * y[0].getReal();
+        for (int i = 1; i < (n >> 1); i++) {
+            transformed[2 * i] = -y[i].getImaginary();
+            transformed[2 * i + 1] = y[i].getReal() + transformed[2 * i - 1];
+        }
+
+        return transformed;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/transform/RealTransformer.java b/src/main/java/org/apache/commons/math3/transform/RealTransformer.java
new file mode 100644
index 0000000..b5a105b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/transform/RealTransformer.java
@@ -0,0 +1,68 @@
+/*
+ * 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.math3.transform;
+
+import org.apache.commons.math3.analysis.UnivariateFunction;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NonMonotonicSequenceException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+
+/**
+ * Interface for one-dimensional data sets transformations producing real results.
+ *
+ * <p>Such transforms include {@link FastSineTransformer sine transform}, {@link
+ * FastCosineTransformer cosine transform} or {@link FastHadamardTransformer Hadamard transform}.
+ * {@link FastFourierTransformer Fourier transform} is of a different kind and does not implement
+ * this interface since it produces {@link org.apache.commons.math3.complex.Complex} results instead
+ * of real ones.
+ *
+ * @since 2.0
+ */
+public interface RealTransformer {
+
+    /**
+     * Returns the (forward, inverse) transform of the specified real data set.
+     *
+     * @param f the real data array to be transformed (signal)
+     * @param type the type of transform (forward, inverse) to be performed
+     * @return the real transformed array (spectrum)
+     * @throws MathIllegalArgumentException if the array cannot be transformed with the given type
+     *     (this may be for example due to array size, which is constrained in some transforms)
+     */
+    double[] transform(double[] f, TransformType type) throws MathIllegalArgumentException;
+
+    /**
+     * Returns the (forward, inverse) transform of the specified real function, sampled on the
+     * specified interval.
+     *
+     * @param f the function to be sampled and transformed
+     * @param min the (inclusive) lower bound for the interval
+     * @param max the (exclusive) upper bound for the interval
+     * @param n the number of sample points
+     * @param type the type of transform (forward, inverse) to be performed
+     * @return the real transformed array
+     * @throws NonMonotonicSequenceException if the lower bound is greater than, or equal to the
+     *     upper bound
+     * @throws NotStrictlyPositiveException if the number of sample points is negative
+     * @throws MathIllegalArgumentException if the sample cannot be transformed with the given type
+     *     (this may be for example due to sample size, which is constrained in some transforms)
+     */
+    double[] transform(UnivariateFunction f, double min, double max, int n, TransformType type)
+            throws NonMonotonicSequenceException,
+                    NotStrictlyPositiveException,
+                    MathIllegalArgumentException;
+}
diff --git a/src/main/java/org/apache/commons/math3/transform/TransformType.java b/src/main/java/org/apache/commons/math3/transform/TransformType.java
new file mode 100644
index 0000000..ae1a6c5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/transform/TransformType.java
@@ -0,0 +1,30 @@
+/*
+ * 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.math3.transform;
+
+/**
+ * This enumeration defines the type of transform which is to be computed.
+ *
+ * @since 3.0
+ */
+public enum TransformType {
+    /** The type to be specified for forward transforms. */
+    FORWARD,
+
+    /** The type to be specified for inverse transforms. */
+    INVERSE;
+}
diff --git a/src/main/java/org/apache/commons/math3/transform/TransformUtils.java b/src/main/java/org/apache/commons/math3/transform/TransformUtils.java
new file mode 100644
index 0000000..5cf83de
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/transform/TransformUtils.java
@@ -0,0 +1,160 @@
+/*
+ * 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.math3.transform;
+
+import org.apache.commons.math3.complex.Complex;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+import java.util.Arrays;
+
+/**
+ * Useful functions for the implementation of various transforms.
+ *
+ * @since 3.0
+ */
+public class TransformUtils {
+    /**
+     * Table of the powers of 2 to facilitate binary search lookup.
+     *
+     * @see #exactLog2(int)
+     */
+    private static final int[] POWERS_OF_TWO = {
+        0x00000001, 0x00000002, 0x00000004, 0x00000008, 0x00000010, 0x00000020,
+        0x00000040, 0x00000080, 0x00000100, 0x00000200, 0x00000400, 0x00000800,
+        0x00001000, 0x00002000, 0x00004000, 0x00008000, 0x00010000, 0x00020000,
+        0x00040000, 0x00080000, 0x00100000, 0x00200000, 0x00400000, 0x00800000,
+        0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, 0x20000000,
+        0x40000000
+    };
+
+    /** Private constructor. */
+    private TransformUtils() {
+        super();
+    }
+
+    /**
+     * Multiply every component in the given real array by the given real number. The change is made
+     * in place.
+     *
+     * @param f the real array to be scaled
+     * @param d the real scaling coefficient
+     * @return a reference to the scaled array
+     */
+    public static double[] scaleArray(double[] f, double d) {
+
+        for (int i = 0; i < f.length; i++) {
+            f[i] *= d;
+        }
+        return f;
+    }
+
+    /**
+     * Multiply every component in the given complex array by the given real number. The change is
+     * made in place.
+     *
+     * @param f the complex array to be scaled
+     * @param d the real scaling coefficient
+     * @return a reference to the scaled array
+     */
+    public static Complex[] scaleArray(Complex[] f, double d) {
+
+        for (int i = 0; i < f.length; i++) {
+            f[i] = new Complex(d * f[i].getReal(), d * f[i].getImaginary());
+        }
+        return f;
+    }
+
+    /**
+     * Builds a new two dimensional array of {@code double} filled with the real and imaginary parts
+     * of the specified {@link Complex} numbers. In the returned array {@code dataRI}, the data is
+     * laid out as follows
+     *
+     * <ul>
+     *   <li>{@code dataRI[0][i] = dataC[i].getReal()},
+     *   <li>{@code dataRI[1][i] = dataC[i].getImaginary()}.
+     * </ul>
+     *
+     * @param dataC the array of {@link Complex} data to be transformed
+     * @return a two dimensional array filled with the real and imaginary parts of the specified
+     *     complex input
+     */
+    public static double[][] createRealImaginaryArray(final Complex[] dataC) {
+        final double[][] dataRI = new double[2][dataC.length];
+        final double[] dataR = dataRI[0];
+        final double[] dataI = dataRI[1];
+        for (int i = 0; i < dataC.length; i++) {
+            final Complex c = dataC[i];
+            dataR[i] = c.getReal();
+            dataI[i] = c.getImaginary();
+        }
+        return dataRI;
+    }
+
+    /**
+     * Builds a new array of {@link Complex} from the specified two dimensional array of real and
+     * imaginary parts. In the returned array {@code dataC}, the data is laid out as follows
+     *
+     * <ul>
+     *   <li>{@code dataC[i].getReal() = dataRI[0][i]},
+     *   <li>{@code dataC[i].getImaginary() = dataRI[1][i]}.
+     * </ul>
+     *
+     * @param dataRI the array of real and imaginary parts to be transformed
+     * @return an array of {@link Complex} with specified real and imaginary parts.
+     * @throws DimensionMismatchException if the number of rows of the specified array is not two,
+     *     or the array is not rectangular
+     */
+    public static Complex[] createComplexArray(final double[][] dataRI)
+            throws DimensionMismatchException {
+
+        if (dataRI.length != 2) {
+            throw new DimensionMismatchException(dataRI.length, 2);
+        }
+        final double[] dataR = dataRI[0];
+        final double[] dataI = dataRI[1];
+        if (dataR.length != dataI.length) {
+            throw new DimensionMismatchException(dataI.length, dataR.length);
+        }
+
+        final int n = dataR.length;
+        final Complex[] c = new Complex[n];
+        for (int i = 0; i < n; i++) {
+            c[i] = new Complex(dataR[i], dataI[i]);
+        }
+        return c;
+    }
+
+    /**
+     * Returns the base-2 logarithm of the specified {@code int}. Throws an exception if {@code n}
+     * is not a power of two.
+     *
+     * @param n the {@code int} whose base-2 logarithm is to be evaluated
+     * @return the base-2 logarithm of {@code n}
+     * @throws MathIllegalArgumentException if {@code n} is not a power of two
+     */
+    public static int exactLog2(final int n) throws MathIllegalArgumentException {
+
+        int index = Arrays.binarySearch(TransformUtils.POWERS_OF_TWO, n);
+        if (index < 0) {
+            throw new MathIllegalArgumentException(
+                    LocalizedFormats.NOT_POWER_OF_TWO_CONSIDER_PADDING, Integer.valueOf(n));
+        }
+        return index;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/transform/package-info.java b/src/main/java/org/apache/commons/math3/transform/package-info.java
new file mode 100644
index 0000000..728c7c4
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/transform/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+/** Implementations of transform methods, including Fast Fourier transforms. */
+package org.apache.commons.math3.transform;
diff --git a/src/main/java/org/apache/commons/math3/util/ArithmeticUtils.java b/src/main/java/org/apache/commons/math3/util/ArithmeticUtils.java
new file mode 100644
index 0000000..8f07818
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/ArithmeticUtils.java
@@ -0,0 +1,845 @@
+/*
+ * 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.math3.util;
+
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.util.Localizable;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+import java.math.BigInteger;
+
+/** Some useful, arithmetics related, additions to the built-in functions in {@link Math}. */
+public final class ArithmeticUtils {
+
+    /** Private constructor. */
+    private ArithmeticUtils() {
+        super();
+    }
+
+    /**
+     * Add two integers, checking for overflow.
+     *
+     * @param x an addend
+     * @param y an addend
+     * @return the sum {@code x+y}
+     * @throws MathArithmeticException if the result can not be represented as an {@code int}.
+     * @since 1.1
+     */
+    public static int addAndCheck(int x, int y) throws MathArithmeticException {
+        long s = (long) x + (long) y;
+        if (s < Integer.MIN_VALUE || s > Integer.MAX_VALUE) {
+            throw new MathArithmeticException(LocalizedFormats.OVERFLOW_IN_ADDITION, x, y);
+        }
+        return (int) s;
+    }
+
+    /**
+     * Add two long integers, checking for overflow.
+     *
+     * @param a an addend
+     * @param b an addend
+     * @return the sum {@code a+b}
+     * @throws MathArithmeticException if the result can not be represented as an long
+     * @since 1.2
+     */
+    public static long addAndCheck(long a, long b) throws MathArithmeticException {
+        return addAndCheck(a, b, LocalizedFormats.OVERFLOW_IN_ADDITION);
+    }
+
+    /**
+     * Returns an exact representation of the <a
+     * href="http://mathworld.wolfram.com/BinomialCoefficient.html">Binomial Coefficient</a>,
+     * "{@code n choose k}", the number of {@code k}-element subsets that can be selected from an
+     * {@code n}-element set.
+     *
+     * <p><Strong>Preconditions</strong>:
+     *
+     * <ul>
+     *   <li>{@code 0 <= k <= n } (otherwise {@code IllegalArgumentException} is thrown)
+     *   <li>The result is small enough to fit into a {@code long}. The largest value of {@code n}
+     *       for which all coefficients are {@code < Long.MAX_VALUE} is 66. If the computed value
+     *       exceeds {@code Long.MAX_VALUE} an {@code ArithMeticException} is thrown.
+     * </ul>
+     *
+     * @param n the size of the set
+     * @param k the size of the subsets to be counted
+     * @return {@code n choose k}
+     * @throws NotPositiveException if {@code n < 0}.
+     * @throws NumberIsTooLargeException if {@code k > n}.
+     * @throws MathArithmeticException if the result is too large to be represented by a long
+     *     integer.
+     * @deprecated use {@link CombinatoricsUtils#binomialCoefficient(int, int)}
+     */
+    @Deprecated
+    public static long binomialCoefficient(final int n, final int k)
+            throws NotPositiveException, NumberIsTooLargeException, MathArithmeticException {
+        return CombinatoricsUtils.binomialCoefficient(n, k);
+    }
+
+    /**
+     * Returns a {@code double} representation of the <a
+     * href="http://mathworld.wolfram.com/BinomialCoefficient.html">Binomial Coefficient</a>,
+     * "{@code n choose k}", the number of {@code k}-element subsets that can be selected from an
+     * {@code n}-element set.
+     *
+     * <p><Strong>Preconditions</strong>:
+     *
+     * <ul>
+     *   <li>{@code 0 <= k <= n } (otherwise {@code IllegalArgumentException} is thrown)
+     *   <li>The result is small enough to fit into a {@code double}. The largest value of {@code n}
+     *       for which all coefficients are < Double.MAX_VALUE is 1029. If the computed value
+     *       exceeds Double.MAX_VALUE, Double.POSITIVE_INFINITY is returned
+     * </ul>
+     *
+     * @param n the size of the set
+     * @param k the size of the subsets to be counted
+     * @return {@code n choose k}
+     * @throws NotPositiveException if {@code n < 0}.
+     * @throws NumberIsTooLargeException if {@code k > n}.
+     * @throws MathArithmeticException if the result is too large to be represented by a long
+     *     integer.
+     * @deprecated use {@link CombinatoricsUtils#binomialCoefficientDouble(int, int)}
+     */
+    @Deprecated
+    public static double binomialCoefficientDouble(final int n, final int k)
+            throws NotPositiveException, NumberIsTooLargeException, MathArithmeticException {
+        return CombinatoricsUtils.binomialCoefficientDouble(n, k);
+    }
+
+    /**
+     * Returns the natural {@code log} of the <a
+     * href="http://mathworld.wolfram.com/BinomialCoefficient.html">Binomial Coefficient</a>,
+     * "{@code n choose k}", the number of {@code k}-element subsets that can be selected from an
+     * {@code n}-element set.
+     *
+     * <p><Strong>Preconditions</strong>:
+     *
+     * <ul>
+     *   <li>{@code 0 <= k <= n } (otherwise {@code IllegalArgumentException} is thrown)
+     * </ul>
+     *
+     * @param n the size of the set
+     * @param k the size of the subsets to be counted
+     * @return {@code n choose k}
+     * @throws NotPositiveException if {@code n < 0}.
+     * @throws NumberIsTooLargeException if {@code k > n}.
+     * @throws MathArithmeticException if the result is too large to be represented by a long
+     *     integer.
+     * @deprecated use {@link CombinatoricsUtils#binomialCoefficientLog(int, int)}
+     */
+    @Deprecated
+    public static double binomialCoefficientLog(final int n, final int k)
+            throws NotPositiveException, NumberIsTooLargeException, MathArithmeticException {
+        return CombinatoricsUtils.binomialCoefficientLog(n, k);
+    }
+
+    /**
+     * Returns n!. Shorthand for {@code n} <a href="http://mathworld.wolfram.com/Factorial.html">
+     * Factorial</a>, the product of the numbers {@code 1,...,n}.
+     *
+     * <p><Strong>Preconditions</strong>:
+     *
+     * <ul>
+     *   <li>{@code n >= 0} (otherwise {@code IllegalArgumentException} is thrown)
+     *   <li>The result is small enough to fit into a {@code long}. The largest value of {@code n}
+     *       for which {@code n!} < Long.MAX_VALUE} is 20. If the computed value exceeds {@code
+     *       Long.MAX_VALUE} an {@code ArithMeticException } is thrown.
+     * </ul>
+     *
+     * @param n argument
+     * @return {@code n!}
+     * @throws MathArithmeticException if the result is too large to be represented by a {@code
+     *     long}.
+     * @throws NotPositiveException if {@code n < 0}.
+     * @throws MathArithmeticException if {@code n > 20}: The factorial value is too large to fit in
+     *     a {@code long}.
+     * @deprecated use {@link CombinatoricsUtils#factorial(int)}
+     */
+    @Deprecated
+    public static long factorial(final int n) throws NotPositiveException, MathArithmeticException {
+        return CombinatoricsUtils.factorial(n);
+    }
+
+    /**
+     * Compute n!, the<a href="http://mathworld.wolfram.com/Factorial.html">factorial</a> of {@code
+     * n} (the product of the numbers 1 to n), as a {@code double}. The result should be small
+     * enough to fit into a {@code double}: The largest {@code n} for which {@code n! <
+     * Double.MAX_VALUE} is 170. If the computed value exceeds {@code Double.MAX_VALUE}, {@code
+     * Double.POSITIVE_INFINITY} is returned.
+     *
+     * @param n Argument.
+     * @return {@code n!}
+     * @throws NotPositiveException if {@code n < 0}.
+     * @deprecated use {@link CombinatoricsUtils#factorialDouble(int)}
+     */
+    @Deprecated
+    public static double factorialDouble(final int n) throws NotPositiveException {
+        return CombinatoricsUtils.factorialDouble(n);
+    }
+
+    /**
+     * Compute the natural logarithm of the factorial of {@code n}.
+     *
+     * @param n Argument.
+     * @return {@code n!}
+     * @throws NotPositiveException if {@code n < 0}.
+     * @deprecated use {@link CombinatoricsUtils#factorialLog(int)}
+     */
+    @Deprecated
+    public static double factorialLog(final int n) throws NotPositiveException {
+        return CombinatoricsUtils.factorialLog(n);
+    }
+
+    /**
+     * Computes the greatest common divisor of the absolute value of two numbers, using a modified
+     * version of the "binary gcd" method. See Knuth 4.5.2 algorithm B. The algorithm is due to
+     * Josef Stein (1961). <br>
+     * Special cases:
+     *
+     * <ul>
+     *   <li>The invocations {@code gcd(Integer.MIN_VALUE, Integer.MIN_VALUE)}, {@code
+     *       gcd(Integer.MIN_VALUE, 0)} and {@code gcd(0, Integer.MIN_VALUE)} throw an {@code
+     *       ArithmeticException}, because the result would be 2^31, which is too large for an int
+     *       value.
+     *   <li>The result of {@code gcd(x, x)}, {@code gcd(0, x)} and {@code gcd(x, 0)} is the
+     *       absolute value of {@code x}, except for the special cases above.
+     *   <li>The invocation {@code gcd(0, 0)} is the only one which returns {@code 0}.
+     * </ul>
+     *
+     * @param p Number.
+     * @param q Number.
+     * @return the greatest common divisor (never negative).
+     * @throws MathArithmeticException if the result cannot be represented as a non-negative {@code
+     *     int} value.
+     * @since 1.1
+     */
+    public static int gcd(int p, int q) throws MathArithmeticException {
+        int a = p;
+        int b = q;
+        if (a == 0 || b == 0) {
+            if (a == Integer.MIN_VALUE || b == Integer.MIN_VALUE) {
+                throw new MathArithmeticException(LocalizedFormats.GCD_OVERFLOW_32_BITS, p, q);
+            }
+            return FastMath.abs(a + b);
+        }
+
+        long al = a;
+        long bl = b;
+        boolean useLong = false;
+        if (a < 0) {
+            if (Integer.MIN_VALUE == a) {
+                useLong = true;
+            } else {
+                a = -a;
+            }
+            al = -al;
+        }
+        if (b < 0) {
+            if (Integer.MIN_VALUE == b) {
+                useLong = true;
+            } else {
+                b = -b;
+            }
+            bl = -bl;
+        }
+        if (useLong) {
+            if (al == bl) {
+                throw new MathArithmeticException(LocalizedFormats.GCD_OVERFLOW_32_BITS, p, q);
+            }
+            long blbu = bl;
+            bl = al;
+            al = blbu % al;
+            if (al == 0) {
+                if (bl > Integer.MAX_VALUE) {
+                    throw new MathArithmeticException(LocalizedFormats.GCD_OVERFLOW_32_BITS, p, q);
+                }
+                return (int) bl;
+            }
+            blbu = bl;
+
+            // Now "al" and "bl" fit in an "int".
+            b = (int) al;
+            a = (int) (blbu % al);
+        }
+
+        return gcdPositive(a, b);
+    }
+
+    /**
+     * Computes the greatest common divisor of two <em>positive</em> numbers (this precondition is
+     * <em>not</em> checked and the result is undefined if not fulfilled) using the "binary gcd"
+     * method which avoids division and modulo operations. See Knuth 4.5.2 algorithm B. The
+     * algorithm is due to Josef Stein (1961). <br>
+     * Special cases:
+     *
+     * <ul>
+     *   <li>The result of {@code gcd(x, x)}, {@code gcd(0, x)} and {@code gcd(x, 0)} is the value
+     *       of {@code x}.
+     *   <li>The invocation {@code gcd(0, 0)} is the only one which returns {@code 0}.
+     * </ul>
+     *
+     * @param a Positive number.
+     * @param b Positive number.
+     * @return the greatest common divisor.
+     */
+    private static int gcdPositive(int a, int b) {
+        if (a == 0) {
+            return b;
+        } else if (b == 0) {
+            return a;
+        }
+
+        // Make "a" and "b" odd, keeping track of common power of 2.
+        final int aTwos = Integer.numberOfTrailingZeros(a);
+        a >>= aTwos;
+        final int bTwos = Integer.numberOfTrailingZeros(b);
+        b >>= bTwos;
+        final int shift = FastMath.min(aTwos, bTwos);
+
+        // "a" and "b" are positive.
+        // If a > b then "gdc(a, b)" is equal to "gcd(a - b, b)".
+        // If a < b then "gcd(a, b)" is equal to "gcd(b - a, a)".
+        // Hence, in the successive iterations:
+        //  "a" becomes the absolute difference of the current values,
+        //  "b" becomes the minimum of the current values.
+        while (a != b) {
+            final int delta = a - b;
+            b = Math.min(a, b);
+            a = Math.abs(delta);
+
+            // Remove any power of 2 in "a" ("b" is guaranteed to be odd).
+            a >>= Integer.numberOfTrailingZeros(a);
+        }
+
+        // Recover the common power of 2.
+        return a << shift;
+    }
+
+    /**
+     * Gets the greatest common divisor of the absolute value of two numbers, using the "binary gcd"
+     * method which avoids division and modulo operations. See Knuth 4.5.2 algorithm B. This
+     * algorithm is due to Josef Stein (1961). Special cases:
+     *
+     * <ul>
+     *   <li>The invocations {@code gcd(Long.MIN_VALUE, Long.MIN_VALUE)}, {@code gcd(Long.MIN_VALUE,
+     *       0L)} and {@code gcd(0L, Long.MIN_VALUE)} throw an {@code ArithmeticException}, because
+     *       the result would be 2^63, which is too large for a long value.
+     *   <li>The result of {@code gcd(x, x)}, {@code gcd(0L, x)} and {@code gcd(x, 0L)} is the
+     *       absolute value of {@code x}, except for the special cases above.
+     *   <li>The invocation {@code gcd(0L, 0L)} is the only one which returns {@code 0L}.
+     * </ul>
+     *
+     * @param p Number.
+     * @param q Number.
+     * @return the greatest common divisor, never negative.
+     * @throws MathArithmeticException if the result cannot be represented as a non-negative {@code
+     *     long} value.
+     * @since 2.1
+     */
+    public static long gcd(final long p, final long q) throws MathArithmeticException {
+        long u = p;
+        long v = q;
+        if ((u == 0) || (v == 0)) {
+            if ((u == Long.MIN_VALUE) || (v == Long.MIN_VALUE)) {
+                throw new MathArithmeticException(LocalizedFormats.GCD_OVERFLOW_64_BITS, p, q);
+            }
+            return FastMath.abs(u) + FastMath.abs(v);
+        }
+        // keep u and v negative, as negative integers range down to
+        // -2^63, while positive numbers can only be as large as 2^63-1
+        // (i.e. we can't necessarily negate a negative number without
+        // overflow)
+        /* assert u!=0 && v!=0; */
+        if (u > 0) {
+            u = -u;
+        } // make u negative
+        if (v > 0) {
+            v = -v;
+        } // make v negative
+        // B1. [Find power of 2]
+        int k = 0;
+        while ((u & 1) == 0 && (v & 1) == 0 && k < 63) { // while u and v are
+            // both even...
+            u /= 2;
+            v /= 2;
+            k++; // cast out twos.
+        }
+        if (k == 63) {
+            throw new MathArithmeticException(LocalizedFormats.GCD_OVERFLOW_64_BITS, p, q);
+        }
+        // B2. Initialize: u and v have been divided by 2^k and at least
+        // one is odd.
+        long t = ((u & 1) == 1) ? v : -(u / 2) /* B3 */;
+        // t negative: u was odd, v may be even (t replaces v)
+        // t positive: u was even, v is odd (t replaces u)
+        do {
+            /* assert u<0 && v<0; */
+            // B4/B3: cast out twos from t.
+            while ((t & 1) == 0) { // while t is even..
+                t /= 2; // cast out twos
+            }
+            // B5 [reset max(u,v)]
+            if (t > 0) {
+                u = -t;
+            } else {
+                v = t;
+            }
+            // B6/B3. at this point both u and v should be odd.
+            t = (v - u) / 2;
+            // |u| larger: t positive (replace u)
+            // |v| larger: t negative (replace v)
+        } while (t != 0);
+        return -u * (1L << k); // gcd is u*2^k
+    }
+
+    /**
+     * Returns the least common multiple of the absolute value of two numbers, using the formula
+     * {@code lcm(a,b) = (a / gcd(a,b)) * b}. Special cases:
+     *
+     * <ul>
+     *   <li>The invocations {@code lcm(Integer.MIN_VALUE, n)} and {@code lcm(n,
+     *       Integer.MIN_VALUE)}, where {@code abs(n)} is a power of 2, throw an {@code
+     *       ArithmeticException}, because the result would be 2^31, which is too large for an int
+     *       value.
+     *   <li>The result of {@code lcm(0, x)} and {@code lcm(x, 0)} is {@code 0} for any {@code x}.
+     * </ul>
+     *
+     * @param a Number.
+     * @param b Number.
+     * @return the least common multiple, never negative.
+     * @throws MathArithmeticException if the result cannot be represented as a non-negative {@code
+     *     int} value.
+     * @since 1.1
+     */
+    public static int lcm(int a, int b) throws MathArithmeticException {
+        if (a == 0 || b == 0) {
+            return 0;
+        }
+        int lcm = FastMath.abs(ArithmeticUtils.mulAndCheck(a / gcd(a, b), b));
+        if (lcm == Integer.MIN_VALUE) {
+            throw new MathArithmeticException(LocalizedFormats.LCM_OVERFLOW_32_BITS, a, b);
+        }
+        return lcm;
+    }
+
+    /**
+     * Returns the least common multiple of the absolute value of two numbers, using the formula
+     * {@code lcm(a,b) = (a / gcd(a,b)) * b}. Special cases:
+     *
+     * <ul>
+     *   <li>The invocations {@code lcm(Long.MIN_VALUE, n)} and {@code lcm(n, Long.MIN_VALUE)},
+     *       where {@code abs(n)} is a power of 2, throw an {@code ArithmeticException}, because the
+     *       result would be 2^63, which is too large for an int value.
+     *   <li>The result of {@code lcm(0L, x)} and {@code lcm(x, 0L)} is {@code 0L} for any {@code
+     *       x}.
+     * </ul>
+     *
+     * @param a Number.
+     * @param b Number.
+     * @return the least common multiple, never negative.
+     * @throws MathArithmeticException if the result cannot be represented as a non-negative {@code
+     *     long} value.
+     * @since 2.1
+     */
+    public static long lcm(long a, long b) throws MathArithmeticException {
+        if (a == 0 || b == 0) {
+            return 0;
+        }
+        long lcm = FastMath.abs(ArithmeticUtils.mulAndCheck(a / gcd(a, b), b));
+        if (lcm == Long.MIN_VALUE) {
+            throw new MathArithmeticException(LocalizedFormats.LCM_OVERFLOW_64_BITS, a, b);
+        }
+        return lcm;
+    }
+
+    /**
+     * Multiply two integers, checking for overflow.
+     *
+     * @param x Factor.
+     * @param y Factor.
+     * @return the product {@code x * y}.
+     * @throws MathArithmeticException if the result can not be represented as an {@code int}.
+     * @since 1.1
+     */
+    public static int mulAndCheck(int x, int y) throws MathArithmeticException {
+        long m = ((long) x) * ((long) y);
+        if (m < Integer.MIN_VALUE || m > Integer.MAX_VALUE) {
+            throw new MathArithmeticException();
+        }
+        return (int) m;
+    }
+
+    /**
+     * Multiply two long integers, checking for overflow.
+     *
+     * @param a Factor.
+     * @param b Factor.
+     * @return the product {@code a * b}.
+     * @throws MathArithmeticException if the result can not be represented as a {@code long}.
+     * @since 1.2
+     */
+    public static long mulAndCheck(long a, long b) throws MathArithmeticException {
+        long ret;
+        if (a > b) {
+            // use symmetry to reduce boundary cases
+            ret = mulAndCheck(b, a);
+        } else {
+            if (a < 0) {
+                if (b < 0) {
+                    // check for positive overflow with negative a, negative b
+                    if (a >= Long.MAX_VALUE / b) {
+                        ret = a * b;
+                    } else {
+                        throw new MathArithmeticException();
+                    }
+                } else if (b > 0) {
+                    // check for negative overflow with negative a, positive b
+                    if (Long.MIN_VALUE / b <= a) {
+                        ret = a * b;
+                    } else {
+                        throw new MathArithmeticException();
+                    }
+                } else {
+                    // assert b == 0
+                    ret = 0;
+                }
+            } else if (a > 0) {
+                // assert a > 0
+                // assert b > 0
+
+                // check for positive overflow with positive a, positive b
+                if (a <= Long.MAX_VALUE / b) {
+                    ret = a * b;
+                } else {
+                    throw new MathArithmeticException();
+                }
+            } else {
+                // assert a == 0
+                ret = 0;
+            }
+        }
+        return ret;
+    }
+
+    /**
+     * Subtract two integers, checking for overflow.
+     *
+     * @param x Minuend.
+     * @param y Subtrahend.
+     * @return the difference {@code x - y}.
+     * @throws MathArithmeticException if the result can not be represented as an {@code int}.
+     * @since 1.1
+     */
+    public static int subAndCheck(int x, int y) throws MathArithmeticException {
+        long s = (long) x - (long) y;
+        if (s < Integer.MIN_VALUE || s > Integer.MAX_VALUE) {
+            throw new MathArithmeticException(LocalizedFormats.OVERFLOW_IN_SUBTRACTION, x, y);
+        }
+        return (int) s;
+    }
+
+    /**
+     * Subtract two long integers, checking for overflow.
+     *
+     * @param a Value.
+     * @param b Value.
+     * @return the difference {@code a - b}.
+     * @throws MathArithmeticException if the result can not be represented as a {@code long}.
+     * @since 1.2
+     */
+    public static long subAndCheck(long a, long b) throws MathArithmeticException {
+        long ret;
+        if (b == Long.MIN_VALUE) {
+            if (a < 0) {
+                ret = a - b;
+            } else {
+                throw new MathArithmeticException(LocalizedFormats.OVERFLOW_IN_ADDITION, a, -b);
+            }
+        } else {
+            // use additive inverse
+            ret = addAndCheck(a, -b, LocalizedFormats.OVERFLOW_IN_ADDITION);
+        }
+        return ret;
+    }
+
+    /**
+     * Raise an int to an int power.
+     *
+     * @param k Number to raise.
+     * @param e Exponent (must be positive or zero).
+     * @return \( k^e \)
+     * @throws NotPositiveException if {@code e < 0}.
+     * @throws MathArithmeticException if the result would overflow.
+     */
+    public static int pow(final int k, final int e)
+            throws NotPositiveException, MathArithmeticException {
+        if (e < 0) {
+            throw new NotPositiveException(LocalizedFormats.EXPONENT, e);
+        }
+
+        try {
+            int exp = e;
+            int result = 1;
+            int k2p = k;
+            while (true) {
+                if ((exp & 0x1) != 0) {
+                    result = mulAndCheck(result, k2p);
+                }
+
+                exp >>= 1;
+                if (exp == 0) {
+                    break;
+                }
+
+                k2p = mulAndCheck(k2p, k2p);
+            }
+
+            return result;
+        } catch (MathArithmeticException mae) {
+            // Add context information.
+            mae.getContext().addMessage(LocalizedFormats.OVERFLOW);
+            mae.getContext().addMessage(LocalizedFormats.BASE, k);
+            mae.getContext().addMessage(LocalizedFormats.EXPONENT, e);
+
+            // Rethrow.
+            throw mae;
+        }
+    }
+
+    /**
+     * Raise an int to a long power.
+     *
+     * @param k Number to raise.
+     * @param e Exponent (must be positive or zero).
+     * @return k<sup>e</sup>
+     * @throws NotPositiveException if {@code e < 0}.
+     * @deprecated As of 3.3. Please use {@link #pow(int,int)} instead.
+     */
+    @Deprecated
+    public static int pow(final int k, long e) throws NotPositiveException {
+        if (e < 0) {
+            throw new NotPositiveException(LocalizedFormats.EXPONENT, e);
+        }
+
+        int result = 1;
+        int k2p = k;
+        while (e != 0) {
+            if ((e & 0x1) != 0) {
+                result *= k2p;
+            }
+            k2p *= k2p;
+            e >>= 1;
+        }
+
+        return result;
+    }
+
+    /**
+     * Raise a long to an int power.
+     *
+     * @param k Number to raise.
+     * @param e Exponent (must be positive or zero).
+     * @return \( k^e \)
+     * @throws NotPositiveException if {@code e < 0}.
+     * @throws MathArithmeticException if the result would overflow.
+     */
+    public static long pow(final long k, final int e)
+            throws NotPositiveException, MathArithmeticException {
+        if (e < 0) {
+            throw new NotPositiveException(LocalizedFormats.EXPONENT, e);
+        }
+
+        try {
+            int exp = e;
+            long result = 1;
+            long k2p = k;
+            while (true) {
+                if ((exp & 0x1) != 0) {
+                    result = mulAndCheck(result, k2p);
+                }
+
+                exp >>= 1;
+                if (exp == 0) {
+                    break;
+                }
+
+                k2p = mulAndCheck(k2p, k2p);
+            }
+
+            return result;
+        } catch (MathArithmeticException mae) {
+            // Add context information.
+            mae.getContext().addMessage(LocalizedFormats.OVERFLOW);
+            mae.getContext().addMessage(LocalizedFormats.BASE, k);
+            mae.getContext().addMessage(LocalizedFormats.EXPONENT, e);
+
+            // Rethrow.
+            throw mae;
+        }
+    }
+
+    /**
+     * Raise a long to a long power.
+     *
+     * @param k Number to raise.
+     * @param e Exponent (must be positive or zero).
+     * @return k<sup>e</sup>
+     * @throws NotPositiveException if {@code e < 0}.
+     * @deprecated As of 3.3. Please use {@link #pow(long,int)} instead.
+     */
+    @Deprecated
+    public static long pow(final long k, long e) throws NotPositiveException {
+        if (e < 0) {
+            throw new NotPositiveException(LocalizedFormats.EXPONENT, e);
+        }
+
+        long result = 1l;
+        long k2p = k;
+        while (e != 0) {
+            if ((e & 0x1) != 0) {
+                result *= k2p;
+            }
+            k2p *= k2p;
+            e >>= 1;
+        }
+
+        return result;
+    }
+
+    /**
+     * Raise a BigInteger to an int power.
+     *
+     * @param k Number to raise.
+     * @param e Exponent (must be positive or zero).
+     * @return k<sup>e</sup>
+     * @throws NotPositiveException if {@code e < 0}.
+     */
+    public static BigInteger pow(final BigInteger k, int e) throws NotPositiveException {
+        if (e < 0) {
+            throw new NotPositiveException(LocalizedFormats.EXPONENT, e);
+        }
+
+        return k.pow(e);
+    }
+
+    /**
+     * Raise a BigInteger to a long power.
+     *
+     * @param k Number to raise.
+     * @param e Exponent (must be positive or zero).
+     * @return k<sup>e</sup>
+     * @throws NotPositiveException if {@code e < 0}.
+     */
+    public static BigInteger pow(final BigInteger k, long e) throws NotPositiveException {
+        if (e < 0) {
+            throw new NotPositiveException(LocalizedFormats.EXPONENT, e);
+        }
+
+        BigInteger result = BigInteger.ONE;
+        BigInteger k2p = k;
+        while (e != 0) {
+            if ((e & 0x1) != 0) {
+                result = result.multiply(k2p);
+            }
+            k2p = k2p.multiply(k2p);
+            e >>= 1;
+        }
+
+        return result;
+    }
+
+    /**
+     * Raise a BigInteger to a BigInteger power.
+     *
+     * @param k Number to raise.
+     * @param e Exponent (must be positive or zero).
+     * @return k<sup>e</sup>
+     * @throws NotPositiveException if {@code e < 0}.
+     */
+    public static BigInteger pow(final BigInteger k, BigInteger e) throws NotPositiveException {
+        if (e.compareTo(BigInteger.ZERO) < 0) {
+            throw new NotPositiveException(LocalizedFormats.EXPONENT, e);
+        }
+
+        BigInteger result = BigInteger.ONE;
+        BigInteger k2p = k;
+        while (!BigInteger.ZERO.equals(e)) {
+            if (e.testBit(0)) {
+                result = result.multiply(k2p);
+            }
+            k2p = k2p.multiply(k2p);
+            e = e.shiftRight(1);
+        }
+
+        return result;
+    }
+
+    /**
+     * Returns the <a href="http://mathworld.wolfram.com/StirlingNumberoftheSecondKind.html">
+     * Stirling number of the second kind</a>, "{@code S(n,k)}", the number of ways of partitioning
+     * an {@code n}-element set into {@code k} non-empty subsets.
+     *
+     * <p>The preconditions are {@code 0 <= k <= n } (otherwise {@code NotPositiveException} is
+     * thrown)
+     *
+     * @param n the size of the set
+     * @param k the number of non-empty subsets
+     * @return {@code S(n,k)}
+     * @throws NotPositiveException if {@code k < 0}.
+     * @throws NumberIsTooLargeException if {@code k > n}.
+     * @throws MathArithmeticException if some overflow happens, typically for n exceeding 25 and k
+     *     between 20 and n-2 (S(n,n-1) is handled specifically and does not overflow)
+     * @since 3.1
+     * @deprecated use {@link CombinatoricsUtils#stirlingS2(int, int)}
+     */
+    @Deprecated
+    public static long stirlingS2(final int n, final int k)
+            throws NotPositiveException, NumberIsTooLargeException, MathArithmeticException {
+        return CombinatoricsUtils.stirlingS2(n, k);
+    }
+
+    /**
+     * Add two long integers, checking for overflow.
+     *
+     * @param a Addend.
+     * @param b Addend.
+     * @param pattern Pattern to use for any thrown exception.
+     * @return the sum {@code a + b}.
+     * @throws MathArithmeticException if the result cannot be represented as a {@code long}.
+     * @since 1.2
+     */
+    private static long addAndCheck(long a, long b, Localizable pattern)
+            throws MathArithmeticException {
+        final long result = a + b;
+        if (!((a ^ b) < 0 | (a ^ result) >= 0)) {
+            throw new MathArithmeticException(pattern, a, b);
+        }
+        return result;
+    }
+
+    /**
+     * Returns true if the argument is a power of two.
+     *
+     * @param n the number to test
+     * @return true if the argument is a power of two
+     */
+    public static boolean isPowerOfTwo(long n) {
+        return (n > 0) && ((n & (n - 1)) == 0);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/util/BigReal.java b/src/main/java/org/apache/commons/math3/util/BigReal.java
new file mode 100644
index 0000000..54c59e8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/BigReal.java
@@ -0,0 +1,362 @@
+/*
+ * 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.math3.util;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.FieldElement;
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.math.MathContext;
+import java.math.RoundingMode;
+
+/**
+ * Arbitrary precision decimal number.
+ *
+ * <p>This class is a simple wrapper around the standard <code>BigDecimal</code> in order to
+ * implement the {@link FieldElement} interface.
+ *
+ * @since 2.0
+ */
+public class BigReal implements FieldElement<BigReal>, Comparable<BigReal>, Serializable {
+
+    /** A big real representing 0. */
+    public static final BigReal ZERO = new BigReal(BigDecimal.ZERO);
+
+    /** A big real representing 1. */
+    public static final BigReal ONE = new BigReal(BigDecimal.ONE);
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 4984534880991310382L;
+
+    /** Underlying BigDecimal. */
+    private final BigDecimal d;
+
+    /** Rounding mode for divisions. * */
+    private RoundingMode roundingMode = RoundingMode.HALF_UP;
+
+    /*** BigDecimal scale ***/
+    private int scale = 64;
+
+    /**
+     * Build an instance from a BigDecimal.
+     *
+     * @param val value of the instance
+     */
+    public BigReal(BigDecimal val) {
+        d = val;
+    }
+
+    /**
+     * Build an instance from a BigInteger.
+     *
+     * @param val value of the instance
+     */
+    public BigReal(BigInteger val) {
+        d = new BigDecimal(val);
+    }
+
+    /**
+     * Build an instance from an unscaled BigInteger.
+     *
+     * @param unscaledVal unscaled value
+     * @param scale scale to use
+     */
+    public BigReal(BigInteger unscaledVal, int scale) {
+        d = new BigDecimal(unscaledVal, scale);
+    }
+
+    /**
+     * Build an instance from an unscaled BigInteger.
+     *
+     * @param unscaledVal unscaled value
+     * @param scale scale to use
+     * @param mc to used
+     */
+    public BigReal(BigInteger unscaledVal, int scale, MathContext mc) {
+        d = new BigDecimal(unscaledVal, scale, mc);
+    }
+
+    /**
+     * Build an instance from a BigInteger.
+     *
+     * @param val value of the instance
+     * @param mc context to use
+     */
+    public BigReal(BigInteger val, MathContext mc) {
+        d = new BigDecimal(val, mc);
+    }
+
+    /**
+     * Build an instance from a characters representation.
+     *
+     * @param in character representation of the value
+     */
+    public BigReal(char[] in) {
+        d = new BigDecimal(in);
+    }
+
+    /**
+     * Build an instance from a characters representation.
+     *
+     * @param in character representation of the value
+     * @param offset offset of the first character to analyze
+     * @param len length of the array slice to analyze
+     */
+    public BigReal(char[] in, int offset, int len) {
+        d = new BigDecimal(in, offset, len);
+    }
+
+    /**
+     * Build an instance from a characters representation.
+     *
+     * @param in character representation of the value
+     * @param offset offset of the first character to analyze
+     * @param len length of the array slice to analyze
+     * @param mc context to use
+     */
+    public BigReal(char[] in, int offset, int len, MathContext mc) {
+        d = new BigDecimal(in, offset, len, mc);
+    }
+
+    /**
+     * Build an instance from a characters representation.
+     *
+     * @param in character representation of the value
+     * @param mc context to use
+     */
+    public BigReal(char[] in, MathContext mc) {
+        d = new BigDecimal(in, mc);
+    }
+
+    /**
+     * Build an instance from a double.
+     *
+     * @param val value of the instance
+     */
+    public BigReal(double val) {
+        d = new BigDecimal(val);
+    }
+
+    /**
+     * Build an instance from a double.
+     *
+     * @param val value of the instance
+     * @param mc context to use
+     */
+    public BigReal(double val, MathContext mc) {
+        d = new BigDecimal(val, mc);
+    }
+
+    /**
+     * Build an instance from an int.
+     *
+     * @param val value of the instance
+     */
+    public BigReal(int val) {
+        d = new BigDecimal(val);
+    }
+
+    /**
+     * Build an instance from an int.
+     *
+     * @param val value of the instance
+     * @param mc context to use
+     */
+    public BigReal(int val, MathContext mc) {
+        d = new BigDecimal(val, mc);
+    }
+
+    /**
+     * Build an instance from a long.
+     *
+     * @param val value of the instance
+     */
+    public BigReal(long val) {
+        d = new BigDecimal(val);
+    }
+
+    /**
+     * Build an instance from a long.
+     *
+     * @param val value of the instance
+     * @param mc context to use
+     */
+    public BigReal(long val, MathContext mc) {
+        d = new BigDecimal(val, mc);
+    }
+
+    /**
+     * Build an instance from a String representation.
+     *
+     * @param val character representation of the value
+     */
+    public BigReal(String val) {
+        d = new BigDecimal(val);
+    }
+
+    /**
+     * Build an instance from a String representation.
+     *
+     * @param val character representation of the value
+     * @param mc context to use
+     */
+    public BigReal(String val, MathContext mc) {
+        d = new BigDecimal(val, mc);
+    }
+
+    /***
+     * Gets the rounding mode for division operations
+     * The default is {@code RoundingMode.HALF_UP}
+     * @return the rounding mode.
+     * @since 2.1
+     */
+    public RoundingMode getRoundingMode() {
+        return roundingMode;
+    }
+
+    /***
+     * Sets the rounding mode for decimal divisions.
+     * @param roundingMode rounding mode for decimal divisions
+     * @since 2.1
+     */
+    public void setRoundingMode(RoundingMode roundingMode) {
+        this.roundingMode = roundingMode;
+    }
+
+    /***
+     * Sets the scale for division operations.
+     * The default is 64
+     * @return the scale
+     * @since 2.1
+     */
+    public int getScale() {
+        return scale;
+    }
+
+    /***
+     * Sets the scale for division operations.
+     * @param scale scale for division operations
+     * @since 2.1
+     */
+    public void setScale(int scale) {
+        this.scale = scale;
+    }
+
+    /** {@inheritDoc} */
+    public BigReal add(BigReal a) {
+        return new BigReal(d.add(a.d));
+    }
+
+    /** {@inheritDoc} */
+    public BigReal subtract(BigReal a) {
+        return new BigReal(d.subtract(a.d));
+    }
+
+    /** {@inheritDoc} */
+    public BigReal negate() {
+        return new BigReal(d.negate());
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws MathArithmeticException if {@code a} is zero
+     */
+    public BigReal divide(BigReal a) throws MathArithmeticException {
+        try {
+            return new BigReal(d.divide(a.d, scale, roundingMode));
+        } catch (ArithmeticException e) {
+            // Division by zero has occurred
+            throw new MathArithmeticException(LocalizedFormats.ZERO_NOT_ALLOWED);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws MathArithmeticException if {@code this} is zero
+     */
+    public BigReal reciprocal() throws MathArithmeticException {
+        try {
+            return new BigReal(BigDecimal.ONE.divide(d, scale, roundingMode));
+        } catch (ArithmeticException e) {
+            // Division by zero has occurred
+            throw new MathArithmeticException(LocalizedFormats.ZERO_NOT_ALLOWED);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public BigReal multiply(BigReal a) {
+        return new BigReal(d.multiply(a.d));
+    }
+
+    /** {@inheritDoc} */
+    public BigReal multiply(final int n) {
+        return new BigReal(d.multiply(new BigDecimal(n)));
+    }
+
+    /** {@inheritDoc} */
+    public int compareTo(BigReal a) {
+        return d.compareTo(a.d);
+    }
+
+    /**
+     * Get the double value corresponding to the instance.
+     *
+     * @return double value corresponding to the instance
+     */
+    public double doubleValue() {
+        return d.doubleValue();
+    }
+
+    /**
+     * Get the BigDecimal value corresponding to the instance.
+     *
+     * @return BigDecimal value corresponding to the instance
+     */
+    public BigDecimal bigDecimalValue() {
+        return d;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+
+        if (other instanceof BigReal) {
+            return d.equals(((BigReal) other).d);
+        }
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        return d.hashCode();
+    }
+
+    /** {@inheritDoc} */
+    public Field<BigReal> getField() {
+        return BigRealField.getInstance();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/util/BigRealField.java b/src/main/java/org/apache/commons/math3/util/BigRealField.java
new file mode 100644
index 0000000..cde8d34
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/BigRealField.java
@@ -0,0 +1,87 @@
+/*
+ * 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.math3.util;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.FieldElement;
+
+import java.io.Serializable;
+
+/**
+ * Representation of real numbers with arbitrary precision field.
+ *
+ * <p>This class is a singleton.
+ *
+ * @see BigReal
+ * @since 2.0
+ */
+public class BigRealField implements Field<BigReal>, Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 4756431066541037559L;
+
+    /** Private constructor for the singleton. */
+    private BigRealField() {}
+
+    /**
+     * Get the unique instance.
+     *
+     * @return the unique instance
+     */
+    public static BigRealField getInstance() {
+        return LazyHolder.INSTANCE;
+    }
+
+    /** {@inheritDoc} */
+    public BigReal getOne() {
+        return BigReal.ONE;
+    }
+
+    /** {@inheritDoc} */
+    public BigReal getZero() {
+        return BigReal.ZERO;
+    }
+
+    /** {@inheritDoc} */
+    public Class<? extends FieldElement<BigReal>> getRuntimeClass() {
+        return BigReal.class;
+    }
+
+    // CHECKSTYLE: stop HideUtilityClassConstructor
+    /**
+     * Holder for the instance.
+     *
+     * <p>We use here the Initialization On Demand Holder Idiom.
+     */
+    private static class LazyHolder {
+        /** Cached field instance. */
+        private static final BigRealField INSTANCE = new BigRealField();
+    }
+
+    // CHECKSTYLE: resume HideUtilityClassConstructor
+
+    /**
+     * Handle deserialization of the singleton.
+     *
+     * @return the singleton instance
+     */
+    private Object readResolve() {
+        // return the singleton instance
+        return LazyHolder.INSTANCE;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/util/CentralPivotingStrategy.java b/src/main/java/org/apache/commons/math3/util/CentralPivotingStrategy.java
new file mode 100644
index 0000000..3762b8e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/CentralPivotingStrategy.java
@@ -0,0 +1,45 @@
+/*
+ * 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.math3.util;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+
+import java.io.Serializable;
+
+/**
+ * A mid point strategy based on the average of begin and end indices.
+ *
+ * @since 3.4
+ */
+public class CentralPivotingStrategy implements PivotingStrategyInterface, Serializable {
+
+    /** Serializable UID. */
+    private static final long serialVersionUID = 20140713L;
+
+    /**
+     * {@inheritDoc} This in particular picks a average of begin and end indices
+     *
+     * @return The index corresponding to a simple average of the first and the last element indices
+     *     of the array slice
+     * @throws MathIllegalArgumentException when indices exceeds range
+     */
+    public int pivotIndex(final double[] work, final int begin, final int end)
+            throws MathIllegalArgumentException {
+        MathArrays.verifyValues(work, begin, end - begin);
+        return begin + (end - begin) / 2;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/util/Combinations.java b/src/main/java/org/apache/commons/math3/util/Combinations.java
new file mode 100644
index 0000000..8721dae
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/Combinations.java
@@ -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.math3.util;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathInternalError;
+import org.apache.commons.math3.exception.OutOfRangeException;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Utility to create <a href="http://en.wikipedia.org/wiki/Combination">combinations</a> {@code (n,
+ * k)} of {@code k} elements in a set of {@code n} elements.
+ *
+ * @since 3.3
+ */
+public class Combinations implements Iterable<int[]> {
+    /** Size of the set from which combinations are drawn. */
+    private final int n;
+
+    /** Number of elements in each combination. */
+    private final int k;
+
+    /** Iteration order. */
+    private final IterationOrder iterationOrder;
+
+    /** Describes the type of iteration performed by the {@link #iterator() iterator}. */
+    private enum IterationOrder {
+        /** Lexicographic order. */
+        LEXICOGRAPHIC
+    }
+
+    /**
+     * Creates an instance whose range is the k-element subsets of {0, ..., n - 1} represented as
+     * {@code int[]} arrays.
+     *
+     * <p>The iteration order is lexicographic: the arrays returned by the {@link #iterator()
+     * iterator} are sorted in descending order and they are visited in lexicographic order with
+     * significance from right to left. For example, {@code new Combinations(4, 2).iterator()}
+     * returns an iterator that will generate the following sequence of arrays on successive calls
+     * to {@code next()}:<br>
+     * {@code [0, 1], [0, 2], [1, 2], [0, 3], [1, 3], [2, 3]} If {@code k == 0} an iterator
+     * containing an empty array is returned; if {@code k == n} an iterator containing [0, ..., n -
+     * 1] is returned.
+     *
+     * @param n Size of the set from which subsets are selected.
+     * @param k Size of the subsets to be enumerated.
+     * @throws org.apache.commons.math3.exception.NotPositiveException if {@code n < 0}.
+     * @throws org.apache.commons.math3.exception.NumberIsTooLargeException if {@code k > n}.
+     */
+    public Combinations(int n, int k) {
+        this(n, k, IterationOrder.LEXICOGRAPHIC);
+    }
+
+    /**
+     * Creates an instance whose range is the k-element subsets of {0, ..., n - 1} represented as
+     * {@code int[]} arrays.
+     *
+     * <p>If the {@code iterationOrder} argument is set to {@link IterationOrder#LEXICOGRAPHIC}, the
+     * arrays returned by the {@link #iterator() iterator} are sorted in descending order and they
+     * are visited in lexicographic order with significance from right to left. For example, {@code
+     * new Combinations(4, 2).iterator()} returns an iterator that will generate the following
+     * sequence of arrays on successive calls to {@code next()}:<br>
+     * {@code [0, 1], [0, 2], [1, 2], [0, 3], [1, 3], [2, 3]} If {@code k == 0} an iterator
+     * containing an empty array is returned; if {@code k == n} an iterator containing [0, ..., n -
+     * 1] is returned.
+     *
+     * @param n Size of the set from which subsets are selected.
+     * @param k Size of the subsets to be enumerated.
+     * @param iterationOrder Specifies the {@link #iterator() iteration order}.
+     * @throws org.apache.commons.math3.exception.NotPositiveException if {@code n < 0}.
+     * @throws org.apache.commons.math3.exception.NumberIsTooLargeException if {@code k > n}.
+     */
+    private Combinations(int n, int k, IterationOrder iterationOrder) {
+        CombinatoricsUtils.checkBinomial(n, k);
+        this.n = n;
+        this.k = k;
+        this.iterationOrder = iterationOrder;
+    }
+
+    /**
+     * Gets the size of the set from which combinations are drawn.
+     *
+     * @return the size of the universe.
+     */
+    public int getN() {
+        return n;
+    }
+
+    /**
+     * Gets the number of elements in each combination.
+     *
+     * @return the size of the subsets to be enumerated.
+     */
+    public int getK() {
+        return k;
+    }
+
+    /** {@inheritDoc} */
+    public Iterator<int[]> iterator() {
+        if (k == 0 || k == n) {
+            return new SingletonIterator(MathArrays.natural(k));
+        }
+
+        switch (iterationOrder) {
+            case LEXICOGRAPHIC:
+                return new LexicographicIterator(n, k);
+            default:
+                throw new MathInternalError(); // Should never happen.
+        }
+    }
+
+    /**
+     * Defines a lexicographic ordering of combinations. The returned comparator allows to compare
+     * any two combinations that can be produced by this instance's {@link #iterator() iterator}.
+     * Its {@code compare(int[],int[])} method will throw exceptions if passed combinations that are
+     * inconsistent with this instance:
+     *
+     * <ul>
+     *   <li>{@code DimensionMismatchException} if the array lengths are not equal to {@code k},
+     *   <li>{@code OutOfRangeException} if an element of the array is not within the interval [0,
+     *       {@code n}).
+     * </ul>
+     *
+     * @return a lexicographic comparator.
+     */
+    public Comparator<int[]> comparator() {
+        return new LexicographicComparator(n, k);
+    }
+
+    /**
+     * Lexicographic combinations iterator.
+     *
+     * <p>Implementation follows Algorithm T in <i>The Art of Computer Programming</i> Internet
+     * Draft (PRE-FASCICLE 3A), "A Draft of Section 7.2.1.3 Generating All Combinations</a>, D.
+     * Knuth, 2004.
+     *
+     * <p>The degenerate cases {@code k == 0} and {@code k == n} are NOT handled by this
+     * implementation. If constructor arguments satisfy {@code k == 0} or {@code k >= n}, no
+     * exception is generated, but the iterator is empty.
+     */
+    private static class LexicographicIterator implements Iterator<int[]> {
+        /** Size of subsets returned by the iterator */
+        private final int k;
+
+        /**
+         * c[1], ..., c[k] stores the next combination; c[k + 1], c[k + 2] are sentinels.
+         *
+         * <p>Note that c[0] is "wasted" but this makes it a little easier to follow the code.
+         */
+        private final int[] c;
+
+        /** Return value for {@link #hasNext()} */
+        private boolean more = true;
+
+        /** Marker: smallest index such that c[j + 1] > j */
+        private int j;
+
+        /**
+         * Construct a CombinationIterator to enumerate k-sets from n.
+         *
+         * <p>NOTE: If {@code k === 0} or {@code k >= n}, the Iterator will be empty (that is,
+         * {@link #hasNext()} will return {@code false} immediately.
+         *
+         * @param n size of the set from which subsets are enumerated
+         * @param k size of the subsets to enumerate
+         */
+        LexicographicIterator(int n, int k) {
+            this.k = k;
+            c = new int[k + 3];
+            if (k == 0 || k >= n) {
+                more = false;
+                return;
+            }
+            // Initialize c to start with lexicographically first k-set
+            for (int i = 1; i <= k; i++) {
+                c[i] = i - 1;
+            }
+            // Initialize sentinels
+            c[k + 1] = n;
+            c[k + 2] = 0;
+            j = k; // Set up invariant: j is smallest index such that c[j + 1] > j
+        }
+
+        /** {@inheritDoc} */
+        public boolean hasNext() {
+            return more;
+        }
+
+        /** {@inheritDoc} */
+        public int[] next() {
+            if (!more) {
+                throw new NoSuchElementException();
+            }
+            // Copy return value (prepared by last activation)
+            final int[] ret = new int[k];
+            System.arraycopy(c, 1, ret, 0, k);
+
+            // Prepare next iteration
+            // T2 and T6 loop
+            int x = 0;
+            if (j > 0) {
+                x = j;
+                c[j] = x;
+                j--;
+                return ret;
+            }
+            // T3
+            if (c[1] + 1 < c[2]) {
+                c[1]++;
+                return ret;
+            } else {
+                j = 2;
+            }
+            // T4
+            boolean stepDone = false;
+            while (!stepDone) {
+                c[j - 1] = j - 2;
+                x = c[j] + 1;
+                if (x == c[j + 1]) {
+                    j++;
+                } else {
+                    stepDone = true;
+                }
+            }
+            // T5
+            if (j > k) {
+                more = false;
+                return ret;
+            }
+            // T6
+            c[j] = x;
+            j--;
+            return ret;
+        }
+
+        /** Not supported. */
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    /**
+     * Iterator with just one element to handle degenerate cases (full array, empty array) for
+     * combination iterator.
+     */
+    private static class SingletonIterator implements Iterator<int[]> {
+        /** Singleton array */
+        private final int[] singleton;
+
+        /** True on initialization, false after first call to next */
+        private boolean more = true;
+
+        /**
+         * Create a singleton iterator providing the given array.
+         *
+         * @param singleton array returned by the iterator
+         */
+        SingletonIterator(final int[] singleton) {
+            this.singleton = singleton;
+        }
+
+        /**
+         * @return True until next is called the first time, then false
+         */
+        public boolean hasNext() {
+            return more;
+        }
+
+        /**
+         * @return the singleton in first activation; throws NSEE thereafter
+         */
+        public int[] next() {
+            if (more) {
+                more = false;
+                return singleton;
+            } else {
+                throw new NoSuchElementException();
+            }
+        }
+
+        /** Not supported */
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    /**
+     * Defines the lexicographic ordering of combinations, using the {@link #lexNorm(int[])} method.
+     */
+    private static class LexicographicComparator implements Comparator<int[]>, Serializable {
+        /** Serializable version identifier. */
+        private static final long serialVersionUID = 20130906L;
+
+        /** Size of the set from which combinations are drawn. */
+        private final int n;
+
+        /** Number of elements in each combination. */
+        private final int k;
+
+        /**
+         * @param n Size of the set from which subsets are selected.
+         * @param k Size of the subsets to be enumerated.
+         */
+        LexicographicComparator(int n, int k) {
+            this.n = n;
+            this.k = k;
+        }
+
+        /**
+         * {@inheritDoc}
+         *
+         * @throws DimensionMismatchException if the array lengths are not equal to {@code k}.
+         * @throws OutOfRangeException if an element of the array is not within the interval [0,
+         *     {@code n}).
+         */
+        public int compare(int[] c1, int[] c2) {
+            if (c1.length != k) {
+                throw new DimensionMismatchException(c1.length, k);
+            }
+            if (c2.length != k) {
+                throw new DimensionMismatchException(c2.length, k);
+            }
+
+            // Method "lexNorm" works with ordered arrays.
+            final int[] c1s = MathArrays.copyOf(c1);
+            Arrays.sort(c1s);
+            final int[] c2s = MathArrays.copyOf(c2);
+            Arrays.sort(c2s);
+
+            final long v1 = lexNorm(c1s);
+            final long v2 = lexNorm(c2s);
+
+            if (v1 < v2) {
+                return -1;
+            } else if (v1 > v2) {
+                return 1;
+            } else {
+                return 0;
+            }
+        }
+
+        /**
+         * Computes the value (in base 10) represented by the digit (interpreted in base {@code n})
+         * in the input array in reverse order. For example if {@code c} is {@code {3, 2, 1}}, and
+         * {@code n} is 3, the method will return 18.
+         *
+         * @param c Input array.
+         * @return the lexicographic norm.
+         * @throws OutOfRangeException if an element of the array is not within the interval [0,
+         *     {@code n}).
+         */
+        private long lexNorm(int[] c) {
+            long ret = 0;
+            for (int i = 0; i < c.length; i++) {
+                final int digit = c[i];
+                if (digit < 0 || digit >= n) {
+                    throw new OutOfRangeException(digit, 0, n - 1);
+                }
+
+                ret += c[i] * ArithmeticUtils.pow(n, i);
+            }
+            return ret;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/util/CombinatoricsUtils.java b/src/main/java/org/apache/commons/math3/util/CombinatoricsUtils.java
new file mode 100644
index 0000000..9743a6b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/CombinatoricsUtils.java
@@ -0,0 +1,460 @@
+/*
+ * 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.math3.util;
+
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+import java.util.Iterator;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Combinatorial utilities.
+ *
+ * @since 3.3
+ */
+public final class CombinatoricsUtils {
+
+    /** All long-representable factorials */
+    static final long[] FACTORIALS =
+            new long[] {
+                1l,
+                1l,
+                2l,
+                6l,
+                24l,
+                120l,
+                720l,
+                5040l,
+                40320l,
+                362880l,
+                3628800l,
+                39916800l,
+                479001600l,
+                6227020800l,
+                87178291200l,
+                1307674368000l,
+                20922789888000l,
+                355687428096000l,
+                6402373705728000l,
+                121645100408832000l,
+                2432902008176640000l
+            };
+
+    /** Stirling numbers of the second kind. */
+    static final AtomicReference<long[][]> STIRLING_S2 = new AtomicReference<long[][]>(null);
+
+    /** Private constructor (class contains only static methods). */
+    private CombinatoricsUtils() {}
+
+    /**
+     * Returns an exact representation of the <a
+     * href="http://mathworld.wolfram.com/BinomialCoefficient.html">Binomial Coefficient</a>,
+     * "{@code n choose k}", the number of {@code k}-element subsets that can be selected from an
+     * {@code n}-element set.
+     *
+     * <p><Strong>Preconditions</strong>:
+     *
+     * <ul>
+     *   <li>{@code 0 <= k <= n } (otherwise {@code MathIllegalArgumentException} is thrown)
+     *   <li>The result is small enough to fit into a {@code long}. The largest value of {@code n}
+     *       for which all coefficients are {@code < Long.MAX_VALUE} is 66. If the computed value
+     *       exceeds {@code Long.MAX_VALUE} a {@code MathArithMeticException} is thrown.
+     * </ul>
+     *
+     * @param n the size of the set
+     * @param k the size of the subsets to be counted
+     * @return {@code n choose k}
+     * @throws NotPositiveException if {@code n < 0}.
+     * @throws NumberIsTooLargeException if {@code k > n}.
+     * @throws MathArithmeticException if the result is too large to be represented by a long
+     *     integer.
+     */
+    public static long binomialCoefficient(final int n, final int k)
+            throws NotPositiveException, NumberIsTooLargeException, MathArithmeticException {
+        CombinatoricsUtils.checkBinomial(n, k);
+        if ((n == k) || (k == 0)) {
+            return 1;
+        }
+        if ((k == 1) || (k == n - 1)) {
+            return n;
+        }
+        // Use symmetry for large k
+        if (k > n / 2) {
+            return binomialCoefficient(n, n - k);
+        }
+
+        // We use the formula
+        // (n choose k) = n! / (n-k)! / k!
+        // (n choose k) == ((n-k+1)*...*n) / (1*...*k)
+        // which could be written
+        // (n choose k) == (n-1 choose k-1) * n / k
+        long result = 1;
+        if (n <= 61) {
+            // For n <= 61, the naive implementation cannot overflow.
+            int i = n - k + 1;
+            for (int j = 1; j <= k; j++) {
+                result = result * i / j;
+                i++;
+            }
+        } else if (n <= 66) {
+            // For n > 61 but n <= 66, the result cannot overflow,
+            // but we must take care not to overflow intermediate values.
+            int i = n - k + 1;
+            for (int j = 1; j <= k; j++) {
+                // We know that (result * i) is divisible by j,
+                // but (result * i) may overflow, so we split j:
+                // Filter out the gcd, d, so j/d and i/d are integer.
+                // result is divisible by (j/d) because (j/d)
+                // is relative prime to (i/d) and is a divisor of
+                // result * (i/d).
+                final long d = ArithmeticUtils.gcd(i, j);
+                result = (result / (j / d)) * (i / d);
+                i++;
+            }
+        } else {
+            // For n > 66, a result overflow might occur, so we check
+            // the multiplication, taking care to not overflow
+            // unnecessary.
+            int i = n - k + 1;
+            for (int j = 1; j <= k; j++) {
+                final long d = ArithmeticUtils.gcd(i, j);
+                result = ArithmeticUtils.mulAndCheck(result / (j / d), i / d);
+                i++;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Returns a {@code double} representation of the <a
+     * href="http://mathworld.wolfram.com/BinomialCoefficient.html">Binomial Coefficient</a>,
+     * "{@code n choose k}", the number of {@code k}-element subsets that can be selected from an
+     * {@code n}-element set.
+     *
+     * <p><Strong>Preconditions</strong>:
+     *
+     * <ul>
+     *   <li>{@code 0 <= k <= n } (otherwise {@code IllegalArgumentException} is thrown)
+     *   <li>The result is small enough to fit into a {@code double}. The largest value of {@code n}
+     *       for which all coefficients are less than Double.MAX_VALUE is 1029. If the computed
+     *       value exceeds Double.MAX_VALUE, Double.POSITIVE_INFINITY is returned
+     * </ul>
+     *
+     * @param n the size of the set
+     * @param k the size of the subsets to be counted
+     * @return {@code n choose k}
+     * @throws NotPositiveException if {@code n < 0}.
+     * @throws NumberIsTooLargeException if {@code k > n}.
+     * @throws MathArithmeticException if the result is too large to be represented by a long
+     *     integer.
+     */
+    public static double binomialCoefficientDouble(final int n, final int k)
+            throws NotPositiveException, NumberIsTooLargeException, MathArithmeticException {
+        CombinatoricsUtils.checkBinomial(n, k);
+        if ((n == k) || (k == 0)) {
+            return 1d;
+        }
+        if ((k == 1) || (k == n - 1)) {
+            return n;
+        }
+        if (k > n / 2) {
+            return binomialCoefficientDouble(n, n - k);
+        }
+        if (n < 67) {
+            return binomialCoefficient(n, k);
+        }
+
+        double result = 1d;
+        for (int i = 1; i <= k; i++) {
+            result *= (double) (n - k + i) / (double) i;
+        }
+
+        return FastMath.floor(result + 0.5);
+    }
+
+    /**
+     * Returns the natural {@code log} of the <a
+     * href="http://mathworld.wolfram.com/BinomialCoefficient.html">Binomial Coefficient</a>,
+     * "{@code n choose k}", the number of {@code k}-element subsets that can be selected from an
+     * {@code n}-element set.
+     *
+     * <p><Strong>Preconditions</strong>:
+     *
+     * <ul>
+     *   <li>{@code 0 <= k <= n } (otherwise {@code MathIllegalArgumentException} is thrown)
+     * </ul>
+     *
+     * @param n the size of the set
+     * @param k the size of the subsets to be counted
+     * @return {@code n choose k}
+     * @throws NotPositiveException if {@code n < 0}.
+     * @throws NumberIsTooLargeException if {@code k > n}.
+     * @throws MathArithmeticException if the result is too large to be represented by a long
+     *     integer.
+     */
+    public static double binomialCoefficientLog(final int n, final int k)
+            throws NotPositiveException, NumberIsTooLargeException, MathArithmeticException {
+        CombinatoricsUtils.checkBinomial(n, k);
+        if ((n == k) || (k == 0)) {
+            return 0;
+        }
+        if ((k == 1) || (k == n - 1)) {
+            return FastMath.log(n);
+        }
+
+        /*
+         * For values small enough to do exact integer computation,
+         * return the log of the exact value
+         */
+        if (n < 67) {
+            return FastMath.log(binomialCoefficient(n, k));
+        }
+
+        /*
+         * Return the log of binomialCoefficientDouble for values that will not
+         * overflow binomialCoefficientDouble
+         */
+        if (n < 1030) {
+            return FastMath.log(binomialCoefficientDouble(n, k));
+        }
+
+        if (k > n / 2) {
+            return binomialCoefficientLog(n, n - k);
+        }
+
+        /*
+         * Sum logs for values that could overflow
+         */
+        double logSum = 0;
+
+        // n!/(n-k)!
+        for (int i = n - k + 1; i <= n; i++) {
+            logSum += FastMath.log(i);
+        }
+
+        // divide by k!
+        for (int i = 2; i <= k; i++) {
+            logSum -= FastMath.log(i);
+        }
+
+        return logSum;
+    }
+
+    /**
+     * Returns n!. Shorthand for {@code n} <a href="http://mathworld.wolfram.com/Factorial.html">
+     * Factorial</a>, the product of the numbers {@code 1,...,n}.
+     *
+     * <p><Strong>Preconditions</strong>:
+     *
+     * <ul>
+     *   <li>{@code n >= 0} (otherwise {@code MathIllegalArgumentException} is thrown)
+     *   <li>The result is small enough to fit into a {@code long}. The largest value of {@code n}
+     *       for which {@code n!} does not exceed Long.MAX_VALUE} is 20. If the computed value
+     *       exceeds {@code Long.MAX_VALUE} an {@code MathArithMeticException } is thrown.
+     * </ul>
+     *
+     * @param n argument
+     * @return {@code n!}
+     * @throws MathArithmeticException if the result is too large to be represented by a {@code
+     *     long}.
+     * @throws NotPositiveException if {@code n < 0}.
+     * @throws MathArithmeticException if {@code n > 20}: The factorial value is too large to fit in
+     *     a {@code long}.
+     */
+    public static long factorial(final int n) throws NotPositiveException, MathArithmeticException {
+        if (n < 0) {
+            throw new NotPositiveException(LocalizedFormats.FACTORIAL_NEGATIVE_PARAMETER, n);
+        }
+        if (n > 20) {
+            throw new MathArithmeticException();
+        }
+        return FACTORIALS[n];
+    }
+
+    /**
+     * Compute n!, the<a href="http://mathworld.wolfram.com/Factorial.html">factorial</a> of {@code
+     * n} (the product of the numbers 1 to n), as a {@code double}. The result should be small
+     * enough to fit into a {@code double}: The largest {@code n} for which {@code n!} does not
+     * exceed {@code Double.MAX_VALUE} is 170. If the computed value exceeds {@code
+     * Double.MAX_VALUE}, {@code Double.POSITIVE_INFINITY} is returned.
+     *
+     * @param n Argument.
+     * @return {@code n!}
+     * @throws NotPositiveException if {@code n < 0}.
+     */
+    public static double factorialDouble(final int n) throws NotPositiveException {
+        if (n < 0) {
+            throw new NotPositiveException(LocalizedFormats.FACTORIAL_NEGATIVE_PARAMETER, n);
+        }
+        if (n < 21) {
+            return FACTORIALS[n];
+        }
+        return FastMath.floor(FastMath.exp(CombinatoricsUtils.factorialLog(n)) + 0.5);
+    }
+
+    /**
+     * Compute the natural logarithm of the factorial of {@code n}.
+     *
+     * @param n Argument.
+     * @return {@code n!}
+     * @throws NotPositiveException if {@code n < 0}.
+     */
+    public static double factorialLog(final int n) throws NotPositiveException {
+        if (n < 0) {
+            throw new NotPositiveException(LocalizedFormats.FACTORIAL_NEGATIVE_PARAMETER, n);
+        }
+        if (n < 21) {
+            return FastMath.log(FACTORIALS[n]);
+        }
+        double logSum = 0;
+        for (int i = 2; i <= n; i++) {
+            logSum += FastMath.log(i);
+        }
+        return logSum;
+    }
+
+    /**
+     * Returns the <a href="http://mathworld.wolfram.com/StirlingNumberoftheSecondKind.html">
+     * Stirling number of the second kind</a>, "{@code S(n,k)}", the number of ways of partitioning
+     * an {@code n}-element set into {@code k} non-empty subsets.
+     *
+     * <p>The preconditions are {@code 0 <= k <= n } (otherwise {@code NotPositiveException} is
+     * thrown)
+     *
+     * @param n the size of the set
+     * @param k the number of non-empty subsets
+     * @return {@code S(n,k)}
+     * @throws NotPositiveException if {@code k < 0}.
+     * @throws NumberIsTooLargeException if {@code k > n}.
+     * @throws MathArithmeticException if some overflow happens, typically for n exceeding 25 and k
+     *     between 20 and n-2 (S(n,n-1) is handled specifically and does not overflow)
+     * @since 3.1
+     */
+    public static long stirlingS2(final int n, final int k)
+            throws NotPositiveException, NumberIsTooLargeException, MathArithmeticException {
+        if (k < 0) {
+            throw new NotPositiveException(k);
+        }
+        if (k > n) {
+            throw new NumberIsTooLargeException(k, n, true);
+        }
+
+        long[][] stirlingS2 = STIRLING_S2.get();
+
+        if (stirlingS2 == null) {
+            // the cache has never been initialized, compute the first numbers
+            // by direct recurrence relation
+
+            // as S(26,9) = 11201516780955125625 is larger than Long.MAX_VALUE
+            // we must stop computation at row 26
+            final int maxIndex = 26;
+            stirlingS2 = new long[maxIndex][];
+            stirlingS2[0] = new long[] {1l};
+            for (int i = 1; i < stirlingS2.length; ++i) {
+                stirlingS2[i] = new long[i + 1];
+                stirlingS2[i][0] = 0;
+                stirlingS2[i][1] = 1;
+                stirlingS2[i][i] = 1;
+                for (int j = 2; j < i; ++j) {
+                    stirlingS2[i][j] = j * stirlingS2[i - 1][j] + stirlingS2[i - 1][j - 1];
+                }
+            }
+
+            // atomically save the cache
+            STIRLING_S2.compareAndSet(null, stirlingS2);
+        }
+
+        if (n < stirlingS2.length) {
+            // the number is in the small cache
+            return stirlingS2[n][k];
+        } else {
+            // use explicit formula to compute the number without caching it
+            if (k == 0) {
+                return 0;
+            } else if (k == 1 || k == n) {
+                return 1;
+            } else if (k == 2) {
+                return (1l << (n - 1)) - 1l;
+            } else if (k == n - 1) {
+                return binomialCoefficient(n, 2);
+            } else {
+                // definition formula: note that this may trigger some overflow
+                long sum = 0;
+                long sign = ((k & 0x1) == 0) ? 1 : -1;
+                for (int j = 1; j <= k; ++j) {
+                    sign = -sign;
+                    sum += sign * binomialCoefficient(k, j) * ArithmeticUtils.pow(j, n);
+                    if (sum < 0) {
+                        // there was an overflow somewhere
+                        throw new MathArithmeticException(
+                                LocalizedFormats.ARGUMENT_OUTSIDE_DOMAIN,
+                                n,
+                                0,
+                                stirlingS2.length - 1);
+                    }
+                }
+                return sum / factorial(k);
+            }
+        }
+    }
+
+    /**
+     * Returns an iterator whose range is the k-element subsets of {0, ..., n - 1} represented as
+     * {@code int[]} arrays.
+     *
+     * <p>The arrays returned by the iterator are sorted in descending order and they are visited in
+     * lexicographic order with significance from right to left. For example,
+     * combinationsIterator(4, 2) returns an Iterator that will generate the following sequence of
+     * arrays on successive calls to {@code next()}:
+     *
+     * <p>{@code [0, 1], [0, 2], [1, 2], [0, 3], [1, 3], [2, 3]}
+     *
+     * <p>If {@code k == 0} an Iterator containing an empty array is returned and if {@code k == n}
+     * an Iterator containing [0, ..., n -1] is returned.
+     *
+     * @param n Size of the set from which subsets are selected.
+     * @param k Size of the subsets to be enumerated.
+     * @return an {@link Iterator iterator} over the k-sets in n.
+     * @throws NotPositiveException if {@code n < 0}.
+     * @throws NumberIsTooLargeException if {@code k > n}.
+     */
+    public static Iterator<int[]> combinationsIterator(int n, int k) {
+        return new Combinations(n, k).iterator();
+    }
+
+    /**
+     * Check binomial preconditions.
+     *
+     * @param n Size of the set.
+     * @param k Size of the subsets to be counted.
+     * @throws NotPositiveException if {@code n < 0}.
+     * @throws NumberIsTooLargeException if {@code k > n}.
+     */
+    public static void checkBinomial(final int n, final int k)
+            throws NumberIsTooLargeException, NotPositiveException {
+        if (n < k) {
+            throw new NumberIsTooLargeException(
+                    LocalizedFormats.BINOMIAL_INVALID_PARAMETERS_ORDER, k, n, true);
+        }
+        if (n < 0) {
+            throw new NotPositiveException(LocalizedFormats.BINOMIAL_NEGATIVE_PARAMETER, n);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/util/CompositeFormat.java b/src/main/java/org/apache/commons/math3/util/CompositeFormat.java
new file mode 100644
index 0000000..63dfd59
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/CompositeFormat.java
@@ -0,0 +1,215 @@
+/*
+ * 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.math3.util;
+
+import java.text.FieldPosition;
+import java.text.NumberFormat;
+import java.text.ParsePosition;
+import java.util.Locale;
+
+/** Base class for formatters of composite objects (complex numbers, vectors ...). */
+public class CompositeFormat {
+
+    /** Class contains only static methods. */
+    private CompositeFormat() {}
+
+    /**
+     * Create a default number format. The default number format is based on {@link
+     * NumberFormat#getInstance()} with the only customizing that the maximum number of fraction
+     * digits is set to 10.
+     *
+     * @return the default number format.
+     */
+    public static NumberFormat getDefaultNumberFormat() {
+        return getDefaultNumberFormat(Locale.getDefault());
+    }
+
+    /**
+     * Create a default number format. The default number format is based on {@link
+     * NumberFormat#getInstance(java.util.Locale)} with the only customizing that the maximum number
+     * of fraction digits is set to 10.
+     *
+     * @param locale the specific locale used by the format.
+     * @return the default number format specific to the given locale.
+     */
+    public static NumberFormat getDefaultNumberFormat(final Locale locale) {
+        final NumberFormat nf = NumberFormat.getInstance(locale);
+        nf.setMaximumFractionDigits(10);
+        return nf;
+    }
+
+    /**
+     * Parses <code>source</code> until a non-whitespace character is found.
+     *
+     * @param source the string to parse
+     * @param pos input/output parsing parameter. On output, <code>pos</code> holds the index of the
+     *     next non-whitespace character.
+     */
+    public static void parseAndIgnoreWhitespace(final String source, final ParsePosition pos) {
+        parseNextCharacter(source, pos);
+        pos.setIndex(pos.getIndex() - 1);
+    }
+
+    /**
+     * Parses <code>source</code> until a non-whitespace character is found.
+     *
+     * @param source the string to parse
+     * @param pos input/output parsing parameter.
+     * @return the first non-whitespace character.
+     */
+    public static char parseNextCharacter(final String source, final ParsePosition pos) {
+        int index = pos.getIndex();
+        final int n = source.length();
+        char ret = 0;
+
+        if (index < n) {
+            char c;
+            do {
+                c = source.charAt(index++);
+            } while (Character.isWhitespace(c) && index < n);
+            pos.setIndex(index);
+
+            if (index < n) {
+                ret = c;
+            }
+        }
+
+        return ret;
+    }
+
+    /**
+     * Parses <code>source</code> for special double values. These values include Double.NaN,
+     * Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY.
+     *
+     * @param source the string to parse
+     * @param value the special value to parse.
+     * @param pos input/output parsing parameter.
+     * @return the special number.
+     */
+    private static Number parseNumber(
+            final String source, final double value, final ParsePosition pos) {
+        Number ret = null;
+
+        StringBuilder sb = new StringBuilder();
+        sb.append('(');
+        sb.append(value);
+        sb.append(')');
+
+        final int n = sb.length();
+        final int startIndex = pos.getIndex();
+        final int endIndex = startIndex + n;
+        if (endIndex < source.length()
+                && source.substring(startIndex, endIndex).compareTo(sb.toString()) == 0) {
+            ret = Double.valueOf(value);
+            pos.setIndex(endIndex);
+        }
+
+        return ret;
+    }
+
+    /**
+     * Parses <code>source</code> for a number. This method can parse normal, numeric values as well
+     * as special values. These special values include Double.NaN, Double.POSITIVE_INFINITY,
+     * Double.NEGATIVE_INFINITY.
+     *
+     * @param source the string to parse
+     * @param format the number format used to parse normal, numeric values.
+     * @param pos input/output parsing parameter.
+     * @return the parsed number.
+     */
+    public static Number parseNumber(
+            final String source, final NumberFormat format, final ParsePosition pos) {
+        final int startIndex = pos.getIndex();
+        Number number = format.parse(source, pos);
+        final int endIndex = pos.getIndex();
+
+        // check for error parsing number
+        if (startIndex == endIndex) {
+            // try parsing special numbers
+            final double[] special = {
+                Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY
+            };
+            for (int i = 0; i < special.length; ++i) {
+                number = parseNumber(source, special[i], pos);
+                if (number != null) {
+                    break;
+                }
+            }
+        }
+
+        return number;
+    }
+
+    /**
+     * Parse <code>source</code> for an expected fixed string.
+     *
+     * @param source the string to parse
+     * @param expected expected string
+     * @param pos input/output parsing parameter.
+     * @return true if the expected string was there
+     */
+    public static boolean parseFixedstring(
+            final String source, final String expected, final ParsePosition pos) {
+
+        final int startIndex = pos.getIndex();
+        final int endIndex = startIndex + expected.length();
+        if ((startIndex >= source.length())
+                || (endIndex > source.length())
+                || (source.substring(startIndex, endIndex).compareTo(expected) != 0)) {
+            // set index back to start, error index should be the start index
+            pos.setIndex(startIndex);
+            pos.setErrorIndex(startIndex);
+            return false;
+        }
+
+        // the string was here
+        pos.setIndex(endIndex);
+        return true;
+    }
+
+    /**
+     * Formats a double value to produce a string. In general, the value is formatted using the
+     * formatting rules of <code>format</code>. There are three exceptions to this:
+     *
+     * <ol>
+     *   <li>NaN is formatted as '(NaN)'
+     *   <li>Positive infinity is formatted as '(Infinity)'
+     *   <li>Negative infinity is formatted as '(-Infinity)'
+     * </ol>
+     *
+     * @param value the double to format.
+     * @param format the format used.
+     * @param toAppendTo where the text is to be appended
+     * @param pos On input: an alignment field, if desired. On output: the offsets of the alignment
+     *     field
+     * @return the value passed in as toAppendTo.
+     */
+    public static StringBuffer formatDouble(
+            final double value,
+            final NumberFormat format,
+            final StringBuffer toAppendTo,
+            final FieldPosition pos) {
+        if (Double.isNaN(value) || Double.isInfinite(value)) {
+            toAppendTo.append('(');
+            toAppendTo.append(value);
+            toAppendTo.append(')');
+        } else {
+            format.format(value, toAppendTo, pos);
+        }
+        return toAppendTo;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/util/ContinuedFraction.java b/src/main/java/org/apache/commons/math3/util/ContinuedFraction.java
new file mode 100644
index 0000000..69b4964
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/ContinuedFraction.java
@@ -0,0 +1,181 @@
+/*
+ * 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.math3.util;
+
+import org.apache.commons.math3.exception.ConvergenceException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+/**
+ * Provides a generic means to evaluate continued fractions. Subclasses simply provided the a and b
+ * coefficients to evaluate the continued fraction.
+ *
+ * <p>References:
+ *
+ * <ul>
+ *   <li><a href="http://mathworld.wolfram.com/ContinuedFraction.html">Continued Fraction</a>
+ * </ul>
+ */
+public abstract class ContinuedFraction {
+    /** Maximum allowed numerical error. */
+    private static final double DEFAULT_EPSILON = 10e-9;
+
+    /** Default constructor. */
+    protected ContinuedFraction() {
+        super();
+    }
+
+    /**
+     * Access the n-th a coefficient of the continued fraction. Since a can be a function of the
+     * evaluation point, x, that is passed in as well.
+     *
+     * @param n the coefficient index to retrieve.
+     * @param x the evaluation point.
+     * @return the n-th a coefficient.
+     */
+    protected abstract double getA(int n, double x);
+
+    /**
+     * Access the n-th b coefficient of the continued fraction. Since b can be a function of the
+     * evaluation point, x, that is passed in as well.
+     *
+     * @param n the coefficient index to retrieve.
+     * @param x the evaluation point.
+     * @return the n-th b coefficient.
+     */
+    protected abstract double getB(int n, double x);
+
+    /**
+     * Evaluates the continued fraction at the value x.
+     *
+     * @param x the evaluation point.
+     * @return the value of the continued fraction evaluated at x.
+     * @throws ConvergenceException if the algorithm fails to converge.
+     */
+    public double evaluate(double x) throws ConvergenceException {
+        return evaluate(x, DEFAULT_EPSILON, Integer.MAX_VALUE);
+    }
+
+    /**
+     * Evaluates the continued fraction at the value x.
+     *
+     * @param x the evaluation point.
+     * @param epsilon maximum error allowed.
+     * @return the value of the continued fraction evaluated at x.
+     * @throws ConvergenceException if the algorithm fails to converge.
+     */
+    public double evaluate(double x, double epsilon) throws ConvergenceException {
+        return evaluate(x, epsilon, Integer.MAX_VALUE);
+    }
+
+    /**
+     * Evaluates the continued fraction at the value x.
+     *
+     * @param x the evaluation point.
+     * @param maxIterations maximum number of convergents
+     * @return the value of the continued fraction evaluated at x.
+     * @throws ConvergenceException if the algorithm fails to converge.
+     * @throws MaxCountExceededException if maximal number of iterations is reached
+     */
+    public double evaluate(double x, int maxIterations)
+            throws ConvergenceException, MaxCountExceededException {
+        return evaluate(x, DEFAULT_EPSILON, maxIterations);
+    }
+
+    /**
+     * Evaluates the continued fraction at the value x.
+     *
+     * <p>The implementation of this method is based on the modified Lentz algorithm as described on
+     * page 18 ff. in:
+     *
+     * <ul>
+     *   <li>I. J. Thompson, A. R. Barnett. "Coulomb and Bessel Functions of Complex Arguments and
+     *       Order." <a target="_blank"
+     *       href="http://www.fresco.org.uk/papers/Thompson-JCP64p490.pdf">
+     *       http://www.fresco.org.uk/papers/Thompson-JCP64p490.pdf</a>
+     * </ul>
+     *
+     * <b>Note:</b> the implementation uses the terms a<sub>i</sub> and b<sub>i</sub> as defined in
+     * <a href="http://mathworld.wolfram.com/ContinuedFraction.html">Continued Fraction @
+     * MathWorld</a>.
+     *
+     * @param x the evaluation point.
+     * @param epsilon maximum error allowed.
+     * @param maxIterations maximum number of convergents
+     * @return the value of the continued fraction evaluated at x.
+     * @throws ConvergenceException if the algorithm fails to converge.
+     * @throws MaxCountExceededException if maximal number of iterations is reached
+     */
+    public double evaluate(double x, double epsilon, int maxIterations)
+            throws ConvergenceException, MaxCountExceededException {
+        final double small = 1e-50;
+        double hPrev = getA(0, x);
+
+        // use the value of small as epsilon criteria for zero checks
+        if (Precision.equals(hPrev, 0.0, small)) {
+            hPrev = small;
+        }
+
+        int n = 1;
+        double dPrev = 0.0;
+        double cPrev = hPrev;
+        double hN = hPrev;
+
+        while (n < maxIterations) {
+            final double a = getA(n, x);
+            final double b = getB(n, x);
+
+            double dN = a + b * dPrev;
+            if (Precision.equals(dN, 0.0, small)) {
+                dN = small;
+            }
+            double cN = a + b / cPrev;
+            if (Precision.equals(cN, 0.0, small)) {
+                cN = small;
+            }
+
+            dN = 1 / dN;
+            final double deltaN = cN * dN;
+            hN = hPrev * deltaN;
+
+            if (Double.isInfinite(hN)) {
+                throw new ConvergenceException(
+                        LocalizedFormats.CONTINUED_FRACTION_INFINITY_DIVERGENCE, x);
+            }
+            if (Double.isNaN(hN)) {
+                throw new ConvergenceException(
+                        LocalizedFormats.CONTINUED_FRACTION_NAN_DIVERGENCE, x);
+            }
+
+            if (FastMath.abs(deltaN - 1.0) < epsilon) {
+                break;
+            }
+
+            dPrev = dN;
+            cPrev = cN;
+            hPrev = hN;
+            n++;
+        }
+
+        if (n >= maxIterations) {
+            throw new MaxCountExceededException(
+                    LocalizedFormats.NON_CONVERGENT_CONTINUED_FRACTION, maxIterations, x);
+        }
+
+        return hN;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/util/Decimal64.java b/src/main/java/org/apache/commons/math3/util/Decimal64.java
new file mode 100644
index 0000000..4d25339
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/Decimal64.java
@@ -0,0 +1,806 @@
+/*
+ * 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.math3.util;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+
+/**
+ * This class wraps a {@code double} value in an object. It is similar to the standard class {@link
+ * Double}, while also implementing the {@link RealFieldElement} interface.
+ *
+ * @since 3.1
+ */
+public class Decimal64 extends Number
+        implements RealFieldElement<Decimal64>, Comparable<Decimal64> {
+
+    /** The constant value of {@code 0d} as a {@code Decimal64}. */
+    public static final Decimal64 ZERO;
+
+    /** The constant value of {@code 1d} as a {@code Decimal64}. */
+    public static final Decimal64 ONE;
+
+    /** The constant value of {@link Double#NEGATIVE_INFINITY} as a {@code Decimal64}. */
+    public static final Decimal64 NEGATIVE_INFINITY;
+
+    /** The constant value of {@link Double#POSITIVE_INFINITY} as a {@code Decimal64}. */
+    public static final Decimal64 POSITIVE_INFINITY;
+
+    /** The constant value of {@link Double#NaN} as a {@code Decimal64}. */
+    public static final Decimal64 NAN;
+
+    /** */
+    private static final long serialVersionUID = 20120227L;
+
+    static {
+        ZERO = new Decimal64(0d);
+        ONE = new Decimal64(1d);
+        NEGATIVE_INFINITY = new Decimal64(Double.NEGATIVE_INFINITY);
+        POSITIVE_INFINITY = new Decimal64(Double.POSITIVE_INFINITY);
+        NAN = new Decimal64(Double.NaN);
+    }
+
+    /** The primitive {@code double} value of this object. */
+    private final double value;
+
+    /**
+     * Creates a new instance of this class.
+     *
+     * @param x the primitive {@code double} value of the object to be created
+     */
+    public Decimal64(final double x) {
+        this.value = x;
+    }
+
+    /*
+     * Methods from the FieldElement interface.
+     */
+
+    /** {@inheritDoc} */
+    public Field<Decimal64> getField() {
+        return Decimal64Field.getInstance();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The current implementation strictly enforces {@code this.add(a).equals(new
+     * Decimal64(this.doubleValue() + a.doubleValue()))}.
+     */
+    public Decimal64 add(final Decimal64 a) {
+        return new Decimal64(this.value + a.value);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The current implementation strictly enforces {@code this.subtract(a).equals(new
+     * Decimal64(this.doubleValue() - a.doubleValue()))}.
+     */
+    public Decimal64 subtract(final Decimal64 a) {
+        return new Decimal64(this.value - a.value);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The current implementation strictly enforces {@code this.negate().equals(new
+     * Decimal64(-this.doubleValue()))}.
+     */
+    public Decimal64 negate() {
+        return new Decimal64(-this.value);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The current implementation strictly enforces {@code this.multiply(a).equals(new
+     * Decimal64(this.doubleValue() * a.doubleValue()))}.
+     */
+    public Decimal64 multiply(final Decimal64 a) {
+        return new Decimal64(this.value * a.value);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The current implementation strictly enforces {@code this.multiply(n).equals(new
+     * Decimal64(n * this.doubleValue()))}.
+     */
+    public Decimal64 multiply(final int n) {
+        return new Decimal64(n * this.value);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The current implementation strictly enforces {@code this.divide(a).equals(new
+     * Decimal64(this.doubleValue() / a.doubleValue()))}.
+     */
+    public Decimal64 divide(final Decimal64 a) {
+        return new Decimal64(this.value / a.value);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The current implementation strictly enforces {@code this.reciprocal().equals(new
+     * Decimal64(1.0 / this.doubleValue()))}.
+     */
+    public Decimal64 reciprocal() {
+        return new Decimal64(1.0 / this.value);
+    }
+
+    /*
+     * Methods from the Number abstract class
+     */
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The current implementation performs casting to a {@code byte}.
+     */
+    @Override
+    public byte byteValue() {
+        return (byte) value;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The current implementation performs casting to a {@code short}.
+     */
+    @Override
+    public short shortValue() {
+        return (short) value;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The current implementation performs casting to a {@code int}.
+     */
+    @Override
+    public int intValue() {
+        return (int) value;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The current implementation performs casting to a {@code long}.
+     */
+    @Override
+    public long longValue() {
+        return (long) value;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The current implementation performs casting to a {@code float}.
+     */
+    @Override
+    public float floatValue() {
+        return (float) value;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double doubleValue() {
+        return value;
+    }
+
+    /*
+     * Methods from the Comparable interface.
+     */
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The current implementation returns the same value as <center> {@code new
+     * Double(this.doubleValue()).compareTo(new Double(o.doubleValue()))} </center>
+     *
+     * @see Double#compareTo(Double)
+     */
+    public int compareTo(final Decimal64 o) {
+        return Double.compare(this.value, o.value);
+    }
+
+    /*
+     * Methods from the Object abstract class.
+     */
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj instanceof Decimal64) {
+            final Decimal64 that = (Decimal64) obj;
+            return Double.doubleToLongBits(this.value) == Double.doubleToLongBits(that.value);
+        }
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The current implementation returns the same value as {@code new
+     * Double(this.doubleValue()).hashCode()}
+     *
+     * @see Double#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        long v = Double.doubleToLongBits(value);
+        return (int) (v ^ (v >>> 32));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The returned {@code String} is equal to {@code Double.toString(this.doubleValue())}
+     *
+     * @see Double#toString(double)
+     */
+    @Override
+    public String toString() {
+        return Double.toString(value);
+    }
+
+    /*
+     * Methods inspired by the Double class.
+     */
+
+    /**
+     * Returns {@code true} if {@code this} double precision number is infinite ({@link
+     * Double#POSITIVE_INFINITY} or {@link Double#NEGATIVE_INFINITY}).
+     *
+     * @return {@code true} if {@code this} number is infinite
+     */
+    public boolean isInfinite() {
+        return Double.isInfinite(value);
+    }
+
+    /**
+     * Returns {@code true} if {@code this} double precision number is Not-a-Number ({@code NaN}),
+     * false otherwise.
+     *
+     * @return {@code true} if {@code this} is {@code NaN}
+     */
+    public boolean isNaN() {
+        return Double.isNaN(value);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public double getReal() {
+        return value;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 add(final double a) {
+        return new Decimal64(value + a);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 subtract(final double a) {
+        return new Decimal64(value - a);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 multiply(final double a) {
+        return new Decimal64(value * a);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 divide(final double a) {
+        return new Decimal64(value / a);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 remainder(final double a) {
+        return new Decimal64(FastMath.IEEEremainder(value, a));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 remainder(final Decimal64 a) {
+        return new Decimal64(FastMath.IEEEremainder(value, a.value));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 abs() {
+        return new Decimal64(FastMath.abs(value));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 ceil() {
+        return new Decimal64(FastMath.ceil(value));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 floor() {
+        return new Decimal64(FastMath.floor(value));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 rint() {
+        return new Decimal64(FastMath.rint(value));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public long round() {
+        return FastMath.round(value);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 signum() {
+        return new Decimal64(FastMath.signum(value));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 copySign(final Decimal64 sign) {
+        return new Decimal64(FastMath.copySign(value, sign.value));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 copySign(final double sign) {
+        return new Decimal64(FastMath.copySign(value, sign));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 scalb(final int n) {
+        return new Decimal64(FastMath.scalb(value, n));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 hypot(final Decimal64 y) {
+        return new Decimal64(FastMath.hypot(value, y.value));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 sqrt() {
+        return new Decimal64(FastMath.sqrt(value));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 cbrt() {
+        return new Decimal64(FastMath.cbrt(value));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 rootN(final int n) {
+        if (value < 0) {
+            return new Decimal64(-FastMath.pow(-value, 1.0 / n));
+        } else {
+            return new Decimal64(FastMath.pow(value, 1.0 / n));
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 pow(final double p) {
+        return new Decimal64(FastMath.pow(value, p));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 pow(final int n) {
+        return new Decimal64(FastMath.pow(value, n));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 pow(final Decimal64 e) {
+        return new Decimal64(FastMath.pow(value, e.value));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 exp() {
+        return new Decimal64(FastMath.exp(value));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 expm1() {
+        return new Decimal64(FastMath.expm1(value));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 log() {
+        return new Decimal64(FastMath.log(value));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 log1p() {
+        return new Decimal64(FastMath.log1p(value));
+    }
+
+    /**
+     * Base 10 logarithm.
+     *
+     * @return base 10 logarithm of the instance
+     * @since 3.2
+     */
+    public Decimal64 log10() {
+        return new Decimal64(FastMath.log10(value));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 cos() {
+        return new Decimal64(FastMath.cos(value));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 sin() {
+        return new Decimal64(FastMath.sin(value));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 tan() {
+        return new Decimal64(FastMath.tan(value));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 acos() {
+        return new Decimal64(FastMath.acos(value));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 asin() {
+        return new Decimal64(FastMath.asin(value));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 atan() {
+        return new Decimal64(FastMath.atan(value));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 atan2(final Decimal64 x) {
+        return new Decimal64(FastMath.atan2(value, x.value));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 cosh() {
+        return new Decimal64(FastMath.cosh(value));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 sinh() {
+        return new Decimal64(FastMath.sinh(value));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 tanh() {
+        return new Decimal64(FastMath.tanh(value));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 acosh() {
+        return new Decimal64(FastMath.acosh(value));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 asinh() {
+        return new Decimal64(FastMath.asinh(value));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 atanh() {
+        return new Decimal64(FastMath.atanh(value));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 linearCombination(final Decimal64[] a, final Decimal64[] b)
+            throws DimensionMismatchException {
+        if (a.length != b.length) {
+            throw new DimensionMismatchException(a.length, b.length);
+        }
+        final double[] aDouble = new double[a.length];
+        final double[] bDouble = new double[b.length];
+        for (int i = 0; i < a.length; ++i) {
+            aDouble[i] = a[i].value;
+            bDouble[i] = b[i].value;
+        }
+        return new Decimal64(MathArrays.linearCombination(aDouble, bDouble));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 linearCombination(final double[] a, final Decimal64[] b)
+            throws DimensionMismatchException {
+        if (a.length != b.length) {
+            throw new DimensionMismatchException(a.length, b.length);
+        }
+        final double[] bDouble = new double[b.length];
+        for (int i = 0; i < a.length; ++i) {
+            bDouble[i] = b[i].value;
+        }
+        return new Decimal64(MathArrays.linearCombination(a, bDouble));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 linearCombination(
+            final Decimal64 a1, final Decimal64 b1, final Decimal64 a2, final Decimal64 b2) {
+        return new Decimal64(
+                MathArrays.linearCombination(
+                        a1.value, b1.value,
+                        a2.value, b2.value));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 linearCombination(
+            final double a1, final Decimal64 b1, final double a2, final Decimal64 b2) {
+        return new Decimal64(
+                MathArrays.linearCombination(
+                        a1, b1.value,
+                        a2, b2.value));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 linearCombination(
+            final Decimal64 a1,
+            final Decimal64 b1,
+            final Decimal64 a2,
+            final Decimal64 b2,
+            final Decimal64 a3,
+            final Decimal64 b3) {
+        return new Decimal64(
+                MathArrays.linearCombination(
+                        a1.value, b1.value,
+                        a2.value, b2.value,
+                        a3.value, b3.value));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 linearCombination(
+            final double a1,
+            final Decimal64 b1,
+            final double a2,
+            final Decimal64 b2,
+            final double a3,
+            final Decimal64 b3) {
+        return new Decimal64(
+                MathArrays.linearCombination(
+                        a1, b1.value,
+                        a2, b2.value,
+                        a3, b3.value));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 linearCombination(
+            final Decimal64 a1,
+            final Decimal64 b1,
+            final Decimal64 a2,
+            final Decimal64 b2,
+            final Decimal64 a3,
+            final Decimal64 b3,
+            final Decimal64 a4,
+            final Decimal64 b4) {
+        return new Decimal64(
+                MathArrays.linearCombination(
+                        a1.value, b1.value,
+                        a2.value, b2.value,
+                        a3.value, b3.value,
+                        a4.value, b4.value));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 3.2
+     */
+    public Decimal64 linearCombination(
+            final double a1,
+            final Decimal64 b1,
+            final double a2,
+            final Decimal64 b2,
+            final double a3,
+            final Decimal64 b3,
+            final double a4,
+            final Decimal64 b4) {
+        return new Decimal64(
+                MathArrays.linearCombination(
+                        a1, b1.value,
+                        a2, b2.value,
+                        a3, b3.value,
+                        a4, b4.value));
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/util/Decimal64Field.java b/src/main/java/org/apache/commons/math3/util/Decimal64Field.java
new file mode 100644
index 0000000..2b07826
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/Decimal64Field.java
@@ -0,0 +1,61 @@
+/*
+ * 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.math3.util;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.FieldElement;
+
+/**
+ * The field of double precision floating-point numbers.
+ *
+ * @since 3.1
+ * @see Decimal64
+ */
+public class Decimal64Field implements Field<Decimal64> {
+
+    /** The unique instance of this class. */
+    private static final Decimal64Field INSTANCE = new Decimal64Field();
+
+    /** Default constructor. */
+    private Decimal64Field() {
+        // Do nothing
+    }
+
+    /**
+     * Returns the unique instance of this class.
+     *
+     * @return the unique instance of this class
+     */
+    public static final Decimal64Field getInstance() {
+        return INSTANCE;
+    }
+
+    /** {@inheritDoc} */
+    public Decimal64 getZero() {
+        return Decimal64.ZERO;
+    }
+
+    /** {@inheritDoc} */
+    public Decimal64 getOne() {
+        return Decimal64.ONE;
+    }
+
+    /** {@inheritDoc} */
+    public Class<? extends FieldElement<Decimal64>> getRuntimeClass() {
+        return Decimal64.class;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/util/DefaultTransformer.java b/src/main/java/org/apache/commons/math3/util/DefaultTransformer.java
new file mode 100644
index 0000000..ba92d1b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/DefaultTransformer.java
@@ -0,0 +1,79 @@
+/*
+ * 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.math3.util;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+import java.io.Serializable;
+
+/**
+ * A Default NumberTransformer for java.lang.Numbers and Numeric Strings. This provides some simple
+ * conversion capabilities to turn any java.lang.Number into a primitive double or to turn a String
+ * representation of a Number into a double.
+ */
+public class DefaultTransformer implements NumberTransformer, Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 4019938025047800455L;
+
+    /**
+     * @param o the object that gets transformed.
+     * @return a double primitive representation of the Object o.
+     * @throws NullArgumentException if Object <code>o</code> is {@code null}.
+     * @throws MathIllegalArgumentException if Object <code>o</code> cannot successfully be
+     *     transformed
+     * @see <a
+     *     href="http://commons.apache.org/collections/api-release/org/apache/commons/collections/Transformer.html">Commons
+     *     Collections Transformer</a>
+     */
+    public double transform(Object o) throws NullArgumentException, MathIllegalArgumentException {
+
+        if (o == null) {
+            throw new NullArgumentException(LocalizedFormats.OBJECT_TRANSFORMATION);
+        }
+
+        if (o instanceof Number) {
+            return ((Number) o).doubleValue();
+        }
+
+        try {
+            return Double.parseDouble(o.toString());
+        } catch (NumberFormatException e) {
+            throw new MathIllegalArgumentException(
+                    LocalizedFormats.CANNOT_TRANSFORM_TO_DOUBLE, o.toString());
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+        return other instanceof DefaultTransformer;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        // some arbitrary number ...
+        return 401993047;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/util/DoubleArray.java b/src/main/java/org/apache/commons/math3/util/DoubleArray.java
new file mode 100644
index 0000000..f5762a9
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/DoubleArray.java
@@ -0,0 +1,96 @@
+/*
+ * 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.math3.util;
+
+/**
+ * Provides a standard interface for double arrays. Allows different array implementations to
+ * support various storage mechanisms such as automatic expansion, contraction, and array "rolling".
+ */
+public interface DoubleArray {
+
+    /**
+     * Returns the number of elements currently in the array. Please note that this may be different
+     * from the length of the internal storage array.
+     *
+     * @return number of elements
+     */
+    int getNumElements();
+
+    /**
+     * Returns the element at the specified index. Note that if an out of bounds index is supplied a
+     * ArrayIndexOutOfBoundsException will be thrown.
+     *
+     * @param index index to fetch a value from
+     * @return value stored at the specified index
+     * @throws ArrayIndexOutOfBoundsException if <code>index</code> is less than zero or is greater
+     *     than <code>getNumElements() - 1</code>.
+     */
+    double getElement(int index);
+
+    /**
+     * Sets the element at the specified index. If the specified index is greater than <code>
+     * getNumElements() - 1</code>, the <code>numElements</code> property is increased to <code>
+     * index +1</code> and additional storage is allocated (if necessary) for the new element and
+     * all (uninitialized) elements between the new element and the previous end of the array).
+     *
+     * @param index index to store a value in
+     * @param value value to store at the specified index
+     * @throws ArrayIndexOutOfBoundsException if <code>index</code> is less than zero.
+     */
+    void setElement(int index, double value);
+
+    /**
+     * Adds an element to the end of this expandable array
+     *
+     * @param value to be added to end of array
+     */
+    void addElement(double value);
+
+    /**
+     * Adds elements to the end of this expandable array
+     *
+     * @param values to be added to end of array
+     */
+    void addElements(double[] values);
+
+    /**
+     * Adds an element to the end of the array and removes the first element in the array. Returns
+     * the discarded first element. The effect is similar to a push operation in a FIFO queue.
+     *
+     * <p>Example: If the array contains the elements 1, 2, 3, 4 (in that order) and
+     * addElementRolling(5) is invoked, the result is an array containing the entries 2, 3, 4, 5 and
+     * the value returned is 1.
+     *
+     * @param value the value to be added to the array
+     * @return the value which has been discarded or "pushed" out of the array by this rolling
+     *     insert
+     */
+    double addElementRolling(double value);
+
+    /**
+     * Returns a double[] array containing the elements of this <code>DoubleArray</code>. If the
+     * underlying implementation is array-based, this method should always return a copy, rather
+     * than a reference to the underlying array so that changes made to the returned array have no
+     * effect on the <code>DoubleArray.</code>
+     *
+     * @return all elements added to the array
+     */
+    double[] getElements();
+
+    /** Clear the double array */
+    void clear();
+}
diff --git a/src/main/java/org/apache/commons/math3/util/FastMath.java b/src/main/java/org/apache/commons/math3/util/FastMath.java
new file mode 100644
index 0000000..f40961e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/FastMath.java
@@ -0,0 +1,4508 @@
+/*
+ * 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.math3.util;
+
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+import java.io.PrintStream;
+
+/**
+ * Faster, more accurate, portable alternative to {@link Math} and {@link StrictMath} for large
+ * scale computation.
+ *
+ * <p>FastMath is a drop-in replacement for both Math and StrictMath. This means that for any method
+ * in Math (say {@code Math.sin(x)} or {@code Math.cbrt(y)}), user can directly change the class and
+ * use the methods as is (using {@code FastMath.sin(x)} or {@code FastMath.cbrt(y)} in the previous
+ * example).
+ *
+ * <p>FastMath speed is achieved by relying heavily on optimizing compilers to native code present
+ * in many JVMs today and use of large tables. The larger tables are lazily initialised on first
+ * use, so that the setup time does not penalise methods that don't need them.
+ *
+ * <p>Note that FastMath is extensively used inside Apache Commons Math, so by calling some
+ * algorithms, the overhead when the the tables need to be intialised will occur regardless of the
+ * end-user calling FastMath methods directly or not. Performance figures for a specific JVM and
+ * hardware can be evaluated by running the FastMathTestPerformance tests in the test directory of
+ * the source distribution.
+ *
+ * <p>FastMath accuracy should be mostly independent of the JVM as it relies only on IEEE-754 basic
+ * operations and on embedded tables. Almost all operations are accurate to about 0.5 ulp throughout
+ * the domain range. This statement, of course is only a rough global observed behavior, it is
+ * <em>not</em> a guarantee for <em>every</em> double numbers input (see William Kahan's <a
+ * href="http://en.wikipedia.org/wiki/Rounding#The_table-maker.27s_dilemma">Table Maker's
+ * Dilemma</a>).
+ *
+ * <p>FastMath additionally implements the following methods not found in Math/StrictMath:
+ *
+ * <ul>
+ *   <li>{@link #asinh(double)}
+ *   <li>{@link #acosh(double)}
+ *   <li>{@link #atanh(double)}
+ * </ul>
+ *
+ * The following methods are found in Math/StrictMath since 1.6 only, they are provided by FastMath
+ * even in 1.5 Java virtual machines
+ *
+ * <ul>
+ *   <li>{@link #copySign(double, double)}
+ *   <li>{@link #getExponent(double)}
+ *   <li>{@link #nextAfter(double,double)}
+ *   <li>{@link #nextUp(double)}
+ *   <li>{@link #scalb(double, int)}
+ *   <li>{@link #copySign(float, float)}
+ *   <li>{@link #getExponent(float)}
+ *   <li>{@link #nextAfter(float,double)}
+ *   <li>{@link #nextUp(float)}
+ *   <li>{@link #scalb(float, int)}
+ * </ul>
+ *
+ * @since 2.2
+ */
+public class FastMath {
+    /** Archimede's constant PI, ratio of circle circumference to diameter. */
+    public static final double PI = 105414357.0 / 33554432.0 + 1.984187159361080883e-9;
+
+    /** Napier's constant e, base of the natural logarithm. */
+    public static final double E = 2850325.0 / 1048576.0 + 8.254840070411028747e-8;
+
+    /** Index of exp(0) in the array of integer exponentials. */
+    static final int EXP_INT_TABLE_MAX_INDEX = 750;
+
+    /** Length of the array of integer exponentials. */
+    static final int EXP_INT_TABLE_LEN = EXP_INT_TABLE_MAX_INDEX * 2;
+
+    /** Logarithm table length. */
+    static final int LN_MANT_LEN = 1024;
+
+    /** Exponential fractions table length. */
+    static final int EXP_FRAC_TABLE_LEN = 1025; // 0, 1/1024, ... 1024/1024
+
+    /** StrictMath.log(Double.MAX_VALUE): {@value} */
+    private static final double LOG_MAX_VALUE = StrictMath.log(Double.MAX_VALUE);
+
+    /**
+     * Indicator for tables initialization.
+     *
+     * <p>This compile-time constant should be set to true only if one explicitly wants to compute
+     * the tables at class loading time instead of using the already computed ones provided as
+     * literal arrays below.
+     */
+    private static final boolean RECOMPUTE_TABLES_AT_RUNTIME = false;
+
+    /** log(2) (high bits). */
+    private static final double LN_2_A = 0.693147063255310059;
+
+    /** log(2) (low bits). */
+    private static final double LN_2_B = 1.17304635250823482e-7;
+
+    /** Coefficients for log, when input 0.99 < x < 1.01. */
+    private static final double LN_QUICK_COEF[][] = {
+        {1.0, 5.669184079525E-24},
+        {-0.25, -0.25},
+        {0.3333333134651184, 1.986821492305628E-8},
+        {-0.25, -6.663542893624021E-14},
+        {0.19999998807907104, 1.1921056801463227E-8},
+        {-0.1666666567325592, -7.800414592973399E-9},
+        {0.1428571343421936, 5.650007086920087E-9},
+        {-0.12502530217170715, -7.44321345601866E-11},
+        {0.11113807559013367, 9.219544613762692E-9},
+    };
+
+    /** Coefficients for log in the range of 1.0 < x < 1.0 + 2^-10. */
+    private static final double LN_HI_PREC_COEF[][] = {
+        {1.0, -6.032174644509064E-23},
+        {-0.25, -0.25},
+        {0.3333333134651184, 1.9868161777724352E-8},
+        {-0.2499999701976776, -2.957007209750105E-8},
+        {0.19999954104423523, 1.5830993332061267E-10},
+        {-0.16624879837036133, -2.6033824355191673E-8}
+    };
+
+    /** Sine, Cosine, Tangent tables are for 0, 1/8, 2/8, ... 13/8 = PI/2 approx. */
+    private static final int SINE_TABLE_LEN = 14;
+
+    /** Sine table (high bits). */
+    private static final double SINE_TABLE_A[] = {
+        +0.0d,
+        +0.1246747374534607d,
+        +0.24740394949913025d,
+        +0.366272509098053d,
+        +0.4794255495071411d,
+        +0.5850973129272461d,
+        +0.6816387176513672d,
+        +0.7675435543060303d,
+        +0.8414709568023682d,
+        +0.902267575263977d,
+        +0.9489846229553223d,
+        +0.9808930158615112d,
+        +0.9974949359893799d,
+        +0.9985313415527344d,
+    };
+
+    /** Sine table (low bits). */
+    private static final double SINE_TABLE_B[] = {
+        +0.0d,
+        -4.068233003401932E-9d,
+        +9.755392680573412E-9d,
+        +1.9987994582857286E-8d,
+        -1.0902938113007961E-8d,
+        -3.9986783938944604E-8d,
+        +4.23719669792332E-8d,
+        -5.207000323380292E-8d,
+        +2.800552834259E-8d,
+        +1.883511811213715E-8d,
+        -3.5997360512765566E-9d,
+        +4.116164446561962E-8d,
+        +5.0614674548127384E-8d,
+        -1.0129027912496858E-9d,
+    };
+
+    /** Cosine table (high bits). */
+    private static final double COSINE_TABLE_A[] = {
+        +1.0d,
+        +0.9921976327896118d,
+        +0.9689123630523682d,
+        +0.9305076599121094d,
+        +0.8775825500488281d,
+        +0.8109631538391113d,
+        +0.7316888570785522d,
+        +0.6409968137741089d,
+        +0.5403022766113281d,
+        +0.4311765432357788d,
+        +0.3153223395347595d,
+        +0.19454771280288696d,
+        +0.07073719799518585d,
+        -0.05417713522911072d,
+    };
+
+    /** Cosine table (low bits). */
+    private static final double COSINE_TABLE_B[] = {
+        +0.0d,
+        +3.4439717236742845E-8d,
+        +5.865827662008209E-8d,
+        -3.7999795083850525E-8d,
+        +1.184154459111628E-8d,
+        -3.43338934259355E-8d,
+        +1.1795268640216787E-8d,
+        +4.438921624363781E-8d,
+        +2.925681159240093E-8d,
+        -2.6437112632041807E-8d,
+        +2.2860509143963117E-8d,
+        -4.813899778443457E-9d,
+        +3.6725170580355583E-9d,
+        +2.0217439756338078E-10d,
+    };
+
+    /** Tangent table, used by atan() (high bits). */
+    private static final double TANGENT_TABLE_A[] = {
+        +0.0d,
+        +0.1256551444530487d,
+        +0.25534194707870483d,
+        +0.3936265707015991d,
+        +0.5463024377822876d,
+        +0.7214844226837158d,
+        +0.9315965175628662d,
+        +1.1974215507507324d,
+        +1.5574076175689697d,
+        +2.092571258544922d,
+        +3.0095696449279785d,
+        +5.041914939880371d,
+        +14.101419448852539d,
+        -18.430862426757812d,
+    };
+
+    /** Tangent table, used by atan() (low bits). */
+    private static final double TANGENT_TABLE_B[] = {
+        +0.0d,
+        -7.877917738262007E-9d,
+        -2.5857668567479893E-8d,
+        +5.2240336371356666E-9d,
+        +5.206150291559893E-8d,
+        +1.8307188599677033E-8d,
+        -5.7618793749770706E-8d,
+        +7.848361555046424E-8d,
+        +1.0708593250394448E-7d,
+        +1.7827257129423813E-8d,
+        +2.893485277253286E-8d,
+        +3.1660099222737955E-7d,
+        +4.983191803254889E-7d,
+        -3.356118100840571E-7d,
+    };
+
+    /** Bits of 1/(2*pi), need for reducePayneHanek(). */
+    private static final long RECIP_2PI[] =
+            new long[] {
+                (0x28be60dbL << 32) | 0x9391054aL,
+                (0x7f09d5f4L << 32) | 0x7d4d3770L,
+                (0x36d8a566L << 32) | 0x4f10e410L,
+                (0x7f9458eaL << 32) | 0xf7aef158L,
+                (0x6dc91b8eL << 32) | 0x909374b8L,
+                (0x01924bbaL << 32) | 0x82746487L,
+                (0x3f877ac7L << 32) | 0x2c4a69cfL,
+                (0xba208d7dL << 32) | 0x4baed121L,
+                (0x3a671c09L << 32) | 0xad17df90L,
+                (0x4e64758eL << 32) | 0x60d4ce7dL,
+                (0x272117e2L << 32) | 0xef7e4a0eL,
+                (0xc7fe25ffL << 32) | 0xf7816603L,
+                (0xfbcbc462L << 32) | 0xd6829b47L,
+                (0xdb4d9fb3L << 32) | 0xc9f2c26dL,
+                (0xd3d18fd9L << 32) | 0xa797fa8bL,
+                (0x5d49eeb1L << 32) | 0xfaf97c5eL,
+                (0xcf41ce7dL << 32) | 0xe294a4baL,
+                0x9afed7ecL << 32
+            };
+
+    /** Bits of pi/4, need for reducePayneHanek(). */
+    private static final long PI_O_4_BITS[] =
+            new long[] {(0xc90fdaa2L << 32) | 0x2168c234L, (0xc4c6628bL << 32) | 0x80dc1cd1L};
+
+    /**
+     * Eighths. This is used by sinQ, because its faster to do a table lookup than a multiply in
+     * this time-critical routine
+     */
+    private static final double EIGHTHS[] = {
+        0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1.0, 1.125, 1.25, 1.375, 1.5, 1.625
+    };
+
+    /** Table of 2^((n+2)/3) */
+    private static final double CBRTTWO[] = {
+        0.6299605249474366, 0.7937005259840998, 1.0, 1.2599210498948732, 1.5874010519681994
+    };
+
+    /*
+     *  There are 52 bits in the mantissa of a double.
+     *  For additional precision, the code splits double numbers into two parts,
+     *  by clearing the low order 30 bits if possible, and then performs the arithmetic
+     *  on each half separately.
+     */
+
+    /**
+     * 0x40000000 - used to split a double into two parts, both with the low order bits cleared.
+     * Equivalent to 2^30.
+     */
+    private static final long HEX_40000000 = 0x40000000L; // 1073741824L
+
+    /** Mask used to clear low order 30 bits */
+    private static final long MASK_30BITS = -1L - (HEX_40000000 - 1); // 0xFFFFFFFFC0000000L;
+
+    /** Mask used to clear the non-sign part of an int. */
+    private static final int MASK_NON_SIGN_INT = 0x7fffffff;
+
+    /** Mask used to clear the non-sign part of a long. */
+    private static final long MASK_NON_SIGN_LONG = 0x7fffffffffffffffl;
+
+    /** Mask used to extract exponent from double bits. */
+    private static final long MASK_DOUBLE_EXPONENT = 0x7ff0000000000000L;
+
+    /** Mask used to extract mantissa from double bits. */
+    private static final long MASK_DOUBLE_MANTISSA = 0x000fffffffffffffL;
+
+    /** Mask used to add implicit high order bit for normalized double. */
+    private static final long IMPLICIT_HIGH_BIT = 0x0010000000000000L;
+
+    /** 2^52 - double numbers this large must be integral (no fraction) or NaN or Infinite */
+    private static final double TWO_POWER_52 = 4503599627370496.0;
+
+    /** Constant: {@value}. */
+    private static final double F_1_3 = 1d / 3d;
+
+    /** Constant: {@value}. */
+    private static final double F_1_5 = 1d / 5d;
+
+    /** Constant: {@value}. */
+    private static final double F_1_7 = 1d / 7d;
+
+    /** Constant: {@value}. */
+    private static final double F_1_9 = 1d / 9d;
+
+    /** Constant: {@value}. */
+    private static final double F_1_11 = 1d / 11d;
+
+    /** Constant: {@value}. */
+    private static final double F_1_13 = 1d / 13d;
+
+    /** Constant: {@value}. */
+    private static final double F_1_15 = 1d / 15d;
+
+    /** Constant: {@value}. */
+    private static final double F_1_17 = 1d / 17d;
+
+    /** Constant: {@value}. */
+    private static final double F_3_4 = 3d / 4d;
+
+    /** Constant: {@value}. */
+    private static final double F_15_16 = 15d / 16d;
+
+    /** Constant: {@value}. */
+    private static final double F_13_14 = 13d / 14d;
+
+    /** Constant: {@value}. */
+    private static final double F_11_12 = 11d / 12d;
+
+    /** Constant: {@value}. */
+    private static final double F_9_10 = 9d / 10d;
+
+    /** Constant: {@value}. */
+    private static final double F_7_8 = 7d / 8d;
+
+    /** Constant: {@value}. */
+    private static final double F_5_6 = 5d / 6d;
+
+    /** Constant: {@value}. */
+    private static final double F_1_2 = 1d / 2d;
+
+    /** Constant: {@value}. */
+    private static final double F_1_4 = 1d / 4d;
+
+    /** Private Constructor */
+    private FastMath() {}
+
+    // Generic helper methods
+
+    /**
+     * Get the high order bits from the mantissa. Equivalent to adding and subtracting HEX_40000 but
+     * also works for very large numbers
+     *
+     * @param d the value to split
+     * @return the high order part of the mantissa
+     */
+    private static double doubleHighPart(double d) {
+        if (d > -Precision.SAFE_MIN && d < Precision.SAFE_MIN) {
+            return d; // These are un-normalised - don't try to convert
+        }
+        long xl =
+                Double.doubleToRawLongBits(
+                        d); // can take raw bits because just gonna convert it back
+        xl &= MASK_30BITS; // Drop low order bits
+        return Double.longBitsToDouble(xl);
+    }
+
+    /**
+     * Compute the square root of a number.
+     *
+     * <p><b>Note:</b> this implementation currently delegates to {@link Math#sqrt}
+     *
+     * @param a number on which evaluation is done
+     * @return square root of a
+     */
+    public static double sqrt(final double a) {
+        return Math.sqrt(a);
+    }
+
+    /**
+     * Compute the hyperbolic cosine of a number.
+     *
+     * @param x number on which evaluation is done
+     * @return hyperbolic cosine of x
+     */
+    public static double cosh(double x) {
+        if (x != x) {
+            return x;
+        }
+
+        // cosh[z] = (exp(z) + exp(-z))/2
+
+        // for numbers with magnitude 20 or so,
+        // exp(-z) can be ignored in comparison with exp(z)
+
+        if (x > 20) {
+            if (x >= LOG_MAX_VALUE) {
+                // Avoid overflow (MATH-905).
+                final double t = exp(0.5 * x);
+                return (0.5 * t) * t;
+            } else {
+                return 0.5 * exp(x);
+            }
+        } else if (x < -20) {
+            if (x <= -LOG_MAX_VALUE) {
+                // Avoid overflow (MATH-905).
+                final double t = exp(-0.5 * x);
+                return (0.5 * t) * t;
+            } else {
+                return 0.5 * exp(-x);
+            }
+        }
+
+        final double hiPrec[] = new double[2];
+        if (x < 0.0) {
+            x = -x;
+        }
+        exp(x, 0.0, hiPrec);
+
+        double ya = hiPrec[0] + hiPrec[1];
+        double yb = -(ya - hiPrec[0] - hiPrec[1]);
+
+        double temp = ya * HEX_40000000;
+        double yaa = ya + temp - temp;
+        double yab = ya - yaa;
+
+        // recip = 1/y
+        double recip = 1.0 / ya;
+        temp = recip * HEX_40000000;
+        double recipa = recip + temp - temp;
+        double recipb = recip - recipa;
+
+        // Correct for rounding in division
+        recipb += (1.0 - yaa * recipa - yaa * recipb - yab * recipa - yab * recipb) * recip;
+        // Account for yb
+        recipb += -yb * recip * recip;
+
+        // y = y + 1/y
+        temp = ya + recipa;
+        yb += -(temp - ya - recipa);
+        ya = temp;
+        temp = ya + recipb;
+        yb += -(temp - ya - recipb);
+        ya = temp;
+
+        double result = ya + yb;
+        result *= 0.5;
+        return result;
+    }
+
+    /**
+     * Compute the hyperbolic sine of a number.
+     *
+     * @param x number on which evaluation is done
+     * @return hyperbolic sine of x
+     */
+    public static double sinh(double x) {
+        boolean negate = false;
+        if (x != x) {
+            return x;
+        }
+
+        // sinh[z] = (exp(z) - exp(-z) / 2
+
+        // for values of z larger than about 20,
+        // exp(-z) can be ignored in comparison with exp(z)
+
+        if (x > 20) {
+            if (x >= LOG_MAX_VALUE) {
+                // Avoid overflow (MATH-905).
+                final double t = exp(0.5 * x);
+                return (0.5 * t) * t;
+            } else {
+                return 0.5 * exp(x);
+            }
+        } else if (x < -20) {
+            if (x <= -LOG_MAX_VALUE) {
+                // Avoid overflow (MATH-905).
+                final double t = exp(-0.5 * x);
+                return (-0.5 * t) * t;
+            } else {
+                return -0.5 * exp(-x);
+            }
+        }
+
+        if (x == 0) {
+            return x;
+        }
+
+        if (x < 0.0) {
+            x = -x;
+            negate = true;
+        }
+
+        double result;
+
+        if (x > 0.25) {
+            double hiPrec[] = new double[2];
+            exp(x, 0.0, hiPrec);
+
+            double ya = hiPrec[0] + hiPrec[1];
+            double yb = -(ya - hiPrec[0] - hiPrec[1]);
+
+            double temp = ya * HEX_40000000;
+            double yaa = ya + temp - temp;
+            double yab = ya - yaa;
+
+            // recip = 1/y
+            double recip = 1.0 / ya;
+            temp = recip * HEX_40000000;
+            double recipa = recip + temp - temp;
+            double recipb = recip - recipa;
+
+            // Correct for rounding in division
+            recipb += (1.0 - yaa * recipa - yaa * recipb - yab * recipa - yab * recipb) * recip;
+            // Account for yb
+            recipb += -yb * recip * recip;
+
+            recipa = -recipa;
+            recipb = -recipb;
+
+            // y = y + 1/y
+            temp = ya + recipa;
+            yb += -(temp - ya - recipa);
+            ya = temp;
+            temp = ya + recipb;
+            yb += -(temp - ya - recipb);
+            ya = temp;
+
+            result = ya + yb;
+            result *= 0.5;
+        } else {
+            double hiPrec[] = new double[2];
+            expm1(x, hiPrec);
+
+            double ya = hiPrec[0] + hiPrec[1];
+            double yb = -(ya - hiPrec[0] - hiPrec[1]);
+
+            /* Compute expm1(-x) = -expm1(x) / (expm1(x) + 1) */
+            double denom = 1.0 + ya;
+            double denomr = 1.0 / denom;
+            double denomb = -(denom - 1.0 - ya) + yb;
+            double ratio = ya * denomr;
+            double temp = ratio * HEX_40000000;
+            double ra = ratio + temp - temp;
+            double rb = ratio - ra;
+
+            temp = denom * HEX_40000000;
+            double za = denom + temp - temp;
+            double zb = denom - za;
+
+            rb += (ya - za * ra - za * rb - zb * ra - zb * rb) * denomr;
+
+            // Adjust for yb
+            rb += yb * denomr; // numerator
+            rb += -ya * denomb * denomr * denomr; // denominator
+
+            // y = y - 1/y
+            temp = ya + ra;
+            yb += -(temp - ya - ra);
+            ya = temp;
+            temp = ya + rb;
+            yb += -(temp - ya - rb);
+            ya = temp;
+
+            result = ya + yb;
+            result *= 0.5;
+        }
+
+        if (negate) {
+            result = -result;
+        }
+
+        return result;
+    }
+
+    /**
+     * Compute the hyperbolic tangent of a number.
+     *
+     * @param x number on which evaluation is done
+     * @return hyperbolic tangent of x
+     */
+    public static double tanh(double x) {
+        boolean negate = false;
+
+        if (x != x) {
+            return x;
+        }
+
+        // tanh[z] = sinh[z] / cosh[z]
+        // = (exp(z) - exp(-z)) / (exp(z) + exp(-z))
+        // = (exp(2x) - 1) / (exp(2x) + 1)
+
+        // for magnitude > 20, sinh[z] == cosh[z] in double precision
+
+        if (x > 20.0) {
+            return 1.0;
+        }
+
+        if (x < -20) {
+            return -1.0;
+        }
+
+        if (x == 0) {
+            return x;
+        }
+
+        if (x < 0.0) {
+            x = -x;
+            negate = true;
+        }
+
+        double result;
+        if (x >= 0.5) {
+            double hiPrec[] = new double[2];
+            // tanh(x) = (exp(2x) - 1) / (exp(2x) + 1)
+            exp(x * 2.0, 0.0, hiPrec);
+
+            double ya = hiPrec[0] + hiPrec[1];
+            double yb = -(ya - hiPrec[0] - hiPrec[1]);
+
+            /* Numerator */
+            double na = -1.0 + ya;
+            double nb = -(na + 1.0 - ya);
+            double temp = na + yb;
+            nb += -(temp - na - yb);
+            na = temp;
+
+            /* Denominator */
+            double da = 1.0 + ya;
+            double db = -(da - 1.0 - ya);
+            temp = da + yb;
+            db += -(temp - da - yb);
+            da = temp;
+
+            temp = da * HEX_40000000;
+            double daa = da + temp - temp;
+            double dab = da - daa;
+
+            // ratio = na/da
+            double ratio = na / da;
+            temp = ratio * HEX_40000000;
+            double ratioa = ratio + temp - temp;
+            double ratiob = ratio - ratioa;
+
+            // Correct for rounding in division
+            ratiob += (na - daa * ratioa - daa * ratiob - dab * ratioa - dab * ratiob) / da;
+
+            // Account for nb
+            ratiob += nb / da;
+            // Account for db
+            ratiob += -db * na / da / da;
+
+            result = ratioa + ratiob;
+        } else {
+            double hiPrec[] = new double[2];
+            // tanh(x) = expm1(2x) / (expm1(2x) + 2)
+            expm1(x * 2.0, hiPrec);
+
+            double ya = hiPrec[0] + hiPrec[1];
+            double yb = -(ya - hiPrec[0] - hiPrec[1]);
+
+            /* Numerator */
+            double na = ya;
+            double nb = yb;
+
+            /* Denominator */
+            double da = 2.0 + ya;
+            double db = -(da - 2.0 - ya);
+            double temp = da + yb;
+            db += -(temp - da - yb);
+            da = temp;
+
+            temp = da * HEX_40000000;
+            double daa = da + temp - temp;
+            double dab = da - daa;
+
+            // ratio = na/da
+            double ratio = na / da;
+            temp = ratio * HEX_40000000;
+            double ratioa = ratio + temp - temp;
+            double ratiob = ratio - ratioa;
+
+            // Correct for rounding in division
+            ratiob += (na - daa * ratioa - daa * ratiob - dab * ratioa - dab * ratiob) / da;
+
+            // Account for nb
+            ratiob += nb / da;
+            // Account for db
+            ratiob += -db * na / da / da;
+
+            result = ratioa + ratiob;
+        }
+
+        if (negate) {
+            result = -result;
+        }
+
+        return result;
+    }
+
+    /**
+     * Compute the inverse hyperbolic cosine of a number.
+     *
+     * @param a number on which evaluation is done
+     * @return inverse hyperbolic cosine of a
+     */
+    public static double acosh(final double a) {
+        return FastMath.log(a + FastMath.sqrt(a * a - 1));
+    }
+
+    /**
+     * Compute the inverse hyperbolic sine of a number.
+     *
+     * @param a number on which evaluation is done
+     * @return inverse hyperbolic sine of a
+     */
+    public static double asinh(double a) {
+        boolean negative = false;
+        if (a < 0) {
+            negative = true;
+            a = -a;
+        }
+
+        double absAsinh;
+        if (a > 0.167) {
+            absAsinh = FastMath.log(FastMath.sqrt(a * a + 1) + a);
+        } else {
+            final double a2 = a * a;
+            if (a > 0.097) {
+                absAsinh =
+                        a
+                                * (1
+                                        - a2
+                                                * (F_1_3
+                                                        - a2
+                                                                * (F_1_5
+                                                                        - a2
+                                                                                * (F_1_7
+                                                                                        - a2
+                                                                                                * (F_1_9
+                                                                                                        - a2
+                                                                                                                * (F_1_11
+                                                                                                                        - a2
+                                                                                                                                * (F_1_13
+                                                                                                                                        - a2
+                                                                                                                                                * (F_1_15
+                                                                                                                                                        - a2
+                                                                                                                                                                * F_1_17
+                                                                                                                                                                * F_15_16)
+                                                                                                                                                * F_13_14)
+                                                                                                                                * F_11_12)
+                                                                                                                * F_9_10)
+                                                                                                * F_7_8)
+                                                                                * F_5_6)
+                                                                * F_3_4)
+                                                * F_1_2);
+            } else if (a > 0.036) {
+                absAsinh =
+                        a
+                                * (1
+                                        - a2
+                                                * (F_1_3
+                                                        - a2
+                                                                * (F_1_5
+                                                                        - a2
+                                                                                * (F_1_7
+                                                                                        - a2
+                                                                                                * (F_1_9
+                                                                                                        - a2
+                                                                                                                * (F_1_11
+                                                                                                                        - a2
+                                                                                                                                * F_1_13
+                                                                                                                                * F_11_12)
+                                                                                                                * F_9_10)
+                                                                                                * F_7_8)
+                                                                                * F_5_6)
+                                                                * F_3_4)
+                                                * F_1_2);
+            } else if (a > 0.0036) {
+                absAsinh =
+                        a
+                                * (1
+                                        - a2
+                                                * (F_1_3
+                                                        - a2
+                                                                * (F_1_5
+                                                                        - a2
+                                                                                * (F_1_7
+                                                                                        - a2 * F_1_9
+                                                                                                * F_7_8)
+                                                                                * F_5_6)
+                                                                * F_3_4)
+                                                * F_1_2);
+            } else {
+                absAsinh = a * (1 - a2 * (F_1_3 - a2 * F_1_5 * F_3_4) * F_1_2);
+            }
+        }
+
+        return negative ? -absAsinh : absAsinh;
+    }
+
+    /**
+     * Compute the inverse hyperbolic tangent of a number.
+     *
+     * @param a number on which evaluation is done
+     * @return inverse hyperbolic tangent of a
+     */
+    public static double atanh(double a) {
+        boolean negative = false;
+        if (a < 0) {
+            negative = true;
+            a = -a;
+        }
+
+        double absAtanh;
+        if (a > 0.15) {
+            absAtanh = 0.5 * FastMath.log((1 + a) / (1 - a));
+        } else {
+            final double a2 = a * a;
+            if (a > 0.087) {
+                absAtanh =
+                        a
+                                * (1
+                                        + a2
+                                                * (F_1_3
+                                                        + a2
+                                                                * (F_1_5
+                                                                        + a2
+                                                                                * (F_1_7
+                                                                                        + a2
+                                                                                                * (F_1_9
+                                                                                                        + a2
+                                                                                                                * (F_1_11
+                                                                                                                        + a2
+                                                                                                                                * (F_1_13
+                                                                                                                                        + a2
+                                                                                                                                                * (F_1_15
+                                                                                                                                                        + a2
+                                                                                                                                                                * F_1_17))))))));
+            } else if (a > 0.031) {
+                absAtanh =
+                        a
+                                * (1
+                                        + a2
+                                                * (F_1_3
+                                                        + a2
+                                                                * (F_1_5
+                                                                        + a2
+                                                                                * (F_1_7
+                                                                                        + a2
+                                                                                                * (F_1_9
+                                                                                                        + a2
+                                                                                                                * (F_1_11
+                                                                                                                        + a2
+                                                                                                                                * F_1_13))))));
+            } else if (a > 0.003) {
+                absAtanh = a * (1 + a2 * (F_1_3 + a2 * (F_1_5 + a2 * (F_1_7 + a2 * F_1_9))));
+            } else {
+                absAtanh = a * (1 + a2 * (F_1_3 + a2 * F_1_5));
+            }
+        }
+
+        return negative ? -absAtanh : absAtanh;
+    }
+
+    /**
+     * Compute the signum of a number. The signum is -1 for negative numbers, +1 for positive
+     * numbers and 0 otherwise
+     *
+     * @param a number on which evaluation is done
+     * @return -1.0, -0.0, +0.0, +1.0 or NaN depending on sign of a
+     */
+    public static double signum(final double a) {
+        return (a < 0.0) ? -1.0 : ((a > 0.0) ? 1.0 : a); // return +0.0/-0.0/NaN depending on a
+    }
+
+    /**
+     * Compute the signum of a number. The signum is -1 for negative numbers, +1 for positive
+     * numbers and 0 otherwise
+     *
+     * @param a number on which evaluation is done
+     * @return -1.0, -0.0, +0.0, +1.0 or NaN depending on sign of a
+     */
+    public static float signum(final float a) {
+        return (a < 0.0f) ? -1.0f : ((a > 0.0f) ? 1.0f : a); // return +0.0/-0.0/NaN depending on a
+    }
+
+    /**
+     * Compute next number towards positive infinity.
+     *
+     * @param a number to which neighbor should be computed
+     * @return neighbor of a towards positive infinity
+     */
+    public static double nextUp(final double a) {
+        return nextAfter(a, Double.POSITIVE_INFINITY);
+    }
+
+    /**
+     * Compute next number towards positive infinity.
+     *
+     * @param a number to which neighbor should be computed
+     * @return neighbor of a towards positive infinity
+     */
+    public static float nextUp(final float a) {
+        return nextAfter(a, Float.POSITIVE_INFINITY);
+    }
+
+    /**
+     * Compute next number towards negative infinity.
+     *
+     * @param a number to which neighbor should be computed
+     * @return neighbor of a towards negative infinity
+     * @since 3.4
+     */
+    public static double nextDown(final double a) {
+        return nextAfter(a, Double.NEGATIVE_INFINITY);
+    }
+
+    /**
+     * Compute next number towards negative infinity.
+     *
+     * @param a number to which neighbor should be computed
+     * @return neighbor of a towards negative infinity
+     * @since 3.4
+     */
+    public static float nextDown(final float a) {
+        return nextAfter(a, Float.NEGATIVE_INFINITY);
+    }
+
+    /**
+     * Returns a pseudo-random number between 0.0 and 1.0.
+     *
+     * <p><b>Note:</b> this implementation currently delegates to {@link Math#random}
+     *
+     * @return a random number between 0.0 and 1.0
+     */
+    public static double random() {
+        return Math.random();
+    }
+
+    /**
+     * Exponential function.
+     *
+     * <p>Computes exp(x), function result is nearly rounded. It will be correctly rounded to the
+     * theoretical value for 99.9% of input values, otherwise it will have a 1 ULP error.
+     *
+     * <p>Method: Lookup intVal = exp(int(x)) Lookup fracVal = exp(int(x-int(x) / 1024.0) * 1024.0
+     * ); Compute z as the exponential of the remaining bits by a polynomial minus one exp(x) =
+     * intVal * fracVal * (1 + z)
+     *
+     * <p>Accuracy: Calculation is done with 63 bits of precision, so result should be correctly
+     * rounded for 99.9% of input values, with less than 1 ULP error otherwise.
+     *
+     * @param x a double
+     * @return double e<sup>x</sup>
+     */
+    public static double exp(double x) {
+        return exp(x, 0.0, null);
+    }
+
+    /**
+     * Internal helper method for exponential function.
+     *
+     * @param x original argument of the exponential function
+     * @param extra extra bits of precision on input (To Be Confirmed)
+     * @param hiPrec extra bits of precision on output (To Be Confirmed)
+     * @return exp(x)
+     */
+    private static double exp(double x, double extra, double[] hiPrec) {
+        double intPartA;
+        double intPartB;
+        int intVal = (int) x;
+
+        /* Lookup exp(floor(x)).
+         * intPartA will have the upper 22 bits, intPartB will have the lower
+         * 52 bits.
+         */
+        if (x < 0.0) {
+
+            // We don't check against intVal here as conversion of large negative double values
+            // may be affected by a JIT bug. Subsequent comparisons can safely use intVal
+            if (x < -746d) {
+                if (hiPrec != null) {
+                    hiPrec[0] = 0.0;
+                    hiPrec[1] = 0.0;
+                }
+                return 0.0;
+            }
+
+            if (intVal < -709) {
+                /* This will produce a subnormal output */
+                final double result = exp(x + 40.19140625, extra, hiPrec) / 285040095144011776.0;
+                if (hiPrec != null) {
+                    hiPrec[0] /= 285040095144011776.0;
+                    hiPrec[1] /= 285040095144011776.0;
+                }
+                return result;
+            }
+
+            if (intVal == -709) {
+                /* exp(1.494140625) is nearly a machine number... */
+                final double result = exp(x + 1.494140625, extra, hiPrec) / 4.455505956692756620;
+                if (hiPrec != null) {
+                    hiPrec[0] /= 4.455505956692756620;
+                    hiPrec[1] /= 4.455505956692756620;
+                }
+                return result;
+            }
+
+            intVal--;
+
+        } else {
+            if (intVal > 709) {
+                if (hiPrec != null) {
+                    hiPrec[0] = Double.POSITIVE_INFINITY;
+                    hiPrec[1] = 0.0;
+                }
+                return Double.POSITIVE_INFINITY;
+            }
+        }
+
+        intPartA = ExpIntTable.EXP_INT_TABLE_A[EXP_INT_TABLE_MAX_INDEX + intVal];
+        intPartB = ExpIntTable.EXP_INT_TABLE_B[EXP_INT_TABLE_MAX_INDEX + intVal];
+
+        /* Get the fractional part of x, find the greatest multiple of 2^-10 less than
+         * x and look up the exp function of it.
+         * fracPartA will have the upper 22 bits, fracPartB the lower 52 bits.
+         */
+        final int intFrac = (int) ((x - intVal) * 1024.0);
+        final double fracPartA = ExpFracTable.EXP_FRAC_TABLE_A[intFrac];
+        final double fracPartB = ExpFracTable.EXP_FRAC_TABLE_B[intFrac];
+
+        /* epsilon is the difference in x from the nearest multiple of 2^-10.  It
+         * has a value in the range 0 <= epsilon < 2^-10.
+         * Do the subtraction from x as the last step to avoid possible loss of precision.
+         */
+        final double epsilon = x - (intVal + intFrac / 1024.0);
+
+        /* Compute z = exp(epsilon) - 1.0 via a minimax polynomial.  z has
+        full double precision (52 bits).  Since z < 2^-10, we will have
+        62 bits of precision when combined with the constant 1.  This will be
+        used in the last addition below to get proper rounding. */
+
+        /* Remez generated polynomial.  Converges on the interval [0, 2^-10], error
+        is less than 0.5 ULP */
+        double z = 0.04168701738764507;
+        z = z * epsilon + 0.1666666505023083;
+        z = z * epsilon + 0.5000000000042687;
+        z = z * epsilon + 1.0;
+        z = z * epsilon + -3.940510424527919E-20;
+
+        /* Compute (intPartA+intPartB) * (fracPartA+fracPartB) by binomial
+        expansion.
+        tempA is exact since intPartA and intPartB only have 22 bits each.
+        tempB will have 52 bits of precision.
+          */
+        double tempA = intPartA * fracPartA;
+        double tempB = intPartA * fracPartB + intPartB * fracPartA + intPartB * fracPartB;
+
+        /* Compute the result.  (1+z)(tempA+tempB).  Order of operations is
+        important.  For accuracy add by increasing size.  tempA is exact and
+        much larger than the others.  If there are extra bits specified from the
+        pow() function, use them. */
+        final double tempC = tempB + tempA;
+
+        // If tempC is positive infinite, the evaluation below could result in NaN,
+        // because z could be negative at the same time.
+        if (tempC == Double.POSITIVE_INFINITY) {
+            return Double.POSITIVE_INFINITY;
+        }
+
+        final double result;
+        if (extra != 0.0) {
+            result = tempC * extra * z + tempC * extra + tempC * z + tempB + tempA;
+        } else {
+            result = tempC * z + tempB + tempA;
+        }
+
+        if (hiPrec != null) {
+            // If requesting high precision
+            hiPrec[0] = tempA;
+            hiPrec[1] = tempC * extra * z + tempC * extra + tempC * z + tempB;
+        }
+
+        return result;
+    }
+
+    /**
+     * Compute exp(x) - 1
+     *
+     * @param x number to compute shifted exponential
+     * @return exp(x) - 1
+     */
+    public static double expm1(double x) {
+        return expm1(x, null);
+    }
+
+    /**
+     * Internal helper method for expm1
+     *
+     * @param x number to compute shifted exponential
+     * @param hiPrecOut receive high precision result for -1.0 < x < 1.0
+     * @return exp(x) - 1
+     */
+    private static double expm1(double x, double hiPrecOut[]) {
+        if (x != x || x == 0.0) { // NaN or zero
+            return x;
+        }
+
+        if (x <= -1.0 || x >= 1.0) {
+            // If not between +/- 1.0
+            // return exp(x) - 1.0;
+            double hiPrec[] = new double[2];
+            exp(x, 0.0, hiPrec);
+            if (x > 0.0) {
+                return -1.0 + hiPrec[0] + hiPrec[1];
+            } else {
+                final double ra = -1.0 + hiPrec[0];
+                double rb = -(ra + 1.0 - hiPrec[0]);
+                rb += hiPrec[1];
+                return ra + rb;
+            }
+        }
+
+        double baseA;
+        double baseB;
+        double epsilon;
+        boolean negative = false;
+
+        if (x < 0.0) {
+            x = -x;
+            negative = true;
+        }
+
+        {
+            int intFrac = (int) (x * 1024.0);
+            double tempA = ExpFracTable.EXP_FRAC_TABLE_A[intFrac] - 1.0;
+            double tempB = ExpFracTable.EXP_FRAC_TABLE_B[intFrac];
+
+            double temp = tempA + tempB;
+            tempB = -(temp - tempA - tempB);
+            tempA = temp;
+
+            temp = tempA * HEX_40000000;
+            baseA = tempA + temp - temp;
+            baseB = tempB + (tempA - baseA);
+
+            epsilon = x - intFrac / 1024.0;
+        }
+
+        /* Compute expm1(epsilon) */
+        double zb = 0.008336750013465571;
+        zb = zb * epsilon + 0.041666663879186654;
+        zb = zb * epsilon + 0.16666666666745392;
+        zb = zb * epsilon + 0.49999999999999994;
+        zb *= epsilon;
+        zb *= epsilon;
+
+        double za = epsilon;
+        double temp = za + zb;
+        zb = -(temp - za - zb);
+        za = temp;
+
+        temp = za * HEX_40000000;
+        temp = za + temp - temp;
+        zb += za - temp;
+        za = temp;
+
+        /* Combine the parts.   expm1(a+b) = expm1(a) + expm1(b) + expm1(a)*expm1(b) */
+        double ya = za * baseA;
+        // double yb = za*baseB + zb*baseA + zb*baseB;
+        temp = ya + za * baseB;
+        double yb = -(temp - ya - za * baseB);
+        ya = temp;
+
+        temp = ya + zb * baseA;
+        yb += -(temp - ya - zb * baseA);
+        ya = temp;
+
+        temp = ya + zb * baseB;
+        yb += -(temp - ya - zb * baseB);
+        ya = temp;
+
+        // ya = ya + za + baseA;
+        // yb = yb + zb + baseB;
+        temp = ya + baseA;
+        yb += -(temp - baseA - ya);
+        ya = temp;
+
+        temp = ya + za;
+        // yb += (ya > za) ? -(temp - ya - za) : -(temp - za - ya);
+        yb += -(temp - ya - za);
+        ya = temp;
+
+        temp = ya + baseB;
+        // yb += (ya > baseB) ? -(temp - ya - baseB) : -(temp - baseB - ya);
+        yb += -(temp - ya - baseB);
+        ya = temp;
+
+        temp = ya + zb;
+        // yb += (ya > zb) ? -(temp - ya - zb) : -(temp - zb - ya);
+        yb += -(temp - ya - zb);
+        ya = temp;
+
+        if (negative) {
+            /* Compute expm1(-x) = -expm1(x) / (expm1(x) + 1) */
+            double denom = 1.0 + ya;
+            double denomr = 1.0 / denom;
+            double denomb = -(denom - 1.0 - ya) + yb;
+            double ratio = ya * denomr;
+            temp = ratio * HEX_40000000;
+            final double ra = ratio + temp - temp;
+            double rb = ratio - ra;
+
+            temp = denom * HEX_40000000;
+            za = denom + temp - temp;
+            zb = denom - za;
+
+            rb += (ya - za * ra - za * rb - zb * ra - zb * rb) * denomr;
+
+            // f(x) = x/1+x
+            // Compute f'(x)
+            // Product rule:  d(uv) = du*v + u*dv
+            // Chain rule:  d(f(g(x)) = f'(g(x))*f(g'(x))
+            // d(1/x) = -1/(x*x)
+            // d(1/1+x) = -1/( (1+x)^2) *  1 =  -1/((1+x)*(1+x))
+            // d(x/1+x) = -x/((1+x)(1+x)) + 1/1+x = 1 / ((1+x)(1+x))
+
+            // Adjust for yb
+            rb += yb * denomr; // numerator
+            rb += -ya * denomb * denomr * denomr; // denominator
+
+            // negate
+            ya = -ra;
+            yb = -rb;
+        }
+
+        if (hiPrecOut != null) {
+            hiPrecOut[0] = ya;
+            hiPrecOut[1] = yb;
+        }
+
+        return ya + yb;
+    }
+
+    /**
+     * Natural logarithm.
+     *
+     * @param x a double
+     * @return log(x)
+     */
+    public static double log(final double x) {
+        return log(x, null);
+    }
+
+    /**
+     * Internal helper method for natural logarithm function.
+     *
+     * @param x original argument of the natural logarithm function
+     * @param hiPrec extra bits of precision on output (To Be Confirmed)
+     * @return log(x)
+     */
+    private static double log(final double x, final double[] hiPrec) {
+        if (x == 0) { // Handle special case of +0/-0
+            return Double.NEGATIVE_INFINITY;
+        }
+        long bits = Double.doubleToRawLongBits(x);
+
+        /* Handle special cases of negative input, and NaN */
+        if (((bits & 0x8000000000000000L) != 0 || x != x) && x != 0.0) {
+            if (hiPrec != null) {
+                hiPrec[0] = Double.NaN;
+            }
+
+            return Double.NaN;
+        }
+
+        /* Handle special cases of Positive infinity. */
+        if (x == Double.POSITIVE_INFINITY) {
+            if (hiPrec != null) {
+                hiPrec[0] = Double.POSITIVE_INFINITY;
+            }
+
+            return Double.POSITIVE_INFINITY;
+        }
+
+        /* Extract the exponent */
+        int exp = (int) (bits >> 52) - 1023;
+
+        if ((bits & 0x7ff0000000000000L) == 0) {
+            // Subnormal!
+            if (x == 0) {
+                // Zero
+                if (hiPrec != null) {
+                    hiPrec[0] = Double.NEGATIVE_INFINITY;
+                }
+
+                return Double.NEGATIVE_INFINITY;
+            }
+
+            /* Normalize the subnormal number. */
+            bits <<= 1;
+            while ((bits & 0x0010000000000000L) == 0) {
+                --exp;
+                bits <<= 1;
+            }
+        }
+
+        if ((exp == -1 || exp == 0) && x < 1.01 && x > 0.99 && hiPrec == null) {
+            /* The normal method doesn't work well in the range [0.99, 1.01], so call do a straight
+            polynomial expansion in higer precision. */
+
+            /* Compute x - 1.0 and split it */
+            double xa = x - 1.0;
+            double xb = xa - x + 1.0;
+            double tmp = xa * HEX_40000000;
+            double aa = xa + tmp - tmp;
+            double ab = xa - aa;
+            xa = aa;
+            xb = ab;
+
+            final double[] lnCoef_last = LN_QUICK_COEF[LN_QUICK_COEF.length - 1];
+            double ya = lnCoef_last[0];
+            double yb = lnCoef_last[1];
+
+            for (int i = LN_QUICK_COEF.length - 2; i >= 0; i--) {
+                /* Multiply a = y * x */
+                aa = ya * xa;
+                ab = ya * xb + yb * xa + yb * xb;
+                /* split, so now y = a */
+                tmp = aa * HEX_40000000;
+                ya = aa + tmp - tmp;
+                yb = aa - ya + ab;
+
+                /* Add  a = y + lnQuickCoef */
+                final double[] lnCoef_i = LN_QUICK_COEF[i];
+                aa = ya + lnCoef_i[0];
+                ab = yb + lnCoef_i[1];
+                /* Split y = a */
+                tmp = aa * HEX_40000000;
+                ya = aa + tmp - tmp;
+                yb = aa - ya + ab;
+            }
+
+            /* Multiply a = y * x */
+            aa = ya * xa;
+            ab = ya * xb + yb * xa + yb * xb;
+            /* split, so now y = a */
+            tmp = aa * HEX_40000000;
+            ya = aa + tmp - tmp;
+            yb = aa - ya + ab;
+
+            return ya + yb;
+        }
+
+        // lnm is a log of a number in the range of 1.0 - 2.0, so 0 <= lnm < ln(2)
+        final double[] lnm = lnMant.LN_MANT[(int) ((bits & 0x000ffc0000000000L) >> 42)];
+
+        /*
+        double epsilon = x / Double.longBitsToDouble(bits & 0xfffffc0000000000L);
+
+        epsilon -= 1.0;
+             */
+
+        // y is the most significant 10 bits of the mantissa
+        // double y = Double.longBitsToDouble(bits & 0xfffffc0000000000L);
+        // double epsilon = (x - y) / y;
+        final double epsilon =
+                (bits & 0x3ffffffffffL) / (TWO_POWER_52 + (bits & 0x000ffc0000000000L));
+
+        double lnza = 0.0;
+        double lnzb = 0.0;
+
+        if (hiPrec != null) {
+            /* split epsilon -> x */
+            double tmp = epsilon * HEX_40000000;
+            double aa = epsilon + tmp - tmp;
+            double ab = epsilon - aa;
+            double xa = aa;
+            double xb = ab;
+
+            /* Need a more accurate epsilon, so adjust the division. */
+            final double numer = bits & 0x3ffffffffffL;
+            final double denom = TWO_POWER_52 + (bits & 0x000ffc0000000000L);
+            aa = numer - xa * denom - xb * denom;
+            xb += aa / denom;
+
+            /* Remez polynomial evaluation */
+            final double[] lnCoef_last = LN_HI_PREC_COEF[LN_HI_PREC_COEF.length - 1];
+            double ya = lnCoef_last[0];
+            double yb = lnCoef_last[1];
+
+            for (int i = LN_HI_PREC_COEF.length - 2; i >= 0; i--) {
+                /* Multiply a = y * x */
+                aa = ya * xa;
+                ab = ya * xb + yb * xa + yb * xb;
+                /* split, so now y = a */
+                tmp = aa * HEX_40000000;
+                ya = aa + tmp - tmp;
+                yb = aa - ya + ab;
+
+                /* Add  a = y + lnHiPrecCoef */
+                final double[] lnCoef_i = LN_HI_PREC_COEF[i];
+                aa = ya + lnCoef_i[0];
+                ab = yb + lnCoef_i[1];
+                /* Split y = a */
+                tmp = aa * HEX_40000000;
+                ya = aa + tmp - tmp;
+                yb = aa - ya + ab;
+            }
+
+            /* Multiply a = y * x */
+            aa = ya * xa;
+            ab = ya * xb + yb * xa + yb * xb;
+
+            /* split, so now lnz = a */
+            /*
+            tmp = aa * 1073741824.0;
+            lnza = aa + tmp - tmp;
+            lnzb = aa - lnza + ab;
+                   */
+            lnza = aa + ab;
+            lnzb = -(lnza - aa - ab);
+        } else {
+            /* High precision not required.  Eval Remez polynomial
+            using standard double precision */
+            lnza = -0.16624882440418567;
+            lnza = lnza * epsilon + 0.19999954120254515;
+            lnza = lnza * epsilon + -0.2499999997677497;
+            lnza = lnza * epsilon + 0.3333333333332802;
+            lnza = lnza * epsilon + -0.5;
+            lnza = lnza * epsilon + 1.0;
+            lnza *= epsilon;
+        }
+
+        /* Relative sizes:
+         * lnzb     [0, 2.33E-10]
+         * lnm[1]   [0, 1.17E-7]
+         * ln2B*exp [0, 1.12E-4]
+         * lnza      [0, 9.7E-4]
+         * lnm[0]   [0, 0.692]
+         * ln2A*exp [0, 709]
+         */
+
+        /* Compute the following sum:
+         * lnzb + lnm[1] + ln2B*exp + lnza + lnm[0] + ln2A*exp;
+         */
+
+        // return lnzb + lnm[1] + ln2B*exp + lnza + lnm[0] + ln2A*exp;
+        double a = LN_2_A * exp;
+        double b = 0.0;
+        double c = a + lnm[0];
+        double d = -(c - a - lnm[0]);
+        a = c;
+        b += d;
+
+        c = a + lnza;
+        d = -(c - a - lnza);
+        a = c;
+        b += d;
+
+        c = a + LN_2_B * exp;
+        d = -(c - a - LN_2_B * exp);
+        a = c;
+        b += d;
+
+        c = a + lnm[1];
+        d = -(c - a - lnm[1]);
+        a = c;
+        b += d;
+
+        c = a + lnzb;
+        d = -(c - a - lnzb);
+        a = c;
+        b += d;
+
+        if (hiPrec != null) {
+            hiPrec[0] = a;
+            hiPrec[1] = b;
+        }
+
+        return a + b;
+    }
+
+    /**
+     * Computes log(1 + x).
+     *
+     * @param x Number.
+     * @return {@code log(1 + x)}.
+     */
+    public static double log1p(final double x) {
+        if (x == -1) {
+            return Double.NEGATIVE_INFINITY;
+        }
+
+        if (x == Double.POSITIVE_INFINITY) {
+            return Double.POSITIVE_INFINITY;
+        }
+
+        if (x > 1e-6 || x < -1e-6) {
+            final double xpa = 1 + x;
+            final double xpb = -(xpa - 1 - x);
+
+            final double[] hiPrec = new double[2];
+            final double lores = log(xpa, hiPrec);
+            if (Double.isInfinite(lores)) { // Don't allow this to be converted to NaN
+                return lores;
+            }
+
+            // Do a taylor series expansion around xpa:
+            //   f(x+y) = f(x) + f'(x) y + f''(x)/2 y^2
+            final double fx1 = xpb / xpa;
+            final double epsilon = 0.5 * fx1 + 1;
+            return epsilon * fx1 + hiPrec[1] + hiPrec[0];
+        } else {
+            // Value is small |x| < 1e6, do a Taylor series centered on 1.
+            final double y = (x * F_1_3 - F_1_2) * x + 1;
+            return y * x;
+        }
+    }
+
+    /**
+     * Compute the base 10 logarithm.
+     *
+     * @param x a number
+     * @return log10(x)
+     */
+    public static double log10(final double x) {
+        final double hiPrec[] = new double[2];
+
+        final double lores = log(x, hiPrec);
+        if (Double.isInfinite(lores)) { // don't allow this to be converted to NaN
+            return lores;
+        }
+
+        final double tmp = hiPrec[0] * HEX_40000000;
+        final double lna = hiPrec[0] + tmp - tmp;
+        final double lnb = hiPrec[0] - lna + hiPrec[1];
+
+        final double rln10a = 0.4342944622039795;
+        final double rln10b = 1.9699272335463627E-8;
+
+        return rln10b * lnb + rln10b * lna + rln10a * lnb + rln10a * lna;
+    }
+
+    /**
+     * Computes the <a href="http://mathworld.wolfram.com/Logarithm.html">logarithm</a> in a given
+     * base.
+     *
+     * <p>Returns {@code NaN} if either argument is negative. If {@code base} is 0 and {@code x} is
+     * positive, 0 is returned. If {@code base} is positive and {@code x} is 0, {@code
+     * Double.NEGATIVE_INFINITY} is returned. If both arguments are 0, the result is {@code NaN}.
+     *
+     * @param base Base of the logarithm, must be greater than 0.
+     * @param x Argument, must be greater than 0.
+     * @return the value of the logarithm, i.e. the number {@code y} such that <code>
+     *     base<sup>y</sup> = x</code>.
+     * @since 1.2 (previously in {@code MathUtils}, moved as of version 3.0)
+     */
+    public static double log(double base, double x) {
+        return log(x) / log(base);
+    }
+
+    /**
+     * Power function. Compute x^y.
+     *
+     * @param x a double
+     * @param y a double
+     * @return double
+     */
+    public static double pow(final double x, final double y) {
+
+        if (y == 0) {
+            // y = -0 or y = +0
+            return 1.0;
+        } else {
+
+            final long yBits = Double.doubleToRawLongBits(y);
+            final int yRawExp = (int) ((yBits & MASK_DOUBLE_EXPONENT) >> 52);
+            final long yRawMantissa = yBits & MASK_DOUBLE_MANTISSA;
+            final long xBits = Double.doubleToRawLongBits(x);
+            final int xRawExp = (int) ((xBits & MASK_DOUBLE_EXPONENT) >> 52);
+            final long xRawMantissa = xBits & MASK_DOUBLE_MANTISSA;
+
+            if (yRawExp > 1085) {
+                // y is either a very large integral value that does not fit in a long or it is a
+                // special number
+
+                if ((yRawExp == 2047 && yRawMantissa != 0)
+                        || (xRawExp == 2047 && xRawMantissa != 0)) {
+                    // NaN
+                    return Double.NaN;
+                } else if (xRawExp == 1023 && xRawMantissa == 0) {
+                    // x = -1.0 or x = +1.0
+                    if (yRawExp == 2047) {
+                        // y is infinite
+                        return Double.NaN;
+                    } else {
+                        // y is a large even integer
+                        return 1.0;
+                    }
+                } else {
+                    // the absolute value of x is either greater or smaller than 1.0
+
+                    // if yRawExp == 2047 and mantissa is 0, y = -infinity or y = +infinity
+                    // if 1085 < yRawExp < 2047, y is simply a large number, however, due to limited
+                    // accuracy, at this magnitude it behaves just like infinity with regards to x
+                    if ((y > 0) ^ (xRawExp < 1023)) {
+                        // either y = +infinity (or large engouh) and abs(x) > 1.0
+                        // or     y = -infinity (or large engouh) and abs(x) < 1.0
+                        return Double.POSITIVE_INFINITY;
+                    } else {
+                        // either y = +infinity (or large engouh) and abs(x) < 1.0
+                        // or     y = -infinity (or large engouh) and abs(x) > 1.0
+                        return +0.0;
+                    }
+                }
+
+            } else {
+                // y is a regular non-zero number
+
+                if (yRawExp >= 1023) {
+                    // y may be an integral value, which should be handled specifically
+                    final long yFullMantissa = IMPLICIT_HIGH_BIT | yRawMantissa;
+                    if (yRawExp < 1075) {
+                        // normal number with negative shift that may have a fractional part
+                        final long integralMask = (-1L) << (1075 - yRawExp);
+                        if ((yFullMantissa & integralMask) == yFullMantissa) {
+                            // all fractional bits are 0, the number is really integral
+                            final long l = yFullMantissa >> (1075 - yRawExp);
+                            return FastMath.pow(x, (y < 0) ? -l : l);
+                        }
+                    } else {
+                        // normal number with positive shift, always an integral value
+                        // we know it fits in a primitive long because yRawExp > 1085 has been
+                        // handled above
+                        final long l = yFullMantissa << (yRawExp - 1075);
+                        return FastMath.pow(x, (y < 0) ? -l : l);
+                    }
+                }
+
+                // y is a non-integral value
+
+                if (x == 0) {
+                    // x = -0 or x = +0
+                    // the integer powers have already been handled above
+                    return y < 0 ? Double.POSITIVE_INFINITY : +0.0;
+                } else if (xRawExp == 2047) {
+                    if (xRawMantissa == 0) {
+                        // x = -infinity or x = +infinity
+                        return (y < 0) ? +0.0 : Double.POSITIVE_INFINITY;
+                    } else {
+                        // NaN
+                        return Double.NaN;
+                    }
+                } else if (x < 0) {
+                    // the integer powers have already been handled above
+                    return Double.NaN;
+                } else {
+
+                    // this is the general case, for regular fractional numbers x and y
+
+                    // Split y into ya and yb such that y = ya+yb
+                    final double tmp = y * HEX_40000000;
+                    final double ya = (y + tmp) - tmp;
+                    final double yb = y - ya;
+
+                    /* Compute ln(x) */
+                    final double lns[] = new double[2];
+                    final double lores = log(x, lns);
+                    if (Double.isInfinite(lores)) { // don't allow this to be converted to NaN
+                        return lores;
+                    }
+
+                    double lna = lns[0];
+                    double lnb = lns[1];
+
+                    /* resplit lns */
+                    final double tmp1 = lna * HEX_40000000;
+                    final double tmp2 = (lna + tmp1) - tmp1;
+                    lnb += lna - tmp2;
+                    lna = tmp2;
+
+                    // y*ln(x) = (aa+ab)
+                    final double aa = lna * ya;
+                    final double ab = lna * yb + lnb * ya + lnb * yb;
+
+                    lna = aa + ab;
+                    lnb = -(lna - aa - ab);
+
+                    double z = 1.0 / 120.0;
+                    z = z * lnb + (1.0 / 24.0);
+                    z = z * lnb + (1.0 / 6.0);
+                    z = z * lnb + 0.5;
+                    z = z * lnb + 1.0;
+                    z *= lnb;
+
+                    final double result = exp(lna, z, null);
+                    // result = result + result * z;
+                    return result;
+                }
+            }
+        }
+    }
+
+    /**
+     * Raise a double to an int power.
+     *
+     * @param d Number to raise.
+     * @param e Exponent.
+     * @return d<sup>e</sup>
+     * @since 3.1
+     */
+    public static double pow(double d, int e) {
+        return pow(d, (long) e);
+    }
+
+    /**
+     * Raise a double to a long power.
+     *
+     * @param d Number to raise.
+     * @param e Exponent.
+     * @return d<sup>e</sup>
+     * @since 3.6
+     */
+    public static double pow(double d, long e) {
+        if (e == 0) {
+            return 1.0;
+        } else if (e > 0) {
+            return new Split(d).pow(e).full;
+        } else {
+            return new Split(d).reciprocal().pow(-e).full;
+        }
+    }
+
+    /** Class operator on double numbers split into one 26 bits number and one 27 bits number. */
+    private static class Split {
+
+        /** Split version of NaN. */
+        public static final Split NAN = new Split(Double.NaN, 0);
+
+        /** Split version of positive infinity. */
+        public static final Split POSITIVE_INFINITY = new Split(Double.POSITIVE_INFINITY, 0);
+
+        /** Split version of negative infinity. */
+        public static final Split NEGATIVE_INFINITY = new Split(Double.NEGATIVE_INFINITY, 0);
+
+        /** Full number. */
+        private final double full;
+
+        /** High order bits. */
+        private final double high;
+
+        /** Low order bits. */
+        private final double low;
+
+        /**
+         * Simple constructor.
+         *
+         * @param x number to split
+         */
+        Split(final double x) {
+            full = x;
+            high = Double.longBitsToDouble(Double.doubleToRawLongBits(x) & ((-1L) << 27));
+            low = x - high;
+        }
+
+        /**
+         * Simple constructor.
+         *
+         * @param high high order bits
+         * @param low low order bits
+         */
+        Split(final double high, final double low) {
+            this(
+                    high == 0.0
+                            ? (low == 0.0
+                                            && Double.doubleToRawLongBits(high)
+                                                    == Long.MIN_VALUE /* negative zero */
+                                    ? -0.0
+                                    : low)
+                            : high + low,
+                    high,
+                    low);
+        }
+
+        /**
+         * Simple constructor.
+         *
+         * @param full full number
+         * @param high high order bits
+         * @param low low order bits
+         */
+        Split(final double full, final double high, final double low) {
+            this.full = full;
+            this.high = high;
+            this.low = low;
+        }
+
+        /**
+         * Multiply the instance by another one.
+         *
+         * @param b other instance to multiply by
+         * @return product
+         */
+        public Split multiply(final Split b) {
+            // beware the following expressions must NOT be simplified, they rely on floating point
+            // arithmetic properties
+            final Split mulBasic = new Split(full * b.full);
+            final double mulError =
+                    low * b.low - (((mulBasic.full - high * b.high) - low * b.high) - high * b.low);
+            return new Split(mulBasic.high, mulBasic.low + mulError);
+        }
+
+        /**
+         * Compute the reciprocal of the instance.
+         *
+         * @return reciprocal of the instance
+         */
+        public Split reciprocal() {
+
+            final double approximateInv = 1.0 / full;
+            final Split splitInv = new Split(approximateInv);
+
+            // if 1.0/d were computed perfectly, remultiplying it by d should give 1.0
+            // we want to estimate the error so we can fix the low order bits of approximateInvLow
+            // beware the following expressions must NOT be simplified, they rely on floating point
+            // arithmetic properties
+            final Split product = multiply(splitInv);
+            final double error = (product.high - 1) + product.low;
+
+            // better accuracy estimate of reciprocal
+            return Double.isNaN(error)
+                    ? splitInv
+                    : new Split(splitInv.high, splitInv.low - error / full);
+        }
+
+        /**
+         * Computes this^e.
+         *
+         * @param e exponent (beware, here it MUST be > 0; the only exclusion is Long.MIN_VALUE)
+         * @return d^e, split in high and low bits
+         * @since 3.6
+         */
+        private Split pow(final long e) {
+
+            // prepare result
+            Split result = new Split(1);
+
+            // d^(2p)
+            Split d2p = new Split(full, high, low);
+
+            for (long p = e; p != 0; p >>>= 1) {
+
+                if ((p & 0x1) != 0) {
+                    // accurate multiplication result = result * d^(2p) using Veltkamp TwoProduct
+                    // algorithm
+                    result = result.multiply(d2p);
+                }
+
+                // accurate squaring d^(2(p+1)) = d^(2p) * d^(2p) using Veltkamp TwoProduct
+                // algorithm
+                d2p = d2p.multiply(d2p);
+            }
+
+            if (Double.isNaN(result.full)) {
+                if (Double.isNaN(full)) {
+                    return Split.NAN;
+                } else {
+                    // some intermediate numbers exceeded capacity,
+                    // and the low order bits became NaN (because infinity - infinity = NaN)
+                    if (FastMath.abs(full) < 1) {
+                        return new Split(FastMath.copySign(0.0, full), 0.0);
+                    } else if (full < 0 && (e & 0x1) == 1) {
+                        return Split.NEGATIVE_INFINITY;
+                    } else {
+                        return Split.POSITIVE_INFINITY;
+                    }
+                }
+            } else {
+                return result;
+            }
+        }
+    }
+
+    /**
+     * Computes sin(x) - x, where |x| < 1/16. Use a Remez polynomial approximation.
+     *
+     * @param x a number smaller than 1/16
+     * @return sin(x) - x
+     */
+    private static double polySine(final double x) {
+        double x2 = x * x;
+
+        double p = 2.7553817452272217E-6;
+        p = p * x2 + -1.9841269659586505E-4;
+        p = p * x2 + 0.008333333333329196;
+        p = p * x2 + -0.16666666666666666;
+        // p *= x2;
+        // p *= x;
+        p = p * x2 * x;
+
+        return p;
+    }
+
+    /**
+     * Computes cos(x) - 1, where |x| < 1/16. Use a Remez polynomial approximation.
+     *
+     * @param x a number smaller than 1/16
+     * @return cos(x) - 1
+     */
+    private static double polyCosine(double x) {
+        double x2 = x * x;
+
+        double p = 2.479773539153719E-5;
+        p = p * x2 + -0.0013888888689039883;
+        p = p * x2 + 0.041666666666621166;
+        p = p * x2 + -0.49999999999999994;
+        p *= x2;
+
+        return p;
+    }
+
+    /**
+     * Compute sine over the first quadrant (0 < x < pi/2). Use combination of table lookup and
+     * rational polynomial expansion.
+     *
+     * @param xa number from which sine is requested
+     * @param xb extra bits for x (may be 0.0)
+     * @return sin(xa + xb)
+     */
+    private static double sinQ(double xa, double xb) {
+        int idx = (int) ((xa * 8.0) + 0.5);
+        final double epsilon = xa - EIGHTHS[idx]; // idx*0.125;
+
+        // Table lookups
+        final double sintA = SINE_TABLE_A[idx];
+        final double sintB = SINE_TABLE_B[idx];
+        final double costA = COSINE_TABLE_A[idx];
+        final double costB = COSINE_TABLE_B[idx];
+
+        // Polynomial eval of sin(epsilon), cos(epsilon)
+        double sinEpsA = epsilon;
+        double sinEpsB = polySine(epsilon);
+        final double cosEpsA = 1.0;
+        final double cosEpsB = polyCosine(epsilon);
+
+        // Split epsilon   xa + xb = x
+        final double temp = sinEpsA * HEX_40000000;
+        double temp2 = (sinEpsA + temp) - temp;
+        sinEpsB += sinEpsA - temp2;
+        sinEpsA = temp2;
+
+        /* Compute sin(x) by angle addition formula */
+        double result;
+
+        /* Compute the following sum:
+         *
+         * result = sintA + costA*sinEpsA + sintA*cosEpsB + costA*sinEpsB +
+         *          sintB + costB*sinEpsA + sintB*cosEpsB + costB*sinEpsB;
+         *
+         * Ranges of elements
+         *
+         * xxxtA   0            PI/2
+         * xxxtB   -1.5e-9      1.5e-9
+         * sinEpsA -0.0625      0.0625
+         * sinEpsB -6e-11       6e-11
+         * cosEpsA  1.0
+         * cosEpsB  0           -0.0625
+         *
+         */
+
+        // result = sintA + costA*sinEpsA + sintA*cosEpsB + costA*sinEpsB +
+        //          sintB + costB*sinEpsA + sintB*cosEpsB + costB*sinEpsB;
+
+        // result = sintA + sintA*cosEpsB + sintB + sintB * cosEpsB;
+        // result += costA*sinEpsA + costA*sinEpsB + costB*sinEpsA + costB * sinEpsB;
+        double a = 0;
+        double b = 0;
+
+        double t = sintA;
+        double c = a + t;
+        double d = -(c - a - t);
+        a = c;
+        b += d;
+
+        t = costA * sinEpsA;
+        c = a + t;
+        d = -(c - a - t);
+        a = c;
+        b += d;
+
+        b = b + sintA * cosEpsB + costA * sinEpsB;
+        /*
+        t = sintA*cosEpsB;
+        c = a + t;
+        d = -(c - a - t);
+        a = c;
+        b = b + d;
+
+        t = costA*sinEpsB;
+        c = a + t;
+        d = -(c - a - t);
+        a = c;
+        b = b + d;
+             */
+
+        b = b + sintB + costB * sinEpsA + sintB * cosEpsB + costB * sinEpsB;
+        /*
+        t = sintB;
+        c = a + t;
+        d = -(c - a - t);
+        a = c;
+        b = b + d;
+
+        t = costB*sinEpsA;
+        c = a + t;
+        d = -(c - a - t);
+        a = c;
+        b = b + d;
+
+        t = sintB*cosEpsB;
+        c = a + t;
+        d = -(c - a - t);
+        a = c;
+        b = b + d;
+
+        t = costB*sinEpsB;
+        c = a + t;
+        d = -(c - a - t);
+        a = c;
+        b = b + d;
+             */
+
+        if (xb != 0.0) {
+            t =
+                    ((costA + costB) * (cosEpsA + cosEpsB) - (sintA + sintB) * (sinEpsA + sinEpsB))
+                            * xb; // approximate cosine*xb
+            c = a + t;
+            d = -(c - a - t);
+            a = c;
+            b += d;
+        }
+
+        result = a + b;
+
+        return result;
+    }
+
+    /**
+     * Compute cosine in the first quadrant by subtracting input from PI/2 and then calling sinQ.
+     * This is more accurate as the input approaches PI/2.
+     *
+     * @param xa number from which cosine is requested
+     * @param xb extra bits for x (may be 0.0)
+     * @return cos(xa + xb)
+     */
+    private static double cosQ(double xa, double xb) {
+        final double pi2a = 1.5707963267948966;
+        final double pi2b = 6.123233995736766E-17;
+
+        final double a = pi2a - xa;
+        double b = -(a - pi2a + xa);
+        b += pi2b - xb;
+
+        return sinQ(a, b);
+    }
+
+    /**
+     * Compute tangent (or cotangent) over the first quadrant. 0 < x < pi/2 Use combination of table
+     * lookup and rational polynomial expansion.
+     *
+     * @param xa number from which sine is requested
+     * @param xb extra bits for x (may be 0.0)
+     * @param cotanFlag if true, compute the cotangent instead of the tangent
+     * @return tan(xa+xb) (or cotangent, depending on cotanFlag)
+     */
+    private static double tanQ(double xa, double xb, boolean cotanFlag) {
+
+        int idx = (int) ((xa * 8.0) + 0.5);
+        final double epsilon = xa - EIGHTHS[idx]; // idx*0.125;
+
+        // Table lookups
+        final double sintA = SINE_TABLE_A[idx];
+        final double sintB = SINE_TABLE_B[idx];
+        final double costA = COSINE_TABLE_A[idx];
+        final double costB = COSINE_TABLE_B[idx];
+
+        // Polynomial eval of sin(epsilon), cos(epsilon)
+        double sinEpsA = epsilon;
+        double sinEpsB = polySine(epsilon);
+        final double cosEpsA = 1.0;
+        final double cosEpsB = polyCosine(epsilon);
+
+        // Split epsilon   xa + xb = x
+        double temp = sinEpsA * HEX_40000000;
+        double temp2 = (sinEpsA + temp) - temp;
+        sinEpsB += sinEpsA - temp2;
+        sinEpsA = temp2;
+
+        /* Compute sin(x) by angle addition formula */
+
+        /* Compute the following sum:
+         *
+         * result = sintA + costA*sinEpsA + sintA*cosEpsB + costA*sinEpsB +
+         *          sintB + costB*sinEpsA + sintB*cosEpsB + costB*sinEpsB;
+         *
+         * Ranges of elements
+         *
+         * xxxtA   0            PI/2
+         * xxxtB   -1.5e-9      1.5e-9
+         * sinEpsA -0.0625      0.0625
+         * sinEpsB -6e-11       6e-11
+         * cosEpsA  1.0
+         * cosEpsB  0           -0.0625
+         *
+         */
+
+        // result = sintA + costA*sinEpsA + sintA*cosEpsB + costA*sinEpsB +
+        //          sintB + costB*sinEpsA + sintB*cosEpsB + costB*sinEpsB;
+
+        // result = sintA + sintA*cosEpsB + sintB + sintB * cosEpsB;
+        // result += costA*sinEpsA + costA*sinEpsB + costB*sinEpsA + costB * sinEpsB;
+        double a = 0;
+        double b = 0;
+
+        // Compute sine
+        double t = sintA;
+        double c = a + t;
+        double d = -(c - a - t);
+        a = c;
+        b += d;
+
+        t = costA * sinEpsA;
+        c = a + t;
+        d = -(c - a - t);
+        a = c;
+        b += d;
+
+        b += sintA * cosEpsB + costA * sinEpsB;
+        b += sintB + costB * sinEpsA + sintB * cosEpsB + costB * sinEpsB;
+
+        double sina = a + b;
+        double sinb = -(sina - a - b);
+
+        // Compute cosine
+
+        a = b = c = d = 0.0;
+
+        t = costA * cosEpsA;
+        c = a + t;
+        d = -(c - a - t);
+        a = c;
+        b += d;
+
+        t = -sintA * sinEpsA;
+        c = a + t;
+        d = -(c - a - t);
+        a = c;
+        b += d;
+
+        b += costB * cosEpsA + costA * cosEpsB + costB * cosEpsB;
+        b -= sintB * sinEpsA + sintA * sinEpsB + sintB * sinEpsB;
+
+        double cosa = a + b;
+        double cosb = -(cosa - a - b);
+
+        if (cotanFlag) {
+            double tmp;
+            tmp = cosa;
+            cosa = sina;
+            sina = tmp;
+            tmp = cosb;
+            cosb = sinb;
+            sinb = tmp;
+        }
+
+        /* estimate and correct, compute 1.0/(cosa+cosb) */
+        /*
+        double est = (sina+sinb)/(cosa+cosb);
+        double err = (sina - cosa*est) + (sinb - cosb*est);
+        est += err/(cosa+cosb);
+        err = (sina - cosa*est) + (sinb - cosb*est);
+             */
+
+        // f(x) = 1/x,   f'(x) = -1/x^2
+
+        double est = sina / cosa;
+
+        /* Split the estimate to get more accurate read on division rounding */
+        temp = est * HEX_40000000;
+        double esta = (est + temp) - temp;
+        double estb = est - esta;
+
+        temp = cosa * HEX_40000000;
+        double cosaa = (cosa + temp) - temp;
+        double cosab = cosa - cosaa;
+
+        // double err = (sina - est*cosa)/cosa;  // Correction for division rounding
+        double err =
+                (sina - esta * cosaa - esta * cosab - estb * cosaa - estb * cosab)
+                        / cosa; // Correction for division rounding
+        err += sinb / cosa; // Change in est due to sinb
+        err += -sina * cosb / cosa / cosa; // Change in est due to cosb
+
+        if (xb != 0.0) {
+            // tan' = 1 + tan^2      cot' = -(1 + cot^2)
+            // Approximate impact of xb
+            double xbadj = xb + est * est * xb;
+            if (cotanFlag) {
+                xbadj = -xbadj;
+            }
+
+            err += xbadj;
+        }
+
+        return est + err;
+    }
+
+    /**
+     * Reduce the input argument using the Payne and Hanek method. This is good for all inputs 0.0 <
+     * x < inf Output is remainder after dividing by PI/2 The result array should contain 3 numbers.
+     * result[0] is the integer portion, so mod 4 this gives the quadrant. result[1] is the upper
+     * bits of the remainder result[2] is the lower bits of the remainder
+     *
+     * @param x number to reduce
+     * @param result placeholder where to put the result
+     */
+    private static void reducePayneHanek(double x, double result[]) {
+        /* Convert input double to bits */
+        long inbits = Double.doubleToRawLongBits(x);
+        int exponent = (int) ((inbits >> 52) & 0x7ff) - 1023;
+
+        /* Convert to fixed point representation */
+        inbits &= 0x000fffffffffffffL;
+        inbits |= 0x0010000000000000L;
+
+        /* Normalize input to be between 0.5 and 1.0 */
+        exponent++;
+        inbits <<= 11;
+
+        /* Based on the exponent, get a shifted copy of recip2pi */
+        long shpi0;
+        long shpiA;
+        long shpiB;
+        int idx = exponent >> 6;
+        int shift = exponent - (idx << 6);
+
+        if (shift != 0) {
+            shpi0 = (idx == 0) ? 0 : (RECIP_2PI[idx - 1] << shift);
+            shpi0 |= RECIP_2PI[idx] >>> (64 - shift);
+            shpiA = (RECIP_2PI[idx] << shift) | (RECIP_2PI[idx + 1] >>> (64 - shift));
+            shpiB = (RECIP_2PI[idx + 1] << shift) | (RECIP_2PI[idx + 2] >>> (64 - shift));
+        } else {
+            shpi0 = (idx == 0) ? 0 : RECIP_2PI[idx - 1];
+            shpiA = RECIP_2PI[idx];
+            shpiB = RECIP_2PI[idx + 1];
+        }
+
+        /* Multiply input by shpiA */
+        long a = inbits >>> 32;
+        long b = inbits & 0xffffffffL;
+
+        long c = shpiA >>> 32;
+        long d = shpiA & 0xffffffffL;
+
+        long ac = a * c;
+        long bd = b * d;
+        long bc = b * c;
+        long ad = a * d;
+
+        long prodB = bd + (ad << 32);
+        long prodA = ac + (ad >>> 32);
+
+        boolean bita = (bd & 0x8000000000000000L) != 0;
+        boolean bitb = (ad & 0x80000000L) != 0;
+        boolean bitsum = (prodB & 0x8000000000000000L) != 0;
+
+        /* Carry */
+        if ((bita && bitb) || ((bita || bitb) && !bitsum)) {
+            prodA++;
+        }
+
+        bita = (prodB & 0x8000000000000000L) != 0;
+        bitb = (bc & 0x80000000L) != 0;
+
+        prodB += bc << 32;
+        prodA += bc >>> 32;
+
+        bitsum = (prodB & 0x8000000000000000L) != 0;
+
+        /* Carry */
+        if ((bita && bitb) || ((bita || bitb) && !bitsum)) {
+            prodA++;
+        }
+
+        /* Multiply input by shpiB */
+        c = shpiB >>> 32;
+        d = shpiB & 0xffffffffL;
+        ac = a * c;
+        bc = b * c;
+        ad = a * d;
+
+        /* Collect terms */
+        ac += (bc + ad) >>> 32;
+
+        bita = (prodB & 0x8000000000000000L) != 0;
+        bitb = (ac & 0x8000000000000000L) != 0;
+        prodB += ac;
+        bitsum = (prodB & 0x8000000000000000L) != 0;
+        /* Carry */
+        if ((bita && bitb) || ((bita || bitb) && !bitsum)) {
+            prodA++;
+        }
+
+        /* Multiply by shpi0 */
+        c = shpi0 >>> 32;
+        d = shpi0 & 0xffffffffL;
+
+        bd = b * d;
+        bc = b * c;
+        ad = a * d;
+
+        prodA += bd + ((bc + ad) << 32);
+
+        /*
+         * prodA, prodB now contain the remainder as a fraction of PI.  We want this as a fraction of
+         * PI/2, so use the following steps:
+         * 1.) multiply by 4.
+         * 2.) do a fixed point muliply by PI/4.
+         * 3.) Convert to floating point.
+         * 4.) Multiply by 2
+         */
+
+        /* This identifies the quadrant */
+        int intPart = (int) (prodA >>> 62);
+
+        /* Multiply by 4 */
+        prodA <<= 2;
+        prodA |= prodB >>> 62;
+        prodB <<= 2;
+
+        /* Multiply by PI/4 */
+        a = prodA >>> 32;
+        b = prodA & 0xffffffffL;
+
+        c = PI_O_4_BITS[0] >>> 32;
+        d = PI_O_4_BITS[0] & 0xffffffffL;
+
+        ac = a * c;
+        bd = b * d;
+        bc = b * c;
+        ad = a * d;
+
+        long prod2B = bd + (ad << 32);
+        long prod2A = ac + (ad >>> 32);
+
+        bita = (bd & 0x8000000000000000L) != 0;
+        bitb = (ad & 0x80000000L) != 0;
+        bitsum = (prod2B & 0x8000000000000000L) != 0;
+
+        /* Carry */
+        if ((bita && bitb) || ((bita || bitb) && !bitsum)) {
+            prod2A++;
+        }
+
+        bita = (prod2B & 0x8000000000000000L) != 0;
+        bitb = (bc & 0x80000000L) != 0;
+
+        prod2B += bc << 32;
+        prod2A += bc >>> 32;
+
+        bitsum = (prod2B & 0x8000000000000000L) != 0;
+
+        /* Carry */
+        if ((bita && bitb) || ((bita || bitb) && !bitsum)) {
+            prod2A++;
+        }
+
+        /* Multiply input by pio4bits[1] */
+        c = PI_O_4_BITS[1] >>> 32;
+        d = PI_O_4_BITS[1] & 0xffffffffL;
+        ac = a * c;
+        bc = b * c;
+        ad = a * d;
+
+        /* Collect terms */
+        ac += (bc + ad) >>> 32;
+
+        bita = (prod2B & 0x8000000000000000L) != 0;
+        bitb = (ac & 0x8000000000000000L) != 0;
+        prod2B += ac;
+        bitsum = (prod2B & 0x8000000000000000L) != 0;
+        /* Carry */
+        if ((bita && bitb) || ((bita || bitb) && !bitsum)) {
+            prod2A++;
+        }
+
+        /* Multiply inputB by pio4bits[0] */
+        a = prodB >>> 32;
+        b = prodB & 0xffffffffL;
+        c = PI_O_4_BITS[0] >>> 32;
+        d = PI_O_4_BITS[0] & 0xffffffffL;
+        ac = a * c;
+        bc = b * c;
+        ad = a * d;
+
+        /* Collect terms */
+        ac += (bc + ad) >>> 32;
+
+        bita = (prod2B & 0x8000000000000000L) != 0;
+        bitb = (ac & 0x8000000000000000L) != 0;
+        prod2B += ac;
+        bitsum = (prod2B & 0x8000000000000000L) != 0;
+        /* Carry */
+        if ((bita && bitb) || ((bita || bitb) && !bitsum)) {
+            prod2A++;
+        }
+
+        /* Convert to double */
+        double tmpA = (prod2A >>> 12) / TWO_POWER_52; // High order 52 bits
+        double tmpB =
+                (((prod2A & 0xfffL) << 40) + (prod2B >>> 24))
+                        / TWO_POWER_52
+                        / TWO_POWER_52; // Low bits
+
+        double sumA = tmpA + tmpB;
+        double sumB = -(sumA - tmpA - tmpB);
+
+        /* Multiply by PI/2 and return */
+        result[0] = intPart;
+        result[1] = sumA * 2.0;
+        result[2] = sumB * 2.0;
+    }
+
+    /**
+     * Sine function.
+     *
+     * @param x Argument.
+     * @return sin(x)
+     */
+    public static double sin(double x) {
+        boolean negative = false;
+        int quadrant = 0;
+        double xa;
+        double xb = 0.0;
+
+        /* Take absolute value of the input */
+        xa = x;
+        if (x < 0) {
+            negative = true;
+            xa = -xa;
+        }
+
+        /* Check for zero and negative zero */
+        if (xa == 0.0) {
+            long bits = Double.doubleToRawLongBits(x);
+            if (bits < 0) {
+                return -0.0;
+            }
+            return 0.0;
+        }
+
+        if (xa != xa || xa == Double.POSITIVE_INFINITY) {
+            return Double.NaN;
+        }
+
+        /* Perform any argument reduction */
+        if (xa > 3294198.0) {
+            // PI * (2**20)
+            // Argument too big for CodyWaite reduction.  Must use
+            // PayneHanek.
+            double reduceResults[] = new double[3];
+            reducePayneHanek(xa, reduceResults);
+            quadrant = ((int) reduceResults[0]) & 3;
+            xa = reduceResults[1];
+            xb = reduceResults[2];
+        } else if (xa > 1.5707963267948966) {
+            final CodyWaite cw = new CodyWaite(xa);
+            quadrant = cw.getK() & 3;
+            xa = cw.getRemA();
+            xb = cw.getRemB();
+        }
+
+        if (negative) {
+            quadrant ^= 2; // Flip bit 1
+        }
+
+        switch (quadrant) {
+            case 0:
+                return sinQ(xa, xb);
+            case 1:
+                return cosQ(xa, xb);
+            case 2:
+                return -sinQ(xa, xb);
+            case 3:
+                return -cosQ(xa, xb);
+            default:
+                return Double.NaN;
+        }
+    }
+
+    /**
+     * Cosine function.
+     *
+     * @param x Argument.
+     * @return cos(x)
+     */
+    public static double cos(double x) {
+        int quadrant = 0;
+
+        /* Take absolute value of the input */
+        double xa = x;
+        if (x < 0) {
+            xa = -xa;
+        }
+
+        if (xa != xa || xa == Double.POSITIVE_INFINITY) {
+            return Double.NaN;
+        }
+
+        /* Perform any argument reduction */
+        double xb = 0;
+        if (xa > 3294198.0) {
+            // PI * (2**20)
+            // Argument too big for CodyWaite reduction.  Must use
+            // PayneHanek.
+            double reduceResults[] = new double[3];
+            reducePayneHanek(xa, reduceResults);
+            quadrant = ((int) reduceResults[0]) & 3;
+            xa = reduceResults[1];
+            xb = reduceResults[2];
+        } else if (xa > 1.5707963267948966) {
+            final CodyWaite cw = new CodyWaite(xa);
+            quadrant = cw.getK() & 3;
+            xa = cw.getRemA();
+            xb = cw.getRemB();
+        }
+
+        // if (negative)
+        //  quadrant = (quadrant + 2) % 4;
+
+        switch (quadrant) {
+            case 0:
+                return cosQ(xa, xb);
+            case 1:
+                return -sinQ(xa, xb);
+            case 2:
+                return -cosQ(xa, xb);
+            case 3:
+                return sinQ(xa, xb);
+            default:
+                return Double.NaN;
+        }
+    }
+
+    /**
+     * Tangent function.
+     *
+     * @param x Argument.
+     * @return tan(x)
+     */
+    public static double tan(double x) {
+        boolean negative = false;
+        int quadrant = 0;
+
+        /* Take absolute value of the input */
+        double xa = x;
+        if (x < 0) {
+            negative = true;
+            xa = -xa;
+        }
+
+        /* Check for zero and negative zero */
+        if (xa == 0.0) {
+            long bits = Double.doubleToRawLongBits(x);
+            if (bits < 0) {
+                return -0.0;
+            }
+            return 0.0;
+        }
+
+        if (xa != xa || xa == Double.POSITIVE_INFINITY) {
+            return Double.NaN;
+        }
+
+        /* Perform any argument reduction */
+        double xb = 0;
+        if (xa > 3294198.0) {
+            // PI * (2**20)
+            // Argument too big for CodyWaite reduction.  Must use
+            // PayneHanek.
+            double reduceResults[] = new double[3];
+            reducePayneHanek(xa, reduceResults);
+            quadrant = ((int) reduceResults[0]) & 3;
+            xa = reduceResults[1];
+            xb = reduceResults[2];
+        } else if (xa > 1.5707963267948966) {
+            final CodyWaite cw = new CodyWaite(xa);
+            quadrant = cw.getK() & 3;
+            xa = cw.getRemA();
+            xb = cw.getRemB();
+        }
+
+        if (xa > 1.5) {
+            // Accuracy suffers between 1.5 and PI/2
+            final double pi2a = 1.5707963267948966;
+            final double pi2b = 6.123233995736766E-17;
+
+            final double a = pi2a - xa;
+            double b = -(a - pi2a + xa);
+            b += pi2b - xb;
+
+            xa = a + b;
+            xb = -(xa - a - b);
+            quadrant ^= 1;
+            negative ^= true;
+        }
+
+        double result;
+        if ((quadrant & 1) == 0) {
+            result = tanQ(xa, xb, false);
+        } else {
+            result = -tanQ(xa, xb, true);
+        }
+
+        if (negative) {
+            result = -result;
+        }
+
+        return result;
+    }
+
+    /**
+     * Arctangent function
+     *
+     * @param x a number
+     * @return atan(x)
+     */
+    public static double atan(double x) {
+        return atan(x, 0.0, false);
+    }
+
+    /**
+     * Internal helper function to compute arctangent.
+     *
+     * @param xa number from which arctangent is requested
+     * @param xb extra bits for x (may be 0.0)
+     * @param leftPlane if true, result angle must be put in the left half plane
+     * @return atan(xa + xb) (or angle shifted by {@code PI} if leftPlane is true)
+     */
+    private static double atan(double xa, double xb, boolean leftPlane) {
+        if (xa == 0.0) { // Matches +/- 0.0; return correct sign
+            return leftPlane ? copySign(Math.PI, xa) : xa;
+        }
+
+        final boolean negate;
+        if (xa < 0) {
+            // negative
+            xa = -xa;
+            xb = -xb;
+            negate = true;
+        } else {
+            negate = false;
+        }
+
+        if (xa > 1.633123935319537E16) { // Very large input
+            return (negate ^ leftPlane) ? (-Math.PI * F_1_2) : (Math.PI * F_1_2);
+        }
+
+        /* Estimate the closest tabulated arctan value, compute eps = xa-tangentTable */
+        final int idx;
+        if (xa < 1) {
+            idx = (int) (((-1.7168146928204136 * xa * xa + 8.0) * xa) + 0.5);
+        } else {
+            final double oneOverXa = 1 / xa;
+            idx =
+                    (int)
+                            (-((-1.7168146928204136 * oneOverXa * oneOverXa + 8.0) * oneOverXa)
+                                    + 13.07);
+        }
+
+        final double ttA = TANGENT_TABLE_A[idx];
+        final double ttB = TANGENT_TABLE_B[idx];
+
+        double epsA = xa - ttA;
+        double epsB = -(epsA - xa + ttA);
+        epsB += xb - ttB;
+
+        double temp = epsA + epsB;
+        epsB = -(temp - epsA - epsB);
+        epsA = temp;
+
+        /* Compute eps = eps / (1.0 + xa*tangent) */
+        temp = xa * HEX_40000000;
+        double ya = xa + temp - temp;
+        double yb = xb + xa - ya;
+        xa = ya;
+        xb += yb;
+
+        // if (idx > 8 || idx == 0)
+        if (idx == 0) {
+            /* If the slope of the arctan is gentle enough (< 0.45), this approximation will suffice */
+            // double denom = 1.0 / (1.0 + xa*tangentTableA[idx] + xb*tangentTableA[idx] +
+            // xa*tangentTableB[idx] + xb*tangentTableB[idx]);
+            final double denom = 1d / (1d + (xa + xb) * (ttA + ttB));
+            // double denom = 1.0 / (1.0 + xa*tangentTableA[idx]);
+            ya = epsA * denom;
+            yb = epsB * denom;
+        } else {
+            double temp2 = xa * ttA;
+            double za = 1d + temp2;
+            double zb = -(za - 1d - temp2);
+            temp2 = xb * ttA + xa * ttB;
+            temp = za + temp2;
+            zb += -(temp - za - temp2);
+            za = temp;
+
+            zb += xb * ttB;
+            ya = epsA / za;
+
+            temp = ya * HEX_40000000;
+            final double yaa = (ya + temp) - temp;
+            final double yab = ya - yaa;
+
+            temp = za * HEX_40000000;
+            final double zaa = (za + temp) - temp;
+            final double zab = za - zaa;
+
+            /* Correct for rounding in division */
+            yb = (epsA - yaa * zaa - yaa * zab - yab * zaa - yab * zab) / za;
+
+            yb += -epsA * zb / za / za;
+            yb += epsB / za;
+        }
+
+        epsA = ya;
+        epsB = yb;
+
+        /* Evaluate polynomial */
+        final double epsA2 = epsA * epsA;
+
+        /*
+        yb = -0.09001346640161823;
+        yb = yb * epsA2 + 0.11110718400605211;
+        yb = yb * epsA2 + -0.1428571349122913;
+        yb = yb * epsA2 + 0.19999999999273194;
+        yb = yb * epsA2 + -0.33333333333333093;
+        yb = yb * epsA2 * epsA;
+             */
+
+        yb = 0.07490822288864472;
+        yb = yb * epsA2 - 0.09088450866185192;
+        yb = yb * epsA2 + 0.11111095942313305;
+        yb = yb * epsA2 - 0.1428571423679182;
+        yb = yb * epsA2 + 0.19999999999923582;
+        yb = yb * epsA2 - 0.33333333333333287;
+        yb = yb * epsA2 * epsA;
+
+        ya = epsA;
+
+        temp = ya + yb;
+        yb = -(temp - ya - yb);
+        ya = temp;
+
+        /* Add in effect of epsB.   atan'(x) = 1/(1+x^2) */
+        yb += epsB / (1d + epsA * epsA);
+
+        final double eighths = EIGHTHS[idx];
+
+        // result = yb + eighths[idx] + ya;
+        double za = eighths + ya;
+        double zb = -(za - eighths - ya);
+        temp = za + yb;
+        zb += -(temp - za - yb);
+        za = temp;
+
+        double result = za + zb;
+
+        if (leftPlane) {
+            // Result is in the left plane
+            final double resultb = -(result - za - zb);
+            final double pia = 1.5707963267948966 * 2;
+            final double pib = 6.123233995736766E-17 * 2;
+
+            za = pia - result;
+            zb = -(za - pia + result);
+            zb += pib - resultb;
+
+            result = za + zb;
+        }
+
+        if (negate ^ leftPlane) {
+            result = -result;
+        }
+
+        return result;
+    }
+
+    /**
+     * Two arguments arctangent function
+     *
+     * @param y ordinate
+     * @param x abscissa
+     * @return phase angle of point (x,y) between {@code -PI} and {@code PI}
+     */
+    public static double atan2(double y, double x) {
+        if (x != x || y != y) {
+            return Double.NaN;
+        }
+
+        if (y == 0) {
+            final double result = x * y;
+            final double invx = 1d / x;
+            final double invy = 1d / y;
+
+            if (invx == 0) { // X is infinite
+                if (x > 0) {
+                    return y; // return +/- 0.0
+                } else {
+                    return copySign(Math.PI, y);
+                }
+            }
+
+            if (x < 0 || invx < 0) {
+                if (y < 0 || invy < 0) {
+                    return -Math.PI;
+                } else {
+                    return Math.PI;
+                }
+            } else {
+                return result;
+            }
+        }
+
+        // y cannot now be zero
+
+        if (y == Double.POSITIVE_INFINITY) {
+            if (x == Double.POSITIVE_INFINITY) {
+                return Math.PI * F_1_4;
+            }
+
+            if (x == Double.NEGATIVE_INFINITY) {
+                return Math.PI * F_3_4;
+            }
+
+            return Math.PI * F_1_2;
+        }
+
+        if (y == Double.NEGATIVE_INFINITY) {
+            if (x == Double.POSITIVE_INFINITY) {
+                return -Math.PI * F_1_4;
+            }
+
+            if (x == Double.NEGATIVE_INFINITY) {
+                return -Math.PI * F_3_4;
+            }
+
+            return -Math.PI * F_1_2;
+        }
+
+        if (x == Double.POSITIVE_INFINITY) {
+            if (y > 0 || 1 / y > 0) {
+                return 0d;
+            }
+
+            if (y < 0 || 1 / y < 0) {
+                return -0d;
+            }
+        }
+
+        if (x == Double.NEGATIVE_INFINITY) {
+            if (y > 0.0 || 1 / y > 0.0) {
+                return Math.PI;
+            }
+
+            if (y < 0 || 1 / y < 0) {
+                return -Math.PI;
+            }
+        }
+
+        // Neither y nor x can be infinite or NAN here
+
+        if (x == 0) {
+            if (y > 0 || 1 / y > 0) {
+                return Math.PI * F_1_2;
+            }
+
+            if (y < 0 || 1 / y < 0) {
+                return -Math.PI * F_1_2;
+            }
+        }
+
+        // Compute ratio r = y/x
+        final double r = y / x;
+        if (Double.isInfinite(r)) { // bypass calculations that can create NaN
+            return atan(r, 0, x < 0);
+        }
+
+        double ra = doubleHighPart(r);
+        double rb = r - ra;
+
+        // Split x
+        final double xa = doubleHighPart(x);
+        final double xb = x - xa;
+
+        rb += (y - ra * xa - ra * xb - rb * xa - rb * xb) / x;
+
+        final double temp = ra + rb;
+        rb = -(temp - ra - rb);
+        ra = temp;
+
+        if (ra == 0) { // Fix up the sign so atan works correctly
+            ra = copySign(0d, y);
+        }
+
+        // Call atan
+        final double result = atan(ra, rb, x < 0);
+
+        return result;
+    }
+
+    /**
+     * Compute the arc sine of a number.
+     *
+     * @param x number on which evaluation is done
+     * @return arc sine of x
+     */
+    public static double asin(double x) {
+        if (x != x) {
+            return Double.NaN;
+        }
+
+        if (x > 1.0 || x < -1.0) {
+            return Double.NaN;
+        }
+
+        if (x == 1.0) {
+            return Math.PI / 2.0;
+        }
+
+        if (x == -1.0) {
+            return -Math.PI / 2.0;
+        }
+
+        if (x == 0.0) { // Matches +/- 0.0; return correct sign
+            return x;
+        }
+
+        /* Compute asin(x) = atan(x/sqrt(1-x*x)) */
+
+        /* Split x */
+        double temp = x * HEX_40000000;
+        final double xa = x + temp - temp;
+        final double xb = x - xa;
+
+        /* Square it */
+        double ya = xa * xa;
+        double yb = xa * xb * 2.0 + xb * xb;
+
+        /* Subtract from 1 */
+        ya = -ya;
+        yb = -yb;
+
+        double za = 1.0 + ya;
+        double zb = -(za - 1.0 - ya);
+
+        temp = za + yb;
+        zb += -(temp - za - yb);
+        za = temp;
+
+        /* Square root */
+        double y;
+        y = sqrt(za);
+        temp = y * HEX_40000000;
+        ya = y + temp - temp;
+        yb = y - ya;
+
+        /* Extend precision of sqrt */
+        yb += (za - ya * ya - 2 * ya * yb - yb * yb) / (2.0 * y);
+
+        /* Contribution of zb to sqrt */
+        double dx = zb / (2.0 * y);
+
+        // Compute ratio r = x/y
+        double r = x / y;
+        temp = r * HEX_40000000;
+        double ra = r + temp - temp;
+        double rb = r - ra;
+
+        rb += (x - ra * ya - ra * yb - rb * ya - rb * yb) / y; // Correct for rounding in division
+        rb += -x * dx / y / y; // Add in effect additional bits of sqrt.
+
+        temp = ra + rb;
+        rb = -(temp - ra - rb);
+        ra = temp;
+
+        return atan(ra, rb, false);
+    }
+
+    /**
+     * Compute the arc cosine of a number.
+     *
+     * @param x number on which evaluation is done
+     * @return arc cosine of x
+     */
+    public static double acos(double x) {
+        if (x != x) {
+            return Double.NaN;
+        }
+
+        if (x > 1.0 || x < -1.0) {
+            return Double.NaN;
+        }
+
+        if (x == -1.0) {
+            return Math.PI;
+        }
+
+        if (x == 1.0) {
+            return 0.0;
+        }
+
+        if (x == 0) {
+            return Math.PI / 2.0;
+        }
+
+        /* Compute acos(x) = atan(sqrt(1-x*x)/x) */
+
+        /* Split x */
+        double temp = x * HEX_40000000;
+        final double xa = x + temp - temp;
+        final double xb = x - xa;
+
+        /* Square it */
+        double ya = xa * xa;
+        double yb = xa * xb * 2.0 + xb * xb;
+
+        /* Subtract from 1 */
+        ya = -ya;
+        yb = -yb;
+
+        double za = 1.0 + ya;
+        double zb = -(za - 1.0 - ya);
+
+        temp = za + yb;
+        zb += -(temp - za - yb);
+        za = temp;
+
+        /* Square root */
+        double y = sqrt(za);
+        temp = y * HEX_40000000;
+        ya = y + temp - temp;
+        yb = y - ya;
+
+        /* Extend precision of sqrt */
+        yb += (za - ya * ya - 2 * ya * yb - yb * yb) / (2.0 * y);
+
+        /* Contribution of zb to sqrt */
+        yb += zb / (2.0 * y);
+        y = ya + yb;
+        yb = -(y - ya - yb);
+
+        // Compute ratio r = y/x
+        double r = y / x;
+
+        // Did r overflow?
+        if (Double.isInfinite(r)) { // x is effectively zero
+            return Math.PI / 2; // so return the appropriate value
+        }
+
+        double ra = doubleHighPart(r);
+        double rb = r - ra;
+
+        rb += (y - ra * xa - ra * xb - rb * xa - rb * xb) / x; // Correct for rounding in division
+        rb += yb / x; // Add in effect additional bits of sqrt.
+
+        temp = ra + rb;
+        rb = -(temp - ra - rb);
+        ra = temp;
+
+        return atan(ra, rb, x < 0);
+    }
+
+    /**
+     * Compute the cubic root of a number.
+     *
+     * @param x number on which evaluation is done
+     * @return cubic root of x
+     */
+    public static double cbrt(double x) {
+        /* Convert input double to bits */
+        long inbits = Double.doubleToRawLongBits(x);
+        int exponent = (int) ((inbits >> 52) & 0x7ff) - 1023;
+        boolean subnormal = false;
+
+        if (exponent == -1023) {
+            if (x == 0) {
+                return x;
+            }
+
+            /* Subnormal, so normalize */
+            subnormal = true;
+            x *= 1.8014398509481984E16; // 2^54
+            inbits = Double.doubleToRawLongBits(x);
+            exponent = (int) ((inbits >> 52) & 0x7ff) - 1023;
+        }
+
+        if (exponent == 1024) {
+            // Nan or infinity.  Don't care which.
+            return x;
+        }
+
+        /* Divide the exponent by 3 */
+        int exp3 = exponent / 3;
+
+        /* p2 will be the nearest power of 2 to x with its exponent divided by 3 */
+        double p2 =
+                Double.longBitsToDouble(
+                        (inbits & 0x8000000000000000L) | (long) (((exp3 + 1023) & 0x7ff)) << 52);
+
+        /* This will be a number between 1 and 2 */
+        final double mant =
+                Double.longBitsToDouble((inbits & 0x000fffffffffffffL) | 0x3ff0000000000000L);
+
+        /* Estimate the cube root of mant by polynomial */
+        double est = -0.010714690733195933;
+        est = est * mant + 0.0875862700108075;
+        est = est * mant + -0.3058015757857271;
+        est = est * mant + 0.7249995199969751;
+        est = est * mant + 0.5039018405998233;
+
+        est *= CBRTTWO[exponent % 3 + 2];
+
+        // est should now be good to about 15 bits of precision.   Do 2 rounds of
+        // Newton's method to get closer,  this should get us full double precision
+        // Scale down x for the purpose of doing newtons method.  This avoids over/under flows.
+        final double xs = x / (p2 * p2 * p2);
+        est += (xs - est * est * est) / (3 * est * est);
+        est += (xs - est * est * est) / (3 * est * est);
+
+        // Do one round of Newton's method in extended precision to get the last bit right.
+        double temp = est * HEX_40000000;
+        double ya = est + temp - temp;
+        double yb = est - ya;
+
+        double za = ya * ya;
+        double zb = ya * yb * 2.0 + yb * yb;
+        temp = za * HEX_40000000;
+        double temp2 = za + temp - temp;
+        zb += za - temp2;
+        za = temp2;
+
+        zb = za * yb + ya * zb + zb * yb;
+        za *= ya;
+
+        double na = xs - za;
+        double nb = -(na - xs + za);
+        nb -= zb;
+
+        est += (na + nb) / (3 * est * est);
+
+        /* Scale by a power of two, so this is exact. */
+        est *= p2;
+
+        if (subnormal) {
+            est *= 3.814697265625E-6; // 2^-18
+        }
+
+        return est;
+    }
+
+    /**
+     * Convert degrees to radians, with error of less than 0.5 ULP
+     *
+     * @param x angle in degrees
+     * @return x converted into radians
+     */
+    public static double toRadians(double x) {
+        if (Double.isInfinite(x) || x == 0.0) { // Matches +/- 0.0; return correct sign
+            return x;
+        }
+
+        // These are PI/180 split into high and low order bits
+        final double facta = 0.01745329052209854;
+        final double factb = 1.997844754509471E-9;
+
+        double xa = doubleHighPart(x);
+        double xb = x - xa;
+
+        double result = xb * factb + xb * facta + xa * factb + xa * facta;
+        if (result == 0) {
+            result *= x; // ensure correct sign if calculation underflows
+        }
+        return result;
+    }
+
+    /**
+     * Convert radians to degrees, with error of less than 0.5 ULP
+     *
+     * @param x angle in radians
+     * @return x converted into degrees
+     */
+    public static double toDegrees(double x) {
+        if (Double.isInfinite(x) || x == 0.0) { // Matches +/- 0.0; return correct sign
+            return x;
+        }
+
+        // These are 180/PI split into high and low order bits
+        final double facta = 57.2957763671875;
+        final double factb = 3.145894820876798E-6;
+
+        double xa = doubleHighPart(x);
+        double xb = x - xa;
+
+        return xb * factb + xb * facta + xa * factb + xa * facta;
+    }
+
+    /**
+     * Absolute value.
+     *
+     * @param x number from which absolute value is requested
+     * @return abs(x)
+     */
+    public static int abs(final int x) {
+        final int i = x >>> 31;
+        return (x ^ (~i + 1)) + i;
+    }
+
+    /**
+     * Absolute value.
+     *
+     * @param x number from which absolute value is requested
+     * @return abs(x)
+     */
+    public static long abs(final long x) {
+        final long l = x >>> 63;
+        // l is one if x negative zero else
+        // ~l+1 is zero if x is positive, -1 if x is negative
+        // x^(~l+1) is x is x is positive, ~x if x is negative
+        // add around
+        return (x ^ (~l + 1)) + l;
+    }
+
+    /**
+     * Absolute value.
+     *
+     * @param x number from which absolute value is requested
+     * @return abs(x)
+     */
+    public static float abs(final float x) {
+        return Float.intBitsToFloat(MASK_NON_SIGN_INT & Float.floatToRawIntBits(x));
+    }
+
+    /**
+     * Absolute value.
+     *
+     * @param x number from which absolute value is requested
+     * @return abs(x)
+     */
+    public static double abs(double x) {
+        return Double.longBitsToDouble(MASK_NON_SIGN_LONG & Double.doubleToRawLongBits(x));
+    }
+
+    /**
+     * Compute least significant bit (Unit in Last Position) for a number.
+     *
+     * @param x number from which ulp is requested
+     * @return ulp(x)
+     */
+    public static double ulp(double x) {
+        if (Double.isInfinite(x)) {
+            return Double.POSITIVE_INFINITY;
+        }
+        return abs(x - Double.longBitsToDouble(Double.doubleToRawLongBits(x) ^ 1));
+    }
+
+    /**
+     * Compute least significant bit (Unit in Last Position) for a number.
+     *
+     * @param x number from which ulp is requested
+     * @return ulp(x)
+     */
+    public static float ulp(float x) {
+        if (Float.isInfinite(x)) {
+            return Float.POSITIVE_INFINITY;
+        }
+        return abs(x - Float.intBitsToFloat(Float.floatToIntBits(x) ^ 1));
+    }
+
+    /**
+     * Multiply a double number by a power of 2.
+     *
+     * @param d number to multiply
+     * @param n power of 2
+     * @return d &times; 2<sup>n</sup>
+     */
+    public static double scalb(final double d, final int n) {
+
+        // first simple and fast handling when 2^n can be represented using normal numbers
+        if ((n > -1023) && (n < 1024)) {
+            return d * Double.longBitsToDouble(((long) (n + 1023)) << 52);
+        }
+
+        // handle special cases
+        if (Double.isNaN(d) || Double.isInfinite(d) || (d == 0)) {
+            return d;
+        }
+        if (n < -2098) {
+            return (d > 0) ? 0.0 : -0.0;
+        }
+        if (n > 2097) {
+            return (d > 0) ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY;
+        }
+
+        // decompose d
+        final long bits = Double.doubleToRawLongBits(d);
+        final long sign = bits & 0x8000000000000000L;
+        int exponent = ((int) (bits >>> 52)) & 0x7ff;
+        long mantissa = bits & 0x000fffffffffffffL;
+
+        // compute scaled exponent
+        int scaledExponent = exponent + n;
+
+        if (n < 0) {
+            // we are really in the case n <= -1023
+            if (scaledExponent > 0) {
+                // both the input and the result are normal numbers, we only adjust the exponent
+                return Double.longBitsToDouble(sign | (((long) scaledExponent) << 52) | mantissa);
+            } else if (scaledExponent > -53) {
+                // the input is a normal number and the result is a subnormal number
+
+                // recover the hidden mantissa bit
+                mantissa |= 1L << 52;
+
+                // scales down complete mantissa, hence losing least significant bits
+                final long mostSignificantLostBit = mantissa & (1L << (-scaledExponent));
+                mantissa >>>= 1 - scaledExponent;
+                if (mostSignificantLostBit != 0) {
+                    // we need to add 1 bit to round up the result
+                    mantissa++;
+                }
+                return Double.longBitsToDouble(sign | mantissa);
+
+            } else {
+                // no need to compute the mantissa, the number scales down to 0
+                return (sign == 0L) ? 0.0 : -0.0;
+            }
+        } else {
+            // we are really in the case n >= 1024
+            if (exponent == 0) {
+
+                // the input number is subnormal, normalize it
+                while ((mantissa >>> 52) != 1) {
+                    mantissa <<= 1;
+                    --scaledExponent;
+                }
+                ++scaledExponent;
+                mantissa &= 0x000fffffffffffffL;
+
+                if (scaledExponent < 2047) {
+                    return Double.longBitsToDouble(
+                            sign | (((long) scaledExponent) << 52) | mantissa);
+                } else {
+                    return (sign == 0L) ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY;
+                }
+
+            } else if (scaledExponent < 2047) {
+                return Double.longBitsToDouble(sign | (((long) scaledExponent) << 52) | mantissa);
+            } else {
+                return (sign == 0L) ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY;
+            }
+        }
+    }
+
+    /**
+     * Multiply a float number by a power of 2.
+     *
+     * @param f number to multiply
+     * @param n power of 2
+     * @return f &times; 2<sup>n</sup>
+     */
+    public static float scalb(final float f, final int n) {
+
+        // first simple and fast handling when 2^n can be represented using normal numbers
+        if ((n > -127) && (n < 128)) {
+            return f * Float.intBitsToFloat((n + 127) << 23);
+        }
+
+        // handle special cases
+        if (Float.isNaN(f) || Float.isInfinite(f) || (f == 0f)) {
+            return f;
+        }
+        if (n < -277) {
+            return (f > 0) ? 0.0f : -0.0f;
+        }
+        if (n > 276) {
+            return (f > 0) ? Float.POSITIVE_INFINITY : Float.NEGATIVE_INFINITY;
+        }
+
+        // decompose f
+        final int bits = Float.floatToIntBits(f);
+        final int sign = bits & 0x80000000;
+        int exponent = (bits >>> 23) & 0xff;
+        int mantissa = bits & 0x007fffff;
+
+        // compute scaled exponent
+        int scaledExponent = exponent + n;
+
+        if (n < 0) {
+            // we are really in the case n <= -127
+            if (scaledExponent > 0) {
+                // both the input and the result are normal numbers, we only adjust the exponent
+                return Float.intBitsToFloat(sign | (scaledExponent << 23) | mantissa);
+            } else if (scaledExponent > -24) {
+                // the input is a normal number and the result is a subnormal number
+
+                // recover the hidden mantissa bit
+                mantissa |= 1 << 23;
+
+                // scales down complete mantissa, hence losing least significant bits
+                final int mostSignificantLostBit = mantissa & (1 << (-scaledExponent));
+                mantissa >>>= 1 - scaledExponent;
+                if (mostSignificantLostBit != 0) {
+                    // we need to add 1 bit to round up the result
+                    mantissa++;
+                }
+                return Float.intBitsToFloat(sign | mantissa);
+
+            } else {
+                // no need to compute the mantissa, the number scales down to 0
+                return (sign == 0) ? 0.0f : -0.0f;
+            }
+        } else {
+            // we are really in the case n >= 128
+            if (exponent == 0) {
+
+                // the input number is subnormal, normalize it
+                while ((mantissa >>> 23) != 1) {
+                    mantissa <<= 1;
+                    --scaledExponent;
+                }
+                ++scaledExponent;
+                mantissa &= 0x007fffff;
+
+                if (scaledExponent < 255) {
+                    return Float.intBitsToFloat(sign | (scaledExponent << 23) | mantissa);
+                } else {
+                    return (sign == 0) ? Float.POSITIVE_INFINITY : Float.NEGATIVE_INFINITY;
+                }
+
+            } else if (scaledExponent < 255) {
+                return Float.intBitsToFloat(sign | (scaledExponent << 23) | mantissa);
+            } else {
+                return (sign == 0) ? Float.POSITIVE_INFINITY : Float.NEGATIVE_INFINITY;
+            }
+        }
+    }
+
+    /**
+     * Get the next machine representable number after a number, moving in the direction of another
+     * number.
+     *
+     * <p>The ordering is as follows (increasing):
+     *
+     * <ul>
+     *   <li>-INFINITY
+     *   <li>-MAX_VALUE
+     *   <li>-MIN_VALUE
+     *   <li>-0.0
+     *   <li>+0.0
+     *   <li>+MIN_VALUE
+     *   <li>+MAX_VALUE
+     *   <li>+INFINITY
+     *   <li>
+     *       <p>If arguments compare equal, then the second argument is returned.
+     *       <p>If {@code direction} is greater than {@code d}, the smallest machine representable
+     *       number strictly greater than {@code d} is returned; if less, then the largest
+     *       representable number strictly less than {@code d} is returned.
+     *       <p>If {@code d} is infinite and direction does not bring it back to finite numbers, it
+     *       is returned unchanged.
+     *
+     * @param d base number
+     * @param direction (the only important thing is whether {@code direction} is greater or smaller
+     *     than {@code d})
+     * @return the next machine representable number in the specified direction
+     */
+    public static double nextAfter(double d, double direction) {
+
+        // handling of some important special cases
+        if (Double.isNaN(d) || Double.isNaN(direction)) {
+            return Double.NaN;
+        } else if (d == direction) {
+            return direction;
+        } else if (Double.isInfinite(d)) {
+            return (d < 0) ? -Double.MAX_VALUE : Double.MAX_VALUE;
+        } else if (d == 0) {
+            return (direction < 0) ? -Double.MIN_VALUE : Double.MIN_VALUE;
+        }
+        // special cases MAX_VALUE to infinity and  MIN_VALUE to 0
+        // are handled just as normal numbers
+        // can use raw bits since already dealt with infinity and NaN
+        final long bits = Double.doubleToRawLongBits(d);
+        final long sign = bits & 0x8000000000000000L;
+        if ((direction < d) ^ (sign == 0L)) {
+            return Double.longBitsToDouble(sign | ((bits & 0x7fffffffffffffffL) + 1));
+        } else {
+            return Double.longBitsToDouble(sign | ((bits & 0x7fffffffffffffffL) - 1));
+        }
+    }
+
+    /**
+     * Get the next machine representable number after a number, moving in the direction of another
+     * number.
+     *
+     * <p>The ordering is as follows (increasing):
+     *
+     * <ul>
+     *   <li>-INFINITY
+     *   <li>-MAX_VALUE
+     *   <li>-MIN_VALUE
+     *   <li>-0.0
+     *   <li>+0.0
+     *   <li>+MIN_VALUE
+     *   <li>+MAX_VALUE
+     *   <li>+INFINITY
+     *   <li>
+     *       <p>If arguments compare equal, then the second argument is returned.
+     *       <p>If {@code direction} is greater than {@code f}, the smallest machine representable
+     *       number strictly greater than {@code f} is returned; if less, then the largest
+     *       representable number strictly less than {@code f} is returned.
+     *       <p>If {@code f} is infinite and direction does not bring it back to finite numbers, it
+     *       is returned unchanged.
+     *
+     * @param f base number
+     * @param direction (the only important thing is whether {@code direction} is greater or smaller
+     *     than {@code f})
+     * @return the next machine representable number in the specified direction
+     */
+    public static float nextAfter(final float f, final double direction) {
+
+        // handling of some important special cases
+        if (Double.isNaN(f) || Double.isNaN(direction)) {
+            return Float.NaN;
+        } else if (f == direction) {
+            return (float) direction;
+        } else if (Float.isInfinite(f)) {
+            return (f < 0f) ? -Float.MAX_VALUE : Float.MAX_VALUE;
+        } else if (f == 0f) {
+            return (direction < 0) ? -Float.MIN_VALUE : Float.MIN_VALUE;
+        }
+        // special cases MAX_VALUE to infinity and  MIN_VALUE to 0
+        // are handled just as normal numbers
+
+        final int bits = Float.floatToIntBits(f);
+        final int sign = bits & 0x80000000;
+        if ((direction < f) ^ (sign == 0)) {
+            return Float.intBitsToFloat(sign | ((bits & 0x7fffffff) + 1));
+        } else {
+            return Float.intBitsToFloat(sign | ((bits & 0x7fffffff) - 1));
+        }
+    }
+
+    /**
+     * Get the largest whole number smaller than x.
+     *
+     * @param x number from which floor is requested
+     * @return a double number f such that f is an integer f <= x < f + 1.0
+     */
+    public static double floor(double x) {
+        long y;
+
+        if (x != x) { // NaN
+            return x;
+        }
+
+        if (x >= TWO_POWER_52 || x <= -TWO_POWER_52) {
+            return x;
+        }
+
+        y = (long) x;
+        if (x < 0 && y != x) {
+            y--;
+        }
+
+        if (y == 0) {
+            return x * y;
+        }
+
+        return y;
+    }
+
+    /**
+     * Get the smallest whole number larger than x.
+     *
+     * @param x number from which ceil is requested
+     * @return a double number c such that c is an integer c - 1.0 < x <= c
+     */
+    public static double ceil(double x) {
+        double y;
+
+        if (x != x) { // NaN
+            return x;
+        }
+
+        y = floor(x);
+        if (y == x) {
+            return y;
+        }
+
+        y += 1.0;
+
+        if (y == 0) {
+            return x * y;
+        }
+
+        return y;
+    }
+
+    /**
+     * Get the whole number that is the nearest to x, or the even one if x is exactly half way
+     * between two integers.
+     *
+     * @param x number from which nearest whole number is requested
+     * @return a double number r such that r is an integer r - 0.5 <= x <= r + 0.5
+     */
+    public static double rint(double x) {
+        double y = floor(x);
+        double d = x - y;
+
+        if (d > 0.5) {
+            if (y == -1.0) {
+                return -0.0; // Preserve sign of operand
+            }
+            return y + 1.0;
+        }
+        if (d < 0.5) {
+            return y;
+        }
+
+        /* half way, round to even */
+        long z = (long) y;
+        return (z & 1) == 0 ? y : y + 1.0;
+    }
+
+    /**
+     * Get the closest long to x.
+     *
+     * @param x number from which closest long is requested
+     * @return closest long to x
+     */
+    public static long round(double x) {
+        return (long) floor(x + 0.5);
+    }
+
+    /**
+     * Get the closest int to x.
+     *
+     * @param x number from which closest int is requested
+     * @return closest int to x
+     */
+    public static int round(final float x) {
+        return (int) floor(x + 0.5f);
+    }
+
+    /**
+     * Compute the minimum of two values
+     *
+     * @param a first value
+     * @param b second value
+     * @return a if a is lesser or equal to b, b otherwise
+     */
+    public static int min(final int a, final int b) {
+        return (a <= b) ? a : b;
+    }
+
+    /**
+     * Compute the minimum of two values
+     *
+     * @param a first value
+     * @param b second value
+     * @return a if a is lesser or equal to b, b otherwise
+     */
+    public static long min(final long a, final long b) {
+        return (a <= b) ? a : b;
+    }
+
+    /**
+     * Compute the minimum of two values
+     *
+     * @param a first value
+     * @param b second value
+     * @return a if a is lesser or equal to b, b otherwise
+     */
+    public static float min(final float a, final float b) {
+        if (a > b) {
+            return b;
+        }
+        if (a < b) {
+            return a;
+        }
+        /* if either arg is NaN, return NaN */
+        if (a != b) {
+            return Float.NaN;
+        }
+        /* min(+0.0,-0.0) == -0.0 */
+        /* 0x80000000 == Float.floatToRawIntBits(-0.0d) */
+        int bits = Float.floatToRawIntBits(a);
+        if (bits == 0x80000000) {
+            return a;
+        }
+        return b;
+    }
+
+    /**
+     * Compute the minimum of two values
+     *
+     * @param a first value
+     * @param b second value
+     * @return a if a is lesser or equal to b, b otherwise
+     */
+    public static double min(final double a, final double b) {
+        if (a > b) {
+            return b;
+        }
+        if (a < b) {
+            return a;
+        }
+        /* if either arg is NaN, return NaN */
+        if (a != b) {
+            return Double.NaN;
+        }
+        /* min(+0.0,-0.0) == -0.0 */
+        /* 0x8000000000000000L == Double.doubleToRawLongBits(-0.0d) */
+        long bits = Double.doubleToRawLongBits(a);
+        if (bits == 0x8000000000000000L) {
+            return a;
+        }
+        return b;
+    }
+
+    /**
+     * Compute the maximum of two values
+     *
+     * @param a first value
+     * @param b second value
+     * @return b if a is lesser or equal to b, a otherwise
+     */
+    public static int max(final int a, final int b) {
+        return (a <= b) ? b : a;
+    }
+
+    /**
+     * Compute the maximum of two values
+     *
+     * @param a first value
+     * @param b second value
+     * @return b if a is lesser or equal to b, a otherwise
+     */
+    public static long max(final long a, final long b) {
+        return (a <= b) ? b : a;
+    }
+
+    /**
+     * Compute the maximum of two values
+     *
+     * @param a first value
+     * @param b second value
+     * @return b if a is lesser or equal to b, a otherwise
+     */
+    public static float max(final float a, final float b) {
+        if (a > b) {
+            return a;
+        }
+        if (a < b) {
+            return b;
+        }
+        /* if either arg is NaN, return NaN */
+        if (a != b) {
+            return Float.NaN;
+        }
+        /* min(+0.0,-0.0) == -0.0 */
+        /* 0x80000000 == Float.floatToRawIntBits(-0.0d) */
+        int bits = Float.floatToRawIntBits(a);
+        if (bits == 0x80000000) {
+            return b;
+        }
+        return a;
+    }
+
+    /**
+     * Compute the maximum of two values
+     *
+     * @param a first value
+     * @param b second value
+     * @return b if a is lesser or equal to b, a otherwise
+     */
+    public static double max(final double a, final double b) {
+        if (a > b) {
+            return a;
+        }
+        if (a < b) {
+            return b;
+        }
+        /* if either arg is NaN, return NaN */
+        if (a != b) {
+            return Double.NaN;
+        }
+        /* min(+0.0,-0.0) == -0.0 */
+        /* 0x8000000000000000L == Double.doubleToRawLongBits(-0.0d) */
+        long bits = Double.doubleToRawLongBits(a);
+        if (bits == 0x8000000000000000L) {
+            return b;
+        }
+        return a;
+    }
+
+    /**
+     * Returns the hypotenuse of a triangle with sides {@code x} and {@code y} -
+     * sqrt(<i>x</i><sup>2</sup>&nbsp;+<i>y</i><sup>2</sup>)<br>
+     * avoiding intermediate overflow or underflow.
+     *
+     * <ul>
+     *   <li>If either argument is infinite, then the result is positive infinity.
+     *   <li>else, if either argument is NaN then the result is NaN.
+     * </ul>
+     *
+     * @param x a value
+     * @param y a value
+     * @return sqrt(<i>x</i><sup>2</sup>&nbsp;+<i>y</i><sup>2</sup>)
+     */
+    public static double hypot(final double x, final double y) {
+        if (Double.isInfinite(x) || Double.isInfinite(y)) {
+            return Double.POSITIVE_INFINITY;
+        } else if (Double.isNaN(x) || Double.isNaN(y)) {
+            return Double.NaN;
+        } else {
+
+            final int expX = getExponent(x);
+            final int expY = getExponent(y);
+            if (expX > expY + 27) {
+                // y is neglectible with respect to x
+                return abs(x);
+            } else if (expY > expX + 27) {
+                // x is neglectible with respect to y
+                return abs(y);
+            } else {
+
+                // find an intermediate scale to avoid both overflow and underflow
+                final int middleExp = (expX + expY) / 2;
+
+                // scale parameters without losing precision
+                final double scaledX = scalb(x, -middleExp);
+                final double scaledY = scalb(y, -middleExp);
+
+                // compute scaled hypotenuse
+                final double scaledH = sqrt(scaledX * scaledX + scaledY * scaledY);
+
+                // remove scaling
+                return scalb(scaledH, middleExp);
+            }
+        }
+    }
+
+    /**
+     * Computes the remainder as prescribed by the IEEE 754 standard. The remainder value is
+     * mathematically equal to {@code x - y*n} where {@code n} is the mathematical integer closest
+     * to the exact mathematical value of the quotient {@code x/y}. If two mathematical integers are
+     * equally close to {@code x/y} then {@code n} is the integer that is even.
+     *
+     * <p>
+     *
+     * <ul>
+     *   <li>If either operand is NaN, the result is NaN.
+     *   <li>If the result is not NaN, the sign of the result equals the sign of the dividend.
+     *   <li>If the dividend is an infinity, or the divisor is a zero, or both, the result is NaN.
+     *   <li>If the dividend is finite and the divisor is an infinity, the result equals the
+     *       dividend.
+     *   <li>If the dividend is a zero and the divisor is finite, the result equals the dividend.
+     * </ul>
+     *
+     * <p><b>Note:</b> this implementation currently delegates to {@link StrictMath#IEEEremainder}
+     *
+     * @param dividend the number to be divided
+     * @param divisor the number by which to divide
+     * @return the remainder, rounded
+     */
+    public static double IEEEremainder(double dividend, double divisor) {
+        return StrictMath.IEEEremainder(dividend, divisor); // TODO provide our own implementation
+    }
+
+    /**
+     * Convert a long to interger, detecting overflows
+     *
+     * @param n number to convert to int
+     * @return integer with same valie as n if no overflows occur
+     * @exception MathArithmeticException if n cannot fit into an int
+     * @since 3.4
+     */
+    public static int toIntExact(final long n) throws MathArithmeticException {
+        if (n < Integer.MIN_VALUE || n > Integer.MAX_VALUE) {
+            throw new MathArithmeticException(LocalizedFormats.OVERFLOW);
+        }
+        return (int) n;
+    }
+
+    /**
+     * Increment a number, detecting overflows.
+     *
+     * @param n number to increment
+     * @return n+1 if no overflows occur
+     * @exception MathArithmeticException if an overflow occurs
+     * @since 3.4
+     */
+    public static int incrementExact(final int n) throws MathArithmeticException {
+
+        if (n == Integer.MAX_VALUE) {
+            throw new MathArithmeticException(LocalizedFormats.OVERFLOW_IN_ADDITION, n, 1);
+        }
+
+        return n + 1;
+    }
+
+    /**
+     * Increment a number, detecting overflows.
+     *
+     * @param n number to increment
+     * @return n+1 if no overflows occur
+     * @exception MathArithmeticException if an overflow occurs
+     * @since 3.4
+     */
+    public static long incrementExact(final long n) throws MathArithmeticException {
+
+        if (n == Long.MAX_VALUE) {
+            throw new MathArithmeticException(LocalizedFormats.OVERFLOW_IN_ADDITION, n, 1);
+        }
+
+        return n + 1;
+    }
+
+    /**
+     * Decrement a number, detecting overflows.
+     *
+     * @param n number to decrement
+     * @return n-1 if no overflows occur
+     * @exception MathArithmeticException if an overflow occurs
+     * @since 3.4
+     */
+    public static int decrementExact(final int n) throws MathArithmeticException {
+
+        if (n == Integer.MIN_VALUE) {
+            throw new MathArithmeticException(LocalizedFormats.OVERFLOW_IN_SUBTRACTION, n, 1);
+        }
+
+        return n - 1;
+    }
+
+    /**
+     * Decrement a number, detecting overflows.
+     *
+     * @param n number to decrement
+     * @return n-1 if no overflows occur
+     * @exception MathArithmeticException if an overflow occurs
+     * @since 3.4
+     */
+    public static long decrementExact(final long n) throws MathArithmeticException {
+
+        if (n == Long.MIN_VALUE) {
+            throw new MathArithmeticException(LocalizedFormats.OVERFLOW_IN_SUBTRACTION, n, 1);
+        }
+
+        return n - 1;
+    }
+
+    /**
+     * Add two numbers, detecting overflows.
+     *
+     * @param a first number to add
+     * @param b second number to add
+     * @return a+b if no overflows occur
+     * @exception MathArithmeticException if an overflow occurs
+     * @since 3.4
+     */
+    public static int addExact(final int a, final int b) throws MathArithmeticException {
+
+        // compute sum
+        final int sum = a + b;
+
+        // check for overflow
+        if ((a ^ b) >= 0 && (sum ^ b) < 0) {
+            throw new MathArithmeticException(LocalizedFormats.OVERFLOW_IN_ADDITION, a, b);
+        }
+
+        return sum;
+    }
+
+    /**
+     * Add two numbers, detecting overflows.
+     *
+     * @param a first number to add
+     * @param b second number to add
+     * @return a+b if no overflows occur
+     * @exception MathArithmeticException if an overflow occurs
+     * @since 3.4
+     */
+    public static long addExact(final long a, final long b) throws MathArithmeticException {
+
+        // compute sum
+        final long sum = a + b;
+
+        // check for overflow
+        if ((a ^ b) >= 0 && (sum ^ b) < 0) {
+            throw new MathArithmeticException(LocalizedFormats.OVERFLOW_IN_ADDITION, a, b);
+        }
+
+        return sum;
+    }
+
+    /**
+     * Subtract two numbers, detecting overflows.
+     *
+     * @param a first number
+     * @param b second number to subtract from a
+     * @return a-b if no overflows occur
+     * @exception MathArithmeticException if an overflow occurs
+     * @since 3.4
+     */
+    public static int subtractExact(final int a, final int b) {
+
+        // compute subtraction
+        final int sub = a - b;
+
+        // check for overflow
+        if ((a ^ b) < 0 && (sub ^ b) >= 0) {
+            throw new MathArithmeticException(LocalizedFormats.OVERFLOW_IN_SUBTRACTION, a, b);
+        }
+
+        return sub;
+    }
+
+    /**
+     * Subtract two numbers, detecting overflows.
+     *
+     * @param a first number
+     * @param b second number to subtract from a
+     * @return a-b if no overflows occur
+     * @exception MathArithmeticException if an overflow occurs
+     * @since 3.4
+     */
+    public static long subtractExact(final long a, final long b) {
+
+        // compute subtraction
+        final long sub = a - b;
+
+        // check for overflow
+        if ((a ^ b) < 0 && (sub ^ b) >= 0) {
+            throw new MathArithmeticException(LocalizedFormats.OVERFLOW_IN_SUBTRACTION, a, b);
+        }
+
+        return sub;
+    }
+
+    /**
+     * Multiply two numbers, detecting overflows.
+     *
+     * @param a first number to multiply
+     * @param b second number to multiply
+     * @return a*b if no overflows occur
+     * @exception MathArithmeticException if an overflow occurs
+     * @since 3.4
+     */
+    public static int multiplyExact(final int a, final int b) {
+        if (((b > 0) && (a > Integer.MAX_VALUE / b || a < Integer.MIN_VALUE / b))
+                || ((b < -1) && (a > Integer.MIN_VALUE / b || a < Integer.MAX_VALUE / b))
+                || ((b == -1) && (a == Integer.MIN_VALUE))) {
+            throw new MathArithmeticException(LocalizedFormats.OVERFLOW_IN_MULTIPLICATION, a, b);
+        }
+        return a * b;
+    }
+
+    /**
+     * Multiply two numbers, detecting overflows.
+     *
+     * @param a first number to multiply
+     * @param b second number to multiply
+     * @return a*b if no overflows occur
+     * @exception MathArithmeticException if an overflow occurs
+     * @since 3.4
+     */
+    public static long multiplyExact(final long a, final long b) {
+        if (((b > 0l) && (a > Long.MAX_VALUE / b || a < Long.MIN_VALUE / b))
+                || ((b < -1l) && (a > Long.MIN_VALUE / b || a < Long.MAX_VALUE / b))
+                || ((b == -1l) && (a == Long.MIN_VALUE))) {
+            throw new MathArithmeticException(LocalizedFormats.OVERFLOW_IN_MULTIPLICATION, a, b);
+        }
+        return a * b;
+    }
+
+    /**
+     * Finds q such that a = q b + r with 0 <= r < b if b > 0 and b < r <= 0 if b < 0.
+     *
+     * <p>This methods returns the same value as integer division when a and b are same signs, but
+     * returns a different value when they are opposite (i.e. q is negative).
+     *
+     * @param a dividend
+     * @param b divisor
+     * @return q such that a = q b + r with 0 <= r < b if b > 0 and b < r <= 0 if b < 0
+     * @exception MathArithmeticException if b == 0
+     * @see #floorMod(int, int)
+     * @since 3.4
+     */
+    public static int floorDiv(final int a, final int b) throws MathArithmeticException {
+
+        if (b == 0) {
+            throw new MathArithmeticException(LocalizedFormats.ZERO_DENOMINATOR);
+        }
+
+        final int m = a % b;
+        if ((a ^ b) >= 0 || m == 0) {
+            // a an b have same sign, or division is exact
+            return a / b;
+        } else {
+            // a and b have opposite signs and division is not exact
+            return (a / b) - 1;
+        }
+    }
+
+    /**
+     * Finds q such that a = q b + r with 0 <= r < b if b > 0 and b < r <= 0 if b < 0.
+     *
+     * <p>This methods returns the same value as integer division when a and b are same signs, but
+     * returns a different value when they are opposite (i.e. q is negative).
+     *
+     * @param a dividend
+     * @param b divisor
+     * @return q such that a = q b + r with 0 <= r < b if b > 0 and b < r <= 0 if b < 0
+     * @exception MathArithmeticException if b == 0
+     * @see #floorMod(long, long)
+     * @since 3.4
+     */
+    public static long floorDiv(final long a, final long b) throws MathArithmeticException {
+
+        if (b == 0l) {
+            throw new MathArithmeticException(LocalizedFormats.ZERO_DENOMINATOR);
+        }
+
+        final long m = a % b;
+        if ((a ^ b) >= 0l || m == 0l) {
+            // a an b have same sign, or division is exact
+            return a / b;
+        } else {
+            // a and b have opposite signs and division is not exact
+            return (a / b) - 1l;
+        }
+    }
+
+    /**
+     * Finds r such that a = q b + r with 0 <= r < b if b > 0 and b < r <= 0 if b < 0.
+     *
+     * <p>This methods returns the same value as integer modulo when a and b are same signs, but
+     * returns a different value when they are opposite (i.e. q is negative).
+     *
+     * @param a dividend
+     * @param b divisor
+     * @return r such that a = q b + r with 0 <= r < b if b > 0 and b < r <= 0 if b < 0
+     * @exception MathArithmeticException if b == 0
+     * @see #floorDiv(int, int)
+     * @since 3.4
+     */
+    public static int floorMod(final int a, final int b) throws MathArithmeticException {
+
+        if (b == 0) {
+            throw new MathArithmeticException(LocalizedFormats.ZERO_DENOMINATOR);
+        }
+
+        final int m = a % b;
+        if ((a ^ b) >= 0 || m == 0) {
+            // a an b have same sign, or division is exact
+            return m;
+        } else {
+            // a and b have opposite signs and division is not exact
+            return b + m;
+        }
+    }
+
+    /**
+     * Finds r such that a = q b + r with 0 <= r < b if b > 0 and b < r <= 0 if b < 0.
+     *
+     * <p>This methods returns the same value as integer modulo when a and b are same signs, but
+     * returns a different value when they are opposite (i.e. q is negative).
+     *
+     * @param a dividend
+     * @param b divisor
+     * @return r such that a = q b + r with 0 <= r < b if b > 0 and b < r <= 0 if b < 0
+     * @exception MathArithmeticException if b == 0
+     * @see #floorDiv(long, long)
+     * @since 3.4
+     */
+    public static long floorMod(final long a, final long b) {
+
+        if (b == 0l) {
+            throw new MathArithmeticException(LocalizedFormats.ZERO_DENOMINATOR);
+        }
+
+        final long m = a % b;
+        if ((a ^ b) >= 0l || m == 0l) {
+            // a an b have same sign, or division is exact
+            return m;
+        } else {
+            // a and b have opposite signs and division is not exact
+            return b + m;
+        }
+    }
+
+    /**
+     * Returns the first argument with the sign of the second argument. A NaN {@code sign} argument
+     * is treated as positive.
+     *
+     * @param magnitude the value to return
+     * @param sign the sign for the returned value
+     * @return the magnitude with the same sign as the {@code sign} argument
+     */
+    public static double copySign(double magnitude, double sign) {
+        // The highest order bit is going to be zero if the
+        // highest order bit of m and s is the same and one otherwise.
+        // So (m^s) will be positive if both m and s have the same sign
+        // and negative otherwise.
+        final long m = Double.doubleToRawLongBits(magnitude); // don't care about NaN
+        final long s = Double.doubleToRawLongBits(sign);
+        if ((m ^ s) >= 0) {
+            return magnitude;
+        }
+        return -magnitude; // flip sign
+    }
+
+    /**
+     * Returns the first argument with the sign of the second argument. A NaN {@code sign} argument
+     * is treated as positive.
+     *
+     * @param magnitude the value to return
+     * @param sign the sign for the returned value
+     * @return the magnitude with the same sign as the {@code sign} argument
+     */
+    public static float copySign(float magnitude, float sign) {
+        // The highest order bit is going to be zero if the
+        // highest order bit of m and s is the same and one otherwise.
+        // So (m^s) will be positive if both m and s have the same sign
+        // and negative otherwise.
+        final int m = Float.floatToRawIntBits(magnitude);
+        final int s = Float.floatToRawIntBits(sign);
+        if ((m ^ s) >= 0) {
+            return magnitude;
+        }
+        return -magnitude; // flip sign
+    }
+
+    /**
+     * Return the exponent of a double number, removing the bias.
+     *
+     * <p>For double numbers of the form 2<sup>x</sup>, the unbiased exponent is exactly x.
+     *
+     * @param d number from which exponent is requested
+     * @return exponent for d in IEEE754 representation, without bias
+     */
+    public static int getExponent(final double d) {
+        // NaN and Infinite will return 1024 anywho so can use raw bits
+        return (int) ((Double.doubleToRawLongBits(d) >>> 52) & 0x7ff) - 1023;
+    }
+
+    /**
+     * Return the exponent of a float number, removing the bias.
+     *
+     * <p>For float numbers of the form 2<sup>x</sup>, the unbiased exponent is exactly x.
+     *
+     * @param f number from which exponent is requested
+     * @return exponent for d in IEEE754 representation, without bias
+     */
+    public static int getExponent(final float f) {
+        // NaN and Infinite will return the same exponent anywho so can use raw bits
+        return ((Float.floatToRawIntBits(f) >>> 23) & 0xff) - 127;
+    }
+
+    /**
+     * Print out contents of arrays, and check the length.
+     *
+     * <p>used to generate the preset arrays originally.
+     *
+     * @param a unused
+     */
+    public static void main(String[] a) {
+        PrintStream out = System.out;
+        FastMathCalc.printarray(
+                out, "EXP_INT_TABLE_A", EXP_INT_TABLE_LEN, ExpIntTable.EXP_INT_TABLE_A);
+        FastMathCalc.printarray(
+                out, "EXP_INT_TABLE_B", EXP_INT_TABLE_LEN, ExpIntTable.EXP_INT_TABLE_B);
+        FastMathCalc.printarray(
+                out, "EXP_FRAC_TABLE_A", EXP_FRAC_TABLE_LEN, ExpFracTable.EXP_FRAC_TABLE_A);
+        FastMathCalc.printarray(
+                out, "EXP_FRAC_TABLE_B", EXP_FRAC_TABLE_LEN, ExpFracTable.EXP_FRAC_TABLE_B);
+        FastMathCalc.printarray(out, "LN_MANT", LN_MANT_LEN, lnMant.LN_MANT);
+        FastMathCalc.printarray(out, "SINE_TABLE_A", SINE_TABLE_LEN, SINE_TABLE_A);
+        FastMathCalc.printarray(out, "SINE_TABLE_B", SINE_TABLE_LEN, SINE_TABLE_B);
+        FastMathCalc.printarray(out, "COSINE_TABLE_A", SINE_TABLE_LEN, COSINE_TABLE_A);
+        FastMathCalc.printarray(out, "COSINE_TABLE_B", SINE_TABLE_LEN, COSINE_TABLE_B);
+        FastMathCalc.printarray(out, "TANGENT_TABLE_A", SINE_TABLE_LEN, TANGENT_TABLE_A);
+        FastMathCalc.printarray(out, "TANGENT_TABLE_B", SINE_TABLE_LEN, TANGENT_TABLE_B);
+    }
+
+    /** Enclose large data table in nested static class so it's only loaded on first access. */
+    private static class ExpIntTable {
+        /**
+         * Exponential evaluated at integer values, exp(x) = expIntTableA[x +
+         * EXP_INT_TABLE_MAX_INDEX] + expIntTableB[x+EXP_INT_TABLE_MAX_INDEX].
+         */
+        private static final double[] EXP_INT_TABLE_A;
+
+        /**
+         * Exponential evaluated at integer values, exp(x) = expIntTableA[x +
+         * EXP_INT_TABLE_MAX_INDEX] + expIntTableB[x+EXP_INT_TABLE_MAX_INDEX]
+         */
+        private static final double[] EXP_INT_TABLE_B;
+
+        static {
+            if (RECOMPUTE_TABLES_AT_RUNTIME) {
+                EXP_INT_TABLE_A = new double[FastMath.EXP_INT_TABLE_LEN];
+                EXP_INT_TABLE_B = new double[FastMath.EXP_INT_TABLE_LEN];
+
+                final double tmp[] = new double[2];
+                final double recip[] = new double[2];
+
+                // Populate expIntTable
+                for (int i = 0; i < FastMath.EXP_INT_TABLE_MAX_INDEX; i++) {
+                    FastMathCalc.expint(i, tmp);
+                    EXP_INT_TABLE_A[i + FastMath.EXP_INT_TABLE_MAX_INDEX] = tmp[0];
+                    EXP_INT_TABLE_B[i + FastMath.EXP_INT_TABLE_MAX_INDEX] = tmp[1];
+
+                    if (i != 0) {
+                        // Negative integer powers
+                        FastMathCalc.splitReciprocal(tmp, recip);
+                        EXP_INT_TABLE_A[FastMath.EXP_INT_TABLE_MAX_INDEX - i] = recip[0];
+                        EXP_INT_TABLE_B[FastMath.EXP_INT_TABLE_MAX_INDEX - i] = recip[1];
+                    }
+                }
+            } else {
+                EXP_INT_TABLE_A = FastMathLiteralArrays.loadExpIntA();
+                EXP_INT_TABLE_B = FastMathLiteralArrays.loadExpIntB();
+            }
+        }
+    }
+
+    /** Enclose large data table in nested static class so it's only loaded on first access. */
+    private static class ExpFracTable {
+        /**
+         * Exponential over the range of 0 - 1 in increments of 2^-10 exp(x/1024) = expFracTableA[x]
+         * + expFracTableB[x]. 1024 = 2^10
+         */
+        private static final double[] EXP_FRAC_TABLE_A;
+
+        /**
+         * Exponential over the range of 0 - 1 in increments of 2^-10 exp(x/1024) = expFracTableA[x]
+         * + expFracTableB[x].
+         */
+        private static final double[] EXP_FRAC_TABLE_B;
+
+        static {
+            if (RECOMPUTE_TABLES_AT_RUNTIME) {
+                EXP_FRAC_TABLE_A = new double[FastMath.EXP_FRAC_TABLE_LEN];
+                EXP_FRAC_TABLE_B = new double[FastMath.EXP_FRAC_TABLE_LEN];
+
+                final double tmp[] = new double[2];
+
+                // Populate expFracTable
+                final double factor = 1d / (EXP_FRAC_TABLE_LEN - 1);
+                for (int i = 0; i < EXP_FRAC_TABLE_A.length; i++) {
+                    FastMathCalc.slowexp(i * factor, tmp);
+                    EXP_FRAC_TABLE_A[i] = tmp[0];
+                    EXP_FRAC_TABLE_B[i] = tmp[1];
+                }
+            } else {
+                EXP_FRAC_TABLE_A = FastMathLiteralArrays.loadExpFracA();
+                EXP_FRAC_TABLE_B = FastMathLiteralArrays.loadExpFracB();
+            }
+        }
+    }
+
+    /** Enclose large data table in nested static class so it's only loaded on first access. */
+    private static class lnMant {
+        /** Extended precision logarithm table over the range 1 - 2 in increments of 2^-10. */
+        private static final double[][] LN_MANT;
+
+        static {
+            if (RECOMPUTE_TABLES_AT_RUNTIME) {
+                LN_MANT = new double[FastMath.LN_MANT_LEN][];
+
+                // Populate lnMant table
+                for (int i = 0; i < LN_MANT.length; i++) {
+                    final double d =
+                            Double.longBitsToDouble((((long) i) << 42) | 0x3ff0000000000000L);
+                    LN_MANT[i] = FastMathCalc.slowLog(d);
+                }
+            } else {
+                LN_MANT = FastMathLiteralArrays.loadLnMant();
+            }
+        }
+    }
+
+    /** Enclose the Cody/Waite reduction (used in "sin", "cos" and "tan"). */
+    private static class CodyWaite {
+        /** k */
+        private final int finalK;
+
+        /** remA */
+        private final double finalRemA;
+
+        /** remB */
+        private final double finalRemB;
+
+        /**
+         * @param xa Argument.
+         */
+        CodyWaite(double xa) {
+            // Estimate k.
+            // k = (int)(xa / 1.5707963267948966);
+            int k = (int) (xa * 0.6366197723675814);
+
+            // Compute remainder.
+            double remA;
+            double remB;
+            while (true) {
+                double a = -k * 1.570796251296997;
+                remA = xa + a;
+                remB = -(remA - xa - a);
+
+                a = -k * 7.549789948768648E-8;
+                double b = remA;
+                remA = a + b;
+                remB += -(remA - b - a);
+
+                a = -k * 6.123233995736766E-17;
+                b = remA;
+                remA = a + b;
+                remB += -(remA - b - a);
+
+                if (remA > 0) {
+                    break;
+                }
+
+                // Remainder is negative, so decrement k and try again.
+                // This should only happen if the input is very close
+                // to an even multiple of pi/2.
+                --k;
+            }
+
+            this.finalK = k;
+            this.finalRemA = remA;
+            this.finalRemB = remB;
+        }
+
+        /**
+         * @return k
+         */
+        int getK() {
+            return finalK;
+        }
+
+        /**
+         * @return remA
+         */
+        double getRemA() {
+            return finalRemA;
+        }
+
+        /**
+         * @return remB
+         */
+        double getRemB() {
+            return finalRemB;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/util/FastMathCalc.java b/src/main/java/org/apache/commons/math3/util/FastMathCalc.java
new file mode 100644
index 0000000..cf0f27c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/FastMathCalc.java
@@ -0,0 +1,678 @@
+/*
+ * 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.math3.util;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+
+import java.io.PrintStream;
+
+/**
+ * Class used to compute the classical functions tables.
+ *
+ * @since 3.0
+ */
+class FastMathCalc {
+
+    /**
+     * 0x40000000 - used to split a double into two parts, both with the low order bits cleared.
+     * Equivalent to 2^30.
+     */
+    private static final long HEX_40000000 = 0x40000000L; // 1073741824L
+
+    /** Factorial table, for Taylor series expansions. 0!, 1!, 2!, ... 19! */
+    private static final double FACT[] =
+            new double[] {
+                +1.0d, // 0
+                +1.0d, // 1
+                +2.0d, // 2
+                +6.0d, // 3
+                +24.0d, // 4
+                +120.0d, // 5
+                +720.0d, // 6
+                +5040.0d, // 7
+                +40320.0d, // 8
+                +362880.0d, // 9
+                +3628800.0d, // 10
+                +39916800.0d, // 11
+                +479001600.0d, // 12
+                +6227020800.0d, // 13
+                +87178291200.0d, // 14
+                +1307674368000.0d, // 15
+                +20922789888000.0d, // 16
+                +355687428096000.0d, // 17
+                +6402373705728000.0d, // 18
+                +121645100408832000.0d, // 19
+            };
+
+    /** Coefficients for slowLog. */
+    private static final double LN_SPLIT_COEF[][] = {
+        {2.0, 0.0},
+        {0.6666666269302368, 3.9736429850260626E-8},
+        {0.3999999761581421, 2.3841857910019882E-8},
+        {0.2857142686843872, 1.7029898543501842E-8},
+        {0.2222222089767456, 1.3245471311735498E-8},
+        {0.1818181574344635, 2.4384203044354907E-8},
+        {0.1538461446762085, 9.140260083262505E-9},
+        {0.13333332538604736, 9.220590270857665E-9},
+        {0.11764700710773468, 1.2393345855018391E-8},
+        {0.10526403784751892, 8.251545029714408E-9},
+        {0.0952233225107193, 1.2675934823758863E-8},
+        {0.08713622391223907, 1.1430250008909141E-8},
+        {0.07842259109020233, 2.404307984052299E-9},
+        {0.08371849358081818, 1.176342548272881E-8},
+        {0.030589580535888672, 1.2958646899018938E-9},
+        {0.14982303977012634, 1.225743062930824E-8},
+    };
+
+    /** Table start declaration. */
+    private static final String TABLE_START_DECL = "    {";
+
+    /** Table end declaration. */
+    private static final String TABLE_END_DECL = "    };";
+
+    /** Private Constructor. */
+    private FastMathCalc() {}
+
+    /**
+     * Build the sine and cosine tables.
+     *
+     * @param SINE_TABLE_A table of the most significant part of the sines
+     * @param SINE_TABLE_B table of the least significant part of the sines
+     * @param COSINE_TABLE_A table of the most significant part of the cosines
+     * @param COSINE_TABLE_B table of the most significant part of the cosines
+     * @param SINE_TABLE_LEN length of the tables
+     * @param TANGENT_TABLE_A table of the most significant part of the tangents
+     * @param TANGENT_TABLE_B table of the most significant part of the tangents
+     */
+    @SuppressWarnings("unused")
+    private static void buildSinCosTables(
+            double[] SINE_TABLE_A,
+            double[] SINE_TABLE_B,
+            double[] COSINE_TABLE_A,
+            double[] COSINE_TABLE_B,
+            int SINE_TABLE_LEN,
+            double[] TANGENT_TABLE_A,
+            double[] TANGENT_TABLE_B) {
+        final double result[] = new double[2];
+
+        /* Use taylor series for 0 <= x <= 6/8 */
+        for (int i = 0; i < 7; i++) {
+            double x = i / 8.0;
+
+            slowSin(x, result);
+            SINE_TABLE_A[i] = result[0];
+            SINE_TABLE_B[i] = result[1];
+
+            slowCos(x, result);
+            COSINE_TABLE_A[i] = result[0];
+            COSINE_TABLE_B[i] = result[1];
+        }
+
+        /* Use angle addition formula to complete table to 13/8, just beyond pi/2 */
+        for (int i = 7; i < SINE_TABLE_LEN; i++) {
+            double xs[] = new double[2];
+            double ys[] = new double[2];
+            double as[] = new double[2];
+            double bs[] = new double[2];
+            double temps[] = new double[2];
+
+            if ((i & 1) == 0) {
+                // Even, use double angle
+                xs[0] = SINE_TABLE_A[i / 2];
+                xs[1] = SINE_TABLE_B[i / 2];
+                ys[0] = COSINE_TABLE_A[i / 2];
+                ys[1] = COSINE_TABLE_B[i / 2];
+
+                /* compute sine */
+                splitMult(xs, ys, result);
+                SINE_TABLE_A[i] = result[0] * 2.0;
+                SINE_TABLE_B[i] = result[1] * 2.0;
+
+                /* Compute cosine */
+                splitMult(ys, ys, as);
+                splitMult(xs, xs, temps);
+                temps[0] = -temps[0];
+                temps[1] = -temps[1];
+                splitAdd(as, temps, result);
+                COSINE_TABLE_A[i] = result[0];
+                COSINE_TABLE_B[i] = result[1];
+            } else {
+                xs[0] = SINE_TABLE_A[i / 2];
+                xs[1] = SINE_TABLE_B[i / 2];
+                ys[0] = COSINE_TABLE_A[i / 2];
+                ys[1] = COSINE_TABLE_B[i / 2];
+                as[0] = SINE_TABLE_A[i / 2 + 1];
+                as[1] = SINE_TABLE_B[i / 2 + 1];
+                bs[0] = COSINE_TABLE_A[i / 2 + 1];
+                bs[1] = COSINE_TABLE_B[i / 2 + 1];
+
+                /* compute sine */
+                splitMult(xs, bs, temps);
+                splitMult(ys, as, result);
+                splitAdd(result, temps, result);
+                SINE_TABLE_A[i] = result[0];
+                SINE_TABLE_B[i] = result[1];
+
+                /* Compute cosine */
+                splitMult(ys, bs, result);
+                splitMult(xs, as, temps);
+                temps[0] = -temps[0];
+                temps[1] = -temps[1];
+                splitAdd(result, temps, result);
+                COSINE_TABLE_A[i] = result[0];
+                COSINE_TABLE_B[i] = result[1];
+            }
+        }
+
+        /* Compute tangent = sine/cosine */
+        for (int i = 0; i < SINE_TABLE_LEN; i++) {
+            double xs[] = new double[2];
+            double ys[] = new double[2];
+            double as[] = new double[2];
+
+            as[0] = COSINE_TABLE_A[i];
+            as[1] = COSINE_TABLE_B[i];
+
+            splitReciprocal(as, ys);
+
+            xs[0] = SINE_TABLE_A[i];
+            xs[1] = SINE_TABLE_B[i];
+
+            splitMult(xs, ys, as);
+
+            TANGENT_TABLE_A[i] = as[0];
+            TANGENT_TABLE_B[i] = as[1];
+        }
+    }
+
+    /**
+     * For x between 0 and pi/4 compute cosine using Talor series cos(x) = 1 - x^2/2! + x^4/4! ...
+     *
+     * @param x number from which cosine is requested
+     * @param result placeholder where to put the result in extended precision (may be null)
+     * @return cos(x)
+     */
+    static double slowCos(final double x, final double result[]) {
+
+        final double xs[] = new double[2];
+        final double ys[] = new double[2];
+        final double facts[] = new double[2];
+        final double as[] = new double[2];
+        split(x, xs);
+        ys[0] = ys[1] = 0.0;
+
+        for (int i = FACT.length - 1; i >= 0; i--) {
+            splitMult(xs, ys, as);
+            ys[0] = as[0];
+            ys[1] = as[1];
+
+            if ((i & 1) != 0) { // skip odd entries
+                continue;
+            }
+
+            split(FACT[i], as);
+            splitReciprocal(as, facts);
+
+            if ((i & 2) != 0) { // alternate terms are negative
+                facts[0] = -facts[0];
+                facts[1] = -facts[1];
+            }
+
+            splitAdd(ys, facts, as);
+            ys[0] = as[0];
+            ys[1] = as[1];
+        }
+
+        if (result != null) {
+            result[0] = ys[0];
+            result[1] = ys[1];
+        }
+
+        return ys[0] + ys[1];
+    }
+
+    /**
+     * For x between 0 and pi/4 compute sine using Taylor expansion: sin(x) = x - x^3/3! + x^5/5! -
+     * x^7/7! ...
+     *
+     * @param x number from which sine is requested
+     * @param result placeholder where to put the result in extended precision (may be null)
+     * @return sin(x)
+     */
+    static double slowSin(final double x, final double result[]) {
+        final double xs[] = new double[2];
+        final double ys[] = new double[2];
+        final double facts[] = new double[2];
+        final double as[] = new double[2];
+        split(x, xs);
+        ys[0] = ys[1] = 0.0;
+
+        for (int i = FACT.length - 1; i >= 0; i--) {
+            splitMult(xs, ys, as);
+            ys[0] = as[0];
+            ys[1] = as[1];
+
+            if ((i & 1) == 0) { // Ignore even numbers
+                continue;
+            }
+
+            split(FACT[i], as);
+            splitReciprocal(as, facts);
+
+            if ((i & 2) != 0) { // alternate terms are negative
+                facts[0] = -facts[0];
+                facts[1] = -facts[1];
+            }
+
+            splitAdd(ys, facts, as);
+            ys[0] = as[0];
+            ys[1] = as[1];
+        }
+
+        if (result != null) {
+            result[0] = ys[0];
+            result[1] = ys[1];
+        }
+
+        return ys[0] + ys[1];
+    }
+
+    /**
+     * For x between 0 and 1, returns exp(x), uses extended precision
+     *
+     * @param x argument of exponential
+     * @param result placeholder where to place exp(x) split in two terms for extra precision (i.e.
+     *     exp(x) = result[0] + result[1]
+     * @return exp(x)
+     */
+    static double slowexp(final double x, final double result[]) {
+        final double xs[] = new double[2];
+        final double ys[] = new double[2];
+        final double facts[] = new double[2];
+        final double as[] = new double[2];
+        split(x, xs);
+        ys[0] = ys[1] = 0.0;
+
+        for (int i = FACT.length - 1; i >= 0; i--) {
+            splitMult(xs, ys, as);
+            ys[0] = as[0];
+            ys[1] = as[1];
+
+            split(FACT[i], as);
+            splitReciprocal(as, facts);
+
+            splitAdd(ys, facts, as);
+            ys[0] = as[0];
+            ys[1] = as[1];
+        }
+
+        if (result != null) {
+            result[0] = ys[0];
+            result[1] = ys[1];
+        }
+
+        return ys[0] + ys[1];
+    }
+
+    /**
+     * Compute split[0], split[1] such that their sum is equal to d, and split[0] has its 30 least
+     * significant bits as zero.
+     *
+     * @param d number to split
+     * @param split placeholder where to place the result
+     */
+    private static void split(final double d, final double split[]) {
+        if (d < 8e298 && d > -8e298) {
+            final double a = d * HEX_40000000;
+            split[0] = (d + a) - a;
+            split[1] = d - split[0];
+        } else {
+            final double a = d * 9.31322574615478515625E-10;
+            split[0] = (d + a - d) * HEX_40000000;
+            split[1] = d - split[0];
+        }
+    }
+
+    /**
+     * Recompute a split.
+     *
+     * @param a input/out array containing the split, changed on output
+     */
+    private static void resplit(final double a[]) {
+        final double c = a[0] + a[1];
+        final double d = -(c - a[0] - a[1]);
+
+        if (c < 8e298 && c > -8e298) { // MAGIC NUMBER
+            double z = c * HEX_40000000;
+            a[0] = (c + z) - z;
+            a[1] = c - a[0] + d;
+        } else {
+            double z = c * 9.31322574615478515625E-10;
+            a[0] = (c + z - c) * HEX_40000000;
+            a[1] = c - a[0] + d;
+        }
+    }
+
+    /**
+     * Multiply two numbers in split form.
+     *
+     * @param a first term of multiplication
+     * @param b second term of multiplication
+     * @param ans placeholder where to put the result
+     */
+    private static void splitMult(double a[], double b[], double ans[]) {
+        ans[0] = a[0] * b[0];
+        ans[1] = a[0] * b[1] + a[1] * b[0] + a[1] * b[1];
+
+        /* Resplit */
+        resplit(ans);
+    }
+
+    /**
+     * Add two numbers in split form.
+     *
+     * @param a first term of addition
+     * @param b second term of addition
+     * @param ans placeholder where to put the result
+     */
+    private static void splitAdd(final double a[], final double b[], final double ans[]) {
+        ans[0] = a[0] + b[0];
+        ans[1] = a[1] + b[1];
+
+        resplit(ans);
+    }
+
+    /**
+     * Compute the reciprocal of in. Use the following algorithm. in = c + d. want to find x + y
+     * such that x+y = 1/(c+d) and x is much larger than y and x has several zero bits on the right.
+     *
+     * <p>Set b = 1/(2^22), a = 1 - b. Thus (a+b) = 1. Use following identity to compute (a+b)/(c+d)
+     *
+     * <p>(a+b)/(c+d) = a/c + (bc - ad) / (c^2 + cd) set x = a/c and y = (bc - ad) / (c^2 + cd) This
+     * will be close to the right answer, but there will be some rounding in the calculation of X.
+     * So by carefully computing 1 - (c+d)(x+y) we can compute an error and add that back in. This
+     * is done carefully so that terms of similar size are subtracted first.
+     *
+     * @param in initial number, in split form
+     * @param result placeholder where to put the result
+     */
+    static void splitReciprocal(final double in[], final double result[]) {
+        final double b = 1.0 / 4194304.0;
+        final double a = 1.0 - b;
+
+        if (in[0] == 0.0) {
+            in[0] = in[1];
+            in[1] = 0.0;
+        }
+
+        result[0] = a / in[0];
+        result[1] = (b * in[0] - a * in[1]) / (in[0] * in[0] + in[0] * in[1]);
+
+        if (result[1] != result[1]) { // can happen if result[1] is NAN
+            result[1] = 0.0;
+        }
+
+        /* Resplit */
+        resplit(result);
+
+        for (int i = 0; i < 2; i++) {
+            /* this may be overkill, probably once is enough */
+            double err =
+                    1.0
+                            - result[0] * in[0]
+                            - result[0] * in[1]
+                            - result[1] * in[0]
+                            - result[1] * in[1];
+            /*err = 1.0 - err; */
+            err *= result[0] + result[1];
+            /*printf("err = %16e\n", err); */
+            result[1] += err;
+        }
+    }
+
+    /**
+     * Compute (a[0] + a[1]) * (b[0] + b[1]) in extended precision.
+     *
+     * @param a first term of the multiplication
+     * @param b second term of the multiplication
+     * @param result placeholder where to put the result
+     */
+    private static void quadMult(final double a[], final double b[], final double result[]) {
+        final double xs[] = new double[2];
+        final double ys[] = new double[2];
+        final double zs[] = new double[2];
+
+        /* a[0] * b[0] */
+        split(a[0], xs);
+        split(b[0], ys);
+        splitMult(xs, ys, zs);
+
+        result[0] = zs[0];
+        result[1] = zs[1];
+
+        /* a[0] * b[1] */
+        split(b[1], ys);
+        splitMult(xs, ys, zs);
+
+        double tmp = result[0] + zs[0];
+        result[1] -= tmp - result[0] - zs[0];
+        result[0] = tmp;
+        tmp = result[0] + zs[1];
+        result[1] -= tmp - result[0] - zs[1];
+        result[0] = tmp;
+
+        /* a[1] * b[0] */
+        split(a[1], xs);
+        split(b[0], ys);
+        splitMult(xs, ys, zs);
+
+        tmp = result[0] + zs[0];
+        result[1] -= tmp - result[0] - zs[0];
+        result[0] = tmp;
+        tmp = result[0] + zs[1];
+        result[1] -= tmp - result[0] - zs[1];
+        result[0] = tmp;
+
+        /* a[1] * b[0] */
+        split(a[1], xs);
+        split(b[1], ys);
+        splitMult(xs, ys, zs);
+
+        tmp = result[0] + zs[0];
+        result[1] -= tmp - result[0] - zs[0];
+        result[0] = tmp;
+        tmp = result[0] + zs[1];
+        result[1] -= tmp - result[0] - zs[1];
+        result[0] = tmp;
+    }
+
+    /**
+     * Compute exp(p) for a integer p in extended precision.
+     *
+     * @param p integer whose exponential is requested
+     * @param result placeholder where to put the result in extended precision
+     * @return exp(p) in standard precision (equal to result[0] + result[1])
+     */
+    static double expint(int p, final double result[]) {
+        // double x = M_E;
+        final double xs[] = new double[2];
+        final double as[] = new double[2];
+        final double ys[] = new double[2];
+        // split(x, xs);
+        // xs[1] = (double)(2.7182818284590452353602874713526625L - xs[0]);
+        // xs[0] = 2.71827697753906250000;
+        // xs[1] = 4.85091998273542816811e-06;
+        // xs[0] = Double.longBitsToDouble(0x4005bf0800000000L);
+        // xs[1] = Double.longBitsToDouble(0x3ed458a2bb4a9b00L);
+
+        /* E */
+        xs[0] = 2.718281828459045;
+        xs[1] = 1.4456468917292502E-16;
+
+        split(1.0, ys);
+
+        while (p > 0) {
+            if ((p & 1) != 0) {
+                quadMult(ys, xs, as);
+                ys[0] = as[0];
+                ys[1] = as[1];
+            }
+
+            quadMult(xs, xs, as);
+            xs[0] = as[0];
+            xs[1] = as[1];
+
+            p >>= 1;
+        }
+
+        if (result != null) {
+            result[0] = ys[0];
+            result[1] = ys[1];
+
+            resplit(result);
+        }
+
+        return ys[0] + ys[1];
+    }
+
+    /**
+     * xi in the range of [1, 2]. 3 5 7 x+1 / x x x \ ln ----- = 2 * | x + ---- + ---- + ---- + ...
+     * | 1-x \ 3 5 7 /
+     *
+     * <p>So, compute a Remez approximation of the following function
+     *
+     * <p>ln ((sqrt(x)+1)/(1-sqrt(x))) / x
+     *
+     * <p>This will be an even function with only positive coefficents. x is in the range [0 - 1/3].
+     *
+     * <p>Transform xi for input to the above function by setting x = (xi-1)/(xi+1). Input to the
+     * polynomial is x^2, then the result is multiplied by x.
+     *
+     * @param xi number from which log is requested
+     * @return log(xi)
+     */
+    static double[] slowLog(double xi) {
+        double x[] = new double[2];
+        double x2[] = new double[2];
+        double y[] = new double[2];
+        double a[] = new double[2];
+
+        split(xi, x);
+
+        /* Set X = (x-1)/(x+1) */
+        x[0] += 1.0;
+        resplit(x);
+        splitReciprocal(x, a);
+        x[0] -= 2.0;
+        resplit(x);
+        splitMult(x, a, y);
+        x[0] = y[0];
+        x[1] = y[1];
+
+        /* Square X -> X2*/
+        splitMult(x, x, x2);
+
+        // x[0] -= 1.0;
+        // resplit(x);
+
+        y[0] = LN_SPLIT_COEF[LN_SPLIT_COEF.length - 1][0];
+        y[1] = LN_SPLIT_COEF[LN_SPLIT_COEF.length - 1][1];
+
+        for (int i = LN_SPLIT_COEF.length - 2; i >= 0; i--) {
+            splitMult(y, x2, a);
+            y[0] = a[0];
+            y[1] = a[1];
+            splitAdd(y, LN_SPLIT_COEF[i], a);
+            y[0] = a[0];
+            y[1] = a[1];
+        }
+
+        splitMult(y, x, a);
+        y[0] = a[0];
+        y[1] = a[1];
+
+        return y;
+    }
+
+    /**
+     * Print an array.
+     *
+     * @param out text output stream where output should be printed
+     * @param name array name
+     * @param expectedLen expected length of the array
+     * @param array2d array data
+     */
+    static void printarray(PrintStream out, String name, int expectedLen, double[][] array2d) {
+        out.println(name);
+        checkLen(expectedLen, array2d.length);
+        out.println(TABLE_START_DECL + " ");
+        int i = 0;
+        for (double[] array : array2d) { // "double array[]" causes PMD parsing error
+            out.print("        {");
+            for (double d : array) { // assume inner array has very few entries
+                out.printf("%-25.25s", format(d)); // multiple entries per line
+            }
+            out.println("}, // " + i++);
+        }
+        out.println(TABLE_END_DECL);
+    }
+
+    /**
+     * Print an array.
+     *
+     * @param out text output stream where output should be printed
+     * @param name array name
+     * @param expectedLen expected length of the array
+     * @param array array data
+     */
+    static void printarray(PrintStream out, String name, int expectedLen, double[] array) {
+        out.println(name + "=");
+        checkLen(expectedLen, array.length);
+        out.println(TABLE_START_DECL);
+        for (double d : array) {
+            out.printf("        %s%n", format(d)); // one entry per line
+        }
+        out.println(TABLE_END_DECL);
+    }
+
+    /**
+     * Format a double.
+     *
+     * @param d double number to format
+     * @return formatted number
+     */
+    static String format(double d) {
+        if (d != d) {
+            return "Double.NaN,";
+        } else {
+            return ((d >= 0) ? "+" : "") + Double.toString(d) + "d,";
+        }
+    }
+
+    /**
+     * Check two lengths are equal.
+     *
+     * @param expectedLen expected length
+     * @param actual actual length
+     * @exception DimensionMismatchException if the two lengths are not equal
+     */
+    private static void checkLen(int expectedLen, int actual) throws DimensionMismatchException {
+        if (expectedLen != actual) {
+            throw new DimensionMismatchException(actual, expectedLen);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/util/FastMathLiteralArrays.java b/src/main/java/org/apache/commons/math3/util/FastMathLiteralArrays.java
new file mode 100644
index 0000000..1092b32
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/FastMathLiteralArrays.java
@@ -0,0 +1,8228 @@
+/*
+ * 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.math3.util;
+
+/** Utility class for loading tabulated data used by {@link FastMath}. */
+class FastMathLiteralArrays {
+    /**
+     * Exponential evaluated at integer values, exp(x) = expIntTableA[x + EXP_INT_TABLE_MAX_INDEX] +
+     * expIntTableB[x+EXP_INT_TABLE_MAX_INDEX].
+     */
+    private static final double[] EXP_INT_A =
+            new double[] {
+                +0.0d,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                +1.2167807682331913E-308d,
+                +3.3075532478807267E-308d,
+                +8.990862214387203E-308d,
+                +2.4439696075216986E-307d,
+                +6.64339758024534E-307d,
+                +1.8058628951432254E-306d,
+                +4.908843759498681E-306d,
+                +1.334362017065677E-305d,
+                +3.627172425759641E-305d,
+                +9.85967600992008E-305d,
+                +2.680137967689915E-304d,
+                +7.285370725133842E-304d,
+                +1.9803689272433392E-303d,
+                +5.3832011494782624E-303d,
+                +1.463305638201413E-302d,
+                +3.9776772027043775E-302d,
+                +1.0812448255518705E-301d,
+                +2.9391280956327795E-301d,
+                +7.989378677301346E-301d,
+                +2.1717383041010577E-300d,
+                +5.903396499766243E-300d,
+                +1.604709595901607E-299d,
+                +4.3620527352131126E-299d,
+                +1.1857289715706991E-298d,
+                +3.2231452986239366E-298d,
+                +8.761416875971053E-298d,
+                +2.381600167287677E-297d,
+                +6.473860152384321E-297d,
+                +1.7597776278732318E-296d,
+                +4.7835721669653157E-296d,
+                +1.3003096668152053E-295d,
+                +3.5346080979652066E-295d,
+                +9.608060944124859E-295d,
+                +2.6117415961302846E-294d,
+                +7.099449830809996E-294d,
+                +1.9298305829106006E-293d,
+                +5.245823134132673E-293d,
+                +1.4259627797225802E-292d,
+                +3.8761686729764145E-292d,
+                +1.0536518897078156E-291d,
+                +2.864122672853628E-291d,
+                +7.785491934690374E-291d,
+                +2.116316283183901E-290d,
+                +5.7527436249968E-290d,
+                +1.5637579898345352E-289d,
+                +4.250734424415339E-289d,
+                +1.1554696041977512E-288d,
+                +3.1408919441362495E-288d,
+                +8.537829238438662E-288d,
+                +2.320822576772103E-287d,
+                +6.308649765138419E-287d,
+                +1.7148689119310826E-286d,
+                +4.66149719271323E-286d,
+                +1.267126226441217E-285d,
+                +3.444406231880653E-285d,
+                +9.362866914115166E-285d,
+                +2.5450911557068313E-284d,
+                +6.918275021321188E-284d,
+                +1.880582039589629E-283d,
+                +5.111952261540649E-283d,
+                +1.3895726688907995E-282d,
+                +3.7772500667438066E-282d,
+                +1.026763015362553E-281d,
+                +2.791031173360063E-281d,
+                +7.586808748646825E-281d,
+                +2.0623086887184633E-280d,
+                +5.605936171588964E-280d,
+                +1.5238514098804918E-279d,
+                +4.1422578754033235E-279d,
+                +1.1259823210174452E-278d,
+                +3.060737220976933E-278d,
+                +8.319947089683576E-278d,
+                +2.2615958035357106E-277d,
+                +6.147655179898435E-277d,
+                +1.6711060014400145E-276d,
+                +4.542536646012133E-276d,
+                +1.2347896500246374E-275d,
+                +3.3565057475434694E-275d,
+                +9.123929070778758E-275d,
+                +2.4801413921885483E-274d,
+                +6.741722283079056E-274d,
+                +1.8325902719086093E-273d,
+                +4.981496462621207E-273d,
+                +1.3541112064618357E-272d,
+                +3.68085620656127E-272d,
+                +1.0005602916630382E-271d,
+                +2.719805132368625E-271d,
+                +7.393196131284108E-271d,
+                +2.0096791226867E-270d,
+                +5.462874707256208E-270d,
+                +1.4849631831943512E-269d,
+                +4.036548930895323E-269d,
+                +1.0972476870931676E-268d,
+                +2.9826282194717127E-268d,
+                +8.107624153838987E-268d,
+                +2.2038806519542315E-267d,
+                +5.990769236615968E-267d,
+                +1.628459873440512E-266d,
+                +4.4266130556431266E-266d,
+                +1.203278237867575E-265d,
+                +3.270849446965521E-265d,
+                +8.891090288030614E-265d,
+                +2.4168487931443637E-264d,
+                +6.569676185250389E-264d,
+                +1.7858231429575898E-263d,
+                +4.85437090269903E-263d,
+                +1.3195548295785448E-262d,
+                +3.5869215528816054E-262d,
+                +9.750264097807267E-262d,
+                +2.650396454019762E-261d,
+                +7.204525142098426E-261d,
+                +1.958392846081373E-260d,
+                +5.32346341339996E-260d,
+                +1.4470673509275515E-259d,
+                +3.9335373658569176E-259d,
+                +1.0692462289051038E-258d,
+                +2.9065128598079075E-258d,
+                +7.900720862969045E-258d,
+                +2.147638465376883E-257d,
+                +5.8378869339035456E-257d,
+                +1.5869022483809747E-256d,
+                +4.3136475849391444E-256d,
+                +1.1725710340687719E-255d,
+                +3.1873780814410126E-255d,
+                +8.66419234315257E-255d,
+                +2.35517168886351E-254d,
+                +6.402020300783889E-254d,
+                +1.740249660600677E-253d,
+                +4.7304887145310405E-253d,
+                +1.2858802448614707E-252d,
+                +3.495384792953975E-252d,
+                +9.501439740542955E-252d,
+                +2.582759362004277E-251d,
+                +7.020668578160457E-251d,
+                +1.908415302517694E-250d,
+                +5.1876107490791666E-250d,
+                +1.4101386971763257E-249d,
+                +3.8331545111676784E-249d,
+                +1.0419594359065132E-248d,
+                +2.8323395451363237E-248d,
+                +7.699097067385825E-248d,
+                +2.0928317096428755E-247d,
+                +5.688906371296133E-247d,
+                +1.5464049837965422E-246d,
+                +4.2035646586788297E-246d,
+                +1.1426473877336358E-245d,
+                +3.106037603716254E-245d,
+                +8.443084996839363E-245d,
+                +2.2950686306677644E-244d,
+                +6.238642390386363E-244d,
+                +1.695838923802857E-243d,
+                +4.6097680405580995E-243d,
+                +1.2530649392922358E-242d,
+                +3.4061835424180075E-242d,
+                +9.25896798127602E-242d,
+                +2.5168480541429286E-241d,
+                +6.841502859109196E-241d,
+                +1.8597132378953187E-240d,
+                +5.055224959032211E-240d,
+                +1.374152583940637E-239d,
+                +3.735333866258403E-239d,
+                +1.0153690688015855E-238d,
+                +2.7600590782738726E-238d,
+                +7.502618487550056E-238d,
+                +2.0394233446495043E-237d,
+                +5.543727690168612E-237d,
+                +1.5069412868172555E-236d,
+                +4.0962906236847E-236d,
+                +1.1134873918971586E-235d,
+                +3.026772467749944E-235d,
+                +8.227620163729258E-235d,
+                +2.2364990583200056E-234d,
+                +6.079434951446575E-234d,
+                +1.6525617499662284E-233d,
+                +4.4921289690525345E-233d,
+                +1.2210872189854344E-232d,
+                +3.3192593301633E-232d,
+                +9.02268127425393E-232d,
+                +2.4526190464373087E-231d,
+                +6.666909874218774E-231d,
+                +1.8122539547625083E-230d,
+                +4.926216840507529E-230d,
+                +1.3390847149416908E-229d,
+                +3.6400093808551196E-229d,
+                +9.894571625944288E-229d,
+                +2.689623698321582E-228d,
+                +7.31115423069187E-228d,
+                +1.9873779569310022E-227d,
+                +5.402252865260326E-227d,
+                +1.4684846983789053E-226d,
+                +3.991755413823315E-226d,
+                +1.0850715739509136E-225d,
+                +2.9495302004590423E-225d,
+                +8.017654713159388E-225d,
+                +2.179424521221378E-224d,
+                +5.924290380648597E-224d,
+                +1.6103890140790331E-223d,
+                +4.377491272857675E-223d,
+                +1.1899254154663847E-222d,
+                +3.2345523990372546E-222d,
+                +8.792425221770645E-222d,
+                +2.3900289095512176E-221d,
+                +6.496772856703278E-221d,
+                +1.7660059778220905E-220d,
+                +4.800501435803201E-220d,
+                +1.3049116216750674E-219d,
+                +3.5471180281159325E-219d,
+                +9.642065709892252E-219d,
+                +2.6209850274990846E-218d,
+                +7.124574366530717E-218d,
+                +1.9366601417010147E-217d,
+                +5.264388476949737E-217d,
+                +1.431009021985696E-216d,
+                +3.889885799962507E-216d,
+                +1.057380684430436E-215d,
+                +2.8742587656021775E-215d,
+                +7.813044552050569E-215d,
+                +2.1238058974550874E-214d,
+                +5.773102661099307E-214d,
+                +1.5692921723471877E-213d,
+                +4.2657777816050375E-213d,
+                +1.1595585743839232E-212d,
+                +3.1520070828798975E-212d,
+                +8.568043768122183E-212d,
+                +2.329035966595791E-211d,
+                +6.33097561889469E-211d,
+                +1.720937714565362E-210d,
+                +4.677993239821998E-210d,
+                +1.2716105485691878E-209d,
+                +3.456595573934475E-209d,
+                +9.396000024637834E-209d,
+                +2.55409795397022E-208d,
+                +6.942757623821567E-208d,
+                +1.887237361505784E-207d,
+                +5.13004286606108E-207d,
+                +1.3944901709366118E-206d,
+                +3.7906173667738715E-206d,
+                +1.0303966192973381E-205d,
+                +2.8009086220877197E-205d,
+                +7.613657850210907E-205d,
+                +2.0696069842597556E-204d,
+                +5.6257755605305175E-204d,
+                +1.5292444435954893E-203d,
+                +4.156916476922876E-203d,
+                +1.12996721591364E-202d,
+                +3.071569248856111E-202d,
+                +8.349390727162016E-202d,
+                +2.2695999828608633E-201d,
+                +6.1694117899971836E-201d,
+                +1.677020107827128E-200d,
+                +4.558612479525779E-200d,
+                +1.2391595516612638E-199d,
+                +3.3683846288580648E-199d,
+                +9.156218120779494E-199d,
+                +2.4889182184335247E-198d,
+                +6.765580431441772E-198d,
+                +1.839075686473352E-197d,
+                +4.999126524757713E-197d,
+                +1.3589033107846643E-196d,
+                +3.6938826366068014E-196d,
+                +1.0041012794280992E-195d,
+                +2.7294301888986675E-195d,
+                +7.419361045185406E-195d,
+                +2.016791373353671E-194d,
+                +5.482208065983983E-194d,
+                +1.490218341008089E-193d,
+                +4.050833763855709E-193d,
+                +1.101130773265179E-192d,
+                +2.993183789477209E-192d,
+                +8.136316299122392E-192d,
+                +2.2116799789922265E-191d,
+                +6.011969568315371E-191d,
+                +1.6342228966392253E-190d,
+                +4.4422779589171113E-190d,
+                +1.2075364784547675E-189d,
+                +3.282424571107068E-189d,
+                +8.92255448602772E-189d,
+                +2.425402115319395E-188d,
+                +6.592926904915355E-188d,
+                +1.79214305133496E-187d,
+                +4.871550528055661E-187d,
+                +1.3242245776666673E-186d,
+                +3.599615946028287E-186d,
+                +9.78476998200719E-186d,
+                +2.659776075359514E-185d,
+                +7.230020851688713E-185d,
+                +1.9653234116333892E-184d,
+                +5.34230278107224E-184d,
+                +1.4521887058451231E-183d,
+                +3.947457923821984E-183d,
+                +1.0730302255093144E-182d,
+                +2.9167986204137332E-182d,
+                +7.928680793406766E-182d,
+                +2.1552386987482013E-181d,
+                +5.858546779607288E-181d,
+                +1.5925182066949723E-180d,
+                +4.328913614497258E-180d,
+                +1.1767205227552116E-179d,
+                +3.198658219194836E-179d,
+                +8.694853785564504E-179d,
+                +2.363506255864984E-178d,
+                +6.42467573615509E-178d,
+                +1.746408207555959E-177d,
+                +4.747229597770176E-177d,
+                +1.2904307529671472E-176d,
+                +3.507754341050756E-176d,
+                +9.535066345267336E-176d,
+                +2.591899541396432E-175d,
+                +7.045512786902009E-175d,
+                +1.9151693415969248E-174d,
+                +5.205969622575851E-174d,
+                +1.4151292367806538E-173d,
+                +3.846720258072078E-173d,
+                +1.045647032279984E-172d,
+                +2.8423629805010285E-172d,
+                +7.726344058192276E-172d,
+                +2.1002377128928765E-171d,
+                +5.709039546124285E-171d,
+                +1.5518778128928824E-170d,
+                +4.218440703602533E-170d,
+                +1.1466910691560932E-169d,
+                +3.1170298734336303E-169d,
+                +8.472965161251656E-169d,
+                +2.303190374523956E-168d,
+                +6.260720440258473E-168d,
+                +1.701840523821621E-167d,
+                +4.62608152166211E-167d,
+                +1.2574995962791943E-166d,
+                +3.418237608335161E-166d,
+                +9.29173407843235E-166d,
+                +2.5257552661512635E-165d,
+                +6.865714679174435E-165d,
+                +1.866294830116931E-164d,
+                +5.073114566291778E-164d,
+                +1.3790154522394582E-163d,
+                +3.7485528226129495E-163d,
+                +1.0189624503698769E-162d,
+                +2.7698267293941856E-162d,
+                +7.529170882336924E-162d,
+                +2.0466404088178596E-161d,
+                +5.56334611651382E-161d,
+                +1.512274346576166E-160d,
+                +4.110787043867721E-160d,
+                +1.1174279267498045E-159d,
+                +3.0374839443564585E-159d,
+                +8.25673801176584E-159d,
+                +2.244414150254963E-158d,
+                +6.1009492034592176E-158d,
+                +1.6584100275603453E-157d,
+                +4.50802633729044E-157d,
+                +1.2254085656601853E-156d,
+                +3.3310057014599044E-156d,
+                +9.054612259832416E-156d,
+                +2.4612985502035675E-155d,
+                +6.690503835950083E-155d,
+                +1.8186679660152888E-154d,
+                +4.9436516047443576E-154d,
+                +1.3438240331106108E-153d,
+                +3.652892398145774E-153d,
+                +9.92958982547828E-153d,
+                +2.6991427376823027E-152d,
+                +7.3370297995122135E-152d,
+                +1.994411660450821E-151d,
+                +5.421372463189529E-151d,
+                +1.4736818914204564E-150d,
+                +4.005882964287806E-150d,
+                +1.088911919926534E-149d,
+                +2.9599693109692324E-149d,
+                +8.046030012041041E-149d,
+                +2.18713790898745E-148d,
+                +5.945256705384597E-148d,
+                +1.6160884846515524E-147d,
+                +4.392983574030969E-147d,
+                +1.1941366764543551E-146d,
+                +3.2460001983475855E-146d,
+                +8.8235440586675E-146d,
+                +2.3984878190403553E-145d,
+                +6.519765758635405E-145d,
+                +1.772256261139753E-144d,
+                +4.817491674217065E-144d,
+                +1.3095299991573769E-143d,
+                +3.559671483107555E-143d,
+                +9.676190774054103E-143d,
+                +2.630261301303634E-142d,
+                +7.149792225695347E-142d,
+                +1.943514969662872E-141d,
+                +5.283020542151163E-141d,
+                +1.4360739330834996E-140d,
+                +3.9036541111764032E-140d,
+                +1.0611230602364477E-139d,
+                +2.8844319473099593E-139d,
+                +7.84069876400596E-139d,
+                +2.1313228444765414E-138d,
+                +5.793536445518422E-138d,
+                +1.5748463788034308E-137d,
+                +4.2808762411845363E-137d,
+                +1.1636629220608724E-136d,
+                +3.163163464591171E-136d,
+                +8.598369704466743E-136d,
+                +2.337279322276433E-135d,
+                +6.353384093665193E-135d,
+                +1.7270287031459572E-134d,
+                +4.694550492773212E-134d,
+                +1.2761111606368036E-133d,
+                +3.4688299108856403E-133d,
+                +9.429257929713919E-133d,
+                +2.5631381141873417E-132d,
+                +6.967331001069377E-132d,
+                +1.8939170679975288E-131d,
+                +5.148199748336684E-131d,
+                +1.3994258162094293E-130d,
+                +3.804034213613942E-130d,
+                +1.0340436948077763E-129d,
+                +2.8108219632627907E-129d,
+                +7.640606938467665E-129d,
+                +2.0769322678328357E-128d,
+                +5.645687086879944E-128d,
+                +1.5346568127351796E-127d,
+                +4.171630237420918E-127d,
+                +1.1339665711932977E-126d,
+                +3.0824406750909563E-126d,
+                +8.37894218404787E-126d,
+                +2.2776327994966818E-125d,
+                +6.191247522703296E-125d,
+                +1.6829556040859853E-124d,
+                +4.5747479502862494E-124d,
+                +1.2435453481209945E-123d,
+                +3.3803067202247166E-123d,
+                +9.188625696750548E-123d,
+                +2.4977273040076145E-122d,
+                +6.789527378582775E-122d,
+                +1.845584943222965E-121d,
+                +5.016820182185716E-121d,
+                +1.3637129731022491E-120d,
+                +3.706956710275979E-120d,
+                +1.0076552294433743E-119d,
+                +2.739090595934893E-119d,
+                +7.445620503219039E-119d,
+                +2.023929422267303E-118d,
+                +5.501611507503037E-118d,
+                +1.4954928881576769E-117d,
+                +4.0651709187617596E-117d,
+                +1.1050280679513555E-116d,
+                +3.003777734030334E-116d,
+                +8.165114384910189E-116d,
+                +2.219508285637377E-115d,
+                +6.033249389304709E-115d,
+                +1.6400070480930697E-114d,
+                +4.458001565878111E-114d,
+                +1.2118105325725891E-113d,
+                +3.2940421731384895E-113d,
+                +8.954135150208654E-113d,
+                +2.433986351722258E-112d,
+                +6.616260705434716E-112d,
+                +1.7984863104885375E-111d,
+                +4.888792154132158E-111d,
+                +1.3289115531074511E-110d,
+                +3.612356038181234E-110d,
+                +9.819402293160495E-110d,
+                +2.6691899766673256E-109d,
+                +7.255611264437603E-109d,
+                +1.9722796756250217E-108d,
+                +5.361211684173837E-108d,
+                +1.4573285967670963E-107d,
+                +3.961429477016909E-107d,
+                +1.0768281419102595E-106d,
+                +2.9271223293841774E-106d,
+                +7.956744351476403E-106d,
+                +2.1628672925745152E-105d,
+                +5.879282834821692E-105d,
+                +1.5981547034872092E-104d,
+                +4.344234755347641E-104d,
+                +1.1808855501885005E-103d,
+                +3.2099795870407646E-103d,
+                +8.725629524586503E-103d,
+                +2.3718718327094683E-102d,
+                +6.44741641521183E-102d,
+                +1.7525895549820557E-101d,
+                +4.7640323331013947E-101d,
+                +1.2949980563724296E-100d,
+                +3.5201699899499525E-100d,
+                +9.56881327374431E-100d,
+                +2.6010732940533088E-99d,
+                +7.070450309820548E-99d,
+                +1.9219478787856753E-98d,
+                +5.2243955659975294E-98d,
+                +1.4201378353978042E-97d,
+                +3.8603349913851996E-97d,
+                +1.0493479260117497E-96d,
+                +2.8524232604238555E-96d,
+                +7.753690709912764E-96d,
+                +2.1076716069929933E-95d,
+                +5.72924572981599E-95d,
+                +1.5573703263204683E-94d,
+                +4.233371554108682E-94d,
+                +1.1507496472539512E-93d,
+                +3.1280620563875923E-93d,
+                +8.5029538631631E-93d,
+                +2.3113425190436427E-92d,
+                +6.28287989314225E-92d,
+                +1.7078641226055994E-91d,
+                +4.6424556110307644E-91d,
+                +1.261950308999819E-90d,
+                +3.430336362898836E-90d,
+                +9.324622137237299E-90d,
+                +2.5346947846365435E-89d,
+                +6.890014851450124E-89d,
+                +1.8729003560057785E-88d,
+                +5.091070300111434E-88d,
+                +1.3838964592430477E-87d,
+                +3.761820584522275E-87d,
+                +1.0225689628581036E-86d,
+                +2.7796303536272215E-86d,
+                +7.555818934379333E-86d,
+                +2.053884626293416E-85d,
+                +5.583037134407759E-85d,
+                +1.5176268538776042E-84d,
+                +4.125337057189083E-84d,
+                +1.121383042095528E-83d,
+                +3.0482348236054953E-83d,
+                +8.285962249116636E-83d,
+                +2.2523580600947705E-82d,
+                +6.122543452787843E-82d,
+                +1.664279766968299E-81d,
+                +4.523982262003404E-81d,
+                +1.2297456769063303E-80d,
+                +3.342795345742034E-80d,
+                +9.086660081726823E-80d,
+                +2.4700104681773258E-79d,
+                +6.714184569587689E-79d,
+                +1.8251046352720517E-78d,
+                +4.961148056969105E-78d,
+                +1.3485799924445315E-77d,
+                +3.665820371396835E-77d,
+                +9.964732578705785E-77d,
+                +2.708695208461993E-76d,
+                +7.362996533913695E-76d,
+                +2.0014700145557332E-75d,
+                +5.440559532453721E-75d,
+                +1.4788974793889734E-74d,
+                +4.020060558571273E-74d,
+                +1.092765612182012E-73d,
+                +2.970445258959489E-73d,
+                +8.074507236705857E-73d,
+                +2.1948784599535102E-72d,
+                +5.966298125808066E-72d,
+                +1.6218081151910012E-71d,
+                +4.408531734441582E-71d,
+                +1.198363039426718E-70d,
+                +3.257488853378793E-70d,
+                +8.854771398921902E-70d,
+                +2.406976727302894E-69d,
+                +6.542840888268955E-69d,
+                +1.778528517418201E-68d,
+                +4.834541417183388E-68d,
+                +1.3141647465063647E-67d,
+                +3.572270133517001E-67d,
+                +9.710435805122717E-67d,
+                +2.63957027915428E-66d,
+                +7.175096392165733E-66d,
+                +1.9503931430716318E-65d,
+                +5.3017188565638215E-65d,
+                +1.4411566290936352E-64d,
+                +3.9174693825966044E-64d,
+                +1.0648786018364265E-63d,
+                +2.8946401383311E-63d,
+                +7.868447965383903E-63d,
+                +2.1388659707647114E-62d,
+                +5.814040618670345E-62d,
+                +1.5804200403673568E-61d,
+                +4.296027044486766E-61d,
+                +1.1677812418806031E-60d,
+                +3.174358801839755E-60d,
+                +8.62880163941313E-60d,
+                +2.345551464945955E-59d,
+                +6.3758692300917355E-59d,
+                +1.733140900346534E-58d,
+                +4.711165925070571E-58d,
+                +1.2806275683797178E-57d,
+                +3.481106736845E-57d,
+                +9.462629520363307E-57d,
+                +2.5722094667974783E-56d,
+                +6.9919903587080315E-56d,
+                +1.9006201022568844E-55d,
+                +5.166420404109835E-55d,
+                +1.4043786616805493E-54d,
+                +3.8174968984748894E-54d,
+                +1.03770335512154E-53d,
+                +2.820769858672565E-53d,
+                +7.667647949477605E-53d,
+                +2.0842827711783212E-52d,
+                +5.6656680900216754E-52d,
+                +1.5400881501571645E-51d,
+                +4.1863938339341257E-51d,
+                +1.1379799629071911E-50d,
+                +3.093350150840571E-50d,
+                +8.408597060399334E-50d,
+                +2.2856938448387544E-49d,
+                +6.2131591878042886E-49d,
+                +1.688911928929718E-48d,
+                +4.5909386437919143E-48d,
+                +1.2479464696643861E-47d,
+                +3.3922703599272275E-47d,
+                +9.221146830884422E-47d,
+                +2.5065676066043174E-46d,
+                +6.8135571305481364E-46d,
+                +1.8521166948363666E-45d,
+                +5.0345752964740226E-45d,
+                +1.368539456379101E-44d,
+                +3.720075801577098E-44d,
+                +1.0112214979786464E-43d,
+                +2.7487849807248755E-43d,
+                +7.47197247068667E-43d,
+                +2.0310928323153876E-42d,
+                +5.521082422279256E-42d,
+                +1.5007857288519654E-41d,
+                +4.0795586181406803E-41d,
+                +1.108938997126179E-40d,
+                +3.0144088843073416E-40d,
+                +8.194012195477669E-40d,
+                +2.2273635587196807E-39d,
+                +6.054601485195952E-39d,
+                +1.6458113136245473E-38d,
+                +4.473779311490168E-38d,
+                +1.2160992719555806E-37d,
+                +3.3057007442449645E-37d,
+                +8.985825281444118E-37d,
+                +2.442600707513088E-36d,
+                +6.639677673630215E-36d,
+                +1.8048513285848406E-35d,
+                +4.906094420881007E-35d,
+                +1.3336148713971936E-34d,
+                +3.625141007634431E-34d,
+                +9.854154449263851E-34d,
+                +2.6786368134431636E-33d,
+                +7.28128971953363E-33d,
+                +1.9792597720953414E-32d,
+                +5.380185921962174E-32d,
+                +1.4624861244004054E-31d,
+                +3.975449484028966E-31d,
+                +1.080639291795678E-30d,
+                +2.9374821418009058E-30d,
+                +7.984904044796711E-30d,
+                +2.1705221445447534E-29d,
+                +5.900089995748943E-29d,
+                +1.6038109389511792E-28d,
+                +4.359610133382778E-28d,
+                +1.185064946717304E-27d,
+                +3.221340469489223E-27d,
+                +8.756510122348782E-27d,
+                +2.380266370880709E-26d,
+                +6.47023467943241E-26d,
+                +1.75879225876483E-25d,
+                +4.780892502168074E-25d,
+                +1.2995814853898995E-24d,
+                +3.5326287852455166E-24d,
+                +9.602680736954162E-24d,
+                +2.6102792042257208E-23d,
+                +7.095474414148981E-23d,
+                +1.9287497671359936E-22d,
+                +5.242885191553114E-22d,
+                +1.4251641388208515E-21d,
+                +3.873997809109103E-21d,
+                +1.0530616658562386E-20d,
+                +2.862518609581133E-20d,
+                +7.78113163345177E-20d,
+                +2.1151310700892382E-19d,
+                +5.74952254077566E-19d,
+                +1.5628822871880503E-18d,
+                +4.24835413113866E-18d,
+                +1.1548223864099742E-17d,
+                +3.139132557537509E-17d,
+                +8.533046968331264E-17d,
+                +2.3195229636950566E-16d,
+                +6.305116324200775E-16d,
+                +1.71390848833098E-15d,
+                +4.6588861918718874E-15d,
+                +1.2664165777252073E-14d,
+                +3.442477422913037E-14d,
+                +9.357622912219837E-14d,
+                +2.5436656904062604E-13d,
+                +6.914399608426436E-13d,
+                +1.879528650772233E-12d,
+                +5.1090893668503945E-12d,
+                +1.3887944613766301E-11d,
+                +3.775134371775124E-11d,
+                +1.0261880234452292E-10d,
+                +2.789468100949932E-10d,
+                +7.582560135332983E-10d,
+                +2.061153470123145E-9d,
+                +5.602796449011294E-9d,
+                +1.5229979055675358E-8d,
+                +4.139937459513021E-8d,
+                +1.1253517584464134E-7d,
+                +3.059023470086686E-7d,
+                +8.315287232107949E-7d,
+                +2.260329438286135E-6d,
+                +6.1442124206223525E-6d,
+                +1.670170240686275E-5d,
+                +4.539993096841499E-5d,
+                +1.2340981629677117E-4d,
+                +3.35462624207139E-4d,
+                +9.118819143623114E-4d,
+                +0.0024787522852420807d,
+                +0.006737947463989258d,
+                +0.018315639346837997d,
+                +0.049787066876888275d,
+                +0.1353352963924408d,
+                +0.3678794503211975d,
+                +1.0d,
+                +2.7182817459106445d,
+                +7.389056205749512d,
+                +20.08553695678711d,
+                +54.59815216064453d,
+                +148.41314697265625d,
+                +403.42877197265625d,
+                +1096.633056640625d,
+                +2980.9580078125d,
+                +8103.083984375d,
+                +22026.46484375d,
+                +59874.140625d,
+                +162754.78125d,
+                +442413.375d,
+                +1202604.25d,
+                +3269017.5d,
+                +8886110.0d,
+                +2.4154952E7d,
+                +6.5659968E7d,
+                +1.78482304E8d,
+                +4.85165184E8d,
+                +1.318815744E9d,
+                +3.584912896E9d,
+                +9.74480384E9d,
+                +2.6489122816E10d,
+                +7.200489472E10d,
+                +1.95729620992E11d,
+                +5.32048248832E11d,
+                +1.446257098752E12d,
+                +3.9313342464E12d,
+                +1.0686474223616E13d,
+                +2.904884772864E13d,
+                +7.8962956959744E13d,
+                +2.14643574308864E14d,
+                +5.83461777702912E14d,
+                +1.586013579247616E15d,
+                +4.31123180027904E15d,
+                +1.1719142537166848E16d,
+                +3.1855931348221952E16d,
+                +8.6593395455164416E16d,
+                +2.35385270340419584E17d,
+                +6.3984347447610573E17d,
+                +1.73927483790327808E18d,
+                +4.7278395262972723E18d,
+                +1.285159987981792E19d,
+                +3.493427277593156E19d,
+                +9.496119530068797E19d,
+                +2.581312717296228E20d,
+                +7.016736290557636E20d,
+                +1.907346499785443E21d,
+                +5.1847060206155E21d,
+                +1.4093490364499379E22d,
+                +3.831007739580998E22d,
+                +1.0413759887481643E23d,
+                +2.8307533984544136E23d,
+                +7.694785471490595E23d,
+                +2.0916595931561093E24d,
+                +5.685720022003016E24d,
+                +1.545539007875769E25d,
+                +4.201209991636407E25d,
+                +1.142007304008196E26d,
+                +3.104297782658242E26d,
+                +8.43835682327257E26d,
+                +2.2937832658080656E27d,
+                +6.23514943204966E27d,
+                +1.694889206675675E28d,
+                +4.607187019879158E28d,
+                +1.2523630909973607E29d,
+                +3.4042761729010895E29d,
+                +9.253781621373885E29d,
+                +2.5154385492401904E30d,
+                +6.837671137556327E30d,
+                +1.8586717056324128E31d,
+                +5.05239404378821E31d,
+                +1.3733830589835937E32d,
+                +3.733241849647479E32d,
+                +1.014800418749161E33d,
+                +2.758513549969986E33d,
+                +7.498416981578345E33d,
+                +2.0382811492597872E34d,
+                +5.540622484676759E34d,
+                +1.5060972626944096E35d,
+                +4.0939972479624634E35d,
+                +1.1128638067747114E36d,
+                +3.0250770246136387E36d,
+                +8.223012393018281E36d,
+                +2.2352467822017166E37d,
+                +6.076029840339376E37d,
+                +1.6516361647240826E38d,
+                +4.4896127778163155E38d,
+                +1.2204032949639917E39d,
+                +3.3174000012927697E39d,
+                +9.017628107716908E39d,
+                +2.451245443147225E40d,
+                +6.663175904917432E40d,
+                +1.8112388823726723E41d,
+                +4.923458004084836E41d,
+                +1.3383347029375378E42d,
+                +3.637970747803715E42d,
+                +9.889030935681123E42d,
+                +2.6881169167589747E43d,
+                +7.307059786371152E43d,
+                +1.986264756071962E44d,
+                +5.399227989109673E44d,
+                +1.467662348860426E45d,
+                +3.989519470441919E45d,
+                +1.0844638420493122E46d,
+                +2.9478781225754055E46d,
+                +8.013164089994031E46d,
+                +2.1782039447564253E47d,
+                +5.920972420778763E47d,
+                +1.609486943324346E48d,
+                +4.3750396394525074E48d,
+                +1.1892591576149107E49d,
+                +3.2327411123173475E49d,
+                +8.787501601904039E49d,
+                +2.3886908001521312E50d,
+                +6.493134033643613E50d,
+                +1.7650169203544438E51d,
+                +4.7978130078372714E51d,
+                +1.3041809768060802E52d,
+                +3.5451314095271004E52d,
+                +9.636666808527841E52d,
+                +2.6195174357581655E53d,
+                +7.120586694432509E53d,
+                +1.9355758655647052E54d,
+                +5.2614409704305464E54d,
+                +1.4302079642723736E55d,
+                +3.8877083524279136E55d,
+                +1.0567886837680406E56d,
+                +2.872649515690124E56d,
+                +7.808670894670738E56d,
+                +2.1226166967029073E57d,
+                +5.769871153180574E57d,
+                +1.568413405104933E58d,
+                +4.263390023436419E58d,
+                +1.1589095247718807E59d,
+                +3.150242850860434E59d,
+                +8.563247933339596E59d,
+                +2.3277319969498524E60d,
+                +6.327431953939798E60d,
+                +1.719974302355042E61d,
+                +4.675374788964851E61d,
+                +1.2708985520400816E62d,
+                +3.454660807101683E62d,
+                +9.390740355567705E62d,
+                +2.5526681615684215E63d,
+                +6.938871462941557E63d,
+                +1.8861808782043154E64d,
+                +5.1271712215233855E64d,
+                +1.3937096689052236E65d,
+                +3.7884955399150257E65d,
+                +1.0298199046367501E66d,
+                +2.799340708992666E66d,
+                +7.609396391563323E66d,
+                +2.0684484008569103E67d,
+                +5.622626080395226E67d,
+                +1.528388084444653E68d,
+                +4.1545899609113734E68d,
+                +1.1293346659459732E69d,
+                +3.069849599753188E69d,
+                +8.344717266683004E69d,
+                +2.268329019570017E70d,
+                +6.165958325782564E70d,
+                +1.676081191364984E71d,
+                +4.556060380835955E71d,
+                +1.2384658100355657E72d,
+                +3.3664990715562672E72d,
+                +9.15109220707761E72d,
+                +2.4875248571153216E73d,
+                +6.761793219649385E73d,
+                +1.8380461271305958E74d,
+                +4.996327312938759E74d,
+                +1.3581426848077408E75d,
+                +3.691814001080034E75d,
+                +1.0035391101975138E76d,
+                +2.7279024753382288E76d,
+                +7.415207287657125E76d,
+                +2.0156621983963848E77d,
+                +5.479138512760614E77d,
+                +1.4893842728520671E78d,
+                +4.048565732162643E78d,
+                +1.1005142643914475E79d,
+                +2.991508131437659E79d,
+                +8.131762373533769E79d,
+                +2.210442148596269E80d,
+                +6.008604166110734E80d,
+                +1.633308028614055E81d,
+                +4.439791652732591E81d,
+                +1.206860599814453E82d,
+                +3.280586734644871E82d,
+                +8.917559854082513E82d,
+                +2.4240442814945802E83d,
+                +6.589235682116406E83d,
+                +1.7911398904871E84d,
+                +4.86882298924053E84d,
+                +1.3234832005748183E85d,
+                +3.597600556519039E85d,
+                +9.77929222446451E85d,
+                +2.658286976862848E86d,
+                +7.225974166887662E86d,
+                +1.9642232209552433E87d,
+                +5.3393125705958075E87d,
+                +1.4513757076459615E88d,
+                +3.945247871835613E88d,
+                +1.0724295693252266E89d,
+                +2.915165904253785E89d,
+                +7.924242330665303E89d,
+                +2.1540322390343345E90d,
+                +5.855267177907345E90d,
+                +1.5916266807316476E91d,
+                +4.326489915443873E91d,
+                +1.1760619079592718E92d,
+                +3.1968677404735245E92d,
+                +8.689987517871135E92d,
+                +2.3621834216830225E93d,
+                +6.421080550439423E93d,
+                +1.7454306955949023E94d,
+                +4.744571892885607E94d,
+                +1.2897084285532175E95d,
+                +3.505791114318544E95d,
+                +9.529727908157224E95d,
+                +2.5904487437231458E96d,
+                +7.041568925985714E96d,
+                +1.9140971884979424E97d,
+                +5.203055142575272E97d,
+                +1.4143368931719686E98d,
+                +3.8445667684706366E98d,
+                +1.0450615121235744E99d,
+                +2.8407720200442806E99d,
+                +7.722018663521402E99d,
+                +2.0990624115923312E100d,
+                +5.705842978547001E100d,
+                +1.5510089388648915E101d,
+                +4.216079296087462E101d,
+                +1.1460491592124923E102d,
+                +3.1152847602082673E102d,
+                +8.468222063292654E102d,
+                +2.3019011105282883E103d,
+                +6.257216813084462E103d,
+                +1.7008878437355237E104d,
+                +4.62349260394851E104d,
+                +1.2567956334920216E105d,
+                +3.416324322370112E105d,
+                +9.286532888251822E105d,
+                +2.5243410574836706E106d,
+                +6.861870970598542E106d,
+                +1.8652499723625443E107d,
+                +5.070274654122399E107d,
+                +1.3782437251846782E108d,
+                +3.746454626411946E108d,
+                +1.0183920005400422E109d,
+                +2.768276122845335E109d,
+                +7.524954624697075E109d,
+                +2.0454950851007314E110d,
+                +5.56023190218245E110d,
+                +1.511427628805191E111d,
+                +4.1084862677372065E111d,
+                +1.1168024085164686E112d,
+                +3.0357834799588566E112d,
+                +8.252116273466952E112d,
+                +2.2431576057283144E113d,
+                +6.097534318207731E113d,
+                +1.65748157925005E114d,
+                +4.5055022172222453E114d,
+                +1.2247224482958058E115d,
+                +3.329140840363789E115d,
+                +9.049543313665034E115d,
+                +2.4599209935197392E116d,
+                +6.686758417135634E116d,
+                +1.817649308779104E117d,
+                +4.940883275207154E117d,
+                +1.3430713954289087E118d,
+                +3.6508464654683645E118d,
+                +9.924030156169606E118d,
+                +2.697631034485758E119d,
+                +7.332921137166064E119d,
+                +1.9932945470297703E120d,
+                +5.418336099279846E120d,
+                +1.472856595860236E121d,
+                +4.0036393271908754E121d,
+                +1.0883019300873278E122d,
+                +2.9583112936666607E122d,
+                +8.041523923017192E122d,
+                +2.1859129781586158E123d,
+                +5.941927186144745E123d,
+                +1.6151834292371802E124d,
+                +4.390523815859274E124d,
+                +1.1934680816813702E125d,
+                +3.2441826014060764E125d,
+                +8.81860282490643E125d,
+                +2.3971445233885962E126d,
+                +6.516115189736396E126d,
+                +1.7712635751001657E127d,
+                +4.814793918384117E127d,
+                +1.3087966177291396E128d,
+                +3.557678449715009E128d,
+                +9.670771210463886E128d,
+                +2.628788218289742E129d,
+                +7.145787619369324E129d,
+                +1.9424264981694277E130d,
+                +5.280062387569078E130d,
+                +1.4352697002457768E131d,
+                +3.901467289560222E131d,
+                +1.0605288965077546E132d,
+                +2.882816299252225E132d,
+                +7.836307815186044E132d,
+                +2.1301292155181736E133d,
+                +5.790291758828013E133d,
+                +1.573964437869041E134d,
+                +4.278478878300888E134d,
+                +1.1630112062985817E135d,
+                +3.1613917467297413E135d,
+                +8.593554223894477E135d,
+                +2.335970335559215E136d,
+                +6.349826172787151E136d,
+                +1.7260616357651607E137d,
+                +4.691921416188566E137d,
+                +1.2753966504932798E138d,
+                +3.466887271843006E138d,
+                +9.423976538577447E138d,
+                +2.561702766944378E139d,
+                +6.963429563637273E139d,
+                +1.892856346657855E140d,
+                +5.1453167686439515E140d,
+                +1.3986421289359558E141d,
+                +3.8019036618832785E141d,
+                +1.033464507572145E142d,
+                +2.809247950589945E142d,
+                +7.636326960498012E142d,
+                +2.075769060297565E143d,
+                +5.64252553828769E143d,
+                +1.5337974510118784E144d,
+                +4.169293918423203E144d,
+                +1.1333315586787883E145d,
+                +3.080714152600695E145d,
+                +8.374250298636991E145d,
+                +2.276357074042286E146d,
+                +6.187780443461367E146d,
+                +1.6820131331794073E147d,
+                +4.572185635487065E147d,
+                +1.2428488853188662E148d,
+                +3.378413594504258E148d,
+                +9.183480622172801E148d,
+                +2.4963286658278886E149d,
+                +6.785725312893433E149d,
+                +1.8445514681108982E150d,
+                +5.014010481958507E150d,
+                +1.3629491735708616E151d,
+                +3.7048805655699485E151d,
+                +1.0070909418550386E152d,
+                +2.7375567044077912E152d,
+                +7.441451374243517E152d,
+                +2.022795961737854E153d,
+                +5.4985298195094216E153d,
+                +1.494655405262451E154d,
+                +4.062894701808608E154d,
+                +1.1044092571980793E155d,
+                +3.002095574584687E155d,
+                +8.160542326793782E155d,
+                +2.218265110516721E156d,
+                +6.02987028472758E156d,
+                +1.6390888071605646E157d,
+                +4.455504920700703E157d,
+                +1.2111317421229415E158d,
+                +3.2921976772303727E158d,
+                +8.94912101169977E158d,
+                +2.432623425087251E159d,
+                +6.612555731556604E159d,
+                +1.7974788874847574E160d,
+                +4.8860545948985793E160d,
+                +1.328167263606087E161d,
+                +3.610333312791256E161d,
+                +9.813901863427107E161d,
+                +2.667695552814763E162d,
+                +7.251548346906463E162d,
+                +1.9711751621240536E163d,
+                +5.3582093498119173E163d,
+                +1.4565123573071036E164d,
+                +3.959211091077107E164d,
+                +1.0762251933089556E165d,
+                +2.9254832789181E165d,
+                +7.952287052787358E165d,
+                +2.161656025361765E166d,
+                +5.8759898326913254E166d,
+                +1.597259768214821E167d,
+                +4.3418021646459346E167d,
+                +1.1802241249113175E168d,
+                +3.2081817253680657E168d,
+                +8.720743087611513E168d,
+                +2.3705435424427623E169d,
+                +6.443805025317327E169d,
+                +1.7516078165936552E170d,
+                +4.7613641572445654E170d,
+                +1.2942728582966776E171d,
+                +3.518198614137319E171d,
+                +9.563454814394247E171d,
+                +2.5996166206245285E172d,
+                +7.066491077377918E172d,
+                +1.920871394985668E173d,
+                +5.221469250951617E173d,
+                +1.4193426880442385E174d,
+                +3.8581732071331E174d,
+                +1.0487601931965087E175d,
+                +2.850825930161946E175d,
+                +7.749348772180658E175d,
+                +2.1064911705560668E176d,
+                +5.726036941135634E176d,
+                +1.5564982816556894E177d,
+                +4.231000988846797E177d,
+                +1.1501053030837989E178d,
+                +3.1263099916916113E178d,
+                +8.498192212235393E178d,
+                +2.3100480183046895E179d,
+                +6.279361500971995E179d,
+                +1.7069074829463731E180d,
+                +4.63985600437427E180d,
+                +1.2612435745231905E181d,
+                +3.4284156709489884E181d,
+                +9.319400030019162E181d,
+                +2.5332752658571312E182d,
+                +6.88615578404537E182d,
+                +1.8718514371423056E183d,
+                +5.088219872370737E183d,
+                +1.3831214731781958E184d,
+                +3.759713966511158E184d,
+                +1.021996184153141E185d,
+                +2.778073442169904E185d,
+                +7.55158797540476E185d,
+                +2.0527342305586606E186d,
+                +5.579910641313343E186d,
+                +1.5167767828844167E187d,
+                +4.123026721295484E187d,
+                +1.1207549425651513E188d,
+                +3.0465278560980536E188d,
+                +8.281321669236493E188d,
+                +2.251096660331649E189d,
+                +6.119114404399683E189d,
+                +1.6633478556884994E190d,
+                +4.521448560089285E190d,
+                +1.2290570545894685E191d,
+                +3.340923580982338E191d,
+                +9.081571104550255E191d,
+                +2.468626868232408E192d,
+                +6.710424255583952E192d,
+                +1.8240823171621646E193d,
+                +4.958369974640573E193d,
+                +1.3478247120462365E194d,
+                +3.6637673548790206E194d,
+                +9.959152908532152E194d,
+                +2.707178052117959E195d,
+                +7.358873642076596E195d,
+                +2.0003490682463053E196d,
+                +5.4375131636754E196d,
+                +1.4780692924846082E197d,
+                +4.01780853635105E197d,
+                +1.0921536132159379E198d,
+                +2.968781250496917E198d,
+                +8.069984512111955E198d,
+                +2.193649279840519E199d,
+                +5.962956589227457E199d,
+                +1.620899738203635E200d,
+                +4.406062052965071E200d,
+                +1.1976919074588434E201d,
+                +3.2556641859513496E201d,
+                +8.849812639395597E201d,
+                +2.40562867677584E202d,
+                +6.539175932653188E202d,
+                +1.7775323307944624E203d,
+                +4.831833881898182E203d,
+                +1.3134287685114547E204d,
+                +3.5702693195009266E204d,
+                +9.704997606668411E204d,
+                +2.63809219778715E205d,
+                +7.171077244202293E205d,
+                +1.949300880034352E206d,
+                +5.298749302736127E206d,
+                +1.4403494631058154E207d,
+                +3.91527572177694E207d,
+                +1.0642823992403076E208d,
+                +2.8930193727937684E208d,
+                +7.8640411896421955E208d,
+                +2.1376680994038112E209d,
+                +5.8107841809216616E209d,
+                +1.5795351101531684E210d,
+                +4.293620869258453E210d,
+                +1.1671272667059652E211d,
+                +3.172580666390786E211d,
+                +8.623968972387222E211d,
+                +2.3442378838418366E212d,
+                +6.372298757235201E212d,
+                +1.7321703934464356E213d,
+                +4.708527306855985E213d,
+                +1.279910496643312E214d,
+                +3.479157135998568E214d,
+                +9.45732984079136E214d,
+                +2.5707689593428096E215d,
+                +6.988074107282322E215d,
+                +1.8995553996578656E216d,
+                +5.1635269305465607E216d,
+                +1.4035923083915864E217d,
+                +3.815359096108819E217d,
+                +1.0371220592190472E218d,
+                +2.819190456167585E218d,
+                +7.663353127378024E218d,
+                +2.083115484919861E219d,
+                +5.662495731848751E219d,
+                +1.5392257142577226E220d,
+                +4.184049381430498E220d,
+                +1.1373425785132867E221d,
+                +3.091617462831603E221d,
+                +8.403887374207366E221d,
+                +2.2844135610697528E222d,
+                +6.209679892802781E222d,
+                +1.6879660933816274E223d,
+                +4.588367423411997E223d,
+                +1.2472476068464461E224d,
+                +3.3903703993793316E224d,
+                +9.215982463319503E224d,
+                +2.5051637206758385E225d,
+                +6.809741127603255E225d,
+                +1.8510795864289367E226d,
+                +5.031755776868959E226d,
+                +1.3677729802316034E227d,
+                +3.7179924024793253E227d,
+                +1.0106552237522032E228d,
+                +2.7472456017809066E228d,
+                +7.467788172398272E228d,
+                +2.029955237703202E229d,
+                +5.517990469846618E229d,
+                +1.4999452522236406E230d,
+                +4.0772734783595525E230d,
+                +1.1083180046837618E231d,
+                +3.012720614547867E231d,
+                +8.18942426109357E231d,
+                +2.2261161215322043E232d,
+                +6.051211457626543E232d,
+                +1.6448897917725177E233d,
+                +4.471273900208441E233d,
+                +1.2154183152078517E234d,
+                +3.3038494682728794E234d,
+                +8.98079409878202E234d,
+                +2.4412328161430576E235d,
+                +6.63595840453991E235d,
+                +1.8038406914061554E236d,
+                +4.90334700062756E236d,
+                +1.3328680266667662E237d,
+                +3.623110695743118E237d,
+                +9.848636053777669E237d,
+                +2.677136737066629E238d,
+                +7.277212447141125E238d,
+                +1.978151484427976E239d,
+                +5.377173488599035E239d,
+                +1.4616672175682191E240d,
+                +3.973222981713661E240d,
+                +1.0800340064859439E241d,
+                +2.935837009891444E241d,
+                +7.980432566722885E241d,
+                +2.169306470354036E242d,
+                +5.896786161387733E242d,
+                +1.6029126916635028E243d,
+                +4.357168123448786E243d,
+                +1.1844011798406507E244d,
+                +3.2195361624179725E244d,
+                +8.751606149833694E244d,
+                +2.3789334438756013E245d,
+                +6.466611224443739E245d,
+                +1.7578073785142153E246d,
+                +4.7782149589194885E246d,
+                +1.2988535295611824E247d,
+                +3.5306502960727705E247d,
+                +9.597302512507479E247d,
+                +2.608817438130718E248d,
+                +7.091500562953208E248d,
+                +1.9276698418065647E249d,
+                +5.239949786641934E249d,
+                +1.42436589329759E250d,
+                +3.8718282216768776E250d,
+                +1.0524719896550007E251d,
+                +2.860915548426704E251d,
+                +7.77677492833005E251d,
+                +2.113946677051906E252d,
+                +5.7463023795153145E252d,
+                +1.56200679236425E253d,
+                +4.2459748085663055E253d,
+                +1.1541756557557508E254d,
+                +3.137374584307575E254d,
+                +8.528268445871411E254d,
+                +2.3182239583484444E255d,
+                +6.301585387776819E255d,
+                +1.7129486892266285E256d,
+                +4.6562769567905925E256d,
+                +1.26570724146049E257d,
+                +3.4405490416979487E257d,
+                +9.352382323649647E257d,
+                +2.54224113415832E258d,
+                +6.910528108396216E258d,
+                +1.8784760208391767E259d,
+                +5.106228040084293E259d,
+                +1.3880166914480165E260d,
+                +3.7730204737910044E260d,
+                +1.0256131352582533E261d,
+                +2.787906051540986E261d,
+                +7.578313650939932E261d,
+                +2.0599991793068063E262d,
+                +5.5996586041611455E262d,
+                +1.522145133131402E263d,
+                +4.137618951061827E263d,
+                +1.1247213964487372E264d,
+                +3.0573102223682595E264d,
+                +8.310629417537063E264d,
+                +2.2590636576955473E265d,
+                +6.1407711078356886E265d,
+                +1.6692346202615142E266d,
+                +4.5374504961394207E266d,
+                +1.2334070098307164E267d,
+                +3.3527476928456816E267d,
+                +9.113713162029408E267d,
+                +2.4773638527240193E268d,
+                +6.734172833429278E268d,
+                +1.8305382378470305E269d,
+                +4.9759187284770303E269d,
+                +1.352594940263854E270d,
+                +3.6767339705169146E270d,
+                +9.994400500679653E270d,
+                +2.716759624268743E271d,
+                +7.384918458508588E271d,
+                +2.007428933605617E272d,
+                +5.456757565532369E272d,
+                +1.4833003969415539E273d,
+                +4.0320284712983994E273d,
+                +1.096019026243815E274d,
+                +2.979288529962515E274d,
+                +8.098545495417704E274d,
+                +2.201412886580694E275d,
+                +5.984060832462728E275d,
+                +1.6266362950862408E276d,
+                +4.4216561713555547E276d,
+                +1.2019307065458128E277d,
+                +3.2671863888979078E277d,
+                +8.881133159512924E277d,
+                +2.4141423627760256E278d,
+                +6.562319473965767E278d,
+                +1.7838233889223407E279d,
+                +4.848934634563382E279d,
+                +1.3180771991576186E280d,
+                +3.5829049382293792E280d,
+                +9.739345931419228E280d,
+                +2.6474285478041252E281d,
+                +7.196457718729758E281d,
+                +1.956199868121249E282d,
+                +5.31750271790054E282d,
+                +1.4454470027638629E283d,
+                +3.929132560365955E283d,
+                +1.0680488848057261E284d,
+                +2.9032581477488686E284d,
+                +7.89187408872514E284d,
+                +2.1452336456259667E285d,
+                +5.831349876080173E285d,
+                +1.5851251724785243E286d,
+                +4.308816643345461E286d,
+                +1.1712579802975851E287d,
+                +3.1838092090922606E287d,
+                +8.654490685278886E287d,
+                +2.3525345191912968E288d,
+                +6.39485115791896E288d,
+                +1.7383009254496851E289d,
+                +4.725191397657393E289d,
+                +1.2844402232816276E290d,
+                +3.491470347090126E290d,
+                +9.490800658395667E290d,
+                +2.579867270991543E291d,
+                +7.012806239173502E291d,
+                +1.906278351789277E292d,
+                +5.181801397059486E292d,
+                +1.408559707497606E293d,
+                +3.8288623079292884E293d,
+                +1.0407926842436056E294d,
+                +2.829168201470791E294d,
+                +7.690475570840264E294d,
+                +2.0904882610105383E295d,
+                +5.68253547942899E295d,
+                +1.544673396032028E296d,
+                +4.1988574190754736E296d,
+                +1.1413677466646359E297d,
+                +3.102559332875688E297d,
+                +8.433630296371073E297d,
+                +2.292498520423419E298d,
+                +6.23165710486722E298d,
+                +1.6939399242810123E299d,
+                +4.604606371472047E299d,
+                +1.2516618713553432E300d,
+                +3.402369329874797E300d,
+                +9.248598815279678E300d,
+                +2.51402968559859E301d,
+                +6.833842035076675E301d,
+                +1.8576309291617257E302d,
+                +5.049564425991982E302d,
+                +1.3726137091534984E303d,
+                +3.7311513682845094E303d,
+                +1.0142320772726397E304d,
+                +2.7569686255975333E304d,
+                +7.494218049456063E304d,
+                +2.037139607241041E305d,
+                +5.5375196488302575E305d,
+                +1.5052539519895093E306d,
+                +4.091704288360009E306d,
+                +1.1122405335641184E307d,
+                +3.023383151402969E307d,
+                +8.218407798110846E307d,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+            };
+
+    /**
+     * Exponential evaluated at integer values, exp(x) = expIntTableA[x + EXP_INT_TABLE_MAX_INDEX] +
+     * expIntTableB[x+EXP_INT_TABLE_MAX_INDEX]
+     */
+    private static final double[] EXP_INT_B =
+            new double[] {
+                +0.0d,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                -1.76097684E-316d,
+                -2.44242319E-315d,
+                -9.879317845E-315d,
+                -1.3811462167E-314d,
+                +2.1775261204E-314d,
+                -1.4379095864E-313d,
+                +1.4219324087E-313d,
+                +1.00605438061E-312d,
+                -1.287101187097E-312d,
+                +5.33839690397E-312d,
+                -9.35130825405E-313d,
+                -4.15218681073E-311d,
+                +4.546040329134E-311d,
+                -1.57333572310673E-310d,
+                +1.05387548454467E-309d,
+                +2.095732474644446E-309d,
+                -2.62524392470767E-310d,
+                +5.86440876259637E-309d,
+                -2.401816502004675E-309d,
+                -2.2711230715729753E-308d,
+                +2.0670460065057715E-307d,
+                +3.436860020483706E-308d,
+                +2.0862243734177337E-306d,
+                -4.637025318037353E-306d,
+                +9.222671009756424E-306d,
+                +6.704597874020559E-305d,
+                +4.351284159444109E-305d,
+                +4.232889602759328E-304d,
+                +1.2840977763293412E-303d,
+                -2.6993478083348727E-303d,
+                -1.053265874779237E-303d,
+                +1.207746682843556E-303d,
+                +5.21281096513035E-303d,
+                +1.6515377082609677E-301d,
+                +3.3951607353932444E-301d,
+                +5.609418227003629E-301d,
+                +4.238775357914848E-300d,
+                -9.441842771290538E-300d,
+                -2.1745347282493023E-299d,
+                -6.203839803215248E-299d,
+                -5.617718879466363E-299d,
+                +5.2869976233132615E-298d,
+                -1.4300075619643524E-298d,
+                +4.3198234936686506E-297d,
+                -2.6448316331572387E-297d,
+                +4.315655444002347E-296d,
+                -7.253671992213344E-296d,
+                -1.1288398461391523E-295d,
+                -4.83901764243093E-296d,
+                +1.7407497662694827E-295d,
+                +1.1969717029666017E-294d,
+                -7.752519943329177E-294d,
+                -4.019569741253664E-293d,
+                -2.4467928392518484E-293d,
+                -1.0269233640424235E-292d,
+                -3.2330960700986594E-292d,
+                -1.440995270758115E-291d,
+                -3.726946038150935E-291d,
+                -1.3424576100819801E-291d,
+                -3.128894928199484E-290d,
+                -5.989337506920005E-290d,
+                -9.438168176533759E-290d,
+                -1.9220613500411237E-289d,
+                +2.1186736024949195E-289d,
+                +6.3015208029537436E-288d,
+                -8.168129112703755E-288d,
+                -1.6040513288090055E-287d,
+                -1.0809972724404233E-287d,
+                -3.080380385962424E-286d,
+                +2.6399157174374624E-286d,
+                +1.3317127674213423E-285d,
+                -3.5821668044872306E-285d,
+                +1.978536584535392E-284d,
+                +1.3399392455370071E-284d,
+                -2.870168560029448E-284d,
+                +3.5311184272663063E-283d,
+                -7.204247881190918E-283d,
+                +3.2425604548983798E-282d,
+                +3.913063150326019E-282d,
+                -2.260957518848075E-281d,
+                +3.807242187736102E-281d,
+                -5.095591405025083E-281d,
+                +2.3400625068490396E-280d,
+                -1.1564717694090882E-280d,
+                -3.517594695450786E-279d,
+                +6.666544384808297E-279d,
+                -9.204784113858607E-279d,
+                +4.8677119923665573E-278d,
+                +7.942176091555472E-278d,
+                -2.5113270522478854E-277d,
+                +5.332900939354667E-277d,
+                -3.491241408725929E-276d,
+                -2.1141094074221325E-276d,
+                +1.722049095222509E-275d,
+                +4.0430160253378594E-275d,
+                +1.9888195459082551E-274d,
+                +3.230089643550739E-275d,
+                +5.077824728028163E-274d,
+                -3.526547961682877E-274d,
+                -6.4376298274983765E-273d,
+                -2.5338279333399964E-272d,
+                -3.614847626733713E-272d,
+                +2.510812179067931E-272d,
+                +3.953806005373127E-272d,
+                +7.112596406315374E-272d,
+                -2.850217520533226E-270d,
+                -8.571477929711754E-270d,
+                +1.2902019831221148E-269d,
+                -6.978783784755863E-270d,
+                +9.89845486618531E-269d,
+                -3.538563171970534E-268d,
+                +3.537475449241181E-268d,
+                +3.6924578046381256E-267d,
+                +1.3555502536444713E-266d,
+                -1.1279742372661484E-266d,
+                +5.475072932318336E-266d,
+                -1.1679889049814275E-265d,
+                -8.946297908979776E-266d,
+                +1.0565816011650582E-264d,
+                -3.2161237736296753E-265d,
+                -6.022045553485609E-264d,
+                -2.0332050860436034E-263d,
+                -1.0488538406930105E-262d,
+                +1.6793752843984384E-262d,
+                +3.2558720916543104E-263d,
+                -1.9546569053899882E-262d,
+                +5.082190670014963E-262d,
+                -1.0188117475357564E-260d,
+                +3.7920054509691455E-261d,
+                -8.330969967504819E-260d,
+                -1.1623181434592597E-259d,
+                +9.09665088462258E-259d,
+                -1.56400149127482E-259d,
+                -7.796557225750673E-258d,
+                +6.751460509863465E-258d,
+                +7.243157658226935E-258d,
+                +1.2574668958946027E-256d,
+                +2.2678858131411216E-256d,
+                +5.1079306249351287E-256d,
+                -5.672261759108003E-257d,
+                +3.476539491009769E-256d,
+                -1.3481093992496937E-254d,
+                -3.314051560952014E-254d,
+                +7.408112967339146E-255d,
+                -7.164884605413269E-254d,
+                -6.456588023278983E-253d,
+                -1.4881197370811587E-252d,
+                +1.7534012237555307E-252d,
+                -1.3070101381473173E-251d,
+                +6.081420141954215E-251d,
+                +6.591143677421159E-251d,
+                +2.6917461073773043E-250d,
+                +3.683043641790553E-251d,
+                +1.2195076420741757E-249d,
+                -8.220283439582378E-249d,
+                +1.637852737426943E-248d,
+                -8.332543237340988E-249d,
+                +2.9581193516975647E-248d,
+                -1.7790661150204172E-247d,
+                -1.7809679916043692E-247d,
+                +8.378574405736031E-247d,
+                -2.883847036065813E-246d,
+                +1.3223776943337897E-245d,
+                +3.098547586845664E-245d,
+                -1.1036542789147287E-244d,
+                -5.7187703271582225E-244d,
+                -1.8058492822440396E-244d,
+                +4.4373726292703545E-243d,
+                -3.4631935816990754E-243d,
+                -1.82770041073856E-243d,
+                +3.845535085273936E-242d,
+                +8.446532344375812E-242d,
+                +2.7751016140238277E-242d,
+                +1.3158882241538003E-241d,
+                -3.579433051074272E-240d,
+                -6.151751570213211E-240d,
+                -2.990535475079021E-239d,
+                +2.3396028616528764E-239d,
+                +7.233790684263346E-239d,
+                +1.0847913100494912E-238d,
+                +7.103148400942551E-238d,
+                +3.463600299750966E-237d,
+                -4.873121855093712E-237d,
+                +1.3407295326570417E-236d,
+                +9.390271617387205E-237d,
+                -2.4767709454727603E-235d,
+                +3.205923535388443E-235d,
+                -1.0074984709952582E-234d,
+                +2.4747880175747574E-234d,
+                -5.146939682310558E-234d,
+                -2.827581009333298E-233d,
+                -3.0307641004671077E-233d,
+                +5.92044714050651E-233d,
+                -2.0582596893119236E-232d,
+                -6.58066591313112E-232d,
+                -4.869955151949929E-231d,
+                -5.763495903609913E-231d,
+                -2.3580462372762525E-230d,
+                +1.8559980428862584E-230d,
+                +2.854978560542175E-229d,
+                +5.637945686485334E-229d,
+                +2.1454644909004582E-228d,
+                -1.1918070206953359E-228d,
+                -5.021851606912854E-228d,
+                +3.861525553653117E-227d,
+                +6.533561982617909E-227d,
+                -3.015709444206057E-226d,
+                -5.042005018212734E-227d,
+                +1.5959614205422845E-225d,
+                +2.0402105689098835E-224d,
+                +5.164902728917601E-224d,
+                +9.981031744879876E-224d,
+                +4.0281104210095145E-223d,
+                +1.1158160971176672E-222d,
+                +2.0736172194624895E-222d,
+                +4.983162653734032E-222d,
+                +2.1753390051977871E-221d,
+                +3.969413618002761E-221d,
+                +1.3961255018698695E-220d,
+                +2.1290855095314206E-220d,
+                +1.1927747883417406E-219d,
+                +3.7264401117998796E-219d,
+                +9.318532410862293E-219d,
+                +2.3414841777613345E-218d,
+                +4.3791842770430786E-218d,
+                +1.7173159016511951E-217d,
+                +3.5037536832675478E-217d,
+                +1.4300098613455884E-216d,
+                +2.4189403362149483E-216d,
+                +9.306541421999056E-216d,
+                +3.442100456607687E-215d,
+                +5.94407068841904E-215d,
+                +2.0483260435783403E-214d,
+                +3.8410992889527954E-214d,
+                +1.2038281262953917E-213d,
+                +3.865007795216205E-213d,
+                +9.754659138599756E-213d,
+                +2.7653605770745684E-212d,
+                +5.359568079675375E-212d,
+                +2.61726605666378E-211d,
+                +5.054202073556894E-211d,
+                +8.707092668016246E-211d,
+                +1.4080573899148006E-210d,
+                +1.288124387778789E-209d,
+                +1.8639901642011898E-209d,
+                +6.076014540574561E-209d,
+                +1.798489141298457E-208d,
+                +2.1525406805994896E-208d,
+                +1.1864056832305874E-207d,
+                +2.1077440662171152E-207d,
+                +1.3784853708457332E-206d,
+                +1.6965806532093783E-206d,
+                +7.241626420445137E-206d,
+                +2.575584299085016E-205d,
+                +6.151951078101721E-205d,
+                +2.40652042118887E-204d,
+                +4.022633486003565E-204d,
+                +5.8840879519086286E-204d,
+                +3.2820308007277566E-203d,
+                +4.31880454864738E-203d,
+                +2.427240455243201E-202d,
+                +7.326955749884755E-202d,
+                +1.4310184489676175E-201d,
+                +4.464279133463661E-201d,
+                +4.895131474682867E-201d,
+                +4.48614966943544E-200d,
+                +8.924048768324976E-200d,
+                +2.5035535029701945E-199d,
+                +6.627829836338812E-199d,
+                +2.6066826304502746E-198d,
+                +8.042275310036546E-198d,
+                +2.115062964308555E-197d,
+                +4.413745413236018E-197d,
+                +1.644449394585716E-196d,
+                +3.138217752973845E-196d,
+                +7.48533983136081E-196d,
+                +2.613626422028823E-195d,
+                +3.6741841454219095E-195d,
+                +5.906102862953403E-195d,
+                +4.4940857547850743E-194d,
+                +5.840064709376958E-194d,
+                +3.087661273836024E-193d,
+                +4.995552216100365E-193d,
+                +1.991444798915497E-192d,
+                +7.097454751809522E-192d,
+                +2.0510193986749737E-191d,
+                +5.759440286608551E-191d,
+                +1.7013941257113314E-190d,
+                +2.1383323934483528E-190d,
+                +8.280292810015406E-190d,
+                +3.138655772049104E-189d,
+                +7.961506427685701E-189d,
+                +2.0579001228504997E-188d,
+                +7.530840351477639E-188d,
+                +1.4582863136475673E-187d,
+                +3.149267215638608E-187d,
+                +5.443114553057336E-187d,
+                +3.4672966834277804E-186d,
+                +7.374944406615125E-186d,
+                +2.7318417252599104E-185d,
+                +7.913674211949961E-185d,
+                +2.5217716516462005E-184d,
+                +4.0866585874353075E-184d,
+                +1.2087698972768686E-183d,
+                +3.7072473866919033E-183d,
+                +1.1333588840402273E-182d,
+                +1.61949812578045E-182d,
+                +6.567779607147072E-182d,
+                +2.422974840736314E-181d,
+                +2.551170809294396E-181d,
+                +1.0905890688083124E-180d,
+                +3.221279639653057E-180d,
+                +7.068244813489027E-180d,
+                +1.3752309224575428E-179d,
+                +7.20154303462761E-179d,
+                +1.5391707185581056E-178d,
+                +7.708777608683431E-178d,
+                +5.597398155472547E-178d,
+                +1.8487854656676722E-177d,
+                +1.0577249492414076E-176d,
+                +2.8926683313922764E-176d,
+                +4.090184282164232E-176d,
+                +1.6142943398013813E-175d,
+                +7.873864351702525E-175d,
+                +2.242630017261011E-174d,
+                +3.4637009373878283E-174d,
+                +1.5907089565090164E-173d,
+                +1.6985075903314236E-173d,
+                +1.1552273904608563E-172d,
+                +2.237894048535414E-172d,
+                +5.321990399912051E-172d,
+                +1.4106062639738257E-171d,
+                +2.9850404523368767E-171d,
+                +1.5683802588004895E-170d,
+                +4.880146806045633E-170d,
+                +1.1489352403441815E-169d,
+                +1.6401586605693734E-169d,
+                +8.29169700697816E-169d,
+                +1.0380723705441457E-168d,
+                +7.126414081261746E-168d,
+                +1.253325949455206E-167d,
+                +2.595079206183114E-167d,
+                +1.537490712803659E-166d,
+                +2.6338455225993276E-166d,
+                +7.994936425058567E-166d,
+                +1.5716634677516025E-165d,
+                +3.669404761339415E-165d,
+                +1.9941628263579332E-164d,
+                +4.5012079983352374E-164d,
+                +7.283163019991001E-164d,
+                +2.398038505188316E-163d,
+                +7.868666894503849E-163d,
+                +2.1478649410390003E-162d,
+                +8.306111510463843E-162d,
+                +1.5453160659068463E-161d,
+                -4.590496588813841E-162d,
+                +3.5449293983801232E-161d,
+                -1.0440854056870505E-160d,
+                -2.321064927632431E-160d,
+                +5.707867001443433E-160d,
+                -2.238614484037969E-159d,
+                +2.482282821883242E-159d,
+                -1.1508772192025259E-158d,
+                +1.9903990578876104E-158d,
+                -1.2116165315442256E-158d,
+                -2.9084557554502667E-157d,
+                -1.1211083853006645E-156d,
+                -1.309893394818129E-156d,
+                +4.2269712317468864E-156d,
+                -7.678973146281339E-156d,
+                +3.2021376921211934E-155d,
+                -7.08313012515209E-155d,
+                +1.944398214330544E-154d,
+                +1.1860061363751161E-153d,
+                +1.5234465914578058E-153d,
+                -2.9020908354550263E-153d,
+                +4.980100072851796E-153d,
+                +2.3101551448625578E-152d,
+                -1.1959241322537072E-151d,
+                -9.27398924154364E-153d,
+                +5.999390491704392E-152d,
+                +1.3373196561281372E-150d,
+                -1.0271780540759147E-150d,
+                +2.575620466387945E-150d,
+                -6.56250013356227E-149d,
+                -1.1961357917482867E-148d,
+                +5.5807813570926636E-148d,
+                +9.252840189663807E-148d,
+                -1.830335419852293E-147d,
+                +9.350990339947455E-147d,
+                -1.6072409939877762E-146d,
+                -2.5309995887229526E-146d,
+                -1.6014373376410622E-146d,
+                -3.303297758377758E-145d,
+                +1.5640419864850202E-145d,
+                +9.544642884951585E-145d,
+                -8.64864445321803E-144d,
+                +7.580392204597681E-144d,
+                +2.678334184447568E-143d,
+                -3.7269289985326055E-143d,
+                -2.851965258161176E-142d,
+                +7.243267286265823E-142d,
+                +4.4510805312036926E-141d,
+                +9.008499734799015E-141d,
+                +1.130435759928337E-140d,
+                -3.096539751496479E-140d,
+                -1.497405487919762E-139d,
+                +3.51519845948652E-139d,
+                -4.713790209541894E-139d,
+                +4.740753295616865E-138d,
+                +9.517570994930463E-138d,
+                -1.8842098029339485E-137d,
+                -3.825558165008403E-137d,
+                +1.1817638600528107E-136d,
+                -3.514601201473235E-136d,
+                -6.344612631552417E-136d,
+                -1.6754164785291923E-136d,
+                +4.445372986583078E-135d,
+                -3.89604237755475E-134d,
+                -1.0155552195374609E-134d,
+                +2.1858142063550155E-134d,
+                +3.497714990137842E-133d,
+                -7.635830383612894E-133d,
+                +1.2050744860079718E-132d,
+                -7.683019590615251E-133d,
+                -3.344806129021162E-131d,
+                -1.6737914131474577E-131d,
+                -4.30610076666344E-131d,
+                +5.184023388254773E-130d,
+                +2.6290763595212492E-129d,
+                +7.90041744728452E-130d,
+                -3.204473056113754E-129d,
+                -2.552517201762272E-128d,
+                +7.130134251490065E-128d,
+                -3.2244113258340395E-127d,
+                -1.064920993515727E-127d,
+                +2.7466520735457463E-126d,
+                +4.368312797746065E-126d,
+                +1.8802599072446818E-125d,
+                -4.257625799463564E-125d,
+                +5.491672256552995E-125d,
+                +3.7298611779671127E-124d,
+                +5.724180836308973E-124d,
+                +1.3861841053630075E-123d,
+                +4.2303826056297614E-123d,
+                +3.5335436928899096E-123d,
+                -2.522906629540626E-122d,
+                +1.0147808005267102E-121d,
+                +6.734406065735473E-122d,
+                -4.948973160958133E-121d,
+                +2.4256181927024344E-120d,
+                +4.9056283164780554E-120d,
+                +6.846440394397547E-120d,
+                +3.512747689569002E-119d,
+                -9.020907406701404E-119d,
+                +2.5718749916003624E-118d,
+                +4.3724191002977524E-119d,
+                +1.001455050575191E-117d,
+                -2.4442443105031435E-117d,
+                +2.38873950760028E-116d,
+                -4.831068747037129E-118d,
+                -5.148989321866988E-116d,
+                +1.7875271881514469E-115d,
+                -1.1821586412088555E-114d,
+                +4.43247726423679E-115d,
+                +4.634817120492781E-114d,
+                +1.671311907037975E-113d,
+                -4.595250028278979E-113d,
+                -5.905511605694905E-113d,
+                -1.3657642265608213E-112d,
+                +2.881416869529271E-112d,
+                +2.1253302469985373E-111d,
+                -5.301386276260592E-111d,
+                +1.4198782892306878E-112d,
+                -3.395494928605007E-110d,
+                +9.284633292147283E-110d,
+                -6.054133004896379E-110d,
+                -8.324100783330331E-109d,
+                -2.193190669794277E-108d,
+                +1.3613655394659198E-107d,
+                +6.463452607647978E-108d,
+                +1.0187183636134904E-106d,
+                +1.0705673935006142E-106d,
+                +2.509050608571019E-106d,
+                -1.5096182622106617E-105d,
+                +1.7794190449526737E-106d,
+                +1.2261246749706581E-104d,
+                +2.1377905661197194E-104d,
+                -2.2015877944429946E-104d,
+                +7.873970951802825E-104d,
+                -1.7999197335480384E-103d,
+                +1.0487383011058756E-105d,
+                -2.9988278531841556E-102d,
+                +4.7976477743232285E-102d,
+                +3.452316818502442E-102d,
+                +5.89953246760617E-101d,
+                -4.0785601577267006E-101d,
+                +2.7214076662438963E-100d,
+                +5.237807655758373E-100d,
+                +6.180972117932364E-99d,
+                -1.3019742873005683E-98d,
+                +4.501188264957416E-99d,
+                -2.4075054705261798E-98d,
+                +1.6503086546628772E-97d,
+                -6.878666975101243E-97d,
+                +1.196718116616528E-96d,
+                +2.476190162339265E-96d,
+                -7.1844969234484515E-96d,
+                +5.088386759261555E-95d,
+                +6.749368983223726E-95d,
+                +1.965737856765605E-94d,
+                -5.574080023496771E-94d,
+                +1.2493696959436675E-93d,
+                +8.533262777516794E-94d,
+                -7.225259028588793E-93d,
+                -7.340587186324432E-93d,
+                -3.482412195764625E-92d,
+                +3.4742610108480497E-91d,
+                -7.177274244758699E-91d,
+                +1.2736636153072213E-90d,
+                -5.730160886217076E-90d,
+                -1.545495535488274E-89d,
+                +1.1304179460367007E-89d,
+                +1.249260560756154E-88d,
+                -4.7439719382414206E-88d,
+                +7.164663249266942E-88d,
+                +1.7617425105337704E-87d,
+                +2.4175248449172035E-87d,
+                -1.043079666926483E-86d,
+                -2.8137609614326677E-86d,
+                -1.2091497144395591E-85d,
+                +3.7944631664558904E-85d,
+                -2.8144926807308225E-85d,
+                +3.9782728352520784E-85d,
+                +4.313978872469646E-84d,
+                +5.82190887044604E-84d,
+                +5.883385169571802E-83d,
+                +1.134857098306787E-82d,
+                +3.468049324128309E-82d,
+                +2.625423995658143E-82d,
+                -3.42827917465521E-81d,
+                +5.119461911618321E-81d,
+                -2.134387988350615E-80d,
+                -4.4703076268400615E-80d,
+                +4.806078883451016E-80d,
+                +2.3820250362443495E-79d,
+                -7.258551497833573E-79d,
+                -4.0297835558876335E-78d,
+                +2.1424166787650852E-78d,
+                -3.2117127164185917E-77d,
+                +4.8459153070935316E-77d,
+                -1.766924303914492E-76d,
+                -2.6921749814579492E-76d,
+                -4.1291070428848755E-76d,
+                +2.2086994756104319E-75d,
+                -7.814146377574201E-75d,
+                -1.9589778310104216E-74d,
+                +6.52658129486538E-74d,
+                +1.7804909509998055E-74d,
+                -4.1900132227036916E-73d,
+                +1.5705861683841123E-72d,
+                -1.904714978998808E-72d,
+                -7.81295459930537E-72d,
+                +2.818537910881676E-71d,
+                +5.840507984320445E-71d,
+                +1.7331720051707087E-70d,
+                +1.936946987935961E-70d,
+                -5.86517231340979E-71d,
+                -1.3277440528416646E-69d,
+                +1.9906256185827793E-69d,
+                +8.668714514280051E-69d,
+                +6.643105496829061E-69d,
+                -2.5436254170647032E-67d,
+                -4.8279217213630774E-67d,
+                -1.2640304072937576E-66d,
+                +3.51187258511716E-66d,
+                +1.4199501303738373E-65d,
+                -1.2351697477129173E-65d,
+                +7.0542365522414836E-65d,
+                +1.030593104122615E-64d,
+                -5.452692909894593E-65d,
+                -9.415506349675128E-64d,
+                -3.6206211446779087E-63d,
+                -1.6699188275658641E-62d,
+                +2.287280262665656E-62d,
+                +7.076135457313529E-62d,
+                +2.9019628518165404E-61d,
+                -3.1305705497720186E-61d,
+                +2.2978757040142953E-60d,
+                +1.2424439441817321E-60d,
+                +7.140343013236265E-60d,
+                +8.633726388939636E-60d,
+                +1.3483035574114863E-58d,
+                +1.653701058949654E-58d,
+                -8.939932297357388E-58d,
+                -1.395320103272191E-57d,
+                +6.440430933947252E-58d,
+                -1.681200826841738E-56d,
+                +3.9904382022898837E-56d,
+                -4.870518577546228E-56d,
+                -1.6990896855901115E-55d,
+                -6.751434891261518E-56d,
+                -1.669012123121194E-54d,
+                -4.079585475491198E-54d,
+                -1.3070436427679952E-53d,
+                -3.090028378908628E-53d,
+                +7.468160889798606E-53d,
+                +6.229095980733463E-53d,
+                +1.4794751934479566E-52d,
+                +1.7444373785853918E-51d,
+                -5.3681978363391484E-52d,
+                +2.71853394036182E-51d,
+                -1.3334367969274016E-50d,
+                -1.6958057665854177E-49d,
+                -1.452507231312146E-49d,
+                +3.3855429446520427E-49d,
+                +4.903687986212687E-49d,
+                +2.2185957416622524E-48d,
+                -9.924196700842429E-48d,
+                +4.285128462851149E-47d,
+                +3.076063086193525E-48d,
+                +4.102052341676543E-46d,
+                +1.1745772638457318E-45d,
+                -5.309047216809048E-47d,
+                +2.72972449891179E-45d,
+                -1.1748423022293739E-44d,
+                +6.626052626622228E-44d,
+                +3.0227439688367925E-44d,
+                -4.740494808228372E-43d,
+                +5.926057457356852E-43d,
+                +3.09768273342776E-42d,
+                -5.589493227475577E-42d,
+                -8.84908716783327E-42d,
+                +2.3684740712822874E-41d,
+                +1.4836491430755657E-40d,
+                +4.5878801324451396E-40d,
+                +1.0585156316103144E-39d,
+                +2.3805896467049493E-39d,
+                +1.0285082556185196E-38d,
+                +2.5187968110874885E-38d,
+                -1.4088399542613178E-38d,
+                -3.00901028043488E-38d,
+                +2.0089026801414973E-37d,
+                -1.3324111396289096E-36d,
+                +5.458481186294964E-36d,
+                -4.8402541351522003E-36d,
+                -1.3331969720555312E-35d,
+                -8.248332290732976E-35d,
+                -1.8349670703969982E-34d,
+                +6.403477383195494E-34d,
+                +3.7813691654412385E-34d,
+                +2.4621305031382827E-33d,
+                -5.634051826192439E-33d,
+                +3.817173955083142E-32d,
+                -6.038239639506472E-32d,
+                -2.130447095555397E-31d,
+                -6.824454861992054E-31d,
+                -1.3455801602048414E-30d,
+                -2.518642767561659E-30d,
+                +8.082792416221215E-30d,
+                +4.718103502869148E-29d,
+                -5.607991635038776E-29d,
+                -1.8042191582018579E-28d,
+                +6.989914264479507E-28d,
+                -2.9031739430339586E-28d,
+                +6.076820259849921E-27d,
+                -3.24981577480893E-27d,
+                -2.7648210023059463E-26d,
+                -9.785306155980342E-26d,
+                +1.241529292737115E-25d,
+                +3.0891604448087654E-25d,
+                +2.3451052074796954E-24d,
+                +6.574128018028633E-24d,
+                -1.3345148716925826E-23d,
+                +4.3594621428644293E-23d,
+                -5.678896695157704E-23d,
+                -4.676849004137386E-23d,
+                -2.281578975407609E-22d,
+                -3.144430608076357E-21d,
+                +5.662033727488754E-22d,
+                -4.30293375386492E-21d,
+                +4.985137671479376E-20d,
+                +1.657668502165438E-19d,
+                -3.3878706977811337E-19d,
+                -7.488022803661722E-19d,
+                +1.725039737424264E-18d,
+                -6.0275040161173166E-18d,
+                -8.081007442213538E-19d,
+                +2.9257892371894816E-17d,
+                +1.5231541295722552E-16d,
+                -1.1474026049124666E-17d,
+                +6.890372706231206E-16d,
+                +2.592721454922832E-15d,
+                -1.1253822296423454E-15d,
+                -2.650684279637763E-14d,
+                -4.107226967119929E-15d,
+                -3.130508064738312E-14d,
+                -6.729414275200856E-14d,
+                -1.6166170913368169E-12d,
+                -1.2059301405584488E-12d,
+                -1.2210091619211167E-11d,
+                +3.695372823623631E-12d,
+                +5.119220484478292E-11d,
+                -1.0857572226543142E-10d,
+                -4.6490379071586397E-10d,
+                -4.5810381714280557E-10d,
+                +1.4909756678328582E-9d,
+                -1.3155828104004438E-8d,
+                -9.149755188170102E-9d,
+                +0.0d,
+                +8.254840070411029E-8d,
+                -1.0681886149151956E-7d,
+                -3.359944163407147E-8d,
+                -2.1275002921718894E-6d,
+                +1.2129920353421116E-5d,
+                +2.1520078872608393E-5d,
+                +1.0178783359926372E-4d,
+                -2.077077172525637E-5d,
+                -5.67996159922899E-5d,
+                +9.510567165169581E-4d,
+                +0.0010901978184553272d,
+                +0.010169003920808009d,
+                +0.017008920503326107d,
+                +0.03416477677774927d,
+                -0.1275278893606981d,
+                +0.5205078726367633d,
+                +0.7535752982147762d,
+                +1.1373305111387886d,
+                -3.036812739155085d,
+                +11.409790277969124d,
+                -9.516785302789955d,
+                -49.86840843831867d,
+                -393.7510973999651d,
+                -686.1565277058598d,
+                +4617.385872524165d,
+                -11563.161235730215d,
+                -8230.201383316231d,
+                -34460.52482632287d,
+                +50744.04207438878d,
+                +357908.46214699093d,
+                +1936607.425231087d,
+                +3222936.695160983d,
+                +5477052.0646243105d,
+                -3.517545711859706E7d,
+                -1.2693418527187027E8d,
+                -2.5316384477288628E8d,
+                -1.6436423669122624E8d,
+                +4.0889180422033095E8d,
+                +4.968829330953611E9d,
+                -3.503399598592085E9d,
+                +1.905394922122271E10d,
+                +1.0361722296739479E11d,
+                -5.806792575852521E10d,
+                +2.3454138776381036E11d,
+                -1.718446464587963E12d,
+                -1.0946634815588584E12d,
+                +1.6889383928999305E13d,
+                -3.784600043778247E13d,
+                +7.270965670658928E13d,
+                -4.9202842786896806E14d,
+                +4.597700093952774E14d,
+                +2.6113557852262235E15d,
+                -4.544525556171388E15d,
+                -9.517971970450354E15d,
+                -2.0634857819227416E16d,
+                -9.7143113104549808E16d,
+                -2.2667083759873216E16d,
+                -7.2285665164439578E17d,
+                +4.1215410760803866E18d,
+                +8.5807488300972206E18d,
+                +1.530436781375042E19d,
+                -1.5453111533064765E19d,
+                -1.0633845571643594E20d,
+                -3.512380426745336E20d,
+                +3.7734658676841284E20d,
+                -3.855478664503271E21d,
+                +7.984485303520287E21d,
+                -1.2296934902142301E22d,
+                +1.042139023692827E22d,
+                +1.2167897656061312E23d,
+                +9.22064170155394E22d,
+                +3.965171513035854E23d,
+                -4.135121057126514E24d,
+                -7.944341754299148E24d,
+                +1.4715152230577016E25d,
+                -3.0635272288480756E25d,
+                -9.54468158713835E25d,
+                +1.5411775738825048E25d,
+                -8.274711842374368E26d,
+                -1.0028324930788433E27d,
+                +5.189062091114782E27d,
+                -2.8583500869462184E28d,
+                -5.198295198128238E28d,
+                +2.9758750368256437E29d,
+                +3.216046320616945E29d,
+                -1.7846700158234043E30d,
+                +3.847174961282827E30d,
+                +9.026991921214922E30d,
+                +4.1358029739592175E30d,
+                -6.461509354879894E29d,
+                +9.704297297526684E31d,
+                +2.9731739067444943E32d,
+                +9.97728609663656E32d,
+                +3.1149346370027763E33d,
+                +2.0051635097366476E34d,
+                +2.819272221032373E34d,
+                +1.6266731695798413E34d,
+                +1.998050894021586E35d,
+                -6.1633417615076335E35d,
+                +2.2505716077585116E36d,
+                +1.9299691540987203E36d,
+                +8.006569251375383E36d,
+                -3.785295042408568E37d,
+                -1.1870498357197593E38d,
+                +1.0010529668998112E38d,
+                +1.3240710866573994E38d,
+                +2.6888010385137123E39d,
+                +1.7400655988987023E39d,
+                -6.402740469853475E39d,
+                -3.93114092562274E40d,
+                +1.2363717201084252E41d,
+                -1.9219116633978794E41d,
+                -1.347867098583136E42d,
+                +7.87675118338788E41d,
+                +3.3932984011177642E41d,
+                -1.9872713979884691E43d,
+                +2.220208491349658E43d,
+                -3.466267817480825E43d,
+                +3.19462030745197E44d,
+                -9.841244788104406E44d,
+                -2.2676593395522725E45d,
+                -1.1349246400274207E46d,
+                -1.1700910284427406E46d,
+                -3.6754317105801715E46d,
+                +1.7647101734915075E47d,
+                +2.122358392979746E47d,
+                +3.156243682143956E47d,
+                +5.356668151937413E47d,
+                +2.7668218233914262E48d,
+                +3.5127708120698784E48d,
+                +1.7884841356632925E49d,
+                +1.716531820904728E50d,
+                -2.9114757102866277E50d,
+                +1.0657703081219677E51d,
+                -7.512169809356372E50d,
+                +1.764200470879736E51d,
+                -1.0088898215431471E52d,
+                -3.1085734725176E52d,
+                +4.3529009584292495E52d,
+                -2.467842129213774E53d,
+                -3.9317379627195146E53d,
+                -4.332335454045836E52d,
+                +7.979013724931926E54d,
+                -1.5038413653121357E55d,
+                +9.310799925566843E55d,
+                -2.2042966348036592E55d,
+                -4.518315366841937E55d,
+                -6.971366338144781E56d,
+                -2.0461505570781806E57d,
+                -8.823884392655312E57d,
+                -1.1264032993918548E58d,
+                -7.692065092509875E58d,
+                -1.8472516879728875E59d,
+                +8.72220314694275E58d,
+                +1.6525336989036362E59d,
+                -3.343201925128334E60d,
+                +5.493352163155986E60d,
+                -2.548073509300398E61d,
+                -9.566541624209933E61d,
+                +4.0891054447206644E61d,
+                -7.724182294653349E62d,
+                +1.0143022354947225E63d,
+                -4.952031310451961E63d,
+                -7.877410133454722E63d,
+                +4.505432606253564E64d,
+                -7.330635250808021E64d,
+                -1.642361029990822E65d,
+                +5.982180242124184E65d,
+                +7.120242132370469E65d,
+                +5.908356249789671E66d,
+                -2.8477710945673134E65d,
+                +6.65688196961235E66d,
+                -9.233295580238604E67d,
+                +3.2850043261803593E68d,
+                +7.041681569694413E68d,
+                -1.5652761725518397E69d,
+                +1.5377053215489084E68d,
+                +1.282130763903269E70d,
+                -2.380286345847567E70d,
+                -7.207022875977515E70d,
+                +2.7641662602473095E71d,
+                +7.685235201534525E71d,
+                +4.3239378585884645E70d,
+                -1.6840562544109314E72d,
+                -5.04128025464686E71d,
+                +5.4557485189210095E73d,
+                +7.160277784358221E73d,
+                +7.636179075087608E73d,
+                -8.18804507680012E74d,
+                +2.807397988979441E75d,
+                +2.165163304600171E75d,
+                -1.3208450062862734E76d,
+                -5.1939252391404724E76d,
+                -6.985952908805853E76d,
+                -1.6259920998287064E77d,
+                +6.098975200926637E77d,
+                -5.63383579957466E77d,
+                -1.5876819186852907E78d,
+                +2.1487475413123092E79d,
+                -3.987619123706934E79d,
+                +9.772655251656639E79d,
+                -1.638756156057952E79d,
+                -7.83892088580041E80d,
+                +1.274413296252691E81d,
+                +2.51946651720982E81d,
+                -2.516866097506943E81d,
+                +1.053956282234684E82d,
+                +1.8279051206232177E83d,
+                +1.2250764591564252E82d,
+                -4.0353723442917463E83d,
+                -1.4121324224340735E84d,
+                -5.45287716696021E84d,
+                -1.7514953095665195E85d,
+                -5.0706081370522526E85d,
+                -4.35799392139009E85d,
+                -3.982538093450217E86d,
+                -1.4591838284752642E87d,
+                +2.5313735821872488E87d,
+                -3.718501227185903E86d,
+                -1.3907979640327008E88d,
+                -5.79002114093961E86d,
+                -1.2500675565781447E89d,
+                +4.8182788286170926E89d,
+                -1.7198866036687559E90d,
+                -4.690417668647599E88d,
+                +1.3020631859056421E91d,
+                -1.3850458263351744E91d,
+                +4.87301010703588E91d,
+                -1.695546877943826E92d,
+                -1.6353756659909833E92d,
+                -1.5483926773679628E93d,
+                -1.8921091400297595E93d,
+                -6.183525570536406E93d,
+                -4.987913342551977E93d,
+                +1.0186485886120274E93d,
+                -1.5343120819745468E95d,
+                -5.262123923229857E95d,
+                +1.618327917706804E96d,
+                -4.135185828158998E96d,
+                -8.016793741945299E96d,
+                -3.0399439534134115E97d,
+                -1.2319346292749103E98d,
+                +7.536337311795176E97d,
+                -3.577715974851322E98d,
+                +2.0521614818695524E99d,
+                +1.2627736197958951E98d,
+                -5.206910481915062E99d,
+                +3.0974593993948837E100d,
+                -9.522726334561169E100d,
+                -1.1909272509710985E100d,
+                -5.056512677995137E101d,
+                +2.0902045062932175E102d,
+                +6.243669516810509E102d,
+                -1.7375090618655787E103d,
+                -2.5445477450140954E103d,
+                +3.619891246849381E103d,
+                +8.90737333900943E103d,
+                -2.7897360297480367E104d,
+                +1.3725786770437066E105d,
+                -8.316530604593264E105d,
+                -6.054541568735673E105d,
+                +7.523374196797555E105d,
+                +1.1475955030427985E107d,
+                +1.5260756679495707E107d,
+                +7.370294848920685E107d,
+                +1.3608995799112174E108d,
+                +1.0700758858011432E108d,
+                -4.989318918773146E108d,
+                -1.6629755787634093E108d,
+                +7.635999584053557E109d,
+                +1.892621828736983E109d,
+                -6.793094743406533E110d,
+                -8.160628910742724E110d,
+                -7.724219106106896E111d,
+                -1.6059226011778748E112d,
+                -1.5277127454062126E112d,
+                +3.911086668967361E112d,
+                +3.529920406834134E113d,
+                -4.3991443996021166E113d,
+                -1.2631909085915044E114d,
+                +3.8656278695544835E114d,
+                +1.71845288713123E115d,
+                +3.7660598745907915E115d,
+                -4.048086182363988E115d,
+                +2.3093822298965837E116d,
+                -9.684925795536813E116d,
+                -3.137992585221854E117d,
+                -5.637415935329794E117d,
+                -1.5536658521931418E118d,
+                -6.336314643222911E118d,
+                +8.550658957115427E118d,
+                -5.591880480212007E119d,
+                +2.4137404318673354E119d,
+                -2.631656656397244E120d,
+                -7.653117429165879E119d,
+                -4.073965591445897E121d,
+                +3.634781057940233E121d,
+                +4.537273754534966E121d,
+                -2.5138919966097735E122d,
+                -1.0292817180691822E123d,
+                -1.4265564976097062E122d,
+                +6.000235114895513E123d,
+                +4.186590347846346E124d,
+                -1.8950538406321535E124d,
+                +7.716762345695022E124d,
+                -4.443798187035849E125d,
+                -2.268994961992292E125d,
+                -2.8169291774231604E126d,
+                -2.749127978087685E126d,
+                -2.2929764629585683E126d,
+                -7.369842361872221E127d,
+                +2.81312841469177E128d,
+                +2.7856896414497757E128d,
+                -3.096733638475319E128d,
+                -5.4199510725063615E129d,
+                -7.315860999413894E129d,
+                +3.6424644535156437E130d,
+                -7.886250961456327E130d,
+                +5.289988151341401E130d,
+                +2.7758613753516344E131d,
+                -2.738246981762776E132d,
+                -2.2667181460478093E132d,
+                -3.614672661225457E131d,
+                +2.325337720526947E133d,
+                +4.16603235883392E133d,
+                -6.50348962894948E133d,
+                +3.851445905038431E134d,
+                -5.46060534001412E134d,
+                +5.4679180659102885E135d,
+                -3.037477806841494E135d,
+                -3.0417051809209134E136d,
+                -6.995964550587914E136d,
+                -3.6897084415718804E137d,
+                -6.938000231893302E137d,
+                +2.403806217004454E138d,
+                -3.4552363953199905E138d,
+                +7.3409917428393E138d,
+                -1.7445917446236717E139d,
+                -6.680679913078676E139d,
+                -8.193572619487537E139d,
+                +5.337290292186291E139d,
+                -3.951314467739045E140d,
+                -4.4662073456574476E141d,
+                +6.249381778908997E141d,
+                -2.928362616578011E142d,
+                -1.6661676835672304E143d,
+                -1.974465323891493E143d,
+                +1.3083870531380308E144d,
+                -2.382825271750576E144d,
+                -5.4826958838142734E144d,
+                +1.5340733916570804E145d,
+                -3.1327120557842516E145d,
+                +1.5790297768522832E146d,
+                +1.1518771984292262E146d,
+                -4.789917000227385E145d,
+                -8.689594184775204E146d,
+                +3.0680417869552433E146d,
+                +4.877860620031438E147d,
+                -3.4650891244084597E148d,
+                +1.8702183451052442E149d,
+                -3.5727227900139915E148d,
+                -1.3457821696677932E150d,
+                +3.3212950284273017E149d,
+                +7.316033240396569E150d,
+                -7.187723217018267E150d,
+                -8.537194547485455E150d,
+                -1.4561530066010593E152d,
+                -7.548155147049997E151d,
+                +1.0047353208353007E153d,
+                -1.2489460589853119E153d,
+                +4.426120229279107E153d,
+                -2.5466223330961086E154d,
+                +8.831699889789037E154d,
+                -2.0258084311749475E155d,
+                -5.525009099476396E155d,
+                -1.0235056525096769E156d,
+                -4.117971654572494E154d,
+                -4.7559175309753334E156d,
+                -1.4656240137098836E157d,
+                -7.675790582869644E157d,
+                -1.0126616322947826E158d,
+                +7.084865265284368E158d,
+                -9.374695893307895E158d,
+                +2.05597910889115E159d,
+                -7.368602086210704E159d,
+                -1.6167825196198978E160d,
+                +2.3832096207000712E160d,
+                +1.3166970112139726E161d,
+                -6.432337568761393E161d,
+                +2.9279594746502846E161d,
+                +4.8926595743317624E162d,
+                +1.2704793774453618E163d,
+                -1.1345910784680524E163d,
+                +7.75933511025868E163d,
+                -1.1441115218462356E163d,
+                +5.162248481759758E164d,
+                +6.362563919556132E164d,
+                -2.8362173224732088E165d,
+                -4.342161053332263E165d,
+                +4.388125271425036E166d,
+                -7.049068240916723E166d,
+                +3.8520227881415595E166d,
+                +2.9274120974020826E167d,
+                -7.500936767542933E167d,
+                -6.540181860667302E168d,
+                +4.664436780622191E168d,
+                -1.436111169285268E169d,
+                -1.0407581736224179E170d,
+                -2.7670181051374297E170d,
+                -6.788169932297778E170d,
+                +1.6997387217850427E171d,
+                -1.0965324942770584E171d,
+                +9.841563119484623E171d,
+                +3.175748919314254E172d,
+                +2.9621181706527444E172d,
+                -3.30101656090905E173d,
+                -3.791840683760427E173d,
+                -2.841347842666459E174d,
+                -7.836327226971707E174d,
+                +9.650358667643114E174d,
+                +5.9994277301267294E175d,
+                -6.0490084078440215E175d,
+                -2.8964095485948707E176d,
+                +9.916187343252014E175d,
+                +2.7535627955313556E176d,
+                +3.886891475472745E177d,
+                +3.1962472803616787E178d,
+                -5.50599549115449E178d,
+                +5.672812341879918E178d,
+                -3.295268490032475E179d,
+                +9.761163062156018E179d,
+                +3.107837179570674E180d,
+                +3.3894811576571423E179d,
+                -5.235397688850367E180d,
+                -5.004237248003625E181d,
+                -1.7544995191195304E182d,
+                +2.645622651144787E182d,
+                -3.459885432869825E182d,
+                -4.0361435606199565E183d,
+                -1.8382923511801317E183d,
+                -1.7332235571505177E184d,
+                +2.847653455671381E184d,
+                +1.7991060813894693E185d,
+                -2.0937429891059164E185d,
+                +5.744446753652847E185d,
+                -2.1349396267483754E184d,
+                -1.2542332720182776E186d,
+                +3.3730714236579374E186d,
+                -5.923734606208998E187d,
+                +2.24669039465627E188d,
+                -1.2588742703536392E188d,
+                +1.474522484905093E189d,
+                -2.4006971787803736E189d,
+                -3.52597540499141E189d,
+                +2.6676722922838097E190d,
+                +5.27579825970359E190d,
+                +2.1360492104281465E191d,
+                +1.9442210982008953E191d,
+                -1.4691239161932232E190d,
+                +3.8218180377739526E192d,
+                +1.9722862688653467E192d,
+                +3.047601928063002E193d,
+                +1.6747356805175311E193d,
+                +7.710512446969693E192d,
+                +1.7780021277684035E194d,
+                -1.4015110811648513E195d,
+                +4.0447634595724164E195d,
+                +9.023639664212642E195d,
+                +1.976868146639626E196d,
+                -9.084495133765657E196d,
+                -1.2023077889892748E196d,
+                +5.7455368446308694E197d,
+                -1.7766273910482863E198d,
+                +3.5590470673352285E198d,
+                +1.1304970373249033E199d,
+                +1.6496143246803731E199d,
+                -2.394588390685223E199d,
+                -1.4677321100833294E199d,
+                -1.1843870433971731E201d,
+                -1.8853982316037226E201d,
+                +2.8829871423339434E201d,
+                +5.369687677705385E200d,
+                +1.8356062677502141E202d,
+                -1.5544655377217875E203d,
+                +2.955364187248884E203d,
+                -2.7651059253677425E203d,
+                +9.903174064539538E203d,
+                -3.284204788892967E204d,
+                -1.5843229740595697E205d,
+                +5.333371443528904E204d,
+                +1.2781631468016048E205d,
+                +3.2188292385399854E205d,
+                -6.619064395428225E206d,
+                +1.291561142865928E207d,
+                +1.3142988156905172E207d,
+                -1.3841980097978606E208d,
+                +6.380177790989479E207d,
+                +1.0386032577072378E209d,
+                +2.7681631086098026E209d,
+                -9.053874899534375E209d,
+                +1.2424707839848734E210d,
+                +1.045546633850141E211d,
+                -1.2448938139338362E211d,
+                +7.221902646057552E211d,
+                +6.651345415954053E211d,
+                -5.8180712702152444E212d,
+                +5.275183961165903E212d,
+                +5.092753117288608E212d,
+                -2.437796532151255E213d,
+                +1.3480763914637323E214d,
+                +5.619995933180841E214d,
+                +2.547000388735681E214d,
+                +4.817319356453926E214d,
+                -7.897146442236022E215d,
+                -7.93844120619577E215d,
+                -4.9489938500591624E216d,
+                -2.862720607805682E216d,
+                -2.9275804461593914E217d,
+                -3.411186219855533E217d,
+                -2.0110092718356274E218d,
+                -8.472642266772353E218d,
+                -4.357990742470246E217d,
+                +4.793444363780116E219d,
+                +1.6544084224626834E220d,
+                -6.017988576347111E220d,
+                -3.580397221598409E220d,
+                -4.7208848667217906E221d,
+                -7.724899660259369E221d,
+                -2.4459728627968634E222d,
+                +3.667348665023154E221d,
+                +4.544122762558404E223d,
+                -4.0573420909530794E223d,
+                -3.2552002992257195E223d,
+                -6.488296536838142E224d,
+                +1.7544839352461719E224d,
+                -4.0873400635183405E225d,
+                -8.833499967268279E225d,
+                -1.0953484767704112E226d,
+                -8.56825295972308E226d,
+                -1.8097633115378247E227d,
+                -6.171564449018882E227d,
+                -4.351843341274115E227d,
+                +2.8032429752543687E228d,
+                -1.0065901934522996E229d,
+                +9.863720960170636E228d,
+                -9.481088691357648E229d,
+                -1.6843492713373762E229d,
+                -1.3282890219894906E230d,
+                +6.883577595238845E230d,
+                -1.153577281189635E231d,
+                -8.009548754642203E231d,
+                -4.722612904888278E232d,
+                -4.768909872963015E232d,
+                +3.2542391242036633E233d,
+                +6.513425781583774E233d,
+                -1.8889614379831606E233d,
+                -2.227647301474917E234d,
+                -4.7971208532986115E234d,
+                +6.693500938105557E234d,
+                -6.587776621471115E234d,
+                +3.0099905634916516E236d,
+                -4.6694407626686244E235d,
+                +2.965546585110978E236d,
+                +5.771457643937893E237d,
+                -9.029878114318277E237d,
+                +8.169926810324408E237d,
+                -1.779945804977441E239d,
+                +4.1218749988429474E239d,
+                +7.201319954099161E239d,
+                -1.530845432304069E240d,
+                -3.861762510530086E240d,
+                -2.4090696463777446E241d,
+                -1.8196842273916379E241d,
+                -1.7959243076374794E242d,
+                -3.7257346819782323E242d,
+                +3.413310324247329E242d,
+                -2.0406580894051073E243d,
+                -1.5335923091350053E243d,
+                -1.056727406551016E244d,
+                -4.6753408714233723E244d,
+                -2.0697130057384643E245d,
+                -1.0356006160554071E245d,
+                +1.1339195187304043E246d,
+                +1.792783182582235E246d,
+                +9.599214853681978E245d,
+                +1.5367645598839362E247d,
+                +2.934570385464815E247d,
+                -1.6411525886171892E248d,
+                +2.2638862982382794E248d,
+                -1.2268014119628852E249d,
+                +4.737693450915584E247d,
+                +6.3818993714899675E249d,
+                +1.2639113706171572E250d,
+                -4.011320021817099E249d,
+                -5.2744376732859406E250d,
+                -3.732266217624991E251d,
+                +1.7591819833844019E252d,
+                -3.292458622014749E252d,
+                -9.161340309319204E252d,
+                -1.728610646009749E253d,
+                +1.1698424008604891E254d,
+                -1.8494343291160577E254d,
+                +2.0568656302182574E254d,
+                +1.0537591246531136E255d,
+                +1.803052068234866E254d,
+                -1.053036399720808E256d,
+                +2.1836166619192508E256d,
+                +1.0368403169781264E257d,
+                -2.0648015610276362E257d,
+                +8.426174035728768E257d,
+                -1.3577357192972777E258d,
+                +2.1313950901331177E258d,
+                +8.919141843592823E258d,
+                -1.1800039972549816E259d,
+                -1.1878772398311421E260d,
+                -1.538273497873993E260d,
+                -4.51305093266001E260d,
+                +1.1241179396053055E261d,
+                +6.154786508667658E261d,
+                -1.0626125049032536E262d,
+                -1.8908603201210102E262d,
+                -4.571195152299358E262d,
+                +1.526100002923062E263d,
+                -9.457084582570225E263d,
+                -1.5460500618825853E264d,
+                -5.598276199126451E264d,
+                -1.2074097381167957E265d,
+                -3.015972957475025E265d,
+                +1.4345106852061226E265d,
+                +8.28479585346867E265d,
+                -3.118741081244705E266d,
+                -1.2054747399765794E266d,
+                +3.4454766202661184E267d,
+                +1.1279135096919439E268d,
+                +1.2066382528772518E268d,
+                +1.1984128162292276E269d,
+                +3.685169705587367E268d,
+                +6.570047690198998E269d,
+                +1.8836492887460383E270d,
+                +7.4364594917181125E270d,
+                +1.2773080633674971E271d,
+                +1.8928981707279692E271d,
+                +4.039437286589528E271d,
+                +1.785277385538302E272d,
+                -6.017681359527226E272d,
+                +1.9716943051755635E273d,
+                -8.772048092842086E271d,
+                +1.5645672698520312E274d,
+                -3.7979660725865874E274d,
+                +5.324902289537048E274d,
+                -1.8806716685063293E274d,
+                +9.320900373401115E275d,
+                +1.4615985810260016E275d,
+                +8.321226457219046E276d,
+                -4.608112855795952E276d,
+                -3.476352191116455E277d,
+                +5.266381689434054E277d,
+                -9.622106063561645E277d,
+                +4.1719443712336026E278d,
+                +4.222411269063919E279d,
+                -6.714376022102489E279d,
+                -1.0732735585199074E280d,
+                -2.5866883048437488E280d,
+                -1.1306860837934988E281d,
+                +3.690690354793168E281d,
+                -5.5299180508885456E281d,
+                +2.7006726968568243E282d,
+                +4.135457669031131E282d,
+                +2.8401159516008676E283d,
+                +5.127265762024798E283d,
+                -3.4893601256685762E283d,
+                -1.145160459652136E283d,
+                +2.1742808735341656E284d,
+                +4.656972469326391E285d,
+                +7.672307991205681E285d,
+                +1.5781599575584034E286d,
+                +4.218682431618625E286d,
+                -2.4602260687026867E287d,
+                +2.7211316452521414E287d,
+                -1.8740018211089393E288d,
+                +2.6367639658206183E288d,
+                -3.102678910525039E288d,
+                +1.1992295328636466E289d,
+                +6.8190133180135345E289d,
+                +5.783203879030497E289d,
+                +5.171047077293295E290d,
+                +1.8396930096213817E290d,
+                +1.4977047507315718E290d,
+                +1.0672499803427623E292d,
+                +3.3310942289102464E291d,
+                -7.962256961838823E292d,
+                +1.7396889119023863E293d,
+                +3.8072183820435085E293d,
+                +2.2772059538865722E294d,
+                -2.0549866377878678E294d,
+                -1.2277120342804144E295d,
+                -3.609949022969024E295d,
+                +1.1479863663699871E296d,
+                -1.5314373779304356E296d,
+                -2.2537635160762597E296d,
+                -6.1370690793508674E296d,
+                -4.996854125490041E297d,
+                -6.883499809714189E297d,
+                -2.595456638706416E298d,
+                -1.1892631528580186E299d,
+                -1.4672600326020399E299d,
+                -3.200068509818696E299d,
+                -7.126913872617518E298d,
+                -3.3655587417265094E300d,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+                Double.NaN,
+            };
+
+    /**
+     * Exponential over the range of 0 - 1 in increments of 2^-10 exp(x/1024) = expFracTableA[x] +
+     * expFracTableB[x]. 1024 = 2^10
+     */
+    private static final double[] EXP_FRAC_A =
+            new double[] {
+                +1.0d,
+                +1.0009770393371582d,
+                +1.0019550323486328d,
+                +1.0029339790344238d,
+                +1.0039138793945312d,
+                +1.004894733428955d,
+                +1.0058765411376953d,
+                +1.006859302520752d,
+                +1.007843017578125d,
+                +1.0088276863098145d,
+                +1.0098135471343994d,
+                +1.0108001232147217d,
+                +1.0117876529693604d,
+                +1.0127761363983154d,
+                +1.013765811920166d,
+                +1.014756202697754d,
+                +1.0157477855682373d,
+                +1.016740083694458d,
+                +1.0177335739135742d,
+                +1.0187277793884277d,
+                +1.0197231769561768d,
+                +1.0207195281982422d,
+                +1.021716833114624d,
+                +1.0227150917053223d,
+                +1.023714303970337d,
+                +1.024714469909668d,
+                +1.0257158279418945d,
+                +1.0267179012298584d,
+                +1.0277209281921387d,
+                +1.0287251472473145d,
+                +1.0297303199768066d,
+                +1.0307364463806152d,
+                +1.0317435264587402d,
+                +1.0327515602111816d,
+                +1.0337605476379395d,
+                +1.0347704887390137d,
+                +1.0357816219329834d,
+                +1.0367934703826904d,
+                +1.037806510925293d,
+                +1.038820505142212d,
+                +1.0398354530334473d,
+                +1.040851354598999d,
+                +1.0418684482574463d,
+                +1.0428862571716309d,
+                +1.043905258178711d,
+                +1.0449252128601074d,
+                +1.0459461212158203d,
+                +1.0469679832458496d,
+                +1.0479910373687744d,
+                +1.0490150451660156d,
+                +1.0500397682189941d,
+                +1.0510656833648682d,
+                +1.0520927906036377d,
+                +1.0531206130981445d,
+                +1.0541496276855469d,
+                +1.0551795959472656d,
+                +1.0562105178833008d,
+                +1.0572423934936523d,
+                +1.0582754611968994d,
+                +1.059309482574463d,
+                +1.0603444576263428d,
+                +1.061380386352539d,
+                +1.0624175071716309d,
+                +1.06345534324646d,
+                +1.0644943714141846d,
+                +1.0655345916748047d,
+                +1.066575527191162d,
+                +1.067617654800415d,
+                +1.0686607360839844d,
+                +1.0697050094604492d,
+                +1.0707499980926514d,
+                +1.071796178817749d,
+                +1.072843313217163d,
+                +1.0738916397094727d,
+                +1.0749409198760986d,
+                +1.075991153717041d,
+                +1.0770423412322998d,
+                +1.078094720840454d,
+                +1.0791480541229248d,
+                +1.080202341079712d,
+                +1.0812578201293945d,
+                +1.0823142528533936d,
+                +1.083371639251709d,
+                +1.08443021774292d,
+                +1.0854897499084473d,
+                +1.086550235748291d,
+                +1.0876119136810303d,
+                +1.088674545288086d,
+                +1.089738130569458d,
+                +1.0908029079437256d,
+                +1.0918686389923096d,
+                +1.092935562133789d,
+                +1.094003438949585d,
+                +1.0950722694396973d,
+                +1.096142053604126d,
+                +1.0972130298614502d,
+                +1.09828519821167d,
+                +1.099358320236206d,
+                +1.1004323959350586d,
+                +1.1015074253082275d,
+                +1.102583646774292d,
+                +1.103661060333252d,
+                +1.1047391891479492d,
+                +1.105818748474121d,
+                +1.1068990230560303d,
+                +1.107980489730835d,
+                +1.1090631484985352d,
+                +1.1101467609405518d,
+                +1.1112313270568848d,
+                +1.1123170852661133d,
+                +1.1134037971496582d,
+                +1.1144917011260986d,
+                +1.1155805587768555d,
+                +1.1166706085205078d,
+                +1.1177616119384766d,
+                +1.1188538074493408d,
+                +1.1199469566345215d,
+                +1.1210410594940186d,
+                +1.1221363544464111d,
+                +1.1232328414916992d,
+                +1.1243302822113037d,
+                +1.1254286766052246d,
+                +1.126528263092041d,
+                +1.127629041671753d,
+                +1.1287307739257812d,
+                +1.129833459854126d,
+                +1.1309373378753662d,
+                +1.132042407989502d,
+                +1.133148431777954d,
+                +1.1342556476593018d,
+                +1.1353638172149658d,
+                +1.1364731788635254d,
+                +1.1375834941864014d,
+                +1.1386950016021729d,
+                +1.1398074626922607d,
+                +1.1409211158752441d,
+                +1.142035961151123d,
+                +1.1431517601013184d,
+                +1.14426851272583d,
+                +1.1453864574432373d,
+                +1.14650559425354d,
+                +1.1476259231567383d,
+                +1.148747205734253d,
+                +1.149869441986084d,
+                +1.1509928703308105d,
+                +1.1521174907684326d,
+                +1.153243064880371d,
+                +1.154369831085205d,
+                +1.1554977893829346d,
+                +1.1566267013549805d,
+                +1.1577568054199219d,
+                +1.1588881015777588d,
+                +1.160020351409912d,
+                +1.161153793334961d,
+                +1.1622881889343262d,
+                +1.163423776626587d,
+                +1.1645605564117432d,
+                +1.1656982898712158d,
+                +1.166837215423584d,
+                +1.1679773330688477d,
+                +1.1691184043884277d,
+                +1.1702606678009033d,
+                +1.1714041233062744d,
+                +1.172548532485962d,
+                +1.173694133758545d,
+                +1.1748409271240234d,
+                +1.1759889125823975d,
+                +1.177137851715088d,
+                +1.1782879829406738d,
+                +1.1794393062591553d,
+                +1.1805915832519531d,
+                +1.1817450523376465d,
+                +1.1828997135162354d,
+                +1.1840553283691406d,
+                +1.1852121353149414d,
+                +1.1863701343536377d,
+                +1.1875293254852295d,
+                +1.1886897087097168d,
+                +1.1898510456085205d,
+                +1.1910135746002197d,
+                +1.1921772956848145d,
+                +1.1933419704437256d,
+                +1.1945080757141113d,
+                +1.1956751346588135d,
+                +1.1968433856964111d,
+                +1.1980125904083252d,
+                +1.1991832256317139d,
+                +1.200354814529419d,
+                +1.2015275955200195d,
+                +1.2027015686035156d,
+                +1.2038767337799072d,
+                +1.2050528526306152d,
+                +1.2062301635742188d,
+                +1.2074086666107178d,
+                +1.2085883617401123d,
+                +1.2097692489624023d,
+                +1.210951328277588d,
+                +1.2121343612670898d,
+                +1.2133188247680664d,
+                +1.2145042419433594d,
+                +1.2156908512115479d,
+                +1.2168786525726318d,
+                +1.2180676460266113d,
+                +1.2192575931549072d,
+                +1.2204489707946777d,
+                +1.2216413021087646d,
+                +1.222834825515747d,
+                +1.224029779434204d,
+                +1.2252256870269775d,
+                +1.2264227867126465d,
+                +1.227621078491211d,
+                +1.2288203239440918d,
+                +1.2300209999084473d,
+                +1.2312228679656982d,
+                +1.2324256896972656d,
+                +1.2336299419403076d,
+                +1.234835147857666d,
+                +1.23604154586792d,
+                +1.2372493743896484d,
+                +1.2384581565856934d,
+                +1.2396681308746338d,
+                +1.2408792972564697d,
+                +1.2420918941497803d,
+                +1.2433054447174072d,
+                +1.2445201873779297d,
+                +1.2457361221313477d,
+                +1.2469532489776611d,
+                +1.2481715679168701d,
+                +1.2493910789489746d,
+                +1.2506117820739746d,
+                +1.2518336772918701d,
+                +1.2530567646026611d,
+                +1.2542810440063477d,
+                +1.2555065155029297d,
+                +1.2567331790924072d,
+                +1.2579610347747803d,
+                +1.2591900825500488d,
+                +1.260420322418213d,
+                +1.2616519927978516d,
+                +1.2628846168518066d,
+                +1.2641184329986572d,
+                +1.2653534412384033d,
+                +1.266589879989624d,
+                +1.2678272724151611d,
+                +1.2690660953521729d,
+                +1.27030611038208d,
+                +1.2715470790863037d,
+                +1.272789478302002d,
+                +1.2740330696105957d,
+                +1.275277853012085d,
+                +1.2765238285064697d,
+                +1.27777099609375d,
+                +1.2790195941925049d,
+                +1.2802691459655762d,
+                +1.281519889831543d,
+                +1.2827720642089844d,
+                +1.2840254306793213d,
+                +1.2852799892425537d,
+                +1.2865357398986816d,
+                +1.287792682647705d,
+                +1.2890510559082031d,
+                +1.2903103828430176d,
+                +1.2915711402893066d,
+                +1.2928330898284912d,
+                +1.2940962314605713d,
+                +1.2953605651855469d,
+                +1.296626091003418d,
+                +1.2978930473327637d,
+                +1.2991611957550049d,
+                +1.3004305362701416d,
+                +1.3017010688781738d,
+                +1.3029727935791016d,
+                +1.304245948791504d,
+                +1.3055200576782227d,
+                +1.3067958354949951d,
+                +1.308072566986084d,
+                +1.3093504905700684d,
+                +1.3106298446655273d,
+                +1.3119103908538818d,
+                +1.3131921291351318d,
+                +1.3144752979278564d,
+                +1.3157594203948975d,
+                +1.317044973373413d,
+                +1.3183319568634033d,
+                +1.31961989402771d,
+                +1.3209092617034912d,
+                +1.322199821472168d,
+                +1.3234915733337402d,
+                +1.324784755706787d,
+                +1.3260791301727295d,
+                +1.3273746967315674d,
+                +1.3286716938018799d,
+                +1.329969882965088d,
+                +1.3312692642211914d,
+                +1.3325698375701904d,
+                +1.333871841430664d,
+                +1.3351752758026123d,
+                +1.336479663848877d,
+                +1.3377854824066162d,
+                +1.339092493057251d,
+                +1.3404009342193604d,
+                +1.3417105674743652d,
+                +1.3430213928222656d,
+                +1.3443336486816406d,
+                +1.3456470966339111d,
+                +1.3469617366790771d,
+                +1.3482778072357178d,
+                +1.349595069885254d,
+                +1.3509137630462646d,
+                +1.352233648300171d,
+                +1.3535549640655518d,
+                +1.3548774719238281d,
+                +1.356201171875d,
+                +1.3575263023376465d,
+                +1.3588526248931885d,
+                +1.360180139541626d,
+                +1.361509084701538d,
+                +1.3628394603729248d,
+                +1.364171028137207d,
+                +1.3655037879943848d,
+                +1.366837978363037d,
+                +1.368173360824585d,
+                +1.3695101737976074d,
+                +1.3708481788635254d,
+                +1.372187614440918d,
+                +1.373528242111206d,
+                +1.3748703002929688d,
+                +1.376213550567627d,
+                +1.3775582313537598d,
+                +1.378904104232788d,
+                +1.380251407623291d,
+                +1.3815999031066895d,
+                +1.3829498291015625d,
+                +1.384300947189331d,
+                +1.3856534957885742d,
+                +1.387007236480713d,
+                +1.3883624076843262d,
+                +1.389719009399414d,
+                +1.3910768032073975d,
+                +1.3924360275268555d,
+                +1.393796443939209d,
+                +1.395158290863037d,
+                +1.3965213298797607d,
+                +1.397885799407959d,
+                +1.3992514610290527d,
+                +1.4006187915802002d,
+                +1.401987075805664d,
+                +1.4033570289611816d,
+                +1.4047281742095947d,
+                +1.4061005115509033d,
+                +1.4074742794036865d,
+                +1.4088494777679443d,
+                +1.4102261066436768d,
+                +1.4116039276123047d,
+                +1.4129831790924072d,
+                +1.4143636226654053d,
+                +1.415745496749878d,
+                +1.4171288013458252d,
+                +1.418513298034668d,
+                +1.4198992252349854d,
+                +1.4212865829467773d,
+                +1.4226751327514648d,
+                +1.424065351486206d,
+                +1.4254565238952637d,
+                +1.426849365234375d,
+                +1.4282433986663818d,
+                +1.4296388626098633d,
+                +1.4310357570648193d,
+                +1.432433843612671d,
+                +1.433833360671997d,
+                +1.4352343082427979d,
+                +1.4366366863250732d,
+                +1.4380402565002441d,
+                +1.4394452571868896d,
+                +1.4408516883850098d,
+                +1.4422595500946045d,
+                +1.4436686038970947d,
+                +1.4450790882110596d,
+                +1.446491003036499d,
+                +1.447904348373413d,
+                +1.4493188858032227d,
+                +1.450735092163086d,
+                +1.4521524906158447d,
+                +1.4535713195800781d,
+                +1.454991340637207d,
+                +1.4564130306243896d,
+                +1.4578359127044678d,
+                +1.4592602252960205d,
+                +1.460686206817627d,
+                +1.4621131420135498d,
+                +1.4635417461395264d,
+                +1.4649717807769775d,
+                +1.4664030075073242d,
+                +1.4678359031677246d,
+                +1.4692699909210205d,
+                +1.470705509185791d,
+                +1.4721424579620361d,
+                +1.4735808372497559d,
+                +1.475020408630371d,
+                +1.47646164894104d,
+                +1.4779040813446045d,
+                +1.4793481826782227d,
+                +1.4807934761047363d,
+                +1.4822404384613037d,
+                +1.4836885929107666d,
+                +1.485138177871704d,
+                +1.4865891933441162d,
+                +1.488041639328003d,
+                +1.4894955158233643d,
+                +1.4909508228302002d,
+                +1.4924075603485107d,
+                +1.493865728378296d,
+                +1.4953253269195557d,
+                +1.49678635597229d,
+                +1.49824857711792d,
+                +1.4997124671936035d,
+                +1.5011777877807617d,
+                +1.5026445388793945d,
+                +1.504112720489502d,
+                +1.505582332611084d,
+                +1.5070531368255615d,
+                +1.5085256099700928d,
+                +1.5099995136260986d,
+                +1.511474847793579d,
+                +1.5129516124725342d,
+                +1.5144298076629639d,
+                +1.5159096717834473d,
+                +1.5173907279968262d,
+                +1.5188732147216797d,
+                +1.5203571319580078d,
+                +1.5218427181243896d,
+                +1.523329496383667d,
+                +1.524817943572998d,
+                +1.5263078212738037d,
+                +1.5277988910675049d,
+                +1.5292916297912598d,
+                +1.5307857990264893d,
+                +1.5322813987731934d,
+                +1.5337786674499512d,
+                +1.5352771282196045d,
+                +1.5367772579193115d,
+                +1.538278579711914d,
+                +1.5397815704345703d,
+                +1.5412859916687012d,
+                +1.5427920818328857d,
+                +1.5442993640899658d,
+                +1.5458080768585205d,
+                +1.547318458557129d,
+                +1.548830270767212d,
+                +1.5503435134887695d,
+                +1.5518584251403809d,
+                +1.5533745288848877d,
+                +1.5548923015594482d,
+                +1.5564115047454834d,
+                +1.5579321384429932d,
+                +1.5594542026519775d,
+                +1.5609779357910156d,
+                +1.5625030994415283d,
+                +1.5640296936035156d,
+                +1.5655577182769775d,
+                +1.5670874118804932d,
+                +1.5686185359954834d,
+                +1.5701510906219482d,
+                +1.5716853141784668d,
+                +1.5732207298278809d,
+                +1.5747578144073486d,
+                +1.5762965679168701d,
+                +1.577836513519287d,
+                +1.5793781280517578d,
+                +1.5809214115142822d,
+                +1.5824658870697021d,
+                +1.5840120315551758d,
+                +1.5855598449707031d,
+                +1.587108850479126d,
+                +1.5886595249176025d,
+                +1.5902118682861328d,
+                +1.5917654037475586d,
+                +1.593320608139038d,
+                +1.5948774814605713d,
+                +1.596435785293579d,
+                +1.5979955196380615d,
+                +1.5995566844940186d,
+                +1.6011195182800293d,
+                +1.6026840209960938d,
+                +1.6042497158050537d,
+                +1.6058173179626465d,
+                +1.6073861122131348d,
+                +1.6089565753936768d,
+                +1.6105287075042725d,
+                +1.6121022701263428d,
+                +1.6136772632598877d,
+                +1.6152539253234863d,
+                +1.6168320178985596d,
+                +1.6184117794036865d,
+                +1.619992971420288d,
+                +1.6215758323669434d,
+                +1.6231601238250732d,
+                +1.6247460842132568d,
+                +1.626333475112915d,
+                +1.627922534942627d,
+                +1.6295130252838135d,
+                +1.6311051845550537d,
+                +1.6326987743377686d,
+                +1.634294033050537d,
+                +1.6358907222747803d,
+                +1.6374890804290771d,
+                +1.6390891075134277d,
+                +1.640690565109253d,
+                +1.6422934532165527d,
+                +1.6438980102539062d,
+                +1.6455042362213135d,
+                +1.6471118927001953d,
+                +1.6487212181091309d,
+                +1.6503322124481201d,
+                +1.651944637298584d,
+                +1.6535584926605225d,
+                +1.6551742553710938d,
+                +1.6567914485931396d,
+                +1.6584100723266602d,
+                +1.6600303649902344d,
+                +1.6616523265838623d,
+                +1.663275957107544d,
+                +1.6649010181427002d,
+                +1.666527509689331d,
+                +1.6681559085845947d,
+                +1.669785737991333d,
+                +1.671417236328125d,
+                +1.6730501651763916d,
+                +1.674684762954712d,
+                +1.676321029663086d,
+                +1.6779589653015137d,
+                +1.679598331451416d,
+                +1.681239366531372d,
+                +1.6828820705413818d,
+                +1.6845262050628662d,
+                +1.6861720085144043d,
+                +1.687819480895996d,
+                +1.6894686222076416d,
+                +1.6911191940307617d,
+                +1.6927716732025146d,
+                +1.6944255828857422d,
+                +1.6960809230804443d,
+                +1.6977381706237793d,
+                +1.6993968486785889d,
+                +1.7010571956634521d,
+                +1.7027192115783691d,
+                +1.7043828964233398d,
+                +1.7060482501983643d,
+                +1.7077150344848633d,
+                +1.709383487701416d,
+                +1.7110536098480225d,
+                +1.7127254009246826d,
+                +1.7143988609313965d,
+                +1.716073989868164d,
+                +1.7177505493164062d,
+                +1.7194287776947021d,
+                +1.7211089134216309d,
+                +1.7227904796600342d,
+                +1.7244737148284912d,
+                +1.726158618927002d,
+                +1.7278449535369873d,
+                +1.7295331954956055d,
+                +1.7312231063842773d,
+                +1.7329144477844238d,
+                +1.7346076965332031d,
+                +1.736302375793457d,
+                +1.7379989624023438d,
+                +1.739696979522705d,
+                +1.7413966655731201d,
+                +1.7430980205535889d,
+                +1.7448012828826904d,
+                +1.7465059757232666d,
+                +1.7482123374938965d,
+                +1.74992036819458d,
+                +1.7516300678253174d,
+                +1.7533416748046875d,
+                +1.7550547122955322d,
+                +1.7567694187164307d,
+                +1.7584857940673828d,
+                +1.7602040767669678d,
+                +1.7619237899780273d,
+                +1.7636451721191406d,
+                +1.7653684616088867d,
+                +1.7670931816101074d,
+                +1.768819808959961d,
+                +1.770547866821289d,
+                +1.77227783203125d,
+                +1.7740094661712646d,
+                +1.775742769241333d,
+                +1.777477741241455d,
+                +1.7792143821716309d,
+                +1.7809526920318604d,
+                +1.7826926708221436d,
+                +1.7844345569610596d,
+                +1.7861778736114502d,
+                +1.7879230976104736d,
+                +1.7896699905395508d,
+                +1.7914185523986816d,
+                +1.7931687831878662d,
+                +1.7949209213256836d,
+                +1.7966744899749756d,
+                +1.7984299659729004d,
+                +1.800187110900879d,
+                +1.8019459247589111d,
+                +1.8037066459655762d,
+                +1.8054687976837158d,
+                +1.8072328567504883d,
+                +1.8089985847473145d,
+                +1.8107659816741943d,
+                +1.812535285949707d,
+                +1.8143062591552734d,
+                +1.8160789012908936d,
+                +1.8178532123565674d,
+                +1.819629430770874d,
+                +1.8214070796966553d,
+                +1.8231868743896484d,
+                +1.8249680995941162d,
+                +1.8267512321472168d,
+                +1.828536033630371d,
+                +1.830322504043579d,
+                +1.83211088180542d,
+                +1.8339009284973145d,
+                +1.8356926441192627d,
+                +1.8374862670898438d,
+                +1.8392815589904785d,
+                +1.841078519821167d,
+                +1.8428773880004883d,
+                +1.8446779251098633d,
+                +1.846480131149292d,
+                +1.8482842445373535d,
+                +1.8500902652740479d,
+                +1.8518977165222168d,
+                +1.8537070751190186d,
+                +1.8555183410644531d,
+                +1.8573312759399414d,
+                +1.8591458797454834d,
+                +1.8609623908996582d,
+                +1.8627805709838867d,
+                +1.864600658416748d,
+                +1.866422414779663d,
+                +1.8682458400726318d,
+                +1.8700714111328125d,
+                +1.8718984127044678d,
+                +1.8737273216247559d,
+                +1.8755581378936768d,
+                +1.8773906230926514d,
+                +1.8792247772216797d,
+                +1.8810608386993408d,
+                +1.8828988075256348d,
+                +1.8847384452819824d,
+                +1.886579990386963d,
+                +1.888423204421997d,
+                +1.890268325805664d,
+                +1.8921151161193848d,
+                +1.8939638137817383d,
+                +1.8958141803741455d,
+                +1.8976664543151855d,
+                +1.8995206356048584d,
+                +1.901376485824585d,
+                +1.9032342433929443d,
+                +1.9050939083099365d,
+                +1.9069552421569824d,
+                +1.908818244934082d,
+                +1.9106833934783936d,
+                +1.9125502109527588d,
+                +1.9144186973571777d,
+                +1.9162893295288086d,
+                +1.9181616306304932d,
+                +1.9200356006622314d,
+                +1.9219114780426025d,
+                +1.9237892627716064d,
+                +1.9256689548492432d,
+                +1.9275505542755127d,
+                +1.929433822631836d,
+                +1.931318759918213d,
+                +1.9332058429718018d,
+                +1.9350945949554443d,
+                +1.9369852542877197d,
+                +1.938877820968628d,
+                +1.940772294998169d,
+                +1.9426684379577637d,
+                +1.9445664882659912d,
+                +1.9464664459228516d,
+                +1.9483680725097656d,
+                +1.9502718448638916d,
+                +1.9521772861480713d,
+                +1.9540846347808838d,
+                +1.955993890762329d,
+                +1.9579050540924072d,
+                +1.959817886352539d,
+                +1.9617326259613037d,
+                +1.9636495113372803d,
+                +1.9655680656433105d,
+                +1.9674885272979736d,
+                +1.9694106578826904d,
+                +1.9713349342346191d,
+                +1.9732608795166016d,
+                +1.975188970565796d,
+                +1.977118730545044d,
+                +1.9790503978729248d,
+                +1.9809842109680176d,
+                +1.982919692993164d,
+                +1.9848570823669434d,
+                +1.9867963790893555d,
+                +1.9887375831604004d,
+                +1.990680456161499d,
+                +1.9926254749298096d,
+                +1.994572401046753d,
+                +1.996521234512329d,
+                +1.998471736907959d,
+                +2.000424385070801d,
+                +2.0023789405822754d,
+                +2.004335403442383d,
+                +2.006293773651123d,
+                +2.008254051208496d,
+                +2.010216236114502d,
+                +2.0121798515319824d,
+                +2.014145851135254d,
+                +2.016113758087158d,
+                +2.0180835723876953d,
+                +2.0200552940368652d,
+                +2.022029399871826d,
+                +2.0240049362182617d,
+                +2.02598237991333d,
+                +2.0279617309570312d,
+                +2.0299429893493652d,
+                +2.0319266319274902d,
+                +2.03391170501709d,
+                +2.0358991622924805d,
+                +2.0378880500793457d,
+                +2.039879322052002d,
+                +2.041872501373291d,
+                +2.0438671112060547d,
+                +2.0458641052246094d,
+                +2.047863006591797d,
+                +2.049863815307617d,
+                +2.0518670082092285d,
+                +2.0538716316223145d,
+                +2.055878162384033d,
+                +2.057887077331543d,
+                +2.0598974227905273d,
+                +2.0619101524353027d,
+                +2.063924789428711d,
+                +2.065941333770752d,
+                +2.067959785461426d,
+                +2.0699801445007324d,
+                +2.07200288772583d,
+                +2.0740270614624023d,
+                +2.0760536193847656d,
+                +2.0780820846557617d,
+                +2.0801124572753906d,
+                +2.0821447372436523d,
+                +2.084178924560547d,
+                +2.0862154960632324d,
+                +2.0882534980773926d,
+                +2.0902938842773438d,
+                +2.0923361778259277d,
+                +2.0943803787231445d,
+                +2.0964269638061523d,
+                +2.0984749794006348d,
+                +2.100525379180908d,
+                +2.1025776863098145d,
+                +2.1046319007873535d,
+                +2.1066884994506836d,
+                +2.1087465286254883d,
+                +2.110806941986084d,
+                +2.1128692626953125d,
+                +2.114933490753174d,
+                +2.117000102996826d,
+                +2.1190686225891113d,
+                +2.1211390495300293d,
+                +2.12321138381958d,
+                +2.1252856254577637d,
+                +2.1273622512817383d,
+                +2.1294407844543457d,
+                +2.131521224975586d,
+                +2.133604049682617d,
+                +2.135688304901123d,
+                +2.13777494430542d,
+                +2.139863967895508d,
+                +2.1419544219970703d,
+                +2.144047260284424d,
+                +2.14614200592041d,
+                +2.1482391357421875d,
+                +2.1503376960754395d,
+                +2.1524391174316406d,
+                +2.1545419692993164d,
+                +2.156647205352783d,
+                +2.1587538719177246d,
+                +2.1608633995056152d,
+                +2.1629743576049805d,
+                +2.1650876998901367d,
+                +2.167203426361084d,
+                +2.169320583343506d,
+                +2.1714401245117188d,
+                +2.1735615730285645d,
+                +2.175685405731201d,
+                +2.1778111457824707d,
+                +2.179938793182373d,
+                +2.1820688247680664d,
+                +2.1842007637023926d,
+                +2.1863350868225098d,
+                +2.1884708404541016d,
+                +2.1906094551086426d,
+                +2.192749500274658d,
+                +2.194891929626465d,
+                +2.1970362663269043d,
+                +2.1991829872131348d,
+                +2.201331615447998d,
+                +2.2034826278686523d,
+                +2.2056355476379395d,
+                +2.2077903747558594d,
+                +2.2099475860595703d,
+                +2.212106704711914d,
+                +2.214268207550049d,
+                +2.2164316177368164d,
+                +2.218596935272217d,
+                +2.220764636993408d,
+                +2.2229342460632324d,
+                +2.2251062393188477d,
+                +2.2272801399230957d,
+                +2.2294564247131348d,
+                +2.2316346168518066d,
+                +2.2338151931762695d,
+                +2.2359976768493652d,
+                +2.2381820678710938d,
+                +2.2403693199157715d,
+                +2.242558002471924d,
+                +2.244749069213867d,
+                +2.2469425201416016d,
+                +2.2491378784179688d,
+                +2.2513351440429688d,
+                +2.2535347938537598d,
+                +2.2557363510131836d,
+                +2.2579402923583984d,
+                +2.2601466178894043d,
+                +2.262354850769043d,
+                +2.2645654678344727d,
+                +2.266777992248535d,
+                +2.2689924240112305d,
+                +2.271209716796875d,
+                +2.273428440093994d,
+                +2.2756495475769043d,
+                +2.2778730392456055d,
+                +2.2800989151000977d,
+                +2.2823266983032227d,
+                +2.2845563888549805d,
+                +2.2867884635925293d,
+                +2.289022922515869d,
+                +2.291259288787842d,
+                +2.2934980392456055d,
+                +2.295738697052002d,
+                +2.2979817390441895d,
+                +2.300227165222168d,
+                +2.3024744987487793d,
+                +2.3047242164611816d,
+                +2.306975841522217d,
+                +2.309229850769043d,
+                +2.31148624420166d,
+                +2.31374454498291d,
+                +2.316005229949951d,
+                +2.318267822265625d,
+                +2.32053279876709d,
+                +2.3228001594543457d,
+                +2.3250694274902344d,
+                +2.3273415565490723d,
+                +2.3296151161193848d,
+                +2.3318915367126465d,
+                +2.334169864654541d,
+                +2.3364500999450684d,
+                +2.338733196258545d,
+                +2.3410181999206543d,
+                +2.3433055877685547d,
+                +2.345594882965088d,
+                +2.347886562347412d,
+                +2.3501806259155273d,
+                +2.3524770736694336d,
+                +2.3547754287719727d,
+                +2.3570761680603027d,
+                +2.3593788146972656d,
+                +2.3616843223571777d,
+                +2.3639917373657227d,
+                +2.3663015365600586d,
+                +2.3686132431030273d,
+                +2.370927333831787d,
+                +2.373243808746338d,
+                +2.3755626678466797d,
+                +2.3778839111328125d,
+                +2.380207061767578d,
+                +2.3825325965881348d,
+                +2.3848605155944824d,
+                +2.387190818786621d,
+                +2.3895230293273926d,
+                +2.391857624053955d,
+                +2.3941946029663086d,
+                +2.396533966064453d,
+                +2.3988752365112305d,
+                +2.401218891143799d,
+                +2.4035654067993164d,
+                +2.4059133529663086d,
+                +2.40826416015625d,
+                +2.4106173515319824d,
+                +2.4129724502563477d,
+                +2.415329933166504d,
+                +2.417689800262451d,
+                +2.4200520515441895d,
+                +2.4224166870117188d,
+                +2.424783229827881d,
+                +2.427152633666992d,
+                +2.4295239448547363d,
+                +2.4318976402282715d,
+                +2.4342737197875977d,
+                +2.436652183532715d,
+                +2.439032554626465d,
+                +2.441415786743164d,
+                +2.4438014030456543d,
+                +2.4461889266967773d,
+                +2.4485788345336914d,
+                +2.4509711265563965d,
+                +2.4533658027648926d,
+                +2.4557628631591797d,
+                +2.458162307739258d,
+                +2.460564136505127d,
+                +2.462968349456787d,
+                +2.46537446975708d,
+                +2.4677834510803223d,
+                +2.4701943397521973d,
+                +2.4726080894470215d,
+                +2.4750237464904785d,
+                +2.4774417877197266d,
+                +2.479862689971924d,
+                +2.482285499572754d,
+                +2.484710693359375d,
+                +2.487138271331787d,
+                +2.4895682334899902d,
+                +2.4920010566711426d,
+                +2.4944357872009277d,
+                +2.496872901916504d,
+                +2.499312400817871d,
+                +2.5017542839050293d,
+                +2.5041985511779785d,
+                +2.5066452026367188d,
+                +2.50909423828125d,
+                +2.5115456581115723d,
+                +2.5139999389648438d,
+                +2.516456127166748d,
+                +2.5189146995544434d,
+                +2.5213756561279297d,
+                +2.5238394737243652d,
+                +2.5263051986694336d,
+                +2.528773307800293d,
+                +2.5312442779541016d,
+                +2.533717155456543d,
+                +2.5361928939819336d,
+                +2.538670539855957d,
+                +2.5411510467529297d,
+                +2.5436339378356934d,
+                +2.546119213104248d,
+                +2.5486068725585938d,
+                +2.5510969161987305d,
+                +2.553589344024658d,
+                +2.556084632873535d,
+                +2.558581829071045d,
+                +2.5610814094543457d,
+                +2.5635838508605957d,
+                +2.5660886764526367d,
+                +2.5685958862304688d,
+                +2.571105480194092d,
+                +2.573617458343506d,
+                +2.576131820678711d,
+                +2.5786490440368652d,
+                +2.5811686515808105d,
+                +2.5836901664733887d,
+                +2.586214542388916d,
+                +2.5887417793273926d,
+                +2.591270923614502d,
+                +2.5938024520874023d,
+                +2.596336841583252d,
+                +2.5988736152648926d,
+                +2.601412773132324d,
+                +2.603954315185547d,
+                +2.6064987182617188d,
+                +2.6090455055236816d,
+                +2.6115946769714355d,
+                +2.6141462326049805d,
+                +2.6167001724243164d,
+                +2.6192569732666016d,
+                +2.6218161582946777d,
+                +2.624377727508545d,
+                +2.626941680908203d,
+                +2.6295084953308105d,
+                +2.632077217102051d,
+                +2.6346492767333984d,
+                +2.637223243713379d,
+                +2.6398000717163086d,
+                +2.6423792839050293d,
+                +2.644960880279541d,
+                +2.6475448608398438d,
+                +2.6501317024230957d,
+                +2.6527209281921387d,
+                +2.655313014984131d,
+                +2.657907009124756d,
+                +2.6605043411254883d,
+                +2.6631035804748535d,
+                +2.665705680847168d,
+                +2.6683101654052734d,
+                +2.67091703414917d,
+                +2.6735267639160156d,
+                +2.6761388778686523d,
+                +2.67875337600708d,
+                +2.681370735168457d,
+                +2.683990478515625d,
+                +2.686613082885742d,
+                +2.689237594604492d,
+                +2.6918654441833496d,
+                +2.69449520111084d,
+                +2.6971278190612793d,
+                +2.699763298034668d,
+                +2.7024011611938477d,
+                +2.7050414085388184d,
+                +2.70768404006958d,
+                +2.710329532623291d,
+                +2.712977886199951d,
+                +2.7156286239624023d,
+                +2.7182817459106445d,
+            };
+
+    /**
+     * Exponential over the range of 0 - 1 in increments of 2^-10 exp(x/1024) = expFracTableA[x] +
+     * expFracTableB[x].
+     */
+    private static final double[] EXP_FRAC_B =
+            new double[] {
+                +0.0d,
+                +1.552583321178453E-10d,
+                +1.2423699995465188E-9d,
+                +4.194022929828008E-9d,
+                +9.94381632344361E-9d,
+                +1.9426261544163577E-8d,
+                +3.3576783010266685E-8d,
+                +5.3331719086630523E-8d,
+                +7.962832297769345E-8d,
+                +1.1340476362128895E-7d,
+                -8.281845251820919E-8d,
+                -3.126416414805498E-8d,
+                +3.058997113995161E-8d,
+                +1.0368579417304741E-7d,
+                -4.9452513107409435E-8d,
+                +4.8955889659397494E-8d,
+                -7.698155155722897E-8d,
+                +5.051784853384516E-8d,
+                -4.443661736519001E-8d,
+                +1.1593958457401774E-7d,
+                +5.575759739697068E-8d,
+                +1.4385227981629147E-8d,
+                -7.227368462584163E-9d,
+                -8.129108387083023E-9d,
+                +1.263202100290635E-8d,
+                +5.600896265625552E-8d,
+                -1.154629885168314E-7d,
+                -2.399186832888246E-8d,
+                +9.295948298604103E-8d,
+                -2.070841011504222E-9d,
+                -6.97066538508643E-8d,
+                -1.0898941254272996E-7d,
+                -1.1895963756343625E-7d,
+                -9.865691193993138E-8d,
+                -4.711988033385175E-8d,
+                +3.6613751875298095E-8d,
+                -8.491135959370133E-8d,
+                +6.610611940107793E-8d,
+                +1.3794148633283659E-8d,
+                -2.462631860370667E-9d,
+                +1.830278273495162E-8d,
+                +7.705834203598065E-8d,
+                -6.364563771711373E-8d,
+                +7.39978436695387E-8d,
+                +1.4122417557484554E-8d,
+                -3.881598887298574E-9d,
+                +2.0958481826069642E-8d,
+                +8.96162975425619E-8d,
+                -3.535214171178576E-8d,
+                -1.1455271549574576E-7d,
+                +9.140964977432485E-8d,
+                +1.0667524445105459E-7d,
+                -6.777752790396222E-8d,
+                +4.586785041291296E-8d,
+                -2.8245462428022094E-8d,
+                -5.071761314397018E-8d,
+                -2.0566368810068663E-8d,
+                +6.319146317890346E-8d,
+                -3.687854305539139E-8d,
+                -8.137269363160008E-8d,
+                -6.930491127388755E-8d,
+                +3.1184473002226595E-10d,
+                -1.0995299963140049E-7d,
+                +7.772668425499348E-8d,
+                +8.750367485925089E-8d,
+                -7.963112393823186E-8d,
+                +5.415131809829094E-8d,
+                +1.3006683896462346E-8d,
+                +3.634736373360733E-8d,
+                -1.132504393233074E-7d,
+                +4.2046187038837375E-8d,
+                +2.6396811618001066E-8d,
+                +7.92177143584738E-8d,
+                -3.691100820545433E-8d,
+                -8.257112559083188E-8d,
+                -5.676200971739166E-8d,
+                +4.151794514828518E-8d,
+                -2.5147255753587636E-8d,
+                -1.7335469415174996E-8d,
+                +6.595784859136531E-8d,
+                -1.2680354928109105E-8d,
+                -1.3824992526093461E-8d,
+                +6.353142754175797E-8d,
+                -1.8021197722549054E-8d,
+                -1.9054827792903468E-8d,
+                +6.144098503892116E-8d,
+                -1.3940903373095247E-8d,
+                -5.7694907599522404E-9d,
+                +8.696863522320578E-8d,
+                +2.6869297963554945E-8d,
+                +5.3366470162689076E-8d,
+                -7.094204160127543E-8d,
+                -1.0662027949814858E-7d,
+                -5.26498707801063E-8d,
+                +9.198855229106814E-8d,
+                +8.989677431456647E-8d,
+                -5.790384407322479E-8d,
+                -1.1197236522467887E-7d,
+                -7.12854317090566E-8d,
+                +6.51813137650059E-8d,
+                +6.003465022483798E-8d,
+                -8.569906238528267E-8d,
+                +1.0584469687624562E-7d,
+                -7.956144278281947E-8d,
+                +7.43676272093501E-8d,
+                +9.182512565315022E-8d,
+                -2.6157563728873715E-8d,
+                -4.012947040998503E-8d,
+                +5.094280572218447E-8d,
+                +9.675095351161728E-9d,
+                +7.552139802281006E-8d,
+                +1.1099566726533146E-8d,
+                +5.58656252899437E-8d,
+                -2.756054703800197E-8d,
+                +2.791018095971047E-10d,
+                -9.799351869734466E-8d,
+                -8.291832428736212E-8d,
+                +4.654720780112994E-8d,
+                +5.302803981406403E-8d,
+                -6.243126731995636E-8d,
+                -6.036655299348577E-8d,
+                +6.026878587378257E-8d,
+                +6.210379583313526E-8d,
+                -5.381287389094251E-8d,
+                -4.8012970400697E-8d,
+                +8.055420567281602E-8d,
+                +9.452180117175641E-8d,
+                -5.057430382371206E-9d,
+                +2.1288872215266507E-8d,
+                -6.380305844689076E-8d,
+                -2.0858800984600168E-8d,
+                -8.724006061713588E-8d,
+                -2.3470351753125604E-8d,
+                -6.690931338790221E-8d,
+                +2.192160831263035E-8d,
+                +5.6648446166177225E-9d,
+                -1.1461755745719884E-7d,
+                -9.944393412663547E-8d,
+                +5.2249837964645906E-8d,
+                +1.0311034276196487E-7d,
+                +5.4203784018566126E-8d,
+                -9.340259278913173E-8d,
+                -1.0022192034216903E-7d,
+                +3.481513333662908E-8d,
+                +7.436036590244714E-8d,
+                +1.9485199912395296E-8d,
+                +1.0968068384729757E-7d,
+                +1.0760175582979094E-7d,
+                +1.4322981952798675E-8d,
+                +6.933855730431659E-8d,
+                +3.530656968851287E-8d,
+                -8.669526204279467E-8d,
+                -5.7169586962345785E-8d,
+                -1.1345515834332824E-7d,
+                -1.605251622332555E-8d,
+                -2.298302779758532E-9d,
+                -7.110952399338234E-8d,
+                +1.70164513845372E-8d,
+                +2.4746155561368937E-8d,
+                -4.6834239957353325E-8d,
+                +4.1781076667923185E-8d,
+                +5.326182134294869E-8d,
+                -1.1302647617762544E-8d,
+                +8.759667154796094E-8d,
+                +1.126326877851684E-7d,
+                +6.48979555673987E-8d,
+                -5.451390316294111E-8d,
+                -6.0896188500539086E-9d,
+                -2.7152010585461855E-8d,
+                -1.1660424775832058E-7d,
+                -3.492984900939992E-8d,
+                -1.944841848873016E-8d,
+                -6.905990750285027E-8d,
+                +5.575538653428039E-8d,
+                +1.1768108384670781E-7d,
+                +1.178204606523101E-7d,
+                +5.727787111340131E-8d,
+                -6.284125161007433E-8d,
+                -3.0118152047565877E-9d,
+                -5.448044533034374E-10d,
+                -5.433154287341921E-8d,
+                +7.515630833946181E-8d,
+                -8.780756503572527E-8d,
+                -6.527407547535494E-8d,
+                -9.45487863616303E-8d,
+                +6.390098458668406E-8d,
+                -6.564672913105876E-8d,
+                -5.238488022920792E-9d,
+                +7.824500749252316E-9d,
+                -2.5339299158309795E-8d,
+                -1.036103313062145E-7d,
+                +1.2550633697348567E-8d,
+                +8.584676196065558E-8d,
+                +1.1740089468291563E-7d,
+                +1.0833697012353316E-7d,
+                +5.978002467397905E-8d,
+                -2.7143806069290897E-8d,
+                +8.711129287069315E-8d,
+                -7.316349947981893E-8d,
+                -3.00015852582934E-8d,
+                -2.0691000399732483E-8d,
+                -4.4100097152254264E-8d,
+                -9.909612209943178E-8d,
+                +5.38733640215475E-8d,
+                -6.0893829005035E-8d,
+                +3.457553391989844E-8d,
+                +1.0300006058273187E-7d,
+                -9.290053015365092E-8d,
+                -7.514966995961323E-8d,
+                -8.10254145615142E-8d,
+                -1.0938612624777085E-7d,
+                +7.932952721989251E-8d,
+                +9.428257290008738E-9d,
+                -7.952636967837795E-8d,
+                +5.203033137154554E-8d,
+                -7.159157201731446E-8d,
+                +2.7593424989059015E-8d,
+                +1.1231621190000476E-7d,
+                -5.469119869891027E-8d,
+                +4.560067256086347E-9d,
+                +5.280427179595944E-8d,
+                +9.119538242455128E-8d,
+                -1.1753008498403413E-7d,
+                -9.537874867759656E-8d,
+                -7.96118345325538E-8d,
+                -6.907085854395348E-8d,
+                -6.259620482221904E-8d,
+                -5.902712448725381E-8d,
+                -5.720173456146447E-8d,
+                -5.5957016861703E-8d,
+                -5.412881689012608E-8d,
+                -5.0551842723970724E-8d,
+                -4.405966390424518E-8d,
+                -3.348471032333413E-8d,
+                -1.7658271111516935E-8d,
+                +4.589506477601956E-9d,
+                +3.4429618182751655E-8d,
+                +7.303420385174346E-8d,
+                -1.168420305422519E-7d,
+                -5.718749537552229E-8d,
+                +1.4754809136835937E-8d,
+                +1.001616104682875E-7d,
+                -3.8207793300052055E-8d,
+                +7.766278405014509E-8d,
+                -2.7883635712109803E-8d,
+                -1.1524714043067699E-7d,
+                +5.517333625963128E-8d,
+                +7.724278756071081E-9d,
+                -1.7990934773848504E-8d,
+                -2.0786347668702902E-8d,
+                +5.251554594269693E-10d,
+                +4.7131849857076246E-8d,
+                -1.1819540733893871E-7d,
+                -1.742885956093543E-8d,
+                +1.1220467571570283E-7d,
+                +3.347954541376715E-8d,
+                -1.399157980498908E-8d,
+                -2.9013441705763093E-8d,
+                -1.0389614239253089E-8d,
+                +4.307749759934266E-8d,
+                -1.0583192018912101E-7d,
+                +2.0919226941745448E-8d,
+                -5.2305110482722706E-8d,
+                -8.588407110184028E-8d,
+                -7.861419797923639E-8d,
+                -2.929085835358592E-8d,
+                +6.329175751021792E-8d,
+                -3.807794163054899E-8d,
+                -9.377320954068088E-8d,
+                -1.0258469865953145E-7d,
+                -6.330187984612758E-8d,
+                +2.5286958775281306E-8d,
+                -7.40238661307607E-8d,
+                +1.1681688445204168E-7d,
+                -1.1623125976292733E-7d,
+                -5.6696107089038004E-8d,
+                +5.822140627806124E-8d,
+                -8.678466172071259E-9d,
+                -1.7757121899175995E-8d,
+                +3.220665454652531E-8d,
+                -9.598330731102836E-8d,
+                +7.573375369829243E-8d,
+                +7.174547784678893E-8d,
+                -1.0672213971363184E-7d,
+                +1.8395252217743006E-8d,
+                -2.8511112548600118E-8d,
+                -7.79306270997787E-9d,
+                +8.178019529487065E-8d,
+                +3.0220784595602374E-9d,
+                -4.4156343103298585E-9d,
+                +6.07014616741277E-8d,
+                -3.8809601937571554E-8d,
+                -6.329342805230603E-8d,
+                -1.1511990258493999E-8d,
+                +1.177739474561431E-7d,
+                +8.738625278484571E-8d,
+                -1.0143341551207646E-7d,
+                +2.9394972678456236E-8d,
+                +4.278345398213486E-9d,
+                +6.28805835150457E-8d,
+                -3.197037359731606E-8d,
+                -4.060821046423735E-8d,
+                +3.82160283750664E-8d,
+                -3.2666060441373307E-8d,
+                -1.3584500601329896E-8d,
+                +9.671332777035621E-8d,
+                +6.10626893063691E-8d,
+                +1.1913723189736356E-7d,
+                +3.3774671482641995E-8d,
+                +4.4651109654500895E-8d,
+                -8.539328154875224E-8d,
+                -1.166799420361101E-7d,
+                -4.794765976694151E-8d,
+                -1.1635256954820579E-7d,
+                -8.221241452580445E-8d,
+                +5.5737717715868425E-8d,
+                +6.034539636024073E-8d,
+                -6.712199323081945E-8d,
+                -8.697724830833087E-8d,
+                +2.0494942705297694E-9d,
+                -3.718924074653624E-8d,
+                +3.499747150995707E-8d,
+                -1.8535359161566028E-8d,
+                +4.1905679587096103E-8d,
+                -2.0821912536551675E-8d,
+                +3.297776915751238E-8d,
+                -3.3835280846270374E-8d,
+                +1.8437339356553904E-8d,
+                -4.734187609526424E-8d,
+                +8.527976799299225E-9d,
+                -5.1088103279787804E-8d,
+                +1.3513294656751725E-8d,
+                -3.480032127343472E-8d,
+                +4.367697180842916E-8d,
+                +1.1815196363705356E-8d,
+                +1.0932279207149782E-7d,
+                +9.907230065250944E-8d,
+                -1.764389559496152E-8d,
+                -1.1135725625095859E-9d,
+                -8.846040040259342E-8d,
+                -3.996962588736431E-8d,
+                -9.276238757878814E-8d,
+                -7.12139818505956E-9d,
+                -2.016525972830718E-8d,
+                +1.0782585410141121E-7d,
+                -9.868269632073771E-8d,
+                +7.686861750031585E-8d,
+                -7.947087669425045E-8d,
+                -8.955768055535647E-8d,
+                +4.791582240886607E-8d,
+                +9.583994718167641E-8d,
+                +5.5524866689108584E-8d,
+                -7.171796605211277E-8d,
+                -4.6157237582310713E-8d,
+                -1.0489751005162237E-7d,
+                -8.204903560604627E-9d,
+                +6.818588687884566E-9d,
+                -5.850916105103205E-8d,
+                +3.5549586192569994E-8d,
+                +5.1896700056778354E-8d,
+                -8.146080588190463E-9d,
+                +9.516285362051742E-8d,
+                -1.1368933260611668E-7d,
+                +8.187871486648885E-8d,
+                -3.206182925646474E-8d,
+                +2.265440168347286E-8d,
+                +8.938334752179552E-9d,
+                -7.187922490287331E-8d,
+                +1.9952407216533937E-8d,
+                +4.734805892507655E-8d,
+                +1.1642439930208906E-8d,
+                -8.582843599651953E-8d,
+                -5.3086706437795354E-9d,
+                +1.6121782610217253E-8d,
+                -2.0197142620980974E-8d,
+                -1.129242035557684E-7d,
+                -2.2298267863810133E-8d,
+                +1.4605950309628873E-8d,
+                -8.663710700190489E-10d,
+                -6.736873974532501E-8d,
+                +5.486523121881414E-8d,
+                -1.0965249168570443E-7d,
+                -8.27343074126263E-8d,
+                -1.0144703278439455E-7d,
+                +7.39809943048038E-8d,
+                -3.193297932837415E-8d,
+                +5.900393284617182E-8d,
+                +1.0973020465397083E-7d,
+                -1.1681436418514489E-7d,
+                +9.5985669644661E-8d,
+                +3.423560333632085E-8d,
+                -6.22836197265283E-8d,
+                +4.621027492345726E-8d,
+                -1.1575484316683829E-7d,
+                -6.997545435826076E-8d,
+                -5.3502441327259514E-8d,
+                -6.49667713553005E-8d,
+                -1.029980741248172E-7d,
+                +7.219393868923887E-8d,
+                -1.4854841678687828E-8d,
+                +1.1406713393562271E-7d,
+                -1.650155887561251E-8d,
+                +7.165331603232264E-8d,
+                -9.692697614257269E-8d,
+                -4.402550702194912E-8d,
+                -6.679737442193143E-9d,
+                +1.6492800268960003E-8d,
+                +2.68759245092879E-8d,
+                +2.5854805721793077E-8d,
+                +1.4815967715704613E-8d,
+                -4.852711011229633E-9d,
+                -3.176199594915881E-8d,
+                -6.452129525125173E-8d,
+                -1.01738658407525E-7d,
+                +9.639780418418697E-8d,
+                +5.4445606140746644E-8d,
+                +1.2219361033150988E-8d,
+                -2.8883532688356087E-8d,
+                -6.746431126005811E-8d,
+                -1.0212284427080097E-7d,
+                +1.0696094577483825E-7d,
+                +8.43527683868743E-8d,
+                +6.987544103716777E-8d,
+                +6.493457409236137E-8d,
+                +7.093715125593688E-8d,
+                +8.929153091001965E-8d,
+                -1.1701113164306871E-7d,
+                -6.972256643013266E-8d,
+                -5.848862070736576E-9d,
+                +7.602385197610123E-8d,
+                -6.110775144284437E-8d,
+                +6.101012058093429E-8d,
+                -3.304167134225169E-8d,
+                -1.0342514383702196E-7d,
+                +8.969907328603505E-8d,
+                +7.091600108064668E-8d,
+                +8.006778743052707E-8d,
+                +1.1857939200074815E-7d,
+                -5.0541412403312774E-8d,
+                +5.0970277930552287E-8d,
+                -5.229355472795119E-8d,
+                +1.1793478462381443E-7d,
+                +8.625007227318527E-8d,
+                +9.250422086873268E-8d,
+                -1.0028661472061573E-7d,
+                -1.384914052949463E-8d,
+                +1.1483560326413004E-7d,
+                +4.878798101459259E-8d,
+                +2.7866921183936055E-8d,
+                +5.3514180410849046E-8d,
+                -1.1124565511436785E-7d,
+                +1.186914813275767E-8d,
+                -5.253258132241335E-8d,
+                -6.458486486369316E-8d,
+                -2.2838888809969377E-8d,
+                +7.415557606805398E-8d,
+                -1.0568403170659571E-8d,
+                -3.7139182948393606E-8d,
+                -4.1022790876160215E-9d,
+                +8.999821367768787E-8d,
+                +8.201043988912348E-9d,
+                -9.616457442665051E-9d,
+                +3.8005886250603055E-8d,
+                -8.588890051473289E-8d,
+                +9.699937202692456E-8d,
+                +1.11298006674538E-7d,
+                -4.1527104733570825E-8d,
+                +1.1682852007826251E-7d,
+                +1.1099648061301941E-7d,
+                -5.755303038890997E-8d,
+                +8.948877445235827E-8d,
+                +7.675780395028194E-8d,
+                -9.427143563390596E-8d,
+                +5.471416081500162E-8d,
+                +4.8354824064383506E-8d,
+                -1.118706134478866E-7d,
+                +5.235528379688445E-8d,
+                +6.567708120053687E-8d,
+                -7.042204992948526E-8d,
+                -1.1603891006723397E-7d,
+                -6.968742825553785E-8d,
+                +7.01199184127881E-8d,
+                +6.645352711199266E-8d,
+                -7.919617109348822E-8d,
+                +1.1149986927391714E-7d,
+                -7.522074418324674E-8d,
+                +7.739252980388984E-8d,
+                +9.39987974788905E-8d,
+                -2.390421480210064E-8d,
+                -3.639873824357815E-8d,
+                +5.8015881615938497E-8d,
+                +2.2423186335040668E-8d,
+                +9.674534330665206E-8d,
+                +4.4068830785712375E-8d,
+                +1.0431875573076199E-7d,
+                +4.0584538834428926E-8d,
+                +9.279423236781974E-8d,
+                +2.404020521381534E-8d,
+                +7.425346071427343E-8d,
+                +6.529321706138789E-9d,
+                +6.080174837146273E-8d,
+                +1.6902327633329284E-10d,
+                +6.456806922371733E-8d,
+                +1.7100134295216033E-8d,
+                +9.770510970673519E-8d,
+                +6.94872148530716E-8d,
+                -6.602926393514549E-8d,
+                -6.889997193778161E-8d,
+                +6.240235720677117E-8d,
+                +9.098790295810902E-8d,
+                +1.8386917534879182E-8d,
+                +8.454972737414241E-8d,
+                +5.259099728747365E-8d,
+                -7.595453077213505E-8d,
+                -6.113203624663034E-8d,
+                +9.859622328905143E-8d,
+                -7.206766550807255E-8d,
+                -9.474579567171831E-8d,
+                +3.210408693366267E-8d,
+                +7.160716418525417E-8d,
+                +2.530870537724554E-8d,
+                -1.0524451040704701E-7d,
+                -8.008561371849434E-8d,
+                +1.0233519853128553E-7d,
+                -3.326791455362767E-8d,
+                -8.504961764629757E-9d,
+                -6.024017201863256E-8d,
+                +5.1500902632092514E-8d,
+                +8.98570720774568E-8d,
+                +5.638724693948384E-8d,
+                -4.734813904255994E-8d,
+                +1.8631451577542948E-8d,
+                +1.7470924137873214E-8d,
+                -4.926470933588261E-8d,
+                +5.84096713620797E-8d,
+                +1.0364355880696472E-7d,
+                +8.800655674349468E-8d,
+                +1.3069802481237792E-8d,
+                +1.1882454749452428E-7d,
+                -6.999215748398631E-8d,
+                -7.49674072510849E-8d,
+                +1.054760847603618E-7d,
+                -3.920012014371067E-9d,
+                +7.526183084319617E-8d,
+                +1.0618494853096868E-7d,
+                +9.043280094115832E-8d,
+                +2.9590395068826316E-8d,
+                -7.475571347653619E-8d,
+                +1.7401160143611842E-8d,
+                +6.923209420670962E-8d,
+                +8.232829924979753E-8d,
+                +5.82825404854514E-8d,
+                -1.3108606792380822E-9d,
+                -9.485602512220194E-8d,
+                +1.7663064617118723E-8d,
+                +9.942682855652123E-8d,
+                -8.638275100090915E-8d,
+                -6.132639063569726E-8d,
+                -6.221897889344726E-8d,
+                -8.745525834919404E-8d,
+                +1.029901759234897E-7d,
+                +3.3888561478632076E-8d,
+                -5.47315553588771E-8d,
+                +7.715994473741065E-8d,
+                -4.566098167230033E-8d,
+                +5.5257514455273825E-8d,
+                -9.530545662611411E-8d,
+                -1.889488909834863E-8d,
+                +4.769006625301079E-8d,
+                +1.0607041998938709E-7d,
+                -8.054981263802322E-8d,
+                -3.370929373457322E-8d,
+                +9.799164177397836E-9d,
+                +5.160291611526656E-8d,
+                +9.333090708652975E-8d,
+                -1.0180490545927503E-7d,
+                -5.533523366931846E-8d,
+                -4.044932340334176E-9d,
+                +5.370131904567218E-8d,
+                -1.1887814032213867E-7d,
+                -4.3307634616102625E-8d,
+                +4.363437558318513E-8d,
+                -9.482896784430338E-8d,
+                +1.9782818312325887E-8d,
+                -8.77224935488516E-8d,
+                +6.113879253864931E-8d,
+                -8.822335132515693E-9d,
+                -5.753754066078771E-8d,
+                -8.335545536862392E-8d,
+                -8.462309712606694E-8d,
+                -5.968586877433824E-8d,
+                -6.887556547891059E-9d,
+                +7.542967150507818E-8d,
+                -4.949331199790077E-8d,
+                +9.684172421525468E-8d,
+                +3.9260317944365246E-8d,
+                +1.784536881359796E-8d,
+                +3.426282345243592E-8d,
+                +9.018025618601154E-8d,
+                -5.1151708476133135E-8d,
+                +8.877492215808044E-8d,
+                +3.479545684576179E-8d,
+                +2.7002575714977818E-8d,
+                +6.707201545505014E-8d,
+                -8.173742908533777E-8d,
+                +5.909041310777802E-8d,
+                +1.439903710393587E-8d,
+                +2.4289317341982113E-8d,
+                +9.044519282818302E-8d,
+                -2.3866331257845713E-8d,
+                -7.853944465095286E-8d,
+                -7.188526769607005E-8d,
+                -2.2132706360079843E-9d,
+                -1.0624985110080394E-7d,
+                +9.453598391231829E-8d,
+                -1.134160131581847E-7d,
+                -1.315295870404327E-8d,
+                -7.981320644583728E-8d,
+                -7.327771300038971E-8d,
+                +8.155647334672472E-9d,
+                -7.222791579580787E-8d,
+                -7.430436987497092E-8d,
+                +3.633404807819848E-9d,
+                -7.512438321498593E-8d,
+                -7.044869765481105E-8d,
+                +1.9372589859580955E-8d,
+                -4.2365298585101096E-8d,
+                -1.552830824758035E-8d,
+                +1.0160071259930585E-7d,
+                +7.232201430620959E-8d,
+                -1.0164389431039905E-7d,
+                +5.826233477413577E-8d,
+                +7.6927415825689E-8d,
+                -4.392309439525734E-8d,
+                -6.414337408955734E-8d,
+                +1.799550702470095E-8d,
+                -3.4194410638967946E-8d,
+                +1.9437762419688045E-8d,
+                -5.7792549966531335E-8d,
+                -2.5731071572354522E-8d,
+                +1.173595905705643E-7d,
+                -1.0361863127101014E-7d,
+                +2.8330789837569332E-8d,
+                +3.81131861433539E-8d,
+                -7.252724942149532E-8d,
+                -6.342604067787756E-8d,
+                +6.716441526213986E-8d,
+                +8.257484966196574E-8d,
+                -1.5443717968117592E-8d,
+                +1.3280021798948244E-8d,
+                -6.79180673261558E-8d,
+                -1.8863249269709046E-8d,
+                -7.62162303263991E-8d,
+                +2.011589233663723E-10d,
+                -2.62683511147141E-8d,
+                +8.455684903712996E-8d,
+                +9.602293320384794E-8d,
+                +9.896378545255258E-9d,
+                +6.636396724067746E-8d,
+                +2.8777050870552646E-8d,
+                -1.0109271059094341E-7d,
+                -8.305334708631055E-8d,
+                +8.467026501338835E-8d,
+                -7.29821745001452E-8d,
+                -7.739491336852633E-8d,
+                +7.321238022013781E-8d,
+                -9.621538067089515E-8d,
+                -1.0705722541811197E-7d,
+                +4.247240125405735E-8d,
+                +1.1574222007764044E-7d,
+                +1.145412771487496E-7d,
+                +4.066036653218687E-8d,
+                -1.0410796803072171E-7d,
+                -7.955085231106037E-8d,
+                +1.1612776191572459E-7d,
+                +7.888519481107568E-9d,
+                +7.436813814737735E-8d,
+                +7.894935661289349E-8d,
+                +2.343525263620692E-8d,
+                -9.036933434595339E-8d,
+                -2.2239222395888823E-8d,
+                -8.784622656707742E-9d,
+                -4.819540032304379E-8d,
+                +9.975892708522332E-8d,
+                -3.9945124955316294E-8d,
+                +1.1345047468988893E-8d,
+                +1.702808472925844E-8d,
+                -2.10770182066344E-8d,
+                -1.0114948914089626E-7d,
+                +1.70518021921727E-8d,
+                +9.693260855961159E-8d,
+                -9.809953482725758E-8d,
+                -8.937957126662392E-8d,
+                -1.134963954323427E-7d,
+                +6.980004387880031E-8d,
+                -1.4494150014095534E-8d,
+                +1.122932337832262E-7d,
+                -2.483811732227808E-8d,
+                +5.278759515330048E-8d,
+                +1.0859222881334994E-7d,
+                -9.400056055939758E-8d,
+                -7.630957994128623E-8d,
+                -7.490757191850264E-8d,
+                -8.794689652049879E-8d,
+                -1.1357810855950775E-7d,
+                +8.846862323478745E-8d,
+                +4.32092015744956E-8d,
+                -9.082923009890997E-9d,
+                -6.655106680680314E-8d,
+                +1.1108184705020206E-7d,
+                +4.8838973948592766E-8d,
+                -1.2998975819628988E-8d,
+                -7.25680516883106E-8d,
+                -1.280024819379844E-7d,
+                -1.7743467191652895E-7d,
+                -2.1899520225809197E-7d,
+                +2.2602433110285232E-7d,
+                +2.0582268590356215E-7d,
+                +1.9911192455808124E-7d,
+                +2.0776878313278689E-7d,
+                +2.3367183133931002E-7d,
+                -1.9813568387704588E-7d,
+                -1.320972037315105E-7d,
+                -4.316580502355056E-8d,
+                +7.054443447243064E-8d,
+                +2.109212796025238E-7d,
+                -9.698281856949837E-8d,
+                +1.0239791185239086E-7d,
+                -1.4271754202157014E-7d,
+                +1.232402895636637E-7d,
+                -5.150590480969644E-8d,
+                -1.882201085012735E-7d,
+                +1.918355503889933E-7d,
+                +1.368893262241355E-7d,
+                +1.256828068633383E-7d,
+                +1.601222826656464E-7d,
+                -2.3472125169205568E-7d,
+                -1.032634625827871E-7d,
+                +7.957037517331382E-8d,
+                -1.6114314525832115E-7d,
+                +1.3018591370778052E-7d,
+                +1.8007284821359149E-9d,
+                -6.75421764491544E-8d,
+                -7.592155950645605E-8d,
+                -2.1414301981236817E-8d,
+                +9.79045937979623E-8d,
+                -1.9287515190177685E-7d,
+                +6.184953843236509E-8d,
+                -8.966500602352001E-8d,
+                -1.686490951669855E-7d,
+                -1.7316830893872364E-7d,
+                -1.0128633727463388E-7d,
+                +4.8935021740786486E-8d,
+                -1.9740129448026905E-7d,
+                +1.1532102163380318E-7d,
+                +3.5371542244169364E-8d,
+                +4.153321337726989E-8d,
+                +1.3575372396796738E-7d,
+                -1.5685449228299222E-7d,
+                +1.1933437776279623E-7d,
+                +1.2599421120614435E-8d,
+                +1.7331079674066365E-9d,
+                +8.869266069401045E-8d,
+                -2.013999442282902E-7d,
+                +8.709065843311144E-8d,
+                +2.453117120472083E-9d,
+                +2.3489472779602617E-8d,
+                +1.5216652792122652E-7d,
+                -8.638415150333099E-8d,
+                -2.1335475961524608E-7d,
+                -2.2677272333821516E-7d,
+                -1.246635423141374E-7d,
+                +9.494921297991565E-8d,
+                -4.27932550865546E-8d,
+                -5.907349480138712E-8d,
+                +4.809072216941908E-8d,
+                -1.9615359732789476E-7d,
+                +1.6385396676990034E-7d,
+                +1.7642714221524228E-7d,
+                -1.564440844355254E-7d,
+                +1.2090653407564583E-7d,
+                +5.679855838941285E-8d,
+                +1.3006497185242537E-7d,
+                -1.341336085949317E-7d,
+                +2.1987686050231372E-7d,
+                -2.3641341460419062E-7d,
+                -7.048932272279454E-8d,
+                -2.3401958604540354E-7d,
+                +2.2867766559333004E-7d,
+                -1.1089952719756529E-7d,
+                +1.7977178878541792E-7d,
+                +1.4903074102418675E-7d,
+                -2.011072593789072E-7d,
+                +8.504948422097802E-8d,
+                +5.5846006716348844E-8d,
+                +1.9014079059505456E-7d,
+                +1.3119976852347583E-8d,
+                +3.645999732952202E-9d,
+                +1.6374611405314333E-7d,
+                +1.8612397134087598E-8d,
+                +4.7113225346448296E-8d,
+                -2.2555535676499395E-7d,
+                +1.5631615647329739E-7d,
+                -2.3574653182047758E-7d,
+                +3.08072210937242E-8d,
+                +4.344259288116142E-9d,
+                +1.6374489573868447E-7d,
+                +3.42171232580676E-8d,
+                +9.46452492584643E-8d,
+                -1.297587351085525E-7d,
+                -1.601065201853145E-7d,
+                +5.6550495386976275E-9d,
+                -1.0725602261510391E-7d,
+                -1.9945408945084193E-8d,
+                -2.071910882200156E-7d,
+                -1.900947109027913E-7d,
+                +3.34069282059055E-8d,
+                -1.145810806477298E-8d,
+                +1.5421457732308477E-7d,
+                +5.5657084775121975E-8d,
+                +1.7177785285061278E-7d,
+                +2.7813027425289027E-8d,
+                +1.0267509648109748E-7d,
+                -7.839574072711142E-8d,
+                -3.648293887796095E-8d,
+                +2.3049492079013518E-7d,
+                -2.290530257391564E-7d,
+                +1.747018414872141E-8d,
+                +1.8477759656842807E-8d,
+                -2.2394073401050633E-7d,
+                -2.3085653185818848E-7d,
+                -1.7598351175286083E-10d,
+                -6.640551220774385E-9d,
+                +2.2868466674913266E-7d,
+                +2.3106230530437902E-7d,
+                +2.594209135294356E-9d,
+                +2.2221434720602702E-8d,
+                -1.847872222755186E-7d,
+                -1.3948659218254467E-7d,
+                +1.6023339607737848E-7d,
+                -2.3718944120137026E-7d,
+                +1.0087056692827474E-7d,
+                +2.228553660510707E-7d,
+                +1.3088328582956644E-7d,
+                -1.7292527438195104E-7d,
+                -2.0961068531216087E-7d,
+                +2.2951597845188004E-8d,
+                +5.005103745740068E-8d,
+                -1.2618366811281002E-7d,
+                -2.6784582477238417E-8d,
+                -1.2645600379949252E-7d,
+                +5.3774170051560117E-8d,
+                +3.9205810725333715E-8d,
+                -1.6802196396307013E-7d,
+                -8.893078799284047E-8d,
+                -1.9821451970481713E-7d,
+                -1.689060694498032E-8d,
+                -1.9648717830943396E-8d,
+                -2.0433926409457167E-7d,
+                -9.1973399031975E-8d,
+                -1.5723449006087263E-7d,
+                +7.887051614592191E-8d,
+                +1.4166246290402286E-7d,
+                +3.330146018487787E-8d,
+                +2.3278688667580978E-7d,
+                -2.1139124097042925E-7d,
+                +1.334449995534113E-7d,
+                -1.6104730195920897E-7d,
+                -1.3902314592614197E-7d,
+                +2.0169027167169864E-7d,
+                -9.040643863751471E-8d,
+                -5.946190852360168E-8d,
+                -1.8013411720005014E-7d,
+                +2.6595401669835947E-8d,
+                +8.607292924069425E-8d,
+                +4.84038176769263E-10d,
+                -2.2798356346688802E-7d,
+                -1.203028719549339E-7d,
+                -1.5111906039270745E-7d,
+                +1.5859915617670956E-7d,
+                -1.426262681506497E-7d,
+                -9.892260062323546E-8d,
+                -1.8492643515928268E-7d,
+                +7.840210076743552E-8d,
+                +2.1643071541578027E-7d,
+                +2.313664294893465E-7d,
+                +1.2541842003811723E-7d,
+                -9.920197743470107E-8d,
+                +3.655589133934081E-8d,
+                +5.807052689551411E-8d,
+                -3.244024724169575E-8d,
+                -2.327564406466327E-7d,
+                -6.38187356721971E-8d,
+                -2.3995994000400915E-10d,
+                -3.9793609609721186E-8d,
+                -1.802510054588344E-7d,
+                +5.745586744591196E-8d,
+                +1.987228872666507E-7d,
+                -2.3105188606976847E-7d,
+                +2.0088042407239129E-7d,
+                +6.624793114025702E-8d,
+                -1.5587043044056635E-7d,
+                +1.3606464059428694E-8d,
+                +1.0008761540741556E-7d,
+                +1.058213771597129E-7d,
+                +3.3058299602856804E-8d,
+                -1.1594886810010702E-7d,
+                +1.378919824418909E-7d,
+                -1.5683631181406778E-7d,
+                -4.4200075770425176E-8d,
+                +1.2250985436706623E-9d,
+                -1.8297013058336644E-8d,
+                -1.005004229646318E-7d,
+                +2.337202285991116E-7d,
+                +3.296104292035678E-8d,
+                -2.23668185816307E-7d,
+                -5.7055442971184756E-8d,
+                +5.82391923137467E-8d,
+                +1.244950238958056E-7d,
+                +1.4399358260219398E-7d,
+                +1.1901862840583523E-7d,
+                +5.1856152603337505E-8d,
+                -5.520562000491495E-8d,
+                -1.9987622893254038E-7d,
+                +9.697418238031897E-8d,
+                -1.1603376405901542E-7d,
+                +1.170714288147407E-7d,
+                -1.550851303094034E-7d,
+                +2.3472546699189522E-8d,
+                +1.78211222185955E-7d,
+                -1.6540009048230807E-7d,
+                -5.137865010872577E-8d,
+                +4.57490653163866E-8d,
+                +1.2829599363166098E-7d,
+                +1.985773325073412E-7d,
+                -2.1792661654989742E-7d,
+                -1.652218131743459E-7d,
+                -1.178234251477505E-7d,
+                -7.34071933723896E-8d,
+                -2.9646587857612632E-8d,
+                +1.5787194498912167E-8d,
+                +6.52252321321176E-8d,
+                +1.2100088103262734E-7d,
+                +1.8544977697201776E-7d,
+                -2.159273204728711E-7d,
+                -1.2711589287782304E-7d,
+                -2.2610609958205195E-8d,
+                +9.993330547750349E-8d,
+                -2.33974236642384E-7d,
+                -6.830955860192377E-8d,
+                +1.2244183812423448E-7d,
+                -1.3620325027706252E-7d,
+                +1.1178574689680927E-7d,
+                -8.490693031052439E-8d,
+                +2.2975389535985893E-7d,
+                +1.0445707500867073E-7d,
+                +1.8405243253979117E-8d,
+                -2.6033812325397097E-8d,
+                -2.6489990728664908E-8d,
+                +1.9409124727247465E-8d,
+                +1.1403826867020365E-7d,
+                -2.1706266226554237E-7d,
+                -1.7839974359909697E-8d,
+                +2.3725087624341041E-7d,
+                +7.37567604176979E-8d,
+                -2.9098805266958403E-8d,
+                -6.892713087722722E-8d,
+                -4.333719263537725E-8d,
+                +5.006436936098099E-8d,
+                +2.1367325342138113E-7d,
+                -2.6949659655907758E-8d,
+                -1.9256682968755803E-7d,
+                +1.960616287777496E-7d,
+                +1.876664741413704E-7d,
+                -2.1534486893602122E-7d,
+                -5.688830723853217E-8d,
+                +1.8861113228746644E-7d,
+                +4.6730779443102234E-8d,
+                -3.275360514112964E-9d,
+                +4.1011920825226876E-8d,
+                +1.820141955326842E-7d,
+                -5.468175655175594E-8d,
+                -1.8981247089866317E-7d,
+                -2.209492705846306E-7d,
+                -1.4566110577298295E-7d,
+                +3.848544860465368E-8d,
+                -1.429109630340783E-7d,
+                -2.105749999899302E-7d,
+                -1.6206609756618993E-7d,
+                +5.058693461947143E-9d,
+                -1.8359244902596882E-7d,
+                +2.2810251664891242E-7d,
+                -1.8791776732592608E-7d,
+                +1.3106843166204263E-9d,
+                -1.5543153797220025E-7d,
+                -1.7884997059081524E-7d,
+                -6.648490725635754E-8d,
+                +1.8412576154421806E-7d,
+                +9.860939269906055E-8d,
+                +1.5627006743114285E-7d,
+                -1.17260039161597E-7d,
+                +2.3416513526430908E-7d,
+                -2.1749172296989992E-7d,
+                -3.9242560971295217E-8d,
+                -1.822826971477839E-7d,
+                -1.6729355321895212E-7d,
+                +8.208715337901827E-9d,
+                -1.301267783434537E-7d,
+                -1.029741755377153E-7d,
+                +9.215765583599035E-8d,
+                -1.907487641016455E-8d,
+                +4.2661388254716074E-8d,
+                -1.9697226735187428E-7d,
+                +2.1819935527247946E-7d,
+                -1.398318929248588E-7d,
+                +1.6195123407015624E-7d,
+                +1.723826394935661E-7d,
+                -1.0602700638269148E-7d,
+                -1.9392742205954563E-7d,
+                -8.880302882034106E-8d,
+                +2.1186420987133E-7d,
+                +2.3375763256988976E-7d,
+                -2.0599801342241997E-8d,
+                -7.184550924856607E-8d,
+                +8.254840070367875E-8d,
+            };
+
+    /** Extended precision logarithm table over the range 1 - 2 in increments of 2^-10. */
+    private static final double[][] LN_MANT =
+            new double[][] {
+                {
+                    +0.0d, +0.0d,
+                }, // 0
+                {
+                    +9.760860120877624E-4d, -3.903230345984362E-11d,
+                }, // 1
+                {
+                    +0.0019512202125042677d, -8.124251825289188E-11d,
+                }, // 2
+                {
+                    +0.0029254043474793434d, -1.8374207360194882E-11d,
+                }, // 3
+                {
+                    +0.0038986406289041042d, -2.1324678121885073E-10d,
+                }, // 4
+                {
+                    +0.004870930686593056d, -4.5199654318611534E-10d,
+                }, // 5
+                {
+                    +0.00584227591753006d, -2.933016992001806E-10d,
+                }, // 6
+                {
+                    +0.006812678650021553d, -2.325147219074669E-10d,
+                }, // 7
+                {
+                    +0.007782140746712685d, -3.046577356838847E-10d,
+                }, // 8
+                {
+                    +0.008750664070248604d, -5.500631513861575E-10d,
+                }, // 9
+                {
+                    +0.00971824862062931d, +8.48292035519895E-10d,
+                }, // 10
+                {
+                    +0.010684899985790253d, +1.1422610134013436E-10d,
+                }, // 11
+                {
+                    +0.01165061630308628d, +9.168889933128375E-10d,
+                }, // 12
+                {
+                    +0.012615403160452843d, -5.303786078838E-10d,
+                }, // 13
+                {
+                    +0.013579258695244789d, -5.688639355498786E-10d,
+                }, // 14
+                {
+                    +0.01454218477010727d, +7.296670293275653E-10d,
+                }, // 15
+                {
+                    +0.015504186972975731d, -4.370104767451421E-10d,
+                }, // 16
+                {
+                    +0.016465261578559875d, +1.43695591408832E-9d,
+                }, // 17
+                {
+                    +0.01742541790008545d, -1.1862263158849434E-9d,
+                }, // 18
+                {
+                    +0.018384650349617004d, -9.482976524690715E-10d,
+                }, // 19
+                {
+                    +0.01934296265244484d, +1.9068609515836638E-10d,
+                }, // 20
+                {
+                    +0.020300358533859253d, +2.655990315697216E-10d,
+                }, // 21
+                {
+                    +0.021256837993860245d, +1.0315548713040775E-9d,
+                }, // 22
+                {
+                    +0.022212404757738113d, +5.13345647019085E-10d,
+                }, // 23
+                {
+                    +0.02316705882549286d, +4.5604151934208014E-10d,
+                }, // 24
+                {
+                    +0.02412080392241478d, -1.1255706987475148E-9d,
+                }, // 25
+                {
+                    +0.025073636323213577d, +1.2289023836765196E-9d,
+                }, // 26
+                {
+                    +0.02602556347846985d, +1.7990281828096504E-9d,
+                }, // 27
+                {
+                    +0.026976589113473892d, -1.4152718164638451E-9d,
+                }, // 28
+                {
+                    +0.02792670577764511d, +7.568772963781632E-10d,
+                }, // 29
+                {
+                    +0.0288759246468544d, -1.1449998592111558E-9d,
+                }, // 30
+                {
+                    +0.029824241995811462d, -1.6850976862319495E-9d,
+                }, // 31
+                {
+                    +0.030771657824516296d, +8.422373919843096E-10d,
+                }, // 32
+                {
+                    +0.0317181795835495d, +6.872350402175489E-10d,
+                }, // 33
+                {
+                    +0.03266380727291107d, -4.541194749189272E-10d,
+                }, // 34
+                {
+                    +0.03360854089260101d, -8.9064764856495E-10d,
+                }, // 35
+                {
+                    +0.034552380442619324d, +1.0640404096769032E-9d,
+                }, // 36
+                {
+                    +0.0354953333735466d, -3.5901655945224663E-10d,
+                }, // 37
+                {
+                    +0.03643739968538284d, -3.4829517943661266E-9d,
+                }, // 38
+                {
+                    +0.037378571927547455d, +8.149473794244232E-10d,
+                }, // 39
+                {
+                    +0.03831886500120163d, -6.990650304449166E-10d,
+                }, // 40
+                {
+                    +0.03925827145576477d, +1.0883076226453258E-9d,
+                }, // 41
+                {
+                    +0.040196798741817474d, +3.845192807999274E-10d,
+                }, // 42
+                {
+                    +0.04113444685935974d, -1.1570594692045927E-9d,
+                }, // 43
+                {
+                    +0.04207121580839157d, -1.8877045166697178E-9d,
+                }, // 44
+                {
+                    +0.043007105588912964d, -1.6332083257987747E-10d,
+                }, // 45
+                {
+                    +0.04394212365150452d, -1.7950057534514933E-9d,
+                }, // 46
+                {
+                    +0.04487626254558563d, +2.302710041648838E-9d,
+                }, // 47
+                {
+                    +0.045809537172317505d, -1.1410233017161343E-9d,
+                }, // 48
+                {
+                    +0.04674194008111954d, -3.0498741599744685E-9d,
+                }, // 49
+                {
+                    +0.04767347127199173d, -1.8026348269183678E-9d,
+                }, // 50
+                {
+                    +0.04860413819551468d, -3.233204600453039E-9d,
+                }, // 51
+                {
+                    +0.04953393340110779d, +1.7211688427961583E-9d,
+                }, // 52
+                {
+                    +0.05046287178993225d, -2.329967807055457E-10d,
+                }, // 53
+                {
+                    +0.05139094591140747d, -4.191810118556531E-11d,
+                }, // 54
+                {
+                    +0.052318163216114044d, -3.5574324788328143E-9d,
+                }, // 55
+                {
+                    +0.053244516253471375d, -1.7346590916458485E-9d,
+                }, // 56
+                {
+                    +0.05417001247406006d, -4.343048751383674E-10d,
+                }, // 57
+                {
+                    +0.055094651877880096d, +1.92909364037955E-9d,
+                }, // 58
+                {
+                    +0.056018441915512085d, -5.139745677199588E-10d,
+                }, // 59
+                {
+                    +0.05694137513637543d, +1.2637629975129189E-9d,
+                }, // 60
+                {
+                    +0.05786345899105072d, +1.3840561112481119E-9d,
+                }, // 61
+                {
+                    +0.058784693479537964d, +1.414889689612056E-9d,
+                }, // 62
+                {
+                    +0.05970507860183716d, +2.9199191907666474E-9d,
+                }, // 63
+                {
+                    +0.0606246218085289d, +7.90594243412116E-12d,
+                }, // 64
+                {
+                    +0.06154331564903259d, +1.6844747839686189E-9d,
+                }, // 65
+                {
+                    +0.06246116757392883d, +2.0498074572151747E-9d,
+                }, // 66
+                {
+                    +0.06337818503379822d, -4.800180493433863E-9d,
+                }, // 67
+                {
+                    +0.06429435312747955d, -2.4220822960064277E-9d,
+                }, // 68
+                {
+                    +0.06520968675613403d, -4.179048566709334E-9d,
+                }, // 69
+                {
+                    +0.06612417101860046d, +6.363872957010456E-9d,
+                }, // 70
+                {
+                    +0.06703783571720123d, +9.339468680056365E-10d,
+                }, // 71
+                {
+                    +0.06795066595077515d, -4.04226739708981E-9d,
+                }, // 72
+                {
+                    +0.0688626617193222d, -7.043545052852817E-9d,
+                }, // 73
+                {
+                    +0.06977382302284241d, -6.552819560439773E-9d,
+                }, // 74
+                {
+                    +0.07068414986133575d, -1.0571674860370546E-9d,
+                }, // 75
+                {
+                    +0.07159365713596344d, -3.948954622015801E-9d,
+                }, // 76
+                {
+                    +0.07250232994556427d, +1.1776625988228244E-9d,
+                }, // 77
+                {
+                    +0.07341018319129944d, +9.221072639606492E-10d,
+                }, // 78
+                {
+                    +0.07431721687316895d, -3.219119568928366E-9d,
+                }, // 79
+                {
+                    +0.0752234160900116d, +5.147575929018918E-9d,
+                }, // 80
+                {
+                    +0.07612881064414978d, -2.291749683541979E-9d,
+                }, // 81
+                {
+                    +0.07703337073326111d, +5.749565906124772E-9d,
+                }, // 82
+                {
+                    +0.07793712615966797d, +9.495158151301779E-10d,
+                }, // 83
+                {
+                    +0.07884006202220917d, -3.144331429489291E-10d,
+                }, // 84
+                {
+                    +0.0797421783208847d, +3.430029236134205E-9d,
+                }, // 85
+                {
+                    +0.08064348995685577d, -1.2499290483167703E-9d,
+                }, // 86
+                {
+                    +0.08154398202896118d, +2.011215719133196E-9d,
+                }, // 87
+                {
+                    +0.08244366943836212d, -2.2728753031387152E-10d,
+                }, // 88
+                {
+                    +0.0833425521850586d, -6.508966857277253E-9d,
+                }, // 89
+                {
+                    +0.0842406153678894d, -4.801131671405377E-10d,
+                }, // 90
+                {
+                    +0.08513787388801575d, +4.406750291994231E-9d,
+                }, // 91
+                {
+                    +0.08603434264659882d, -5.304795662536171E-9d,
+                }, // 92
+                {
+                    +0.08692999184131622d, +1.6284313912612293E-9d,
+                }, // 93
+                {
+                    +0.08782485127449036d, -3.158898981674071E-9d,
+                }, // 94
+                {
+                    +0.08871890604496002d, -3.3324878834139977E-9d,
+                }, // 95
+                {
+                    +0.08961215615272522d, +2.536961912893389E-9d,
+                }, // 96
+                {
+                    +0.09050461649894714d, +9.737596728980696E-10d,
+                }, // 97
+                {
+                    +0.0913962870836258d, -6.600437262505396E-9d,
+                }, // 98
+                {
+                    +0.09228715300559998d, -3.866609889222889E-9d,
+                }, // 99
+                {
+                    +0.09317722916603088d, -4.311847594020281E-9d,
+                }, // 100
+                {
+                    +0.09406651556491852d, -6.525851105645959E-9d,
+                }, // 101
+                {
+                    +0.09495499730110168d, +5.799080912675435E-9d,
+                }, // 102
+                {
+                    +0.09584270417690277d, +4.2634204358490415E-9d,
+                }, // 103
+                {
+                    +0.09672962129116058d, +5.167390528799477E-9d,
+                }, // 104
+                {
+                    +0.09761576354503632d, -4.994827392841906E-9d,
+                }, // 105
+                {
+                    +0.09850110113620758d, +4.970725577861395E-9d,
+                }, // 106
+                {
+                    +0.09938566386699677d, +6.6496705953229645E-9d,
+                }, // 107
+                {
+                    +0.10026945173740387d, +1.4262712796792241E-9d,
+                }, // 108
+                {
+                    +0.1011524498462677d, +5.5822855204629114E-9d,
+                }, // 109
+                {
+                    +0.10203467309474945d, +5.593494835247651E-9d,
+                }, // 110
+                {
+                    +0.10291612148284912d, +2.8332008343480686E-9d,
+                }, // 111
+                {
+                    +0.10379679501056671d, -1.3289231465997192E-9d,
+                }, // 112
+                {
+                    +0.10467669367790222d, -5.526819276639527E-9d,
+                }, // 113
+                {
+                    +0.10555580258369446d, +6.503128678219282E-9d,
+                }, // 114
+                {
+                    +0.10643415153026581d, +6.317463237641817E-9d,
+                }, // 115
+                {
+                    +0.10731174051761627d, -4.728528221305482E-9d,
+                }, // 116
+                {
+                    +0.10818853974342346d, +4.519199083083901E-9d,
+                }, // 117
+                {
+                    +0.10906457901000977d, +5.606492666349878E-9d,
+                }, // 118
+                {
+                    +0.10993985831737518d, -1.220176214398581E-10d,
+                }, // 119
+                {
+                    +0.11081436276435852d, +3.5759315936869937E-9d,
+                }, // 120
+                {
+                    +0.11168810725212097d, +3.1367659571899855E-9d,
+                }, // 121
+                {
+                    +0.11256109178066254d, -1.0543075713098835E-10d,
+                }, // 122
+                {
+                    +0.11343331634998322d, -4.820065619207094E-9d,
+                }, // 123
+                {
+                    +0.11430476605892181d, +5.221136819669415E-9d,
+                }, // 124
+                {
+                    +0.11517547070980072d, +1.5395018670011342E-9d,
+                }, // 125
+                {
+                    +0.11604541540145874d, +3.5638391501880846E-10d,
+                }, // 126
+                {
+                    +0.11691460013389587d, +2.9885336757136527E-9d,
+                }, // 127
+                {
+                    +0.11778303980827332d, -4.151889860890893E-9d,
+                }, // 128
+                {
+                    +0.11865071952342987d, -4.853823938804204E-9d,
+                }, // 129
+                {
+                    +0.11951763927936554d, +2.189226237170704E-9d,
+                }, // 130
+                {
+                    +0.12038381397724152d, +3.3791993048776982E-9d,
+                }, // 131
+                {
+                    +0.1212492436170578d, +1.5811884868243975E-11d,
+                }, // 132
+                {
+                    +0.12211392819881439d, -6.6045909118908625E-9d,
+                }, // 133
+                {
+                    +0.1229778528213501d, -2.8786263916116364E-10d,
+                }, // 134
+                {
+                    +0.12384103238582611d, +5.354472503748251E-9d,
+                }, // 135
+                {
+                    +0.12470348179340363d, -3.2924463896248744E-9d,
+                }, // 136
+                {
+                    +0.12556517124176025d, +4.856678149580005E-9d,
+                }, // 137
+                {
+                    +0.12642613053321838d, +1.2791850600366742E-9d,
+                }, // 138
+                {
+                    +0.12728634476661682d, +2.1525945093362843E-9d,
+                }, // 139
+                {
+                    +0.12814581394195557d, +8.749974471767862E-9d,
+                }, // 140
+                {
+                    +0.129004567861557d, -7.461209161105275E-9d,
+                }, // 141
+                {
+                    +0.12986254692077637d, +1.4390208226263824E-8d,
+                }, // 142
+                {
+                    +0.1307198405265808d, -1.3839477920475328E-8d,
+                }, // 143
+                {
+                    +0.13157635927200317d, -1.483283901239408E-9d,
+                }, // 144
+                {
+                    +0.13243216276168823d, -6.889072914229094E-9d,
+                }, // 145
+                {
+                    +0.1332872211933136d, +9.990351100568362E-10d,
+                }, // 146
+                {
+                    +0.13414156436920166d, -6.370937412495338E-9d,
+                }, // 147
+                {
+                    +0.13499516248703003d, +2.05047480130511E-9d,
+                }, // 148
+                {
+                    +0.1358480453491211d, -2.29509872547079E-9d,
+                }, // 149
+                {
+                    +0.13670018315315247d, +1.16354361977249E-8d,
+                }, // 150
+                {
+                    +0.13755163550376892d, -1.452496267904829E-8d,
+                }, // 151
+                {
+                    +0.1384023129940033d, +9.865115839786888E-9d,
+                }, // 152
+                {
+                    +0.13925230503082275d, -3.369999130712228E-9d,
+                }, // 153
+                {
+                    +0.14010155200958252d, +6.602496401651853E-9d,
+                }, // 154
+                {
+                    +0.14095008373260498d, +1.1205312852298845E-8d,
+                }, // 155
+                {
+                    +0.14179790019989014d, +1.1660367213160203E-8d,
+                }, // 156
+                {
+                    +0.142645001411438d, +9.186471222585239E-9d,
+                }, // 157
+                {
+                    +0.14349138736724854d, +4.999341878263704E-9d,
+                }, // 158
+                {
+                    +0.14433705806732178d, +3.11611905696257E-10d,
+                }, // 159
+                {
+                    +0.14518201351165771d, -3.6671598175618173E-9d,
+                }, // 160
+                {
+                    +0.14602625370025635d, -5.730477881659618E-9d,
+                }, // 161
+                {
+                    +0.14686977863311768d, -4.674900007989718E-9d,
+                }, // 162
+                {
+                    +0.1477125883102417d, +6.999732437141968E-10d,
+                }, // 163
+                {
+                    +0.14855468273162842d, +1.159150872494107E-8d,
+                }, // 164
+                {
+                    +0.14939609169960022d, -6.082714828488485E-10d,
+                }, // 165
+                {
+                    +0.15023678541183472d, -4.905712741596318E-9d,
+                }, // 166
+                {
+                    +0.1510767638683319d, -1.124848988733307E-10d,
+                }, // 167
+                {
+                    +0.15191605687141418d, -1.484557220949851E-8d,
+                }, // 168
+                {
+                    +0.15275460481643677d, +1.1682026251371384E-8d,
+                }, // 169
+                {
+                    +0.15359249711036682d, -8.757272519238786E-9d,
+                }, // 170
+                {
+                    +0.15442964434623718d, +1.4419920764774415E-8d,
+                }, // 171
+                {
+                    +0.15526613593101501d, -7.019891063126053E-9d,
+                }, // 172
+                {
+                    +0.15610191226005554d, -1.230153548825964E-8d,
+                }, // 173
+                {
+                    +0.15693697333335876d, -2.574172005933276E-10d,
+                }, // 174
+                {
+                    +0.15777134895324707d, +4.748140799544371E-10d,
+                }, // 175
+                {
+                    +0.15860503911972046d, -8.943081874891003E-9d,
+                }, // 176
+                {
+                    +0.15943801403045654d, +2.4500739038517657E-9d,
+                }, // 177
+                {
+                    +0.1602703034877777d, +6.007922084557054E-9d,
+                }, // 178
+                {
+                    +0.16110190749168396d, +2.8835418231126645E-9d,
+                }, // 179
+                {
+                    +0.1619328260421753d, -5.772862039728412E-9d,
+                }, // 180
+                {
+                    +0.16276302933692932d, +1.0988372954605789E-8d,
+                }, // 181
+                {
+                    +0.16359257698059082d, -5.292913162607026E-9d,
+                }, // 182
+                {
+                    +0.16442140936851501d, +6.12956339275823E-9d,
+                }, // 183
+                {
+                    +0.16524958610534668d, -1.3210039516811888E-8d,
+                }, // 184
+                {
+                    +0.16607704758644104d, -2.5711014608334873E-9d,
+                }, // 185
+                {
+                    +0.16690382361412048d, +9.37721319457112E-9d,
+                }, // 186
+                {
+                    +0.1677299439907074d, -6.0370682395944045E-9d,
+                }, // 187
+                {
+                    +0.168555349111557d, +1.1918249660105651E-8d,
+                }, // 188
+                {
+                    +0.1693800985813141d, +4.763282949656017E-9d,
+                }, // 189
+                {
+                    +0.17020416259765625d, +3.4223342273948817E-9d,
+                }, // 190
+                {
+                    +0.1710275411605835d, +9.014612241310916E-9d,
+                }, // 191
+                {
+                    +0.1718502640724182d, -7.145758990550526E-9d,
+                }, // 192
+                {
+                    +0.172672301530838d, -1.4142763934081504E-8d,
+                }, // 193
+                {
+                    +0.1734936535358429d, -1.0865453656579032E-8d,
+                }, // 194
+                {
+                    +0.17431432008743286d, +3.794385569450774E-9d,
+                }, // 195
+                {
+                    +0.1751343309879303d, +1.1399188501627291E-9d,
+                }, // 196
+                {
+                    +0.17595365643501282d, +1.2076238768270153E-8d,
+                }, // 197
+                {
+                    +0.1767723262310028d, +7.901084730502162E-9d,
+                }, // 198
+                {
+                    +0.17759034037590027d, -1.0288181007465474E-8d,
+                }, // 199
+                {
+                    +0.1784076690673828d, -1.15945645153806E-8d,
+                }, // 200
+                {
+                    +0.17922431230545044d, +5.073923825786778E-9d,
+                }, // 201
+                {
+                    +0.18004029989242554d, +1.1004278077575267E-8d,
+                }, // 202
+                {
+                    +0.1808556318283081d, +7.2831502374676964E-9d,
+                }, // 203
+                {
+                    +0.18167030811309814d, -5.0054634662706464E-9d,
+                }, // 204
+                {
+                    +0.18248429894447327d, +5.022108460298934E-9d,
+                }, // 205
+                {
+                    +0.18329763412475586d, +8.642254225732676E-9d,
+                }, // 206
+                {
+                    +0.18411031365394592d, +6.931054493326395E-9d,
+                }, // 207
+                {
+                    +0.18492233753204346d, +9.619685356326533E-10d,
+                }, // 208
+                {
+                    +0.18573370575904846d, -8.194157257980706E-9d,
+                }, // 209
+                {
+                    +0.18654438853263855d, +1.0333241479437797E-8d,
+                }, // 210
+                {
+                    +0.1873544454574585d, -1.9948340196027965E-9d,
+                }, // 211
+                {
+                    +0.1881638467311859d, -1.4313002926259948E-8d,
+                }, // 212
+                {
+                    +0.1889725625514984d, +4.241536392174967E-9d,
+                }, // 213
+                {
+                    +0.18978065252304077d, -4.877952454011428E-9d,
+                }, // 214
+                {
+                    +0.1905880868434906d, -1.0813801247641613E-8d,
+                }, // 215
+                {
+                    +0.1913948655128479d, -1.2513218445781325E-8d,
+                }, // 216
+                {
+                    +0.19220098853111267d, -8.925958555729115E-9d,
+                }, // 217
+                {
+                    +0.1930064558982849d, +9.956860681280245E-10d,
+                }, // 218
+                {
+                    +0.193811297416687d, -1.1505428993246996E-8d,
+                }, // 219
+                {
+                    +0.1946154534816742d, +1.4217997464522202E-8d,
+                }, // 220
+                {
+                    +0.19541901350021362d, -1.0200858727747717E-8d,
+                }, // 221
+                {
+                    +0.19622188806533813d, +5.682607223902455E-9d,
+                }, // 222
+                {
+                    +0.1970241367816925d, +3.2988908516009827E-9d,
+                }, // 223
+                {
+                    +0.19782572984695435d, +1.3482965534659446E-8d,
+                }, // 224
+                {
+                    +0.19862669706344604d, +7.462678536479685E-9d,
+                }, // 225
+                {
+                    +0.1994270384311676d, -1.3734273888891115E-8d,
+                }, // 226
+                {
+                    +0.20022669434547424d, +1.0521983802642893E-8d,
+                }, // 227
+                {
+                    +0.20102575421333313d, -8.152742388541905E-9d,
+                }, // 228
+                {
+                    +0.2018241584300995d, -9.133484280193855E-9d,
+                }, // 229
+                {
+                    +0.20262190699577332d, +8.59763959528144E-9d,
+                }, // 230
+                {
+                    +0.2034190595149994d, -1.3548568223001414E-8d,
+                }, // 231
+                {
+                    +0.20421552658081055d, +1.4847880344628818E-8d,
+                }, // 232
+                {
+                    +0.20501139760017395d, +5.390620378060543E-9d,
+                }, // 233
+                {
+                    +0.2058066427707672d, -1.1109834472051523E-8d,
+                }, // 234
+                {
+                    +0.20660123229026794d, -3.845373872038116E-9d,
+                }, // 235
+                {
+                    +0.20739519596099854d, -1.6149279479975042E-9d,
+                }, // 236
+                {
+                    +0.20818853378295898d, -3.4174925203771133E-9d,
+                }, // 237
+                {
+                    +0.2089812457561493d, -8.254443919468538E-9d,
+                }, // 238
+                {
+                    +0.20977330207824707d, +1.4672790944499144E-8d,
+                }, // 239
+                {
+                    +0.2105647623538971d, +6.753452542942992E-9d,
+                }, // 240
+                {
+                    +0.21135559678077698d, -1.218609462241927E-9d,
+                }, // 241
+                {
+                    +0.21214580535888672d, -8.254218316367887E-9d,
+                }, // 242
+                {
+                    +0.21293538808822632d, -1.3366540360587255E-8d,
+                }, // 243
+                {
+                    +0.2137243151664734d, +1.4231244750190031E-8d,
+                }, // 244
+                {
+                    +0.2145126760005951d, -1.3885660525939072E-8d,
+                }, // 245
+                {
+                    +0.21530038118362427d, -7.3304404046850136E-9d,
+                }, // 246
+                {
+                    +0.2160874605178833d, +5.072117654842356E-9d,
+                }, // 247
+                {
+                    +0.21687394380569458d, -5.505080220459036E-9d,
+                }, // 248
+                {
+                    +0.21765980124473572d, -8.286782292266659E-9d,
+                }, // 249
+                {
+                    +0.2184450328350067d, -2.302351152358085E-9d,
+                }, // 250
+                {
+                    +0.21922963857650757d, +1.3416565858314603E-8d,
+                }, // 251
+                {
+                    +0.22001364827156067d, +1.0033721426962048E-8d,
+                }, // 252
+                {
+                    +0.22079706192016602d, -1.1487079818684332E-8d,
+                }, // 253
+                {
+                    +0.22157981991767883d, +9.420348186357043E-9d,
+                }, // 254
+                {
+                    +0.2223619818687439d, +1.4110645699377834E-8d,
+                }, // 255
+                {
+                    +0.2231435477733612d, +3.5408485497116107E-9d,
+                }, // 256
+                {
+                    +0.22392448782920837d, +8.468072777056227E-9d,
+                }, // 257
+                {
+                    +0.2247048318386078d, +4.255446699237779E-11d,
+                }, // 258
+                {
+                    +0.22548454999923706d, +9.016946273084244E-9d,
+                }, // 259
+                {
+                    +0.22626367211341858d, +6.537034810260226E-9d,
+                }, // 260
+                {
+                    +0.22704219818115234d, -6.451285264969768E-9d,
+                }, // 261
+                {
+                    +0.22782009840011597d, +7.979956357126066E-10d,
+                }, // 262
+                {
+                    +0.22859740257263184d, -5.759582672039005E-10d,
+                }, // 263
+                {
+                    +0.22937411069869995d, -9.633854121180397E-9d,
+                }, // 264
+                {
+                    +0.23015019297599792d, +4.363736368635843E-9d,
+                }, // 265
+                {
+                    +0.23092567920684814d, +1.2549416560182509E-8d,
+                }, // 266
+                {
+                    +0.231700599193573d, -1.3946383592553814E-8d,
+                }, // 267
+                {
+                    +0.2324748933315277d, -1.458843364504023E-8d,
+                }, // 268
+                {
+                    +0.23324856162071228d, +1.1551692104697154E-8d,
+                }, // 269
+                {
+                    +0.23402166366577148d, +5.795621295524984E-9d,
+                }, // 270
+                {
+                    +0.23479416966438293d, -1.1301979046684263E-9d,
+                }, // 271
+                {
+                    +0.23556607961654663d, -8.303779721781787E-9d,
+                }, // 272
+                {
+                    +0.23633739352226257d, -1.4805271785394075E-8d,
+                }, // 273
+                {
+                    +0.23710808157920837d, +1.0085373835899469E-8d,
+                }, // 274
+                {
+                    +0.2378782033920288d, +7.679117635349454E-9d,
+                }, // 275
+                {
+                    +0.2386477291584015d, +8.69177352065934E-9d,
+                }, // 276
+                {
+                    +0.23941665887832642d, +1.4034725764547136E-8d,
+                }, // 277
+                {
+                    +0.24018502235412598d, -5.185064518887831E-9d,
+                }, // 278
+                {
+                    +0.2409527599811554d, +1.1544236628121676E-8d,
+                }, // 279
+                {
+                    +0.24171993136405945d, +5.523085719902123E-9d,
+                }, // 280
+                {
+                    +0.24248650670051575d, +7.456824943331887E-9d,
+                }, // 281
+                {
+                    +0.24325251579284668d, -1.1555923403029638E-8d,
+                }, // 282
+                {
+                    +0.24401789903640747d, +8.988361382732908E-9d,
+                }, // 283
+                {
+                    +0.2447827160358429d, +1.0381848020926893E-8d,
+                }, // 284
+                {
+                    +0.24554696679115295d, -6.480706118857055E-9d,
+                }, // 285
+                {
+                    +0.24631062150001526d, -1.0904271124793968E-8d,
+                }, // 286
+                {
+                    +0.2470736801624298d, -1.998183061531611E-9d,
+                }, // 287
+                {
+                    +0.247836172580719d, -8.676137737360023E-9d,
+                }, // 288
+                {
+                    +0.24859806895256042d, -2.4921733203932487E-10d,
+                }, // 289
+                {
+                    +0.2493593990802765d, -5.635173762130303E-9d,
+                }, // 290
+                {
+                    +0.2501201629638672d, -2.3951455355985637E-8d,
+                }, // 291
+                {
+                    +0.25088030099868774d, +5.287121672447825E-9d,
+                }, // 292
+                {
+                    +0.2516399025917053d, -6.447877375049486E-9d,
+                }, // 293
+                {
+                    +0.25239890813827515d, +1.32472428796441E-9d,
+                }, // 294
+                {
+                    +0.2531573176383972d, +2.9479464287605006E-8d,
+                }, // 295
+                {
+                    +0.2539151906967163d, +1.9284247135543574E-8d,
+                }, // 296
+                {
+                    +0.2546725273132324d, -2.8390360197221716E-8d,
+                }, // 297
+                {
+                    +0.255429208278656d, +6.533522495226226E-9d,
+                }, // 298
+                {
+                    +0.2561853528022766d, +5.713225978895991E-9d,
+                }, // 299
+                {
+                    +0.25694090127944946d, +2.9618050962556135E-8d,
+                }, // 300
+                {
+                    +0.25769591331481934d, +1.950605015323617E-8d,
+                }, // 301
+                {
+                    +0.25845038890838623d, -2.3762031507525576E-8d,
+                }, // 302
+                {
+                    +0.2592042088508606d, +1.98818938195077E-8d,
+                }, // 303
+                {
+                    +0.25995755195617676d, -2.751925069084042E-8d,
+                }, // 304
+                {
+                    +0.2607102394104004d, +1.3703391844683932E-8d,
+                }, // 305
+                {
+                    +0.26146239042282104d, +2.5193525310038174E-8d,
+                }, // 306
+                {
+                    +0.2622140049934387d, +7.802219817310385E-9d,
+                }, // 307
+                {
+                    +0.26296502351760864d, +2.1983272709242607E-8d,
+                }, // 308
+                {
+                    +0.2637155055999756d, +8.979279989292184E-9d,
+                }, // 309
+                {
+                    +0.2644653916358948d, +2.9240221157844312E-8d,
+                }, // 310
+                {
+                    +0.265214741230011d, +2.4004885823813374E-8d,
+                }, // 311
+                {
+                    +0.2659635543823242d, -5.885186277410878E-9d,
+                }, // 312
+                {
+                    +0.2667117714881897d, +1.4300386517357162E-11d,
+                }, // 313
+                {
+                    +0.2674594521522522d, -1.7063531531989365E-8d,
+                }, // 314
+                {
+                    +0.26820653676986694d, +3.3218524692903896E-9d,
+                }, // 315
+                {
+                    +0.2689530849456787d, +2.3998252479954764E-9d,
+                }, // 316
+                {
+                    +0.2696990966796875d, -1.8997462070389404E-8d,
+                }, // 317
+                {
+                    +0.27044451236724854d, -4.350745270980051E-10d,
+                }, // 318
+                {
+                    +0.2711893916130066d, -6.892221115467135E-10d,
+                }, // 319
+                {
+                    +0.27193373441696167d, -1.89333199110902E-8d,
+                }, // 320
+                {
+                    +0.272677481174469d, +5.262017392507765E-9d,
+                }, // 321
+                {
+                    +0.27342069149017334d, +1.3115046679980076E-8d,
+                }, // 322
+                {
+                    +0.2741633653640747d, +5.4468361834451975E-9d,
+                }, // 323
+                {
+                    +0.2749055027961731d, -1.692337384653611E-8d,
+                }, // 324
+                {
+                    +0.27564704418182373d, +6.426479056697412E-9d,
+                }, // 325
+                {
+                    +0.2763880491256714d, +1.670735065191342E-8d,
+                }, // 326
+                {
+                    +0.27712851762771606d, +1.4733029698334834E-8d,
+                }, // 327
+                {
+                    +0.27786844968795776d, +1.315498542514467E-9d,
+                }, // 328
+                {
+                    +0.2786078453063965d, -2.2735061539223372E-8d,
+                }, // 329
+                {
+                    +0.27934664487838745d, +2.994379757313727E-9d,
+                }, // 330
+                {
+                    +0.28008490800857544d, +1.970577274107218E-8d,
+                }, // 331
+                {
+                    +0.28082263469696045d, +2.820392733542077E-8d,
+                }, // 332
+                {
+                    +0.2815598249435425d, +2.929187356678173E-8d,
+                }, // 333
+                {
+                    +0.28229647874832153d, +2.377086680926386E-8d,
+                }, // 334
+                {
+                    +0.2830325961112976d, +1.2440393009992529E-8d,
+                }, // 335
+                {
+                    +0.2837681770324707d, -3.901826104778096E-9d,
+                }, // 336
+                {
+                    +0.2845032215118408d, -2.4459827842685974E-8d,
+                }, // 337
+                {
+                    +0.2852376699447632d, +1.1165241398059789E-8d,
+                }, // 338
+                {
+                    +0.28597164154052734d, -1.54434478239181E-8d,
+                }, // 339
+                {
+                    +0.28670501708984375d, +1.5714110564653245E-8d,
+                }, // 340
+                {
+                    +0.28743791580200195d, -1.3782394940142479E-8d,
+                }, // 341
+                {
+                    +0.2881702184677124d, +1.6063569876284005E-8d,
+                }, // 342
+                {
+                    +0.28890204429626465d, -1.317176818216125E-8d,
+                }, // 343
+                {
+                    +0.28963327407836914d, +1.8504673536253893E-8d,
+                }, // 344
+                {
+                    +0.29036402702331543d, -7.334319635123628E-9d,
+                }, // 345
+                {
+                    +0.29109418392181396d, +2.9300903540317107E-8d,
+                }, // 346
+                {
+                    +0.2918238639831543d, +9.979706999541057E-9d,
+                }, // 347
+                {
+                    +0.29255300760269165d, -4.916314210412424E-9d,
+                }, // 348
+                {
+                    +0.293281614780426d, -1.4611908070155308E-8d,
+                }, // 349
+                {
+                    +0.2940096855163574d, -1.833351586679361E-8d,
+                }, // 350
+                {
+                    +0.29473721981048584d, -1.530926726615185E-8d,
+                }, // 351
+                {
+                    +0.2954642176628113d, -4.7689754029101934E-9d,
+                }, // 352
+                {
+                    +0.29619067907333374d, +1.4055868011423819E-8d,
+                }, // 353
+                {
+                    +0.296916663646698d, -1.7672547212604003E-8d,
+                }, // 354
+                {
+                    +0.2976420521736145d, +2.0020234215759705E-8d,
+                }, // 355
+                {
+                    +0.2983669638633728d, +8.688424478730524E-9d,
+                }, // 356
+                {
+                    +0.2990913391113281d, +8.69851089918337E-9d,
+                }, // 357
+                {
+                    +0.29981517791748047d, +2.0810681643102672E-8d,
+                }, // 358
+                {
+                    +0.3005385398864746d, -1.3821169493779352E-8d,
+                }, // 359
+                {
+                    +0.301261305809021d, +2.4769140784919128E-8d,
+                }, // 360
+                {
+                    +0.3019835948944092d, +1.8127576600610336E-8d,
+                }, // 361
+                {
+                    +0.3027053475379944d, +2.6612401062437074E-8d,
+                }, // 362
+                {
+                    +0.3034266233444214d, -8.629042891789934E-9d,
+                }, // 363
+                {
+                    +0.3041473627090454d, -2.724174869314043E-8d,
+                }, // 364
+                {
+                    +0.30486756563186646d, -2.8476975783775358E-8d,
+                }, // 365
+                {
+                    +0.3055872321128845d, -1.1587600174449919E-8d,
+                }, // 366
+                {
+                    +0.3063063621520996d, +2.417189020581056E-8d,
+                }, // 367
+                {
+                    +0.3070250153541565d, +1.99407553679345E-8d,
+                }, // 368
+                {
+                    +0.3077431917190552d, -2.35387025694381E-8d,
+                }, // 369
+                {
+                    +0.3084607720375061d, +1.3683509995845583E-8d,
+                }, // 370
+                {
+                    +0.30917787551879883d, +1.3137214081023085E-8d,
+                }, // 371
+                {
+                    +0.30989450216293335d, -2.444006866174775E-8d,
+                }, // 372
+                {
+                    +0.3106105327606201d, +2.0896888605749563E-8d,
+                }, // 373
+                {
+                    +0.31132614612579346d, -2.893149098508887E-8d,
+                }, // 374
+                {
+                    +0.31204116344451904d, +5.621509038251498E-9d,
+                }, // 375
+                {
+                    +0.3127557039260864d, +6.0778104626050015E-9d,
+                }, // 376
+                {
+                    +0.3134697675704956d, -2.6832941696716294E-8d,
+                }, // 377
+                {
+                    +0.31418323516845703d, +2.6826625274495256E-8d,
+                }, // 378
+                {
+                    +0.31489628553390503d, -1.1030897183911054E-8d,
+                }, // 379
+                {
+                    +0.31560879945755005d, -2.047124671392676E-8d,
+                }, // 380
+                {
+                    +0.3163207769393921d, -7.709990443086711E-10d,
+                }, // 381
+                {
+                    +0.3170322775840759d, -1.0812918808112342E-8d,
+                }, // 382
+                {
+                    +0.3177432417869568d, +9.727979174888975E-9d,
+                }, // 383
+                {
+                    +0.31845372915267944d, +1.9658551724508715E-9d,
+                }, // 384
+                {
+                    +0.3191636800765991d, +2.6222628001695826E-8d,
+                }, // 385
+                {
+                    +0.3198731541633606d, +2.3609400272358744E-8d,
+                }, // 386
+                {
+                    +0.32058215141296387d, -5.159602957634814E-9d,
+                }, // 387
+                {
+                    +0.32129061222076416d, +2.329701319016099E-10d,
+                }, // 388
+                {
+                    +0.32199859619140625d, -1.910633190395738E-8d,
+                }, // 389
+                {
+                    +0.32270604372024536d, -2.863180390093667E-9d,
+                }, // 390
+                {
+                    +0.32341301441192627d, -9.934041364456825E-9d,
+                }, // 391
+                {
+                    +0.3241194486618042d, +1.999240777687192E-8d,
+                }, // 392
+                {
+                    +0.3248254060745239d, +2.801670341647724E-8d,
+                }, // 393
+                {
+                    +0.32553088665008545d, +1.4842534265191358E-8d,
+                }, // 394
+                {
+                    +0.32623589038848877d, -1.882789920477354E-8d,
+                }, // 395
+                {
+                    +0.3269403576850891d, -1.268923579073577E-8d,
+                }, // 396
+                {
+                    +0.32764434814453125d, -2.564688370677835E-8d,
+                }, // 397
+                {
+                    +0.3283478021621704d, +2.6015626820520968E-9d,
+                }, // 398
+                {
+                    +0.32905077934265137d, +1.3147747907784344E-8d,
+                }, // 399
+                {
+                    +0.3297532796859741d, +6.686493860720675E-9d,
+                }, // 400
+                {
+                    +0.33045530319213867d, -1.608884086544153E-8d,
+                }, // 401
+                {
+                    +0.33115679025650024d, +5.118287907840204E-9d,
+                }, // 402
+                {
+                    +0.3318578004837036d, +1.139367970944884E-8d,
+                }, // 403
+                {
+                    +0.3325583338737488d, +3.426327822115399E-9d,
+                }, // 404
+                {
+                    +0.33325839042663574d, -1.809622142990733E-8d,
+                }, // 405
+                {
+                    +0.3339579105377197d, +7.116780143398601E-9d,
+                }, // 406
+                {
+                    +0.3346569538116455d, +2.0145352306345386E-8d,
+                }, // 407
+                {
+                    +0.3353555202484131d, +2.167272474431968E-8d,
+                }, // 408
+                {
+                    +0.33605360984802246d, +1.2380696294966822E-8d,
+                }, // 409
+                {
+                    +0.33675122261047363d, -7.050361059209181E-9d,
+                }, // 410
+                {
+                    +0.3374482989311218d, +2.366314656322868E-8d,
+                }, // 411
+                {
+                    +0.3381449580192566d, -1.4010540194086646E-8d,
+                }, // 412
+                {
+                    +0.3388410806655884d, -1.860165465666482E-10d,
+                }, // 413
+                {
+                    +0.33953672647476196d, +6.206776940880773E-9d,
+                }, // 414
+                {
+                    +0.34023189544677734d, +5.841137379010982E-9d,
+                }, // 415
+                {
+                    +0.3409265875816345d, -6.11041311179286E-10d,
+                }, // 416
+                {
+                    +0.3416208028793335d, -1.2479264502054702E-8d,
+                }, // 417
+                {
+                    +0.34231454133987427d, -2.909443297645926E-8d,
+                }, // 418
+                {
+                    +0.34300774335861206d, +9.815805717097634E-9d,
+                }, // 419
+                {
+                    +0.3437005281448364d, -1.4291517981101049E-8d,
+                }, // 420
+                {
+                    +0.3443927764892578d, +1.8457821628427503E-8d,
+                }, // 421
+                {
+                    +0.34508460760116577d, -1.0481908869377813E-8d,
+                }, // 422
+                {
+                    +0.34577590227127075d, +1.876076001514746E-8d,
+                }, // 423
+                {
+                    +0.3464667797088623d, -1.2362653723769037E-8d,
+                }, // 424
+                {
+                    +0.3471571207046509d, +1.6016578405624026E-8d,
+                }, // 425
+                {
+                    +0.347847044467926d, -1.4652759033760925E-8d,
+                }, // 426
+                {
+                    +0.3485364317893982d, +1.549533655901835E-8d,
+                }, // 427
+                {
+                    +0.34922540187835693d, -1.2093068629412478E-8d,
+                }, // 428
+                {
+                    +0.3499138355255127d, +2.244531711424792E-8d,
+                }, // 429
+                {
+                    +0.35060185194015503d, +5.538565518604807E-10d,
+                }, // 430
+                {
+                    +0.35128939151763916d, -1.7511499366215853E-8d,
+                }, // 431
+                {
+                    +0.3519763946533203d, +2.850385787215544E-8d,
+                }, // 432
+                {
+                    +0.35266298055648804d, +2.003926370146842E-8d,
+                }, // 433
+                {
+                    +0.35334908962249756d, +1.734665280502264E-8d,
+                }, // 434
+                {
+                    +0.3540347218513489d, +2.1071983674869414E-8d,
+                }, // 435
+                {
+                    +0.35471993684768677d, -2.774475773922311E-8d,
+                }, // 436
+                {
+                    +0.3554046154022217d, -9.250975291734664E-9d,
+                }, // 437
+                {
+                    +0.3560888171195984d, +1.7590672330295415E-8d,
+                }, // 438
+                {
+                    +0.35677260160446167d, -6.1837904549178745E-9d,
+                }, // 439
+                {
+                    +0.35745590925216675d, -2.0330362973820856E-8d,
+                }, // 440
+                {
+                    +0.3581387400627136d, -2.42109990366786E-8d,
+                }, // 441
+                {
+                    +0.3588210940361023d, -1.7188958587407816E-8d,
+                }, // 442
+                {
+                    +0.35950297117233276d, +1.3711958590112228E-9d,
+                }, // 443
+                {
+                    +0.3601844310760498d, -2.7501042008405925E-8d,
+                }, // 444
+                {
+                    +0.36086535453796387d, +1.6036460343275798E-8d,
+                }, // 445
+                {
+                    +0.3615458607673645d, +1.3405964389498495E-8d,
+                }, // 446
+                {
+                    +0.36222589015960693d, +2.484237749027735E-8d,
+                }, // 447
+                {
+                    +0.36290550231933594d, -8.629967484362177E-9d,
+                }, // 448
+                {
+                    +0.36358463764190674d, -2.6778729562324134E-8d,
+                }, // 449
+                {
+                    +0.36426329612731934d, -2.8977490516960565E-8d,
+                }, // 450
+                {
+                    +0.36494147777557373d, -1.4601106624823502E-8d,
+                }, // 451
+                {
+                    +0.3656191825866699d, +1.69742947894444E-8d,
+                }, // 452
+                {
+                    +0.3662964701652527d, +6.7666740211281175E-9d,
+                }, // 453
+                {
+                    +0.36697328090667725d, +1.500201674336832E-8d,
+                }, // 454
+                {
+                    +0.3676496744155884d, -1.730424167425052E-8d,
+                }, // 455
+                {
+                    +0.36832553148269653d, +2.9676011119845104E-8d,
+                }, // 456
+                {
+                    +0.36900103092193604d, -2.2253590346826743E-8d,
+                }, // 457
+                {
+                    +0.36967599391937256d, +6.3372065441089185E-9d,
+                }, // 458
+                {
+                    +0.37035053968429565d, -3.145816653215968E-9d,
+                }, // 459
+                {
+                    +0.37102460861206055d, +9.515812117036965E-9d,
+                }, // 460
+                {
+                    +0.371698260307312d, -1.4669965113042639E-8d,
+                }, // 461
+                {
+                    +0.3723714351654053d, -1.548715389333397E-8d,
+                }, // 462
+                {
+                    +0.37304413318634033d, +7.674361647125109E-9d,
+                }, // 463
+                {
+                    +0.37371641397476196d, -4.181177882069608E-9d,
+                }, // 464
+                {
+                    +0.3743882179260254d, +9.158530500130718E-9d,
+                }, // 465
+                {
+                    +0.3750596046447754d, -1.13047236597869E-8d,
+                }, // 466
+                {
+                    +0.3757305145263672d, -5.36108186384227E-9d,
+                }, // 467
+                {
+                    +0.3764009475708008d, +2.7593452284747873E-8d,
+                }, // 468
+                {
+                    +0.37707096338272095d, +2.8557016344085205E-8d,
+                }, // 469
+                {
+                    +0.3777405619621277d, -1.868818164036E-9d,
+                }, // 470
+                {
+                    +0.3784096837043762d, -3.479042513414447E-9d,
+                }, // 471
+                {
+                    +0.37907832860946655d, +2.432550290565648E-8d,
+                }, // 472
+                {
+                    +0.37974655628204346d, +2.2538131805476768E-8d,
+                }, // 473
+                {
+                    +0.38041436672210693d, -8.244395239939089E-9d,
+                }, // 474
+                {
+                    +0.3810817003250122d, -7.821867597227376E-9d,
+                }, // 475
+                {
+                    +0.3817485570907593d, +2.4400089062515914E-8d,
+                }, // 476
+                {
+                    +0.3824149966239929d, +2.9410015940087773E-8d,
+                }, // 477
+                {
+                    +0.38308101892471313d, +7.799913824734797E-9d,
+                }, // 478
+                {
+                    +0.38374656438827515d, +1.976524624939355E-8d,
+                }, // 479
+                {
+                    +0.38441169261932373d, +6.291008309266035E-9d,
+                }, // 480
+                {
+                    +0.3850763440132141d, +2.757030889767851E-8d,
+                }, // 481
+                {
+                    +0.38574057817459106d, +2.4585794728405612E-8d,
+                }, // 482
+                {
+                    +0.3864043951034546d, -2.0764122246389383E-9d,
+                }, // 483
+                {
+                    +0.3870677351951599d, +7.77328837578952E-9d,
+                }, // 484
+                {
+                    +0.3877306580543518d, -4.8859560029989374E-9d,
+                }, // 485
+                {
+                    +0.3883931040763855d, +2.0133131420595028E-8d,
+                }, // 486
+                {
+                    +0.38905513286590576d, +2.380738071335498E-8d,
+                }, // 487
+                {
+                    +0.3897167444229126d, +6.7171126157142075E-9d,
+                }, // 488
+                {
+                    +0.39037787914276123d, +2.9046141593926277E-8d,
+                }, // 489
+                {
+                    +0.3910386562347412d, -2.7836800219410262E-8d,
+                }, // 490
+                {
+                    +0.3916988968849182d, +1.545909820981726E-8d,
+                }, // 491
+                {
+                    +0.39235877990722656d, -1.930436269002062E-8d,
+                }, // 492
+                {
+                    +0.3930181860923767d, -1.2343297554921835E-8d,
+                }, // 493
+                {
+                    +0.3936771750450134d, -2.268889128622553E-8d,
+                }, // 494
+                {
+                    +0.39433568716049194d, +9.835827818608177E-9d,
+                }, // 495
+                {
+                    +0.39499378204345703d, +2.6197411946856397E-8d,
+                }, // 496
+                {
+                    +0.3956514596939087d, +2.6965931069318893E-8d,
+                }, // 497
+                {
+                    +0.3963087201118469d, +1.2710331127772166E-8d,
+                }, // 498
+                {
+                    +0.39696556329727173d, -1.6001563011916016E-8d,
+                }, // 499
+                {
+                    +0.39762192964553833d, +1.0016001590267064E-9d,
+                }, // 500
+                {
+                    +0.3982778787612915d, +4.680767399874334E-9d,
+                }, // 501
+                {
+                    +0.39893341064453125d, -4.399582029272418E-9d,
+                }, // 502
+                {
+                    +0.39958852529525757d, -2.5676078228301587E-8d,
+                }, // 503
+                {
+                    +0.4002431631088257d, +1.0181870233355787E-9d,
+                }, // 504
+                {
+                    +0.40089738368988037d, +1.6639728835984655E-8d,
+                }, // 505
+                {
+                    +0.40155118703842163d, +2.174860642202632E-8d,
+                }, // 506
+                {
+                    +0.40220457315444946d, +1.6903781197123503E-8d,
+                }, // 507
+                {
+                    +0.40285754203796387d, +2.663119647467697E-9d,
+                }, // 508
+                {
+                    +0.40351009368896484d, -2.0416603812329616E-8d,
+                }, // 509
+                {
+                    +0.4041621685028076d, +7.82494078472695E-9d,
+                }, // 510
+                {
+                    +0.40481382608413696d, +2.833770747113627E-8d,
+                }, // 511
+                {
+                    +0.40546512603759766d, -1.7929433274271985E-8d,
+                }, // 512
+                {
+                    +0.40611594915390015d, -1.1214757379328965E-8d,
+                }, // 513
+                {
+                    +0.4067663550376892d, -1.0571553019207106E-8d,
+                }, // 514
+                {
+                    +0.40741634368896484d, -1.5449538712332313E-8d,
+                }, // 515
+                {
+                    +0.40806591510772705d, -2.529950530235105E-8d,
+                }, // 516
+                {
+                    +0.40871500968933105d, +2.0031331601617008E-8d,
+                }, // 517
+                {
+                    +0.4093637466430664d, +1.880755298741952E-9d,
+                }, // 518
+                {
+                    +0.41001206636428833d, -1.9600580584843318E-8d,
+                }, // 519
+                {
+                    +0.41065990924835205d, +1.573691633515306E-8d,
+                }, // 520
+                {
+                    +0.4113073945045471d, -1.0772154376548336E-8d,
+                }, // 521
+                {
+                    +0.411954402923584d, +2.0624330192486066E-8d,
+                }, // 522
+                {
+                    +0.4126010537147522d, -8.741139170029572E-9d,
+                }, // 523
+                {
+                    +0.4132472276687622d, +2.0881457123894216E-8d,
+                }, // 524
+                {
+                    +0.41389304399490356d, -9.177488027521808E-9d,
+                }, // 525
+                {
+                    +0.4145383834838867d, +2.0829952491625585E-8d,
+                }, // 526
+                {
+                    +0.4151833653450012d, -7.767915492597301E-9d,
+                }, // 527
+                {
+                    +0.4158278703689575d, +2.4774753446082082E-8d,
+                }, // 528
+                {
+                    +0.41647201776504517d, -2.1581119071750435E-10d,
+                }, // 529
+                {
+                    +0.4171157479286194d, -2.260047972865202E-8d,
+                }, // 530
+                {
+                    +0.4177590012550354d, +1.775884601423381E-8d,
+                }, // 531
+                {
+                    +0.41840189695358276d, +2.185301053838889E-9d,
+                }, // 532
+                {
+                    +0.4190443754196167d, -9.185071463667081E-9d,
+                }, // 533
+                {
+                    +0.4196864366531372d, -1.5821896727910552E-8d,
+                }, // 534
+                {
+                    +0.4203280806541443d, -1.719582086188318E-8d,
+                }, // 535
+                {
+                    +0.42096930742263794d, -1.2778508303324259E-8d,
+                }, // 536
+                {
+                    +0.42161011695861816d, -2.042639194493364E-9d,
+                }, // 537
+                {
+                    +0.42225050926208496d, +1.5538093219698803E-8d,
+                }, // 538
+                {
+                    +0.4228905439376831d, -1.9115659590156936E-8d,
+                }, // 539
+                {
+                    +0.42353010177612305d, +1.3729680248843432E-8d,
+                }, // 540
+                {
+                    +0.42416930198669434d, -4.611893838830296E-9d,
+                }, // 541
+                {
+                    +0.4248080849647522d, -1.4013456880651706E-8d,
+                }, // 542
+                {
+                    +0.42544645071029663d, -1.3953728897042917E-8d,
+                }, // 543
+                {
+                    +0.42608439922332764d, -3.912427573594197E-9d,
+                }, // 544
+                {
+                    +0.4267219305038452d, +1.6629734283189315E-8d,
+                }, // 545
+                {
+                    +0.42735910415649414d, -1.1413593493354881E-8d,
+                }, // 546
+                {
+                    +0.42799586057662964d, -2.792046157580119E-8d,
+                }, // 547
+                {
+                    +0.42863214015960693d, +2.723009182661306E-8d,
+                }, // 548
+                {
+                    +0.42926812171936035d, -2.4260535621557444E-8d,
+                }, // 549
+                {
+                    +0.42990362644195557d, -3.064060124024764E-9d,
+                }, // 550
+                {
+                    +0.43053877353668213d, -2.787640178598121E-8d,
+                }, // 551
+                {
+                    +0.4311734437942505d, +2.102412085257792E-8d,
+                }, // 552
+                {
+                    +0.4318077564239502d, +2.4939635093999683E-8d,
+                }, // 553
+                {
+                    +0.43244171142578125d, -1.5619414792273914E-8d,
+                }, // 554
+                {
+                    +0.4330751895904541d, +1.9065734894871523E-8d,
+                }, // 555
+                {
+                    +0.4337083101272583d, +1.0294301092654604E-8d,
+                }, // 556
+                {
+                    +0.4343410134315491d, +1.8178469851136E-8d,
+                }, // 557
+                {
+                    +0.4349733591079712d, -1.6379825102473853E-8d,
+                }, // 558
+                {
+                    +0.4356052279472351d, +2.6334323946685834E-8d,
+                }, // 559
+                {
+                    +0.43623673915863037d, +2.761628769925529E-8d,
+                }, // 560
+                {
+                    +0.436867892742157d, -1.2030229087793677E-8d,
+                }, // 561
+                {
+                    +0.4374985694885254d, +2.7106814809424793E-8d,
+                }, // 562
+                {
+                    +0.43812888860702515d, +2.631993083235205E-8d,
+                }, // 563
+                {
+                    +0.43875885009765625d, -1.3890028312254422E-8d,
+                }, // 564
+                {
+                    +0.43938833475112915d, +2.6186133735555794E-8d,
+                }, // 565
+                {
+                    +0.4400174617767334d, +2.783809071694788E-8d,
+                }, // 566
+                {
+                    +0.440646231174469d, -8.436135220472006E-9d,
+                }, // 567
+                {
+                    +0.44127458333969116d, -2.2534815932619883E-8d,
+                }, // 568
+                {
+                    +0.4419025182723999d, -1.3961804471714283E-8d,
+                }, // 569
+                {
+                    +0.4425300359725952d, +1.7778112039716255E-8d,
+                }, // 570
+                {
+                    +0.4431571960449219d, +1.3574569976673652E-8d,
+                }, // 571
+                {
+                    +0.4437839984893799d, -2.607907890164073E-8d,
+                }, // 572
+                {
+                    +0.4444103240966797d, +1.8518879652136628E-8d,
+                }, // 573
+                {
+                    +0.44503629207611084d, +2.865065604247164E-8d,
+                }, // 574
+                {
+                    +0.44566190242767334d, +4.806827797299427E-9d,
+                }, // 575
+                {
+                    +0.4462870955467224d, +7.0816970994232115E-9d,
+                }, // 576
+                {
+                    +0.44691193103790283d, -2.3640641240074437E-8d,
+                }, // 577
+                {
+                    +0.4475363492965698d, -2.7267718387865538E-8d,
+                }, // 578
+                {
+                    +0.4481603503227234d, -3.3126235292976077E-9d,
+                }, // 579
+                {
+                    +0.4487839937210083d, -1.0894001590268427E-8d,
+                }, // 580
+                {
+                    +0.4494072198867798d, +1.0077883359971829E-8d,
+                }, // 581
+                {
+                    +0.4500300884246826d, +4.825712712114668E-10d,
+                }, // 582
+                {
+                    +0.450652539730072d, +2.0407987470746858E-8d,
+                }, // 583
+                {
+                    +0.4512746334075928d, +1.073186581170719E-8d,
+                }, // 584
+                {
+                    +0.4518963694572449d, -2.8064314757880205E-8d,
+                }, // 585
+                {
+                    +0.45251762866973877d, +2.3709316816226527E-8d,
+                }, // 586
+                {
+                    +0.4531385898590088d, -1.2281487504266522E-8d,
+                }, // 587
+                {
+                    +0.4537591338157654d, -1.634864487421458E-8d,
+                }, // 588
+                {
+                    +0.45437926054000854d, +1.1985747222409522E-8d,
+                }, // 589
+                {
+                    +0.45499902963638306d, +1.3594057956219485E-8d,
+                }, // 590
+                {
+                    +0.4556184411048889d, -1.1047585095328619E-8d,
+                }, // 591
+                {
+                    +0.45623743534088135d, -1.8592937532754405E-9d,
+                }, // 592
+                {
+                    +0.4568560719490051d, -1.797135137545755E-8d,
+                }, // 593
+                {
+                    +0.4574742913246155d, +6.943684261645378E-10d,
+                }, // 594
+                {
+                    +0.4580921530723572d, -4.994175141684681E-9d,
+                }, // 595
+                {
+                    +0.45870959758758545d, +2.5039391215625133E-8d,
+                }, // 596
+                {
+                    +0.45932674407958984d, -2.7943366835352838E-8d,
+                }, // 597
+                {
+                    +0.45994341373443604d, +1.534146910128904E-8d,
+                }, // 598
+                {
+                    +0.46055978536605835d, -2.3450920230816267E-8d,
+                }, // 599
+                {
+                    +0.46117573976516724d, -2.4642997069960124E-8d,
+                }, // 600
+                {
+                    +0.4617912769317627d, +1.2232622070370946E-8d,
+                }, // 601
+                {
+                    +0.4624064564704895d, +2.80378133047839E-8d,
+                }, // 602
+                {
+                    +0.46302127838134766d, +2.3238237048117092E-8d,
+                }, // 603
+                {
+                    +0.46363574266433716d, -1.7013046451109475E-9d,
+                }, // 604
+                {
+                    +0.46424978971481323d, +1.3287778803035383E-8d,
+                }, // 605
+                {
+                    +0.46486347913742065d, +9.06393426961373E-9d,
+                }, // 606
+                {
+                    +0.4654768109321594d, -1.3910598647592876E-8d,
+                }, // 607
+                {
+                    +0.46608972549438477d, +4.430214458933614E-9d,
+                }, // 608
+                {
+                    +0.46670228242874146d, +4.942270562885745E-9d,
+                }, // 609
+                {
+                    +0.4673144817352295d, -1.1914734393460718E-8d,
+                }, // 610
+                {
+                    +0.4679262638092041d, +1.3922696570638494E-8d,
+                }, // 611
+                {
+                    +0.46853768825531006d, +2.3307929211781914E-8d,
+                }, // 612
+                {
+                    +0.46914875507354736d, +1.669813444584674E-8d,
+                }, // 613
+                {
+                    +0.469759464263916d, -5.450354376430758E-9d,
+                }, // 614
+                {
+                    +0.47036975622177124d, +1.6922605350647674E-8d,
+                }, // 615
+                {
+                    +0.4709796905517578d, +2.4667033200046904E-8d,
+                }, // 616
+                {
+                    +0.47158926725387573d, +1.8236762070433784E-8d,
+                }, // 617
+                {
+                    +0.472198486328125d, -1.915204563140137E-9d,
+                }, // 618
+                {
+                    +0.47280728816986084d, +2.426795414605756E-8d,
+                }, // 619
+                {
+                    +0.4734157919883728d, -2.19717006713618E-8d,
+                }, // 620
+                {
+                    +0.47402387857437134d, -2.0974352165535873E-8d,
+                }, // 621
+                {
+                    +0.47463154792785645d, +2.770970558184228E-8d,
+                }, // 622
+                {
+                    +0.4752389192581177d, +5.32006955298355E-9d,
+                }, // 623
+                {
+                    +0.47584593296051025d, -2.809054633964104E-8d,
+                }, // 624
+                {
+                    +0.4764525294303894d, -1.2470243596102937E-8d,
+                }, // 625
+                {
+                    +0.4770587682723999d, -6.977226702440138E-9d,
+                }, // 626
+                {
+                    +0.47766464948654175d, -1.1165866833118273E-8d,
+                }, // 627
+                {
+                    +0.47827017307281494d, -2.4591344661022708E-8d,
+                }, // 628
+                {
+                    +0.4788752794265747d, +1.2794996377383974E-8d,
+                }, // 629
+                {
+                    +0.4794800877571106d, -1.7772927065973874E-8d,
+                }, // 630
+                {
+                    +0.48008447885513306d, +3.35657712457243E-9d,
+                }, // 631
+                {
+                    +0.48068851232528687d, +1.7020465042442242E-8d,
+                }, // 632
+                {
+                    +0.481292188167572d, +2.365953779624783E-8d,
+                }, // 633
+                {
+                    +0.4818955063819885d, +2.3713798664443718E-8d,
+                }, // 634
+                {
+                    +0.4824984669685364d, +1.7622455019548098E-8d,
+                }, // 635
+                {
+                    +0.4831010699272156d, +5.823920246566496E-9d,
+                }, // 636
+                {
+                    +0.4837033152580261d, -1.1244184344361017E-8d,
+                }, // 637
+                {
+                    +0.48430514335632324d, +2.645961716432205E-8d,
+                }, // 638
+                {
+                    +0.4849066734313965d, +1.6207809718247905E-10d,
+                }, // 639
+                {
+                    +0.4855077862739563d, +2.9507744508973654E-8d,
+                }, // 640
+                {
+                    +0.48610860109329224d, -4.278201128741098E-9d,
+                }, // 641
+                {
+                    +0.48670899868011475d, +1.844722015961139E-8d,
+                }, // 642
+                {
+                    +0.4873090982437134d, -2.1092372471088425E-8d,
+                }, // 643
+                {
+                    +0.4879087805747986d, -3.2555596107382053E-9d,
+                }, // 644
+                {
+                    +0.48850810527801514d, +1.2784366845429667E-8d,
+                }, // 645
+                {
+                    +0.48910707235336304d, +2.7457984659996047E-8d,
+                }, // 646
+                {
+                    +0.48970574140548706d, -1.8409546441412518E-8d,
+                }, // 647
+                {
+                    +0.49030399322509766d, -5.179903818099661E-9d,
+                }, // 648
+                {
+                    +0.4909018874168396d, +7.97053127828682E-9d,
+                }, // 649
+                {
+                    +0.4914994239807129d, +2.146925464473481E-8d,
+                }, // 650
+                {
+                    +0.4920966625213623d, -2.3861648589988232E-8d,
+                }, // 651
+                {
+                    +0.4926934838294983d, -8.386923035320549E-9d,
+                }, // 652
+                {
+                    +0.4932899475097656d, +8.713990131749256E-9d,
+                }, // 653
+                {
+                    +0.4938860535621643d, +2.7865534085810115E-8d,
+                }, // 654
+                {
+                    +0.4944818615913391d, -1.011325138560159E-8d,
+                }, // 655
+                {
+                    +0.4950772523880005d, +1.4409851026316708E-8d,
+                }, // 656
+                {
+                    +0.495672345161438d, -1.735227547472004E-8d,
+                }, // 657
+                {
+                    +0.49626702070236206d, +1.4231078209064581E-8d,
+                }, // 658
+                {
+                    +0.49686139822006226d, -9.628709342929729E-9d,
+                }, // 659
+                {
+                    +0.4974554181098938d, -2.8907074856577267E-8d,
+                }, // 660
+                {
+                    +0.4980490207672119d, +1.6419797090870802E-8d,
+                }, // 661
+                {
+                    +0.49864232540130615d, +7.561041519403049E-9d,
+                }, // 662
+                {
+                    +0.49923527240753174d, +4.538983468118194E-9d,
+                }, // 663
+                {
+                    +0.49982786178588867d, +7.770560657946324E-9d,
+                }, // 664
+                {
+                    +0.500420093536377d, +1.767197002609876E-8d,
+                }, // 665
+                {
+                    +0.5010119676589966d, +3.46586694799214E-8d,
+                }, // 666
+                {
+                    +0.5016034841537476d, +5.914537964556077E-8d,
+                }, // 667
+                {
+                    +0.5021947622299194d, -2.7663203939320167E-8d,
+                }, // 668
+                {
+                    +0.5027855634689331d, +1.3064749115929298E-8d,
+                }, // 669
+                {
+                    +0.5033761262893677d, -5.667682106730711E-8d,
+                }, // 670
+                {
+                    +0.503966212272644d, +1.9424534974370594E-9d,
+                }, // 671
+                {
+                    +0.5045560598373413d, -4.908494602153544E-8d,
+                }, // 672
+                {
+                    +0.5051454305648804d, +2.906989285008994E-8d,
+                }, // 673
+                {
+                    +0.5057345628738403d, -1.602000800745108E-9d,
+                }, // 674
+                {
+                    +0.5063233375549316d, -2.148245271118002E-8d,
+                }, // 675
+                {
+                    +0.5069117546081543d, -3.016329994276181E-8d,
+                }, // 676
+                {
+                    +0.5074998140335083d, -2.7237099632871992E-8d,
+                }, // 677
+                {
+                    +0.5080875158309937d, -1.2297127301923986E-8d,
+                }, // 678
+                {
+                    +0.5086748600006104d, +1.5062624834468093E-8d,
+                }, // 679
+                {
+                    +0.5092618465423584d, +5.524744954836658E-8d,
+                }, // 680
+                {
+                    +0.5098485946655273d, -1.054736327333046E-8d,
+                }, // 681
+                {
+                    +0.5104348659515381d, +5.650063324725722E-8d,
+                }, // 682
+                {
+                    +0.5110208988189697d, +1.8376017791642605E-8d,
+                }, // 683
+                {
+                    +0.5116065740585327d, -5.309470636324855E-9d,
+                }, // 684
+                {
+                    +0.512191891670227d, -1.4154089255217218E-8d,
+                }, // 685
+                {
+                    +0.5127768516540527d, -7.756800301729815E-9d,
+                }, // 686
+                {
+                    +0.5133614540100098d, +1.4282730618002001E-8d,
+                }, // 687
+                {
+                    +0.5139456987380981d, +5.2364136172269755E-8d,
+                }, // 688
+                {
+                    +0.5145297050476074d, -1.2322940607922115E-8d,
+                }, // 689
+                {
+                    +0.5151132345199585d, +5.903831350855322E-8d,
+                }, // 690
+                {
+                    +0.5156965255737305d, +2.8426856726994483E-8d,
+                }, // 691
+                {
+                    +0.5162794589996338d, +1.544882070711032E-8d,
+                }, // 692
+                {
+                    +0.5168620347976685d, +2.0500353979930155E-8d,
+                }, // 693
+                {
+                    +0.5174442529678345d, +4.397691311390564E-8d,
+                }, // 694
+                {
+                    +0.5180262327194214d, -3.2936025225250634E-8d,
+                }, // 695
+                {
+                    +0.5186077356338501d, +2.857419553449673E-8d,
+                }, // 696
+                {
+                    +0.5191890001296997d, -9.51761338269325E-9d,
+                }, // 697
+                {
+                    +0.5197699069976807d, -2.7609457648450225E-8d,
+                }, // 698
+                {
+                    +0.520350456237793d, -2.5309316441333305E-8d,
+                }, // 699
+                {
+                    +0.5209306478500366d, -2.2258513086839407E-9d,
+                }, // 700
+                {
+                    +0.5215104818344116d, +4.203159541613745E-8d,
+                }, // 701
+                {
+                    +0.5220900774002075d, -1.1356287358852729E-8d,
+                }, // 702
+                {
+                    +0.5226693153381348d, -4.279090925831093E-8d,
+                }, // 703
+                {
+                    +0.5232481956481934d, -5.188364552285819E-8d,
+                }, // 704
+                {
+                    +0.5238267183303833d, -3.82465458937857E-8d,
+                }, // 705
+                {
+                    +0.5244048833847046d, -1.4923330530645769E-9d,
+                }, // 706
+                {
+                    +0.5249826908111572d, +5.8765598932137004E-8d,
+                }, // 707
+                {
+                    +0.5255602598190308d, +2.3703896609663678E-8d,
+                }, // 708
+                {
+                    +0.5261374711990356d, +1.2917117341231647E-8d,
+                }, // 709
+                {
+                    +0.5267143249511719d, +2.6789862192139226E-8d,
+                }, // 710
+                {
+                    +0.527290940284729d, -5.350322253112414E-8d,
+                }, // 711
+                {
+                    +0.5278670787811279d, +1.0839714455426386E-8d,
+                }, // 712
+                {
+                    +0.5284429788589478d, -1.821729591343314E-8d,
+                }, // 713
+                {
+                    +0.5290185213088989d, -2.1083014672301448E-8d,
+                }, // 714
+                {
+                    +0.5295937061309814d, +2.623848491704216E-9d,
+                }, // 715
+                {
+                    +0.5301685333251953d, +5.328392630534142E-8d,
+                }, // 716
+                {
+                    +0.5307431221008301d, +1.206790586971942E-8d,
+                }, // 717
+                {
+                    +0.5313173532485962d, -1.4356011804377797E-9d,
+                }, // 718
+                {
+                    +0.5318912267684937d, +1.3152074173459994E-8d,
+                }, // 719
+                {
+                    +0.5324647426605225d, +5.6208949382936426E-8d,
+                }, // 720
+                {
+                    +0.5330380201339722d, +8.90310227565917E-9d,
+                }, // 721
+                {
+                    +0.5336109399795532d, -9.179458802504127E-9d,
+                }, // 722
+                {
+                    +0.5341835021972656d, +2.337337845617735E-9d,
+                }, // 723
+                {
+                    +0.5347557067871094d, +4.3828918300477925E-8d,
+                }, // 724
+                {
+                    +0.535327672958374d, -3.5392250480081715E-9d,
+                }, // 725
+                {
+                    +0.53589928150177d, -2.0183663375378704E-8d,
+                }, // 726
+                {
+                    +0.5364705324172974d, -5.730898606435436E-9d,
+                }, // 727
+                {
+                    +0.537041425704956d, +4.0191927599879235E-8d,
+                }, // 728
+                {
+                    +0.5376120805740356d, -1.2522542401353875E-9d,
+                }, // 729
+                {
+                    +0.5381823778152466d, -1.0482571326594316E-8d,
+                }, // 730
+                {
+                    +0.5387523174285889d, +1.2871924223480165E-8d,
+                }, // 731
+                {
+                    +0.539322018623352d, -5.002774317612589E-8d,
+                }, // 732
+                {
+                    +0.539891242980957d, +3.960668706590162E-8d,
+                }, // 733
+                {
+                    +0.5404602289199829d, +4.372568630242375E-8d,
+                }, // 734
+                {
+                    +0.5410289764404297d, -3.730232461206926E-8d,
+                }, // 735
+                {
+                    +0.5415972471237183d, +3.5309026109857795E-8d,
+                }, // 736
+                {
+                    +0.5421652793884277d, +2.3508325311148225E-8d,
+                }, // 737
+                {
+                    +0.5427329540252686d, +4.6871403168921666E-8d,
+                }, // 738
+                {
+                    +0.5433003902435303d, -1.3445113140270216E-8d,
+                }, // 739
+                {
+                    +0.5438674688339233d, -3.786663982218041E-8d,
+                }, // 740
+                {
+                    +0.5444341897964478d, -2.602850370608209E-8d,
+                }, // 741
+                {
+                    +0.5450005531311035d, +2.2433348713144506E-8d,
+                }, // 742
+                {
+                    +0.5455666780471802d, -1.1326936872620137E-8d,
+                }, // 743
+                {
+                    +0.5461324453353882d, -7.737252533211342E-9d,
+                }, // 744
+                {
+                    +0.5466978549957275d, +3.3564604642699844E-8d,
+                }, // 745
+                {
+                    +0.5472630262374878d, -6.269066061111782E-9d,
+                }, // 746
+                {
+                    +0.5478278398513794d, -7.667998948729528E-9d,
+                }, // 747
+                {
+                    +0.5483922958374023d, +2.9728170818998143E-8d,
+                }, // 748
+                {
+                    +0.5489565134048462d, -1.2930091396008281E-8d,
+                }, // 749
+                {
+                    +0.5495203733444214d, -1.607434968107079E-8d,
+                }, // 750
+                {
+                    +0.5500838756561279d, +2.0653935146671156E-8d,
+                }, // 751
+                {
+                    +0.5506471395492554d, -2.1596593091833788E-8d,
+                }, // 752
+                {
+                    +0.5512100458145142d, -2.3259315921149476E-8d,
+                }, // 753
+                {
+                    +0.5517725944519043d, +1.6022492496522704E-8d,
+                }, // 754
+                {
+                    +0.5523349046707153d, -2.260433328226171E-8d,
+                }, // 755
+                {
+                    +0.5528968572616577d, -1.957497997726303E-8d,
+                }, // 756
+                {
+                    +0.5534584522247314d, +2.5465477111883854E-8d,
+                }, // 757
+                {
+                    +0.5540198087692261d, -6.33792454933092E-9d,
+                }, // 758
+                {
+                    +0.554580807685852d, +4.577835263278281E-9d,
+                }, // 759
+                {
+                    +0.5551414489746094d, +5.856589221771548E-8d,
+                }, // 760
+                {
+                    +0.5557018518447876d, +3.6769498759522324E-8d,
+                }, // 761
+                {
+                    +0.5562618970870972d, +5.874989409410614E-8d,
+                }, // 762
+                {
+                    +0.5568217039108276d, +5.649147309876989E-9d,
+                }, // 763
+                {
+                    +0.5573811531066895d, -2.9726830960751796E-9d,
+                }, // 764
+                {
+                    +0.5579402446746826d, +3.323458344853057E-8d,
+                }, // 765
+                {
+                    +0.5584990978240967d, -4.588749093664028E-9d,
+                }, // 766
+                {
+                    +0.5590575933456421d, +3.115616594184543E-9d,
+                }, // 767
+                {
+                    +0.5596157312393188d, +5.6696103838614634E-8d,
+                }, // 768
+                {
+                    +0.5601736307144165d, +3.7291263280048303E-8d,
+                }, // 769
+                {
+                    +0.5607312917709351d, -5.4751646725093355E-8d,
+                }, // 770
+                {
+                    +0.5612884759902954d, +1.9332630743320287E-8d,
+                }, // 771
+                {
+                    +0.5618454217910767d, +2.147161515775941E-8d,
+                }, // 772
+                {
+                    +0.5624021291732788d, -4.7989172862560625E-8d,
+                }, // 773
+                {
+                    +0.5629583597183228d, +4.971378973445109E-8d,
+                }, // 774
+                {
+                    +0.5635144710540771d, -4.2702997139152675E-8d,
+                }, // 775
+                {
+                    +0.5640701055526733d, +3.273212962622764E-8d,
+                }, // 776
+                {
+                    +0.5646255016326904d, +3.79438125545842E-8d,
+                }, // 777
+                {
+                    +0.5651806592941284d, -2.6725298288329835E-8d,
+                }, // 778
+                {
+                    +0.5657354593276978d, -4.1723833577410244E-8d,
+                }, // 779
+                {
+                    +0.5662899017333984d, -6.71028256490915E-9d,
+                }, // 780
+                {
+                    +0.56684410572052d, -4.055299181908475E-8d,
+                }, // 781
+                {
+                    +0.567397952079773d, -2.3702295314000405E-8d,
+                }, // 782
+                {
+                    +0.5679514408111572d, +4.4181618172507453E-8d,
+                }, // 783
+                {
+                    +0.5685046911239624d, +4.4228706309734985E-8d,
+                }, // 784
+                {
+                    +0.5690577030181885d, -2.3222346436879016E-8d,
+                }, // 785
+                {
+                    +0.5696103572845459d, -3.862412756175274E-8d,
+                }, // 786
+                {
+                    +0.5701626539230347d, -1.6390743801589046E-9d,
+                }, // 787
+                {
+                    +0.5707147121429443d, -3.1139472791083883E-8d,
+                }, // 788
+                {
+                    +0.5712664127349854d, -7.579587391156013E-9d,
+                }, // 789
+                {
+                    +0.5718178749084473d, -4.983281844744412E-8d,
+                }, // 790
+                {
+                    +0.5723689794540405d, -3.835454246739619E-8d,
+                }, // 791
+                {
+                    +0.5729197263717651d, +2.7190020372374008E-8d,
+                }, // 792
+                {
+                    +0.5734702348709106d, +2.7925807446276126E-8d,
+                }, // 793
+                {
+                    +0.574020504951477d, -3.5813506001861646E-8d,
+                }, // 794
+                {
+                    +0.5745704174041748d, -4.448550564530588E-8d,
+                }, // 795
+                {
+                    +0.5751199722290039d, +2.2423840341717488E-9d,
+                }, // 796
+                {
+                    +0.5756692886352539d, -1.450709904687712E-8d,
+                }, // 797
+                {
+                    +0.5762182474136353d, +2.4806815282282017E-8d,
+                }, // 798
+                {
+                    +0.5767669677734375d, +1.3057724436551892E-9d,
+                }, // 799
+                {
+                    +0.5773153305053711d, +3.4529452510568104E-8d,
+                }, // 800
+                {
+                    +0.5778634548187256d, +5.598413198183808E-9d,
+                }, // 801
+                {
+                    +0.5784112215042114d, +3.405124925700107E-8d,
+                }, // 802
+                {
+                    +0.5789587497711182d, +1.0074354568442952E-9d,
+                }, // 803
+                {
+                    +0.5795059204101562d, +2.600448597385527E-8d,
+                }, // 804
+                {
+                    +0.5800528526306152d, -9.83920263200211E-9d,
+                }, // 805
+                {
+                    +0.5805994272232056d, +1.3012807963586057E-8d,
+                }, // 806
+                {
+                    +0.5811457633972168d, -2.432215917965441E-8d,
+                }, // 807
+                {
+                    +0.5816917419433594d, -2.308736892479391E-9d,
+                }, // 808
+                {
+                    +0.5822374820709229d, -3.983067093146514E-8d,
+                }, // 809
+                {
+                    +0.5827828645706177d, -1.735366061128156E-8d,
+                }, // 810
+                {
+                    +0.5833280086517334d, -5.376251584638963E-8d,
+                }, // 811
+                {
+                    +0.5838727951049805d, -2.952399778965259E-8d,
+                }, // 812
+                {
+                    +0.5844172239303589d, +5.5685313670430624E-8d,
+                }, // 813
+                {
+                    +0.5849615335464478d, -3.6230268489088716E-8d,
+                }, // 814
+                {
+                    +0.5855053663253784d, +5.267948957869391E-8d,
+                }, // 815
+                {
+                    +0.5860490798950195d, -3.489144132234588E-8d,
+                }, // 816
+                {
+                    +0.5865923166275024d, +5.9006122320612716E-8d,
+                }, // 817
+                {
+                    +0.5871354341506958d, -2.2934896740542648E-8d,
+                }, // 818
+                {
+                    +0.5876781940460205d, -4.1975650319859075E-8d,
+                }, // 819
+                {
+                    +0.5882205963134766d, +2.2036094805348692E-9d,
+                }, // 820
+                {
+                    +0.5887627601623535d, -9.287179048539306E-9d,
+                }, // 821
+                {
+                    +0.5893045663833618d, +4.3079982556221595E-8d,
+                }, // 822
+                {
+                    +0.589846134185791d, +4.041399585161321E-8d,
+                }, // 823
+                {
+                    +0.5903874635696411d, -1.696746473863933E-8d,
+                }, // 824
+                {
+                    +0.5909284353256226d, -9.53795080582038E-9d,
+                }, // 825
+                {
+                    +0.5914691686630249d, -5.619010749352923E-8d,
+                }, // 826
+                {
+                    +0.5920095443725586d, -3.7398514182529506E-8d,
+                }, // 827
+                {
+                    +0.5925495624542236d, +4.71524479659295E-8d,
+                }, // 828
+                {
+                    +0.5930894613265991d, -4.0640692434639215E-8d,
+                }, // 829
+                {
+                    +0.5936288833618164d, +5.716453096255401E-8d,
+                }, // 830
+                {
+                    +0.5941681861877441d, -1.6745661720946737E-8d,
+                }, // 831
+                {
+                    +0.5947071313858032d, -2.3639110433141897E-8d,
+                }, // 832
+                {
+                    +0.5952457189559937d, +3.67972590471072E-8d,
+                }, // 833
+                {
+                    +0.595784068107605d, +4.566672575206695E-8d,
+                }, // 834
+                {
+                    +0.5963221788406372d, +3.2813537149653483E-9d,
+                }, // 835
+                {
+                    +0.5968599319458008d, +2.916199305533732E-8d,
+                }, // 836
+                {
+                    +0.5973974466323853d, +4.410412409109416E-9d,
+                }, // 837
+                {
+                    +0.5979346036911011d, +4.85464582112459E-8d,
+                }, // 838
+                {
+                    +0.5984715223312378d, +4.267089756924666E-8d,
+                }, // 839
+                {
+                    +0.5990082025527954d, -1.2906712010774655E-8d,
+                }, // 840
+                {
+                    +0.5995445251464844d, +1.3319784467641742E-9d,
+                }, // 841
+                {
+                    +0.6000806093215942d, -3.35137581974451E-8d,
+                }, // 842
+                {
+                    +0.6006163358688354d, +2.0734340706476473E-9d,
+                }, // 843
+                {
+                    +0.6011518239974976d, -1.0808162722402073E-8d,
+                }, // 844
+                {
+                    +0.601686954498291d, +4.735781872502109E-8d,
+                }, // 845
+                {
+                    +0.6022218465805054d, +5.76686738430634E-8d,
+                }, // 846
+                {
+                    +0.6027565002441406d, +2.043049589651736E-8d,
+                }, // 847
+                {
+                    +0.6032907962799072d, +5.515817703577808E-8d,
+                }, // 848
+                {
+                    +0.6038248538970947d, +4.2947540692649586E-8d,
+                }, // 849
+                {
+                    +0.6043586730957031d, -1.589678872195875E-8d,
+                }, // 850
+                {
+                    +0.6048921346664429d, -1.8613847754677912E-9d,
+                }, // 851
+                {
+                    +0.6054253578186035d, -3.3851886626187444E-8d,
+                }, // 852
+                {
+                    +0.6059582233428955d, +7.64416021682279E-9d,
+                }, // 853
+                {
+                    +0.6064908504486084d, +3.7201467248814224E-9d,
+                }, // 854
+                {
+                    +0.6070232391357422d, -4.532172996647129E-8d,
+                }, // 855
+                {
+                    +0.6075552701950073d, -1.997046552871766E-8d,
+                }, // 856
+                {
+                    +0.6080870628356934d, -3.913411606668587E-8d,
+                }, // 857
+                {
+                    +0.6086184978485107d, +1.6697361107868944E-8d,
+                }, // 858
+                {
+                    +0.609149694442749d, +2.8614950293715483E-8d,
+                }, // 859
+                {
+                    +0.6096806526184082d, -3.081552929643174E-9d,
+                }, // 860
+                {
+                    +0.6102112531661987d, +4.111645931319645E-8d,
+                }, // 861
+                {
+                    +0.6107416152954102d, +4.2298539553668435E-8d,
+                }, // 862
+                {
+                    +0.6112717390060425d, +7.630546413718035E-10d,
+                }, // 863
+                {
+                    +0.6118015050888062d, +3.601718675118614E-8d,
+                }, // 864
+                {
+                    +0.6123310327529907d, +2.914906573537692E-8d,
+                }, // 865
+                {
+                    +0.6128603219985962d, -1.9544361222269494E-8d,
+                }, // 866
+                {
+                    +0.613389253616333d, +9.442671392695732E-9d,
+                }, // 867
+                {
+                    +0.6139179468154907d, -2.8031202304593286E-9d,
+                }, // 868
+                {
+                    +0.6144464015960693d, -5.598619958143586E-8d,
+                }, // 869
+                {
+                    +0.6149744987487793d, -3.060220883766096E-8d,
+                }, // 870
+                {
+                    +0.6155023574829102d, -4.556583652800433E-8d,
+                }, // 871
+                {
+                    +0.6160298585891724d, +1.8626341656366314E-8d,
+                }, // 872
+                {
+                    +0.6165571212768555d, +4.305870564227991E-8d,
+                }, // 873
+                {
+                    +0.6170841455459595d, +2.8024460607734262E-8d,
+                }, // 874
+                {
+                    +0.6176109313964844d, -2.6183651590639875E-8d,
+                }, // 875
+                {
+                    +0.6181373596191406d, -6.406189112730307E-11d,
+                }, // 876
+                {
+                    +0.6186635494232178d, -1.2534241706168776E-8d,
+                }, // 877
+                {
+                    +0.6191893815994263d, +5.5906456251308664E-8d,
+                }, // 878
+                {
+                    +0.6197150945663452d, -3.286964881802063E-8d,
+                }, // 879
+                {
+                    +0.6202404499053955d, -4.0153537978961E-8d,
+                }, // 880
+                {
+                    +0.6207654476165771d, +3.434477109643361E-8d,
+                }, // 881
+                {
+                    +0.6212903261184692d, -4.750377491075032E-8d,
+                }, // 882
+                {
+                    +0.6218148469924927d, -4.699152670372743E-8d,
+                }, // 883
+                {
+                    +0.6223390102386475d, +3.617013128065961E-8d,
+                }, // 884
+                {
+                    +0.6228630542755127d, -3.6149218175202596E-8d,
+                }, // 885
+                {
+                    +0.6233867406845093d, -2.5243286814648133E-8d,
+                }, // 886
+                {
+                    +0.6239101886749268d, -5.003410681432538E-8d,
+                }, // 887
+                {
+                    +0.6244332790374756d, +8.974417915105033E-9d,
+                }, // 888
+                {
+                    +0.6249561309814453d, +3.285935446876949E-8d,
+                }, // 889
+                {
+                    +0.6254787445068359d, +2.190661054038537E-8d,
+                }, // 890
+                {
+                    +0.6260011196136475d, -2.3598354190515998E-8d,
+                }, // 891
+                {
+                    +0.6265231370925903d, +1.5838762427747586E-8d,
+                }, // 892
+                {
+                    +0.6270449161529541d, +2.129323729978037E-8d,
+                }, // 893
+                {
+                    +0.6275664567947388d, -6.950808333865794E-9d,
+                }, // 894
+                {
+                    +0.6280876398086548d, +5.059959203156465E-8d,
+                }, // 895
+                {
+                    +0.6286087036132812d, -4.41909071122557E-8d,
+                }, // 896
+                {
+                    +0.6291294097900391d, -5.262093550784066E-8d,
+                }, // 897
+                {
+                    +0.6296497583389282d, +2.559185648444699E-8d,
+                }, // 898
+                {
+                    +0.6301699876785278d, -4.768920119497491E-8d,
+                }, // 899
+                {
+                    +0.6306898593902588d, -3.376406008397877E-8d,
+                }, // 900
+                {
+                    +0.6312094926834106d, -5.156097914033476E-8d,
+                }, // 901
+                {
+                    +0.6317287683486938d, +1.840992392368355E-8d,
+                }, // 902
+                {
+                    +0.632247805595398d, +5.721951534729663E-8d,
+                }, // 903
+                {
+                    +0.6327667236328125d, -5.406177467045421E-8d,
+                }, // 904
+                {
+                    +0.6332851648330688d, +4.247320713683124E-8d,
+                }, // 905
+                {
+                    +0.6338034868240356d, -1.0524557502830645E-8d,
+                }, // 906
+                {
+                    +0.6343214511871338d, +2.5641927558519502E-8d,
+                }, // 907
+                {
+                    +0.6348391771316528d, +3.204135737993823E-8d,
+                }, // 908
+                {
+                    +0.6353566646575928d, +8.951285029786536E-9d,
+                }, // 909
+                {
+                    +0.6358739137649536d, -4.335116707228395E-8d,
+                }, // 910
+                {
+                    +0.6363908052444458d, -5.380016714089483E-9d,
+                }, // 911
+                {
+                    +0.6369074583053589d, +3.931710344901743E-9d,
+                }, // 912
+                {
+                    +0.6374238729476929d, -1.5140150088220166E-8d,
+                }, // 913
+                {
+                    +0.6379399299621582d, +5.688910024377372E-8d,
+                }, // 914
+                {
+                    +0.638455867767334d, -1.8124135273572568E-8d,
+                }, // 915
+                {
+                    +0.6389714479446411d, -1.486720391901626E-9d,
+                }, // 916
+                {
+                    +0.6394867897033691d, -1.2133811978747018E-8d,
+                }, // 917
+                {
+                    +0.6400018930435181d, -4.9791700939901716E-8d,
+                }, // 918
+                {
+                    +0.6405166387557983d, +5.022188652837274E-9d,
+                }, // 919
+                {
+                    +0.6410311460494995d, +3.337143177933685E-8d,
+                }, // 920
+                {
+                    +0.6415454149246216d, +3.55284719912458E-8d,
+                }, // 921
+                {
+                    +0.6420594453811646d, +1.1765332726757802E-8d,
+                }, // 922
+                {
+                    +0.6425732374191284d, -3.7646381826067834E-8d,
+                }, // 923
+                {
+                    +0.6430866718292236d, +6.773803682579552E-9d,
+                }, // 924
+                {
+                    +0.6435998678207397d, +2.608736797081283E-8d,
+                }, // 925
+                {
+                    +0.6441128253936768d, +2.056466263408266E-8d,
+                }, // 926
+                {
+                    +0.6446255445480347d, -9.524376551107945E-9d,
+                }, // 927
+                {
+                    +0.6451379060745239d, +5.5299060775883977E-8d,
+                }, // 928
+                {
+                    +0.6456501483917236d, -2.3114497793159813E-8d,
+                }, // 929
+                {
+                    +0.6461620330810547d, -6.077779731902102E-9d,
+                }, // 930
+                {
+                    +0.6466736793518066d, -1.2531793589140273E-8d,
+                }, // 931
+                {
+                    +0.6471850872039795d, -4.220866994206517E-8d,
+                }, // 932
+                {
+                    +0.6476961374282837d, +2.4368339445199057E-8d,
+                }, // 933
+                {
+                    +0.6482070684432983d, -5.095229574221907E-8d,
+                }, // 934
+                {
+                    +0.6487176418304443d, -2.9485356677301627E-8d,
+                }, // 935
+                {
+                    +0.6492279767990112d, -3.0173901411577916E-8d,
+                }, // 936
+                {
+                    +0.649738073348999d, -5.275210583909726E-8d,
+                }, // 937
+                {
+                    +0.6502478122711182d, +2.2254737134350224E-8d,
+                }, // 938
+                {
+                    +0.6507574319839478d, -4.330693978322885E-8d,
+                }, // 939
+                {
+                    +0.6512666940689087d, -1.0753950588009912E-8d,
+                }, // 940
+                {
+                    +0.6517757177352905d, +9.686179886293545E-10d,
+                }, // 941
+                {
+                    +0.6522845029830933d, -7.875434494414498E-9d,
+                }, // 942
+                {
+                    +0.6527930498123169d, -3.702271091849158E-8d,
+                }, // 943
+                {
+                    +0.6533012390136719d, +3.2999073763758614E-8d,
+                }, // 944
+                {
+                    +0.6538093090057373d, -3.5966064858620067E-8d,
+                }, // 945
+                {
+                    +0.6543170213699341d, -5.23735298540578E-9d,
+                }, // 946
+                {
+                    +0.6548244953155518d, +6.237715351293023E-9d,
+                }, // 947
+                {
+                    +0.6553317308425903d, -1.279462699936282E-9d,
+                }, // 948
+                {
+                    +0.6558387279510498d, -2.7527887552743672E-8d,
+                }, // 949
+                {
+                    +0.6563453674316406d, +4.696233317356646E-8d,
+                }, // 950
+                {
+                    +0.6568518877029419d, -1.5967172745329108E-8d,
+                }, // 951
+                {
+                    +0.6573580503463745d, +2.2361985518423144E-8d,
+                }, // 952
+                {
+                    +0.657863974571228d, +4.2999935789083046E-8d,
+                }, // 953
+                {
+                    +0.6583696603775024d, +4.620570188811826E-8d,
+                }, // 954
+                {
+                    +0.6588751077651978d, +3.223791487908353E-8d,
+                }, // 955
+                {
+                    +0.659380316734314d, +1.3548138612715822E-9d,
+                }, // 956
+                {
+                    +0.6598852872848511d, -4.618575323863973E-8d,
+                }, // 957
+                {
+                    +0.6603899002075195d, +9.082960673843353E-9d,
+                }, // 958
+                {
+                    +0.6608942747116089d, +4.820873399634487E-8d,
+                }, // 959
+                {
+                    +0.6613985300064087d, -4.776104368314602E-8d,
+                }, // 960
+                {
+                    +0.6619024276733398d, -4.0151502150238136E-8d,
+                }, // 961
+                {
+                    +0.6624060869216919d, -4.791602708710648E-8d,
+                }, // 962
+                {
+                    +0.6629093885421753d, +4.8410188461165925E-8d,
+                }, // 963
+                {
+                    +0.6634125709533691d, +1.0663697110471944E-8d,
+                }, // 964
+                {
+                    +0.6639155149459839d, -4.1691464781797555E-8d,
+                }, // 965
+                {
+                    +0.66441810131073d, +1.080835500478704E-8d,
+                }, // 966
+                {
+                    +0.664920449256897d, +4.920784622407246E-8d,
+                }, // 967
+                {
+                    +0.6654226779937744d, -4.544868396511241E-8d,
+                }, // 968
+                {
+                    +0.6659245491027832d, -3.448944157854234E-8d,
+                }, // 969
+                {
+                    +0.6664261817932129d, -3.6870882345139385E-8d,
+                }, // 970
+                {
+                    +0.6669275760650635d, -5.234055273962444E-8d,
+                }, // 971
+                {
+                    +0.6674286127090454d, +3.856291077979099E-8d,
+                }, // 972
+                {
+                    +0.6679295301437378d, -2.327375671320742E-9d,
+                }, // 973
+                {
+                    +0.6684302091598511d, -5.555080534042001E-8d,
+                }, // 974
+                {
+                    +0.6689305305480957d, -1.6471487337453832E-9d,
+                }, // 975
+                {
+                    +0.6694306135177612d, +4.042486803683015E-8d,
+                }, // 976
+                {
+                    +0.6699305772781372d, -4.8293856891818295E-8d,
+                }, // 977
+                {
+                    +0.6704301834106445d, -2.9134931730784303E-8d,
+                }, // 978
+                {
+                    +0.6709295511245728d, -2.1058207594753368E-8d,
+                }, // 979
+                {
+                    +0.6714286804199219d, -2.3814619551682855E-8d,
+                }, // 980
+                {
+                    +0.6719275712966919d, -3.7155475428252136E-8d,
+                }, // 981
+                {
+                    +0.6724261045455933d, +5.8376834484391746E-8d,
+                }, // 982
+                {
+                    +0.6729245185852051d, +2.4611679969129262E-8d,
+                }, // 983
+                {
+                    +0.6734226942062378d, -1.899407107267079E-8d,
+                }, // 984
+                {
+                    +0.6739205121994019d, +4.7016079464436395E-8d,
+                }, // 985
+                {
+                    +0.6744182109832764d, -1.5529608026276525E-8d,
+                }, // 986
+                {
+                    +0.6749155521392822d, +3.203391672602453E-8d,
+                }, // 987
+                {
+                    +0.6754127740859985d, -4.8465821804075345E-8d,
+                }, // 988
+                {
+                    +0.6759096384048462d, -1.8364507801369988E-8d,
+                }, // 989
+                {
+                    +0.6764062643051147d, +3.3739397633046517E-9d,
+                }, // 990
+                {
+                    +0.6769026517868042d, +1.6994526063192333E-8d,
+                }, // 991
+                {
+                    +0.6773988008499146d, +2.2741891590028428E-8d,
+                }, // 992
+                {
+                    +0.6778947114944458d, +2.0860312877435047E-8d,
+                }, // 993
+                {
+                    +0.678390383720398d, +1.1593703222523284E-8d,
+                }, // 994
+                {
+                    +0.678885817527771d, -4.814386594291911E-9d,
+                }, // 995
+                {
+                    +0.6793810129165649d, -2.812076759125914E-8d,
+                }, // 996
+                {
+                    +0.6798759698867798d, -5.808261186903479E-8d,
+                }, // 997
+                {
+                    +0.680370569229126d, +2.4751837654582522E-8d,
+                }, // 998
+                {
+                    +0.6808650493621826d, -1.7793890245755405E-8d,
+                }, // 999
+                {
+                    +0.6813591718673706d, +5.294053246347931E-8d,
+                }, // 1000
+                {
+                    +0.681853175163269d, -1.2220826223585654E-9d,
+                }, // 1001
+                {
+                    +0.6823468208312988d, +5.8377876767612725E-8d,
+                }, // 1002
+                {
+                    +0.6828403472900391d, -6.437492120743254E-9d,
+                }, // 1003
+                {
+                    +0.6833335161209106d, +4.2990710043633113E-8d,
+                }, // 1004
+                {
+                    +0.6838265657424927d, -3.1516131027023284E-8d,
+                }, // 1005
+                {
+                    +0.684319257736206d, +8.70017386744679E-9d,
+                }, // 1006
+                {
+                    +0.6848117113113403d, +4.466959125843237E-8d,
+                }, // 1007
+                {
+                    +0.6853040456771851d, -4.25782656420497E-8d,
+                }, // 1008
+                {
+                    +0.6857960224151611d, -1.4386267593671393E-8d,
+                }, // 1009
+                {
+                    +0.6862877607345581d, +1.0274494061148778E-8d,
+                }, // 1010
+                {
+                    +0.686779260635376d, +3.164186629229597E-8d,
+                }, // 1011
+                {
+                    +0.6872705221176147d, +4.995334552140326E-8d,
+                }, // 1012
+                {
+                    +0.687761664390564d, -5.3763211240398744E-8d,
+                }, // 1013
+                {
+                    +0.6882524490356445d, -4.0852427502515625E-8d,
+                }, // 1014
+                {
+                    +0.688742995262146d, -3.0287143914420064E-8d,
+                }, // 1015
+                {
+                    +0.6892333030700684d, -2.183125937905008E-8d,
+                }, // 1016
+                {
+                    +0.6897233724594116d, -1.524901992178814E-8d,
+                }, // 1017
+                {
+                    +0.6902132034301758d, -1.0305018010328949E-8d,
+                }, // 1018
+                {
+                    +0.6907027959823608d, -6.764191876212205E-9d,
+                }, // 1019
+                {
+                    +0.6911921501159668d, -4.391824838015402E-9d,
+                }, // 1020
+                {
+                    +0.6916812658309937d, -2.9535446262017846E-9d,
+                }, // 1021
+                {
+                    +0.6921701431274414d, -2.2153227096187463E-9d,
+                }, // 1022
+                {
+                    +0.6926587820053101d, -1.943473623641502E-9d,
+                }, // 1023
+            };
+
+    /** Class contains only static methods. */
+    private FastMathLiteralArrays() {}
+
+    /**
+     * Load "EXP_INT_A".
+     *
+     * @return a clone of the data array.
+     */
+    static double[] loadExpIntA() {
+        return EXP_INT_A.clone();
+    }
+
+    /**
+     * Load "EXP_INT_B".
+     *
+     * @return a clone of the data array.
+     */
+    static double[] loadExpIntB() {
+        return EXP_INT_B.clone();
+    }
+
+    /**
+     * Load "EXP_FRAC_A".
+     *
+     * @return a clone of the data array.
+     */
+    static double[] loadExpFracA() {
+        return EXP_FRAC_A.clone();
+    }
+
+    /**
+     * Load "EXP_FRAC_B".
+     *
+     * @return a clone of the data array.
+     */
+    static double[] loadExpFracB() {
+        return EXP_FRAC_B.clone();
+    }
+
+    /**
+     * Load "LN_MANT".
+     *
+     * @return a clone of the data array.
+     */
+    static double[][] loadLnMant() {
+        return LN_MANT.clone();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/util/Incrementor.java b/src/main/java/org/apache/commons/math3/util/Incrementor.java
new file mode 100644
index 0000000..de9094e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/Incrementor.java
@@ -0,0 +1,218 @@
+/*
+ * 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.math3.util;
+
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NullArgumentException;
+
+/**
+ * Utility that increments a counter until a maximum is reached, at which point, the instance will
+ * by default throw a {@link MaxCountExceededException}. However, the user is able to override this
+ * behaviour by defining a custom {@link MaxCountExceededCallback callback}, in order to e.g. select
+ * which exception must be thrown.
+ *
+ * @since 3.0
+ * @deprecated Use {@link IntegerSequence.Incrementor} instead.
+ */
+@Deprecated
+public class Incrementor {
+    /** Upper limit for the counter. */
+    private int maximalCount;
+
+    /** Current count. */
+    private int count = 0;
+
+    /** Function called at counter exhaustion. */
+    private final MaxCountExceededCallback maxCountCallback;
+
+    /**
+     * Default constructor. For the new instance to be useful, the maximal count must be set by
+     * calling {@link #setMaximalCount(int) setMaximalCount}.
+     */
+    public Incrementor() {
+        this(0);
+    }
+
+    /**
+     * Defines a maximal count.
+     *
+     * @param max Maximal count.
+     */
+    public Incrementor(int max) {
+        this(
+                max,
+                new MaxCountExceededCallback() {
+                    /** {@inheritDoc} */
+                    public void trigger(int max) throws MaxCountExceededException {
+                        throw new MaxCountExceededException(max);
+                    }
+                });
+    }
+
+    /**
+     * Defines a maximal count and a callback method to be triggered at counter exhaustion.
+     *
+     * @param max Maximal count.
+     * @param cb Function to be called when the maximal count has been reached.
+     * @throws NullArgumentException if {@code cb} is {@code null}
+     */
+    public Incrementor(int max, MaxCountExceededCallback cb) throws NullArgumentException {
+        if (cb == null) {
+            throw new NullArgumentException();
+        }
+        maximalCount = max;
+        maxCountCallback = cb;
+    }
+
+    /**
+     * Sets the upper limit for the counter. This does not automatically reset the current count to
+     * zero (see {@link #resetCount()}).
+     *
+     * @param max Upper limit of the counter.
+     */
+    public void setMaximalCount(int max) {
+        maximalCount = max;
+    }
+
+    /**
+     * Gets the upper limit of the counter.
+     *
+     * @return the counter upper limit.
+     */
+    public int getMaximalCount() {
+        return maximalCount;
+    }
+
+    /**
+     * Gets the current count.
+     *
+     * @return the current count.
+     */
+    public int getCount() {
+        return count;
+    }
+
+    /**
+     * Checks whether a single increment is allowed.
+     *
+     * @return {@code false} if the next call to {@link #incrementCount(int) incrementCount} will
+     *     trigger a {@code MaxCountExceededException}, {@code true} otherwise.
+     */
+    public boolean canIncrement() {
+        return count < maximalCount;
+    }
+
+    /**
+     * Performs multiple increments. See the other {@link #incrementCount() incrementCount} method).
+     *
+     * @param value Number of increments.
+     * @throws MaxCountExceededException at counter exhaustion.
+     */
+    public void incrementCount(int value) throws MaxCountExceededException {
+        for (int i = 0; i < value; i++) {
+            incrementCount();
+        }
+    }
+
+    /**
+     * Adds one to the current iteration count. At counter exhaustion, this method will call the
+     * {@link MaxCountExceededCallback#trigger(int) trigger} method of the callback object passed to
+     * the {@link #Incrementor(int,MaxCountExceededCallback) constructor}. If not explictly set, a
+     * default callback is used that will throw a {@code MaxCountExceededException}.
+     *
+     * @throws MaxCountExceededException at counter exhaustion, unless a custom {@link
+     *     MaxCountExceededCallback callback} has been set at construction.
+     */
+    public void incrementCount() throws MaxCountExceededException {
+        if (++count > maximalCount) {
+            maxCountCallback.trigger(maximalCount);
+        }
+    }
+
+    /** Resets the counter to 0. */
+    public void resetCount() {
+        count = 0;
+    }
+
+    /**
+     * Defines a method to be called at counter exhaustion. The {@link #trigger(int) trigger} method
+     * should usually throw an exception.
+     */
+    public interface MaxCountExceededCallback {
+        /**
+         * Function called when the maximal count has been reached.
+         *
+         * @param maximalCount Maximal count.
+         * @throws MaxCountExceededException at counter exhaustion
+         */
+        void trigger(int maximalCount) throws MaxCountExceededException;
+    }
+
+    /**
+     * Create an instance that delegates everything to a {@link IntegerSequence.Incrementor}.
+     *
+     * <p>This factory method is intended only as a temporary hack for internal use in Apache
+     * Commons Math 3.X series, when {@code Incrementor} is required in interface (as a return value
+     * or in protected fields). It should <em>not</em> be used in other cases. The {@link
+     * IntegerSequence.Incrementor} class should be used instead of {@code Incrementor}.
+     *
+     * <p>All methods are mirrored to the underlying {@link IntegerSequence.Incrementor}, as long as
+     * neither {@link #setMaximalCount(int)} nor {@link #resetCount()} are called. If one of these
+     * two methods is called, the created instance becomes independent of the {@link
+     * IntegerSequence.Incrementor} used at creation. The rationale is that {@link
+     * IntegerSequence.Incrementor} cannot change their maximal count and cannot be reset.
+     *
+     * @param incrementor wrapped {@link IntegerSequence.Incrementor}
+     * @return an incrementor wrapping an {@link IntegerSequence.Incrementor}
+     * @since 3.6
+     */
+    public static Incrementor wrap(final IntegerSequence.Incrementor incrementor) {
+        return new Incrementor() {
+
+            /** Underlying incrementor. */
+            private IntegerSequence.Incrementor delegate;
+
+            {
+                // set up matching values at initialization
+                delegate = incrementor;
+                super.setMaximalCount(delegate.getMaximalCount());
+                super.incrementCount(delegate.getCount());
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public void setMaximalCount(int max) {
+                super.setMaximalCount(max);
+                delegate = delegate.withMaximalCount(max);
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public void resetCount() {
+                super.resetCount();
+                delegate = delegate.withStart(0);
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public void incrementCount() {
+                super.incrementCount();
+                delegate.increment();
+            }
+        };
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/util/IntegerSequence.java b/src/main/java/org/apache/commons/math3/util/IntegerSequence.java
new file mode 100644
index 0000000..7859ec3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/IntegerSequence.java
@@ -0,0 +1,328 @@
+/*
+ * 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.math3.util;
+
+import org.apache.commons.math3.exception.MathUnsupportedOperationException;
+import org.apache.commons.math3.exception.MaxCountExceededException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.ZeroException;
+
+import java.util.Iterator;
+
+/**
+ * Provides a sequence of integers.
+ *
+ * @since 3.6
+ */
+public class IntegerSequence {
+    /** Utility class contains only static methods. */
+    private IntegerSequence() {}
+
+    /**
+     * Creates a sequence {@code [start .. end]}. It calls {@link #range(int,int,int) range(start,
+     * end, 1)}.
+     *
+     * @param start First value of the range.
+     * @param end Last value of the range.
+     * @return a range.
+     */
+    public static Range range(int start, int end) {
+        return range(start, end, 1);
+    }
+
+    /**
+     * Creates a sequence \( a_i, i < 0 <= n \) where \( a_i = start + i * step \) and \( n \) is
+     * such that \( a_n <= max \) and \( a_{n+1} > max \).
+     *
+     * @param start First value of the range.
+     * @param max Last value of the range that satisfies the above construction rule.
+     * @param step Increment.
+     * @return a range.
+     */
+    public static Range range(final int start, final int max, final int step) {
+        return new Range(start, max, step);
+    }
+
+    /** Generates a sequence of integers. */
+    public static class Range implements Iterable<Integer> {
+        /** Number of integers contained in this range. */
+        private final int size;
+
+        /** First value. */
+        private final int start;
+
+        /** Final value. */
+        private final int max;
+
+        /** Increment. */
+        private final int step;
+
+        /**
+         * Creates a sequence \( a_i, i < 0 <= n \) where \( a_i = start + i * step \) and \( n \)
+         * is such that \( a_n <= max \) and \( a_{n+1} > max \).
+         *
+         * @param start First value of the range.
+         * @param max Last value of the range that satisfies the above construction rule.
+         * @param step Increment.
+         */
+        public Range(int start, int max, int step) {
+            this.start = start;
+            this.max = max;
+            this.step = step;
+
+            final int s = (max - start) / step + 1;
+            this.size = s < 0 ? 0 : s;
+        }
+
+        /**
+         * Gets the number of elements contained in the range.
+         *
+         * @return the size of the range.
+         */
+        public int size() {
+            return size;
+        }
+
+        /** {@inheritDoc} */
+        public Iterator<Integer> iterator() {
+            return Incrementor.create()
+                    .withStart(start)
+                    .withMaximalCount(max + (step > 0 ? 1 : -1))
+                    .withIncrement(step);
+        }
+    }
+
+    /**
+     * Utility that increments a counter until a maximum is reached, at which point, the instance
+     * will by default throw a {@link MaxCountExceededException}. However, the user is able to
+     * override this behaviour by defining a custom {@link MaxCountExceededCallback callback}, in
+     * order to e.g. select which exception must be thrown.
+     */
+    public static class Incrementor implements Iterator<Integer> {
+        /** Default callback. */
+        private static final MaxCountExceededCallback CALLBACK =
+                new MaxCountExceededCallback() {
+                    /** {@inheritDoc} */
+                    public void trigger(int max) throws MaxCountExceededException {
+                        throw new MaxCountExceededException(max);
+                    }
+                };
+
+        /** Initial value the counter. */
+        private final int init;
+
+        /** Upper limit for the counter. */
+        private final int maximalCount;
+
+        /** Increment. */
+        private final int increment;
+
+        /** Function called at counter exhaustion. */
+        private final MaxCountExceededCallback maxCountCallback;
+
+        /** Current count. */
+        private int count = 0;
+
+        /**
+         * Defines a method to be called at counter exhaustion. The {@link #trigger(int) trigger}
+         * method should usually throw an exception.
+         */
+        public interface MaxCountExceededCallback {
+            /**
+             * Function called when the maximal count has been reached.
+             *
+             * @param maximalCount Maximal count.
+             * @throws MaxCountExceededException at counter exhaustion
+             */
+            void trigger(int maximalCount) throws MaxCountExceededException;
+        }
+
+        /**
+         * Creates an incrementor. The counter will be exhausted either when {@code max} is reached
+         * or when {@code nTimes} increments have been performed.
+         *
+         * @param start Initial value.
+         * @param max Maximal count.
+         * @param step Increment.
+         * @param cb Function to be called when the maximal count has been reached.
+         * @throws NullArgumentException if {@code cb} is {@code null}.
+         */
+        private Incrementor(int start, int max, int step, MaxCountExceededCallback cb)
+                throws NullArgumentException {
+            if (cb == null) {
+                throw new NullArgumentException();
+            }
+            this.init = start;
+            this.maximalCount = max;
+            this.increment = step;
+            this.maxCountCallback = cb;
+            this.count = start;
+        }
+
+        /**
+         * Factory method that creates a default instance. The initial and maximal values are set to
+         * 0. For the new instance to be useful, the maximal count must be set by calling {@link
+         * #withMaximalCount(int) withMaximalCount}.
+         *
+         * @return an new instance.
+         */
+        public static Incrementor create() {
+            return new Incrementor(0, 0, 1, CALLBACK);
+        }
+
+        /**
+         * Creates a new instance with a given initial value. The counter is reset to the initial
+         * value.
+         *
+         * @param start Initial value of the counter.
+         * @return a new instance.
+         */
+        public Incrementor withStart(int start) {
+            return new Incrementor(start, this.maximalCount, this.increment, this.maxCountCallback);
+        }
+
+        /**
+         * Creates a new instance with a given maximal count. The counter is reset to the initial
+         * value.
+         *
+         * @param max Maximal count.
+         * @return a new instance.
+         */
+        public Incrementor withMaximalCount(int max) {
+            return new Incrementor(this.init, max, this.increment, this.maxCountCallback);
+        }
+
+        /**
+         * Creates a new instance with a given increment. The counter is reset to the initial value.
+         *
+         * @param step Increment.
+         * @return a new instance.
+         */
+        public Incrementor withIncrement(int step) {
+            if (step == 0) {
+                throw new ZeroException();
+            }
+            return new Incrementor(this.init, this.maximalCount, step, this.maxCountCallback);
+        }
+
+        /**
+         * Creates a new instance with a given callback. The counter is reset to the initial value.
+         *
+         * @param cb Callback to be called at counter exhaustion.
+         * @return a new instance.
+         */
+        public Incrementor withCallback(MaxCountExceededCallback cb) {
+            return new Incrementor(this.init, this.maximalCount, this.increment, cb);
+        }
+
+        /**
+         * Gets the upper limit of the counter.
+         *
+         * @return the counter upper limit.
+         */
+        public int getMaximalCount() {
+            return maximalCount;
+        }
+
+        /**
+         * Gets the current count.
+         *
+         * @return the current count.
+         */
+        public int getCount() {
+            return count;
+        }
+
+        /**
+         * Checks whether incrementing the counter {@code nTimes} is allowed.
+         *
+         * @return {@code false} if calling {@link #increment()} will trigger a {@code
+         *     MaxCountExceededException}, {@code true} otherwise.
+         */
+        public boolean canIncrement() {
+            return canIncrement(1);
+        }
+
+        /**
+         * Checks whether incrementing the counter several times is allowed.
+         *
+         * @param nTimes Number of increments.
+         * @return {@code false} if calling {@link #increment(int) increment(nTimes)} would call the
+         *     {@link MaxCountExceededCallback callback} {@code true} otherwise.
+         */
+        public boolean canIncrement(int nTimes) {
+            final int finalCount = count + nTimes * increment;
+            return increment < 0 ? finalCount > maximalCount : finalCount < maximalCount;
+        }
+
+        /**
+         * Performs multiple increments.
+         *
+         * @param nTimes Number of increments.
+         * @throws MaxCountExceededException at counter exhaustion.
+         * @throws NotStrictlyPositiveException if {@code nTimes <= 0}.
+         * @see #increment()
+         */
+        public void increment(int nTimes) throws MaxCountExceededException {
+            if (nTimes <= 0) {
+                throw new NotStrictlyPositiveException(nTimes);
+            }
+
+            if (!canIncrement(0)) {
+                maxCountCallback.trigger(maximalCount);
+            }
+            count += nTimes * increment;
+        }
+
+        /**
+         * Adds the increment value to the current iteration count. At counter exhaustion, this
+         * method will call the {@link MaxCountExceededCallback#trigger(int) trigger} method of the
+         * callback object passed to the {@link #withCallback(MaxCountExceededCallback)} method. If
+         * not explicitly set, a default callback is used that will throw a {@code
+         * MaxCountExceededException}.
+         *
+         * @throws MaxCountExceededException at counter exhaustion, unless a custom {@link
+         *     MaxCountExceededCallback callback} has been set.
+         * @see #increment(int)
+         */
+        public void increment() throws MaxCountExceededException {
+            increment(1);
+        }
+
+        /** {@inheritDoc} */
+        public boolean hasNext() {
+            return canIncrement(0);
+        }
+
+        /** {@inheritDoc} */
+        public Integer next() {
+            final int value = count;
+            increment();
+            return value;
+        }
+
+        /**
+         * Not applicable.
+         *
+         * @throws MathUnsupportedOperationException
+         */
+        public void remove() {
+            throw new MathUnsupportedOperationException();
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/util/IterationEvent.java b/src/main/java/org/apache/commons/math3/util/IterationEvent.java
new file mode 100644
index 0000000..5cbca66
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/IterationEvent.java
@@ -0,0 +1,52 @@
+/*
+ * 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.math3.util;
+
+import java.util.EventObject;
+
+/**
+ * The root class from which all events occurring while running an {@link IterationManager} should
+ * be derived.
+ */
+public class IterationEvent extends EventObject {
+    /** */
+    private static final long serialVersionUID = 20120128L;
+
+    /** The number of iterations performed so far. */
+    private final int iterations;
+
+    /**
+     * Creates a new instance of this class.
+     *
+     * @param source the iterative algorithm on which the event initially occurred
+     * @param iterations the number of iterations performed at the time {@code this} event is
+     *     created
+     */
+    public IterationEvent(final Object source, final int iterations) {
+        super(source);
+        this.iterations = iterations;
+    }
+
+    /**
+     * Returns the number of iterations performed at the time {@code this} event is created.
+     *
+     * @return the number of iterations performed
+     */
+    public int getIterations() {
+        return iterations;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/util/IterationListener.java b/src/main/java/org/apache/commons/math3/util/IterationListener.java
new file mode 100644
index 0000000..3064acf
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/IterationListener.java
@@ -0,0 +1,52 @@
+/*
+ * 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.math3.util;
+
+import java.util.EventListener;
+
+/** The listener interface for receiving events occurring in an iterative algorithm. */
+public interface IterationListener extends EventListener {
+    /**
+     * Invoked after completion of the initial phase of the iterative algorithm (prior to the main
+     * iteration loop).
+     *
+     * @param e The {@link IterationEvent} object.
+     */
+    void initializationPerformed(IterationEvent e);
+
+    /**
+     * Invoked each time an iteration is completed (in the main iteration loop).
+     *
+     * @param e The {@link IterationEvent} object.
+     */
+    void iterationPerformed(IterationEvent e);
+
+    /**
+     * Invoked each time a new iteration is completed (in the main iteration loop).
+     *
+     * @param e The {@link IterationEvent} object.
+     */
+    void iterationStarted(IterationEvent e);
+
+    /**
+     * Invoked after completion of the operations which occur after breaking out of the main
+     * iteration loop.
+     *
+     * @param e The {@link IterationEvent} object.
+     */
+    void terminationPerformed(IterationEvent e);
+}
diff --git a/src/main/java/org/apache/commons/math3/util/IterationManager.java b/src/main/java/org/apache/commons/math3/util/IterationManager.java
new file mode 100644
index 0000000..83f350b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/IterationManager.java
@@ -0,0 +1,191 @@
+/*
+ * 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.math3.util;
+
+import org.apache.commons.math3.exception.MaxCountExceededException;
+
+import java.util.Collection;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * This abstract class provides a general framework for managing iterative algorithms. The maximum
+ * number of iterations can be set, and methods are provided to monitor the current iteration count.
+ * A lightweight event framework is also provided.
+ */
+public class IterationManager {
+
+    /** Keeps a count of the number of iterations. */
+    private IntegerSequence.Incrementor iterations;
+
+    /** The collection of all listeners attached to this iterative algorithm. */
+    private final Collection<IterationListener> listeners;
+
+    /**
+     * Creates a new instance of this class.
+     *
+     * @param maxIterations the maximum number of iterations
+     */
+    public IterationManager(final int maxIterations) {
+        this.iterations = IntegerSequence.Incrementor.create().withMaximalCount(maxIterations);
+        this.listeners = new CopyOnWriteArrayList<IterationListener>();
+    }
+
+    /**
+     * Creates a new instance of this class.
+     *
+     * @param maxIterations the maximum number of iterations
+     * @param callBack the function to be called when the maximum number of iterations has been
+     *     reached
+     * @throws org.apache.commons.math3.exception.NullArgumentException if {@code callBack} is
+     *     {@code null}
+     * @since 3.1
+     * @deprecated as of 3.6, replaced with {@link #IterationManager(int,
+     *     org.apache.commons.math3.util.IntegerSequence.Incrementor.MaxCountExceededCallback)}
+     */
+    @Deprecated
+    public IterationManager(
+            final int maxIterations, final Incrementor.MaxCountExceededCallback callBack) {
+        this(
+                maxIterations,
+                new IntegerSequence.Incrementor.MaxCountExceededCallback() {
+                    /** {@inheritDoc} */
+                    public void trigger(final int maximalCount) throws MaxCountExceededException {
+                        callBack.trigger(maximalCount);
+                    }
+                });
+    }
+
+    /**
+     * Creates a new instance of this class.
+     *
+     * @param maxIterations the maximum number of iterations
+     * @param callBack the function to be called when the maximum number of iterations has been
+     *     reached
+     * @throws org.apache.commons.math3.exception.NullArgumentException if {@code callBack} is
+     *     {@code null}
+     * @since 3.6
+     */
+    public IterationManager(
+            final int maxIterations,
+            final IntegerSequence.Incrementor.MaxCountExceededCallback callBack) {
+        this.iterations =
+                IntegerSequence.Incrementor.create()
+                        .withMaximalCount(maxIterations)
+                        .withCallback(callBack);
+        this.listeners = new CopyOnWriteArrayList<IterationListener>();
+    }
+
+    /**
+     * Attaches a listener to this manager.
+     *
+     * @param listener A {@code IterationListener} object.
+     */
+    public void addIterationListener(final IterationListener listener) {
+        listeners.add(listener);
+    }
+
+    /**
+     * Informs all registered listeners that the initial phase (prior to the main iteration loop)
+     * has been completed.
+     *
+     * @param e The {@link IterationEvent} object.
+     */
+    public void fireInitializationEvent(final IterationEvent e) {
+        for (IterationListener l : listeners) {
+            l.initializationPerformed(e);
+        }
+    }
+
+    /**
+     * Informs all registered listeners that a new iteration (in the main iteration loop) has been
+     * performed.
+     *
+     * @param e The {@link IterationEvent} object.
+     */
+    public void fireIterationPerformedEvent(final IterationEvent e) {
+        for (IterationListener l : listeners) {
+            l.iterationPerformed(e);
+        }
+    }
+
+    /**
+     * Informs all registered listeners that a new iteration (in the main iteration loop) has been
+     * started.
+     *
+     * @param e The {@link IterationEvent} object.
+     */
+    public void fireIterationStartedEvent(final IterationEvent e) {
+        for (IterationListener l : listeners) {
+            l.iterationStarted(e);
+        }
+    }
+
+    /**
+     * Informs all registered listeners that the final phase (post-iterations) has been completed.
+     *
+     * @param e The {@link IterationEvent} object.
+     */
+    public void fireTerminationEvent(final IterationEvent e) {
+        for (IterationListener l : listeners) {
+            l.terminationPerformed(e);
+        }
+    }
+
+    /**
+     * Returns the number of iterations of this solver, 0 if no iterations has been performed yet.
+     *
+     * @return the number of iterations.
+     */
+    public int getIterations() {
+        return iterations.getCount();
+    }
+
+    /**
+     * Returns the maximum number of iterations.
+     *
+     * @return the maximum number of iterations.
+     */
+    public int getMaxIterations() {
+        return iterations.getMaximalCount();
+    }
+
+    /**
+     * Increments the iteration count by one, and throws an exception if the maximum number of
+     * iterations is reached. This method should be called at the beginning of a new iteration.
+     *
+     * @throws MaxCountExceededException if the maximum number of iterations is reached.
+     */
+    public void incrementIterationCount() throws MaxCountExceededException {
+        iterations.increment();
+    }
+
+    /**
+     * Removes the specified iteration listener from the list of listeners currently attached to
+     * {@code this} object. Attempting to remove a listener which was <em>not</em> previously
+     * registered does not cause any error.
+     *
+     * @param listener The {@link IterationListener} to be removed.
+     */
+    public void removeIterationListener(final IterationListener listener) {
+        listeners.remove(listener);
+    }
+
+    /** Sets the iteration count to 0. This method must be called during the initial phase. */
+    public void resetIterationCount() {
+        iterations = iterations.withStart(0);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/util/KthSelector.java b/src/main/java/org/apache/commons/math3/util/KthSelector.java
new file mode 100644
index 0000000..ed0f1d5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/KthSelector.java
@@ -0,0 +1,154 @@
+/*
+ * 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.math3.util;
+
+import org.apache.commons.math3.exception.NullArgumentException;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+/**
+ * A Simple K<sup>th</sup> selector implementation to pick up the K<sup>th</sup> ordered element
+ * from a work array containing the input numbers.
+ *
+ * @since 3.4
+ */
+public class KthSelector implements Serializable {
+
+    /** Serializable UID. */
+    private static final long serialVersionUID = 20140713L;
+
+    /** Minimum selection size for insertion sort rather than selection. */
+    private static final int MIN_SELECT_SIZE = 15;
+
+    /** A {@link PivotingStrategyInterface} used for pivoting */
+    private final PivotingStrategyInterface pivotingStrategy;
+
+    /** Constructor with default {@link MedianOf3PivotingStrategy median of 3} pivoting strategy */
+    public KthSelector() {
+        this.pivotingStrategy = new MedianOf3PivotingStrategy();
+    }
+
+    /**
+     * Constructor with specified pivoting strategy
+     *
+     * @param pivotingStrategy pivoting strategy to use
+     * @throws NullArgumentException when pivotingStrategy is null
+     * @see MedianOf3PivotingStrategy
+     * @see RandomPivotingStrategy
+     * @see CentralPivotingStrategy
+     */
+    public KthSelector(final PivotingStrategyInterface pivotingStrategy)
+            throws NullArgumentException {
+        MathUtils.checkNotNull(pivotingStrategy);
+        this.pivotingStrategy = pivotingStrategy;
+    }
+
+    /**
+     * Get the pivotin strategy.
+     *
+     * @return pivoting strategy
+     */
+    public PivotingStrategyInterface getPivotingStrategy() {
+        return pivotingStrategy;
+    }
+
+    /**
+     * Select K<sup>th</sup> value in the array.
+     *
+     * @param work work array to use to find out the K<sup>th</sup> value
+     * @param pivotsHeap cached pivots heap that can be used for efficient estimation
+     * @param k the index whose value in the array is of interest
+     * @return K<sup>th</sup> value
+     */
+    public double select(final double[] work, final int[] pivotsHeap, final int k) {
+        int begin = 0;
+        int end = work.length;
+        int node = 0;
+        final boolean usePivotsHeap = pivotsHeap != null;
+        while (end - begin > MIN_SELECT_SIZE) {
+            final int pivot;
+
+            if (usePivotsHeap && node < pivotsHeap.length && pivotsHeap[node] >= 0) {
+                // the pivot has already been found in a previous call
+                // and the array has already been partitioned around it
+                pivot = pivotsHeap[node];
+            } else {
+                // select a pivot and partition work array around it
+                pivot = partition(work, begin, end, pivotingStrategy.pivotIndex(work, begin, end));
+                if (usePivotsHeap && node < pivotsHeap.length) {
+                    pivotsHeap[node] = pivot;
+                }
+            }
+
+            if (k == pivot) {
+                // the pivot was exactly the element we wanted
+                return work[k];
+            } else if (k < pivot) {
+                // the element is in the left partition
+                end = pivot;
+                node = FastMath.min(2 * node + 1, usePivotsHeap ? pivotsHeap.length : end);
+            } else {
+                // the element is in the right partition
+                begin = pivot + 1;
+                node = FastMath.min(2 * node + 2, usePivotsHeap ? pivotsHeap.length : end);
+            }
+        }
+        Arrays.sort(work, begin, end);
+        return work[k];
+    }
+
+    /**
+     * Partition an array slice around a pivot.Partitioning exchanges array elements such that all
+     * elements smaller than pivot are before it and all elements larger than pivot are after it.
+     *
+     * @param work work array
+     * @param begin index of the first element of the slice of work array
+     * @param end index after the last element of the slice of work array
+     * @param pivot initial index of the pivot
+     * @return index of the pivot after partition
+     */
+    private int partition(final double[] work, final int begin, final int end, final int pivot) {
+
+        final double value = work[pivot];
+        work[pivot] = work[begin];
+
+        int i = begin + 1;
+        int j = end - 1;
+        while (i < j) {
+            while (i < j && work[j] > value) {
+                --j;
+            }
+            while (i < j && work[i] < value) {
+                ++i;
+            }
+
+            if (i < j) {
+                final double tmp = work[i];
+                work[i++] = work[j];
+                work[j--] = tmp;
+            }
+        }
+
+        if (i >= end || work[i] > value) {
+            --i;
+        }
+        work[begin] = work[i];
+        work[i] = value;
+        return i;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/util/MathArrays.java b/src/main/java/org/apache/commons/math3/util/MathArrays.java
new file mode 100644
index 0000000..968e626
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/MathArrays.java
@@ -0,0 +1,1912 @@
+/*
+ * 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.math3.util;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.distribution.UniformIntegerDistribution;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.MathInternalError;
+import org.apache.commons.math3.exception.NoDataException;
+import org.apache.commons.math3.exception.NonMonotonicSequenceException;
+import org.apache.commons.math3.exception.NotANumberException;
+import org.apache.commons.math3.exception.NotPositiveException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooLargeException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well19937c;
+
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.TreeSet;
+
+/**
+ * Arrays utilities.
+ *
+ * @since 3.0
+ */
+public class MathArrays {
+
+    /** Private constructor. */
+    private MathArrays() {}
+
+    /**
+     * Real-valued function that operate on an array or a part of it.
+     *
+     * @since 3.1
+     */
+    public interface Function {
+        /**
+         * Operates on an entire array.
+         *
+         * @param array Array to operate on.
+         * @return the result of the operation.
+         */
+        double evaluate(double[] array);
+
+        /**
+         * @param array Array to operate on.
+         * @param startIndex Index of the first element to take into account.
+         * @param numElements Number of elements to take into account.
+         * @return the result of the operation.
+         */
+        double evaluate(double[] array, int startIndex, int numElements);
+    }
+
+    /**
+     * Create a copy of an array scaled by a value.
+     *
+     * @param arr Array to scale.
+     * @param val Scalar.
+     * @return scaled copy of array with each entry multiplied by val.
+     * @since 3.2
+     */
+    public static double[] scale(double val, final double[] arr) {
+        double[] newArr = new double[arr.length];
+        for (int i = 0; i < arr.length; i++) {
+            newArr[i] = arr[i] * val;
+        }
+        return newArr;
+    }
+
+    /**
+     * Multiply each element of an array by a value.
+     *
+     * <p>The array is modified in place (no copy is created).
+     *
+     * @param arr Array to scale
+     * @param val Scalar
+     * @since 3.2
+     */
+    public static void scaleInPlace(double val, final double[] arr) {
+        for (int i = 0; i < arr.length; i++) {
+            arr[i] *= val;
+        }
+    }
+
+    /**
+     * Creates an array whose contents will be the element-by-element addition of the arguments.
+     *
+     * @param a First term of the addition.
+     * @param b Second term of the addition.
+     * @return a new array {@code r} where {@code r[i] = a[i] + b[i]}.
+     * @throws DimensionMismatchException if the array lengths differ.
+     * @since 3.1
+     */
+    public static double[] ebeAdd(double[] a, double[] b) throws DimensionMismatchException {
+        checkEqualLength(a, b);
+
+        final double[] result = a.clone();
+        for (int i = 0; i < a.length; i++) {
+            result[i] += b[i];
+        }
+        return result;
+    }
+
+    /**
+     * Creates an array whose contents will be the element-by-element subtraction of the second
+     * argument from the first.
+     *
+     * @param a First term.
+     * @param b Element to be subtracted.
+     * @return a new array {@code r} where {@code r[i] = a[i] - b[i]}.
+     * @throws DimensionMismatchException if the array lengths differ.
+     * @since 3.1
+     */
+    public static double[] ebeSubtract(double[] a, double[] b) throws DimensionMismatchException {
+        checkEqualLength(a, b);
+
+        final double[] result = a.clone();
+        for (int i = 0; i < a.length; i++) {
+            result[i] -= b[i];
+        }
+        return result;
+    }
+
+    /**
+     * Creates an array whose contents will be the element-by-element multiplication of the
+     * arguments.
+     *
+     * @param a First factor of the multiplication.
+     * @param b Second factor of the multiplication.
+     * @return a new array {@code r} where {@code r[i] = a[i] * b[i]}.
+     * @throws DimensionMismatchException if the array lengths differ.
+     * @since 3.1
+     */
+    public static double[] ebeMultiply(double[] a, double[] b) throws DimensionMismatchException {
+        checkEqualLength(a, b);
+
+        final double[] result = a.clone();
+        for (int i = 0; i < a.length; i++) {
+            result[i] *= b[i];
+        }
+        return result;
+    }
+
+    /**
+     * Creates an array whose contents will be the element-by-element division of the first argument
+     * by the second.
+     *
+     * @param a Numerator of the division.
+     * @param b Denominator of the division.
+     * @return a new array {@code r} where {@code r[i] = a[i] / b[i]}.
+     * @throws DimensionMismatchException if the array lengths differ.
+     * @since 3.1
+     */
+    public static double[] ebeDivide(double[] a, double[] b) throws DimensionMismatchException {
+        checkEqualLength(a, b);
+
+        final double[] result = a.clone();
+        for (int i = 0; i < a.length; i++) {
+            result[i] /= b[i];
+        }
+        return result;
+    }
+
+    /**
+     * Calculates the L<sub>1</sub> (sum of abs) distance between two points.
+     *
+     * @param p1 the first point
+     * @param p2 the second point
+     * @return the L<sub>1</sub> distance between the two points
+     * @throws DimensionMismatchException if the array lengths differ.
+     */
+    public static double distance1(double[] p1, double[] p2) throws DimensionMismatchException {
+        checkEqualLength(p1, p2);
+        double sum = 0;
+        for (int i = 0; i < p1.length; i++) {
+            sum += FastMath.abs(p1[i] - p2[i]);
+        }
+        return sum;
+    }
+
+    /**
+     * Calculates the L<sub>1</sub> (sum of abs) distance between two points.
+     *
+     * @param p1 the first point
+     * @param p2 the second point
+     * @return the L<sub>1</sub> distance between the two points
+     * @throws DimensionMismatchException if the array lengths differ.
+     */
+    public static int distance1(int[] p1, int[] p2) throws DimensionMismatchException {
+        checkEqualLength(p1, p2);
+        int sum = 0;
+        for (int i = 0; i < p1.length; i++) {
+            sum += FastMath.abs(p1[i] - p2[i]);
+        }
+        return sum;
+    }
+
+    /**
+     * Calculates the L<sub>2</sub> (Euclidean) distance between two points.
+     *
+     * @param p1 the first point
+     * @param p2 the second point
+     * @return the L<sub>2</sub> distance between the two points
+     * @throws DimensionMismatchException if the array lengths differ.
+     */
+    public static double distance(double[] p1, double[] p2) throws DimensionMismatchException {
+        checkEqualLength(p1, p2);
+        double sum = 0;
+        for (int i = 0; i < p1.length; i++) {
+            final double dp = p1[i] - p2[i];
+            sum += dp * dp;
+        }
+        return FastMath.sqrt(sum);
+    }
+
+    /**
+     * Calculates the cosine of the angle between two vectors.
+     *
+     * @param v1 Cartesian coordinates of the first vector.
+     * @param v2 Cartesian coordinates of the second vector.
+     * @return the cosine of the angle between the vectors.
+     * @since 3.6
+     */
+    public static double cosAngle(double[] v1, double[] v2) {
+        return linearCombination(v1, v2) / (safeNorm(v1) * safeNorm(v2));
+    }
+
+    /**
+     * Calculates the L<sub>2</sub> (Euclidean) distance between two points.
+     *
+     * @param p1 the first point
+     * @param p2 the second point
+     * @return the L<sub>2</sub> distance between the two points
+     * @throws DimensionMismatchException if the array lengths differ.
+     */
+    public static double distance(int[] p1, int[] p2) throws DimensionMismatchException {
+        checkEqualLength(p1, p2);
+        double sum = 0;
+        for (int i = 0; i < p1.length; i++) {
+            final double dp = p1[i] - p2[i];
+            sum += dp * dp;
+        }
+        return FastMath.sqrt(sum);
+    }
+
+    /**
+     * Calculates the L<sub>&infin;</sub> (max of abs) distance between two points.
+     *
+     * @param p1 the first point
+     * @param p2 the second point
+     * @return the L<sub>&infin;</sub> distance between the two points
+     * @throws DimensionMismatchException if the array lengths differ.
+     */
+    public static double distanceInf(double[] p1, double[] p2) throws DimensionMismatchException {
+        checkEqualLength(p1, p2);
+        double max = 0;
+        for (int i = 0; i < p1.length; i++) {
+            max = FastMath.max(max, FastMath.abs(p1[i] - p2[i]));
+        }
+        return max;
+    }
+
+    /**
+     * Calculates the L<sub>&infin;</sub> (max of abs) distance between two points.
+     *
+     * @param p1 the first point
+     * @param p2 the second point
+     * @return the L<sub>&infin;</sub> distance between the two points
+     * @throws DimensionMismatchException if the array lengths differ.
+     */
+    public static int distanceInf(int[] p1, int[] p2) throws DimensionMismatchException {
+        checkEqualLength(p1, p2);
+        int max = 0;
+        for (int i = 0; i < p1.length; i++) {
+            max = FastMath.max(max, FastMath.abs(p1[i] - p2[i]));
+        }
+        return max;
+    }
+
+    /** Specification of ordering direction. */
+    public enum OrderDirection {
+        /** Constant for increasing direction. */
+        INCREASING,
+        /** Constant for decreasing direction. */
+        DECREASING
+    }
+
+    /**
+     * Check that an array is monotonically increasing or decreasing.
+     *
+     * @param <T> the type of the elements in the specified array
+     * @param val Values.
+     * @param dir Ordering direction.
+     * @param strict Whether the order should be strict.
+     * @return {@code true} if sorted, {@code false} otherwise.
+     */
+    public static <T extends Comparable<? super T>> boolean isMonotonic(
+            T[] val, OrderDirection dir, boolean strict) {
+        T previous = val[0];
+        final int max = val.length;
+        for (int i = 1; i < max; i++) {
+            final int comp;
+            switch (dir) {
+                case INCREASING:
+                    comp = previous.compareTo(val[i]);
+                    if (strict) {
+                        if (comp >= 0) {
+                            return false;
+                        }
+                    } else {
+                        if (comp > 0) {
+                            return false;
+                        }
+                    }
+                    break;
+                case DECREASING:
+                    comp = val[i].compareTo(previous);
+                    if (strict) {
+                        if (comp >= 0) {
+                            return false;
+                        }
+                    } else {
+                        if (comp > 0) {
+                            return false;
+                        }
+                    }
+                    break;
+                default:
+                    // Should never happen.
+                    throw new MathInternalError();
+            }
+
+            previous = val[i];
+        }
+        return true;
+    }
+
+    /**
+     * Check that an array is monotonically increasing or decreasing.
+     *
+     * @param val Values.
+     * @param dir Ordering direction.
+     * @param strict Whether the order should be strict.
+     * @return {@code true} if sorted, {@code false} otherwise.
+     */
+    public static boolean isMonotonic(double[] val, OrderDirection dir, boolean strict) {
+        return checkOrder(val, dir, strict, false);
+    }
+
+    /**
+     * Check that both arrays have the same length.
+     *
+     * @param a Array.
+     * @param b Array.
+     * @param abort Whether to throw an exception if the check fails.
+     * @return {@code true} if the arrays have the same length.
+     * @throws DimensionMismatchException if the lengths differ and {@code abort} is {@code true}.
+     * @since 3.6
+     */
+    public static boolean checkEqualLength(double[] a, double[] b, boolean abort) {
+        if (a.length == b.length) {
+            return true;
+        } else {
+            if (abort) {
+                throw new DimensionMismatchException(a.length, b.length);
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Check that both arrays have the same length.
+     *
+     * @param a Array.
+     * @param b Array.
+     * @throws DimensionMismatchException if the lengths differ.
+     * @since 3.6
+     */
+    public static void checkEqualLength(double[] a, double[] b) {
+        checkEqualLength(a, b, true);
+    }
+
+    /**
+     * Check that both arrays have the same length.
+     *
+     * @param a Array.
+     * @param b Array.
+     * @param abort Whether to throw an exception if the check fails.
+     * @return {@code true} if the arrays have the same length.
+     * @throws DimensionMismatchException if the lengths differ and {@code abort} is {@code true}.
+     * @since 3.6
+     */
+    public static boolean checkEqualLength(int[] a, int[] b, boolean abort) {
+        if (a.length == b.length) {
+            return true;
+        } else {
+            if (abort) {
+                throw new DimensionMismatchException(a.length, b.length);
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Check that both arrays have the same length.
+     *
+     * @param a Array.
+     * @param b Array.
+     * @throws DimensionMismatchException if the lengths differ.
+     * @since 3.6
+     */
+    public static void checkEqualLength(int[] a, int[] b) {
+        checkEqualLength(a, b, true);
+    }
+
+    /**
+     * Check that the given array is sorted.
+     *
+     * @param val Values.
+     * @param dir Ordering direction.
+     * @param strict Whether the order should be strict.
+     * @param abort Whether to throw an exception if the check fails.
+     * @return {@code true} if the array is sorted.
+     * @throws NonMonotonicSequenceException if the array is not sorted and {@code abort} is {@code
+     *     true}.
+     */
+    public static boolean checkOrder(
+            double[] val, OrderDirection dir, boolean strict, boolean abort)
+            throws NonMonotonicSequenceException {
+        double previous = val[0];
+        final int max = val.length;
+
+        int index;
+        ITEM:
+        for (index = 1; index < max; index++) {
+            switch (dir) {
+                case INCREASING:
+                    if (strict) {
+                        if (val[index] <= previous) {
+                            break ITEM;
+                        }
+                    } else {
+                        if (val[index] < previous) {
+                            break ITEM;
+                        }
+                    }
+                    break;
+                case DECREASING:
+                    if (strict) {
+                        if (val[index] >= previous) {
+                            break ITEM;
+                        }
+                    } else {
+                        if (val[index] > previous) {
+                            break ITEM;
+                        }
+                    }
+                    break;
+                default:
+                    // Should never happen.
+                    throw new MathInternalError();
+            }
+
+            previous = val[index];
+        }
+
+        if (index == max) {
+            // Loop completed.
+            return true;
+        }
+
+        // Loop early exit means wrong ordering.
+        if (abort) {
+            throw new NonMonotonicSequenceException(val[index], previous, index, dir, strict);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Check that the given array is sorted.
+     *
+     * @param val Values.
+     * @param dir Ordering direction.
+     * @param strict Whether the order should be strict.
+     * @throws NonMonotonicSequenceException if the array is not sorted.
+     * @since 2.2
+     */
+    public static void checkOrder(double[] val, OrderDirection dir, boolean strict)
+            throws NonMonotonicSequenceException {
+        checkOrder(val, dir, strict, true);
+    }
+
+    /**
+     * Check that the given array is sorted in strictly increasing order.
+     *
+     * @param val Values.
+     * @throws NonMonotonicSequenceException if the array is not sorted.
+     * @since 2.2
+     */
+    public static void checkOrder(double[] val) throws NonMonotonicSequenceException {
+        checkOrder(val, OrderDirection.INCREASING, true);
+    }
+
+    /**
+     * Throws DimensionMismatchException if the input array is not rectangular.
+     *
+     * @param in array to be tested
+     * @throws NullArgumentException if input array is null
+     * @throws DimensionMismatchException if input array is not rectangular
+     * @since 3.1
+     */
+    public static void checkRectangular(final long[][] in)
+            throws NullArgumentException, DimensionMismatchException {
+        MathUtils.checkNotNull(in);
+        for (int i = 1; i < in.length; i++) {
+            if (in[i].length != in[0].length) {
+                throw new DimensionMismatchException(
+                        LocalizedFormats.DIFFERENT_ROWS_LENGTHS, in[i].length, in[0].length);
+            }
+        }
+    }
+
+    /**
+     * Check that all entries of the input array are strictly positive.
+     *
+     * @param in Array to be tested
+     * @throws NotStrictlyPositiveException if any entries of the array are not strictly positive.
+     * @since 3.1
+     */
+    public static void checkPositive(final double[] in) throws NotStrictlyPositiveException {
+        for (int i = 0; i < in.length; i++) {
+            if (in[i] <= 0) {
+                throw new NotStrictlyPositiveException(in[i]);
+            }
+        }
+    }
+
+    /**
+     * Check that no entry of the input array is {@code NaN}.
+     *
+     * @param in Array to be tested.
+     * @throws NotANumberException if an entry is {@code NaN}.
+     * @since 3.4
+     */
+    public static void checkNotNaN(final double[] in) throws NotANumberException {
+        for (int i = 0; i < in.length; i++) {
+            if (Double.isNaN(in[i])) {
+                throw new NotANumberException();
+            }
+        }
+    }
+
+    /**
+     * Check that all entries of the input array are >= 0.
+     *
+     * @param in Array to be tested
+     * @throws NotPositiveException if any array entries are less than 0.
+     * @since 3.1
+     */
+    public static void checkNonNegative(final long[] in) throws NotPositiveException {
+        for (int i = 0; i < in.length; i++) {
+            if (in[i] < 0) {
+                throw new NotPositiveException(in[i]);
+            }
+        }
+    }
+
+    /**
+     * Check all entries of the input array are >= 0.
+     *
+     * @param in Array to be tested
+     * @throws NotPositiveException if any array entries are less than 0.
+     * @since 3.1
+     */
+    public static void checkNonNegative(final long[][] in) throws NotPositiveException {
+        for (int i = 0; i < in.length; i++) {
+            for (int j = 0; j < in[i].length; j++) {
+                if (in[i][j] < 0) {
+                    throw new NotPositiveException(in[i][j]);
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the Cartesian norm (2-norm), handling both overflow and underflow. Translation of the
+     * minpack enorm subroutine.
+     *
+     * <p>The redistribution policy for MINPACK is available <a
+     * href="http://www.netlib.org/minpack/disclaimer">here</a>, for convenience, it is reproduced
+     * below.
+     *
+     * <table border="0" width="80%" cellpadding="10" align="center" bgcolor="#E0E0E0">
+     * <tr><td>
+     *    Minpack Copyright Notice (1999) University of Chicago.
+     *    All rights reserved
+     * </td></tr>
+     * <tr><td>
+     * Redistribution and use in source and binary forms, with or without
+     * modification, are permitted provided that the following conditions
+     * are met:
+     * <ol>
+     *  <li>Redistributions of source code must retain the above copyright
+     *      notice, this list of conditions and the following disclaimer.</li>
+     * <li>Redistributions in binary form must reproduce the above
+     *     copyright notice, this list of conditions and the following
+     *     disclaimer in the documentation and/or other materials provided
+     *     with the distribution.</li>
+     * <li>The end-user documentation included with the redistribution, if any,
+     *     must include the following acknowledgment:
+     *     {@code This product includes software developed by the University of
+     *           Chicago, as Operator of Argonne National Laboratory.}
+     *     Alternately, this acknowledgment may appear in the software itself,
+     *     if and wherever such third-party acknowledgments normally appear.</li>
+     * <li><strong>WARRANTY DISCLAIMER. THE SOFTWARE IS SUPPLIED "AS IS"
+     *     WITHOUT WARRANTY OF ANY KIND. THE COPYRIGHT HOLDER, THE
+     *     UNITED STATES, THE UNITED STATES DEPARTMENT OF ENERGY, AND
+     *     THEIR EMPLOYEES: (1) DISCLAIM ANY WARRANTIES, EXPRESS OR
+     *     IMPLIED, INCLUDING BUT NOT LIMITED TO ANY IMPLIED WARRANTIES
+     *     OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE
+     *     OR NON-INFRINGEMENT, (2) DO NOT ASSUME ANY LEGAL LIABILITY
+     *     OR RESPONSIBILITY FOR THE ACCURACY, COMPLETENESS, OR
+     *     USEFULNESS OF THE SOFTWARE, (3) DO NOT REPRESENT THAT USE OF
+     *     THE SOFTWARE WOULD NOT INFRINGE PRIVATELY OWNED RIGHTS, (4)
+     *     DO NOT WARRANT THAT THE SOFTWARE WILL FUNCTION
+     *     UNINTERRUPTED, THAT IT IS ERROR-FREE OR THAT ANY ERRORS WILL
+     *     BE CORRECTED.</strong></li>
+     * <li><strong>LIMITATION OF LIABILITY. IN NO EVENT WILL THE COPYRIGHT
+     *     HOLDER, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF
+     *     ENERGY, OR THEIR EMPLOYEES: BE LIABLE FOR ANY INDIRECT,
+     *     INCIDENTAL, CONSEQUENTIAL, SPECIAL OR PUNITIVE DAMAGES OF
+     *     ANY KIND OR NATURE, INCLUDING BUT NOT LIMITED TO LOSS OF
+     *     PROFITS OR LOSS OF DATA, FOR ANY REASON WHATSOEVER, WHETHER
+     *     SUCH LIABILITY IS ASSERTED ON THE BASIS OF CONTRACT, TORT
+     *     (INCLUDING NEGLIGENCE OR STRICT LIABILITY), OR OTHERWISE,
+     *     EVEN IF ANY OF SAID PARTIES HAS BEEN WARNED OF THE
+     *     POSSIBILITY OF SUCH LOSS OR DAMAGES.</strong></li>
+     * <ol></td></tr>
+     * </table>
+     *
+     * @param v Vector of doubles.
+     * @return the 2-norm of the vector.
+     * @since 2.2
+     */
+    public static double safeNorm(double[] v) {
+        double rdwarf = 3.834e-20;
+        double rgiant = 1.304e+19;
+        double s1 = 0;
+        double s2 = 0;
+        double s3 = 0;
+        double x1max = 0;
+        double x3max = 0;
+        double floatn = v.length;
+        double agiant = rgiant / floatn;
+        for (int i = 0; i < v.length; i++) {
+            double xabs = FastMath.abs(v[i]);
+            if (xabs < rdwarf || xabs > agiant) {
+                if (xabs > rdwarf) {
+                    if (xabs > x1max) {
+                        double r = x1max / xabs;
+                        s1 = 1 + s1 * r * r;
+                        x1max = xabs;
+                    } else {
+                        double r = xabs / x1max;
+                        s1 += r * r;
+                    }
+                } else {
+                    if (xabs > x3max) {
+                        double r = x3max / xabs;
+                        s3 = 1 + s3 * r * r;
+                        x3max = xabs;
+                    } else {
+                        if (xabs != 0) {
+                            double r = xabs / x3max;
+                            s3 += r * r;
+                        }
+                    }
+                }
+            } else {
+                s2 += xabs * xabs;
+            }
+        }
+        double norm;
+        if (s1 != 0) {
+            norm = x1max * Math.sqrt(s1 + (s2 / x1max) / x1max);
+        } else {
+            if (s2 == 0) {
+                norm = x3max * Math.sqrt(s3);
+            } else {
+                if (s2 >= x3max) {
+                    norm = Math.sqrt(s2 * (1 + (x3max / s2) * (x3max * s3)));
+                } else {
+                    norm = Math.sqrt(x3max * ((s2 / x3max) + (x3max * s3)));
+                }
+            }
+        }
+        return norm;
+    }
+
+    /** A helper data structure holding a double and an integer value. */
+    private static class PairDoubleInteger {
+        /** Key */
+        private final double key;
+
+        /** Value */
+        private final int value;
+
+        /**
+         * @param key Key.
+         * @param value Value.
+         */
+        PairDoubleInteger(double key, int value) {
+            this.key = key;
+            this.value = value;
+        }
+
+        /**
+         * @return the key.
+         */
+        public double getKey() {
+            return key;
+        }
+
+        /**
+         * @return the value.
+         */
+        public int getValue() {
+            return value;
+        }
+    }
+
+    /**
+     * Sort an array in ascending order in place and perform the same reordering of entries on other
+     * arrays. For example, if {@code x = [3, 1, 2], y = [1, 2, 3]} and {@code z = [0, 5, 7]}, then
+     * {@code sortInPlace(x, y, z)} will update {@code x} to {@code [1, 2, 3]}, {@code y} to {@code
+     * [2, 3, 1]} and {@code z} to {@code [5, 7, 0]}.
+     *
+     * @param x Array to be sorted and used as a pattern for permutation of the other arrays.
+     * @param yList Set of arrays whose permutations of entries will follow those performed on
+     *     {@code x}.
+     * @throws DimensionMismatchException if any {@code y} is not the same size as {@code x}.
+     * @throws NullArgumentException if {@code x} or any {@code y} is null.
+     * @since 3.0
+     */
+    public static void sortInPlace(double[] x, double[]... yList)
+            throws DimensionMismatchException, NullArgumentException {
+        sortInPlace(x, OrderDirection.INCREASING, yList);
+    }
+
+    /**
+     * Sort an array in place and perform the same reordering of entries on other arrays. This
+     * method works the same as the other {@link #sortInPlace(double[], double[][]) sortInPlace}
+     * method, but allows the order of the sort to be provided in the {@code dir} parameter.
+     *
+     * @param x Array to be sorted and used as a pattern for permutation of the other arrays.
+     * @param dir Order direction.
+     * @param yList Set of arrays whose permutations of entries will follow those performed on
+     *     {@code x}.
+     * @throws DimensionMismatchException if any {@code y} is not the same size as {@code x}.
+     * @throws NullArgumentException if {@code x} or any {@code y} is null
+     * @since 3.0
+     */
+    public static void sortInPlace(double[] x, final OrderDirection dir, double[]... yList)
+            throws NullArgumentException, DimensionMismatchException {
+
+        // Consistency checks.
+        if (x == null) {
+            throw new NullArgumentException();
+        }
+
+        final int yListLen = yList.length;
+        final int len = x.length;
+
+        for (int j = 0; j < yListLen; j++) {
+            final double[] y = yList[j];
+            if (y == null) {
+                throw new NullArgumentException();
+            }
+            if (y.length != len) {
+                throw new DimensionMismatchException(y.length, len);
+            }
+        }
+
+        // Associate each abscissa "x[i]" with its index "i".
+        final List<PairDoubleInteger> list = new ArrayList<PairDoubleInteger>(len);
+        for (int i = 0; i < len; i++) {
+            list.add(new PairDoubleInteger(x[i], i));
+        }
+
+        // Create comparators for increasing and decreasing orders.
+        final Comparator<PairDoubleInteger> comp =
+                dir == MathArrays.OrderDirection.INCREASING
+                        ? new Comparator<PairDoubleInteger>() {
+                            /** {@inheritDoc} */
+                            public int compare(PairDoubleInteger o1, PairDoubleInteger o2) {
+                                return Double.compare(o1.getKey(), o2.getKey());
+                            }
+                        }
+                        : new Comparator<PairDoubleInteger>() {
+                            /** {@inheritDoc} */
+                            public int compare(PairDoubleInteger o1, PairDoubleInteger o2) {
+                                return Double.compare(o2.getKey(), o1.getKey());
+                            }
+                        };
+
+        // Sort.
+        Collections.sort(list, comp);
+
+        // Modify the original array so that its elements are in
+        // the prescribed order.
+        // Retrieve indices of original locations.
+        final int[] indices = new int[len];
+        for (int i = 0; i < len; i++) {
+            final PairDoubleInteger e = list.get(i);
+            x[i] = e.getKey();
+            indices[i] = e.getValue();
+        }
+
+        // In each of the associated arrays, move the
+        // elements to their new location.
+        for (int j = 0; j < yListLen; j++) {
+            // Input array will be modified in place.
+            final double[] yInPlace = yList[j];
+            final double[] yOrig = yInPlace.clone();
+
+            for (int i = 0; i < len; i++) {
+                yInPlace[i] = yOrig[indices[i]];
+            }
+        }
+    }
+
+    /**
+     * Creates a copy of the {@code source} array.
+     *
+     * @param source Array to be copied.
+     * @return the copied array.
+     */
+    public static int[] copyOf(int[] source) {
+        return copyOf(source, source.length);
+    }
+
+    /**
+     * Creates a copy of the {@code source} array.
+     *
+     * @param source Array to be copied.
+     * @return the copied array.
+     */
+    public static double[] copyOf(double[] source) {
+        return copyOf(source, source.length);
+    }
+
+    /**
+     * Creates a copy of the {@code source} array.
+     *
+     * @param source Array to be copied.
+     * @param len Number of entries to copy. If smaller then the source length, the copy will be
+     *     truncated, if larger it will padded with zeroes.
+     * @return the copied array.
+     */
+    public static int[] copyOf(int[] source, int len) {
+        final int[] output = new int[len];
+        System.arraycopy(source, 0, output, 0, FastMath.min(len, source.length));
+        return output;
+    }
+
+    /**
+     * Creates a copy of the {@code source} array.
+     *
+     * @param source Array to be copied.
+     * @param len Number of entries to copy. If smaller then the source length, the copy will be
+     *     truncated, if larger it will padded with zeroes.
+     * @return the copied array.
+     */
+    public static double[] copyOf(double[] source, int len) {
+        final double[] output = new double[len];
+        System.arraycopy(source, 0, output, 0, FastMath.min(len, source.length));
+        return output;
+    }
+
+    /**
+     * Creates a copy of the {@code source} array.
+     *
+     * @param source Array to be copied.
+     * @param from Initial index of the range to be copied, inclusive.
+     * @param to Final index of the range to be copied, exclusive. (This index may lie outside the
+     *     array.)
+     * @return the copied array.
+     */
+    public static double[] copyOfRange(double[] source, int from, int to) {
+        final int len = to - from;
+        final double[] output = new double[len];
+        System.arraycopy(source, from, output, 0, FastMath.min(len, source.length - from));
+        return output;
+    }
+
+    /**
+     * Compute a linear combination accurately. This method computes the sum of the products <code>
+     * a<sub>i</sub> b<sub>i</sub></code> to high accuracy. It does so by using specific
+     * multiplication and addition algorithms to preserve accuracy and reduce cancellation effects.
+     * <br>
+     * It is based on the 2005 paper <a
+     * href="http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.2.1547">Accurate Sum and Dot
+     * Product</a> by Takeshi Ogita, Siegfried M. Rump, and Shin'ichi Oishi published in SIAM J.
+     * Sci. Comput.
+     *
+     * @param a Factors.
+     * @param b Factors.
+     * @return <code>&Sigma;<sub>i</sub> a<sub>i</sub> b<sub>i</sub></code>.
+     * @throws DimensionMismatchException if arrays dimensions don't match
+     */
+    public static double linearCombination(final double[] a, final double[] b)
+            throws DimensionMismatchException {
+        checkEqualLength(a, b);
+        final int len = a.length;
+
+        if (len == 1) {
+            // Revert to scalar multiplication.
+            return a[0] * b[0];
+        }
+
+        final double[] prodHigh = new double[len];
+        double prodLowSum = 0;
+
+        for (int i = 0; i < len; i++) {
+            final double ai = a[i];
+            final double aHigh =
+                    Double.longBitsToDouble(Double.doubleToRawLongBits(ai) & ((-1L) << 27));
+            final double aLow = ai - aHigh;
+
+            final double bi = b[i];
+            final double bHigh =
+                    Double.longBitsToDouble(Double.doubleToRawLongBits(bi) & ((-1L) << 27));
+            final double bLow = bi - bHigh;
+            prodHigh[i] = ai * bi;
+            final double prodLow =
+                    aLow * bLow - (((prodHigh[i] - aHigh * bHigh) - aLow * bHigh) - aHigh * bLow);
+            prodLowSum += prodLow;
+        }
+
+        final double prodHighCur = prodHigh[0];
+        double prodHighNext = prodHigh[1];
+        double sHighPrev = prodHighCur + prodHighNext;
+        double sPrime = sHighPrev - prodHighNext;
+        double sLowSum = (prodHighNext - (sHighPrev - sPrime)) + (prodHighCur - sPrime);
+
+        final int lenMinusOne = len - 1;
+        for (int i = 1; i < lenMinusOne; i++) {
+            prodHighNext = prodHigh[i + 1];
+            final double sHighCur = sHighPrev + prodHighNext;
+            sPrime = sHighCur - prodHighNext;
+            sLowSum += (prodHighNext - (sHighCur - sPrime)) + (sHighPrev - sPrime);
+            sHighPrev = sHighCur;
+        }
+
+        double result = sHighPrev + (prodLowSum + sLowSum);
+
+        if (Double.isNaN(result)) {
+            // either we have split infinite numbers or some coefficients were NaNs,
+            // just rely on the naive implementation and let IEEE754 handle this
+            result = 0;
+            for (int i = 0; i < len; ++i) {
+                result += a[i] * b[i];
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Compute a linear combination accurately.
+     *
+     * <p>This method computes a<sub>1</sub>&times;b<sub>1</sub> + a<sub>2</sub>&times;b<sub>2</sub>
+     * to high accuracy. It does so by using specific multiplication and addition algorithms to
+     * preserve accuracy and reduce cancellation effects. It is based on the 2005 paper <a
+     * href="http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.2.1547">Accurate Sum and Dot
+     * Product</a> by Takeshi Ogita, Siegfried M. Rump, and Shin'ichi Oishi published in SIAM J.
+     * Sci. Comput.
+     *
+     * @param a1 first factor of the first term
+     * @param b1 second factor of the first term
+     * @param a2 first factor of the second term
+     * @param b2 second factor of the second term
+     * @return a<sub>1</sub>&times;b<sub>1</sub> + a<sub>2</sub>&times;b<sub>2</sub>
+     * @see #linearCombination(double, double, double, double, double, double)
+     * @see #linearCombination(double, double, double, double, double, double, double, double)
+     */
+    public static double linearCombination(
+            final double a1, final double b1, final double a2, final double b2) {
+
+        // the code below is split in many additions/subtractions that may
+        // appear redundant. However, they should NOT be simplified, as they
+        // use IEEE754 floating point arithmetic rounding properties.
+        // The variable naming conventions are that xyzHigh contains the most significant
+        // bits of xyz and xyzLow contains its least significant bits. So theoretically
+        // xyz is the sum xyzHigh + xyzLow, but in many cases below, this sum cannot
+        // be represented in only one double precision number so we preserve two numbers
+        // to hold it as long as we can, combining the high and low order bits together
+        // only at the end, after cancellation may have occurred on high order bits
+
+        // split a1 and b1 as one 26 bits number and one 27 bits number
+        final double a1High =
+                Double.longBitsToDouble(Double.doubleToRawLongBits(a1) & ((-1L) << 27));
+        final double a1Low = a1 - a1High;
+        final double b1High =
+                Double.longBitsToDouble(Double.doubleToRawLongBits(b1) & ((-1L) << 27));
+        final double b1Low = b1 - b1High;
+
+        // accurate multiplication a1 * b1
+        final double prod1High = a1 * b1;
+        final double prod1Low =
+                a1Low * b1Low - (((prod1High - a1High * b1High) - a1Low * b1High) - a1High * b1Low);
+
+        // split a2 and b2 as one 26 bits number and one 27 bits number
+        final double a2High =
+                Double.longBitsToDouble(Double.doubleToRawLongBits(a2) & ((-1L) << 27));
+        final double a2Low = a2 - a2High;
+        final double b2High =
+                Double.longBitsToDouble(Double.doubleToRawLongBits(b2) & ((-1L) << 27));
+        final double b2Low = b2 - b2High;
+
+        // accurate multiplication a2 * b2
+        final double prod2High = a2 * b2;
+        final double prod2Low =
+                a2Low * b2Low - (((prod2High - a2High * b2High) - a2Low * b2High) - a2High * b2Low);
+
+        // accurate addition a1 * b1 + a2 * b2
+        final double s12High = prod1High + prod2High;
+        final double s12Prime = s12High - prod2High;
+        final double s12Low = (prod2High - (s12High - s12Prime)) + (prod1High - s12Prime);
+
+        // final rounding, s12 may have suffered many cancellations, we try
+        // to recover some bits from the extra words we have saved up to now
+        double result = s12High + (prod1Low + prod2Low + s12Low);
+
+        if (Double.isNaN(result)) {
+            // either we have split infinite numbers or some coefficients were NaNs,
+            // just rely on the naive implementation and let IEEE754 handle this
+            result = a1 * b1 + a2 * b2;
+        }
+
+        return result;
+    }
+
+    /**
+     * Compute a linear combination accurately.
+     *
+     * <p>This method computes a<sub>1</sub>&times;b<sub>1</sub> + a<sub>2</sub>&times;b<sub>2</sub>
+     * + a<sub>3</sub>&times;b<sub>3</sub> to high accuracy. It does so by using specific
+     * multiplication and addition algorithms to preserve accuracy and reduce cancellation effects.
+     * It is based on the 2005 paper <a
+     * href="http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.2.1547">Accurate Sum and Dot
+     * Product</a> by Takeshi Ogita, Siegfried M. Rump, and Shin'ichi Oishi published in SIAM J.
+     * Sci. Comput.
+     *
+     * @param a1 first factor of the first term
+     * @param b1 second factor of the first term
+     * @param a2 first factor of the second term
+     * @param b2 second factor of the second term
+     * @param a3 first factor of the third term
+     * @param b3 second factor of the third term
+     * @return a<sub>1</sub>&times;b<sub>1</sub> + a<sub>2</sub>&times;b<sub>2</sub> +
+     *     a<sub>3</sub>&times;b<sub>3</sub>
+     * @see #linearCombination(double, double, double, double)
+     * @see #linearCombination(double, double, double, double, double, double, double, double)
+     */
+    public static double linearCombination(
+            final double a1,
+            final double b1,
+            final double a2,
+            final double b2,
+            final double a3,
+            final double b3) {
+
+        // the code below is split in many additions/subtractions that may
+        // appear redundant. However, they should NOT be simplified, as they
+        // do use IEEE754 floating point arithmetic rounding properties.
+        // The variables naming conventions are that xyzHigh contains the most significant
+        // bits of xyz and xyzLow contains its least significant bits. So theoretically
+        // xyz is the sum xyzHigh + xyzLow, but in many cases below, this sum cannot
+        // be represented in only one double precision number so we preserve two numbers
+        // to hold it as long as we can, combining the high and low order bits together
+        // only at the end, after cancellation may have occurred on high order bits
+
+        // split a1 and b1 as one 26 bits number and one 27 bits number
+        final double a1High =
+                Double.longBitsToDouble(Double.doubleToRawLongBits(a1) & ((-1L) << 27));
+        final double a1Low = a1 - a1High;
+        final double b1High =
+                Double.longBitsToDouble(Double.doubleToRawLongBits(b1) & ((-1L) << 27));
+        final double b1Low = b1 - b1High;
+
+        // accurate multiplication a1 * b1
+        final double prod1High = a1 * b1;
+        final double prod1Low =
+                a1Low * b1Low - (((prod1High - a1High * b1High) - a1Low * b1High) - a1High * b1Low);
+
+        // split a2 and b2 as one 26 bits number and one 27 bits number
+        final double a2High =
+                Double.longBitsToDouble(Double.doubleToRawLongBits(a2) & ((-1L) << 27));
+        final double a2Low = a2 - a2High;
+        final double b2High =
+                Double.longBitsToDouble(Double.doubleToRawLongBits(b2) & ((-1L) << 27));
+        final double b2Low = b2 - b2High;
+
+        // accurate multiplication a2 * b2
+        final double prod2High = a2 * b2;
+        final double prod2Low =
+                a2Low * b2Low - (((prod2High - a2High * b2High) - a2Low * b2High) - a2High * b2Low);
+
+        // split a3 and b3 as one 26 bits number and one 27 bits number
+        final double a3High =
+                Double.longBitsToDouble(Double.doubleToRawLongBits(a3) & ((-1L) << 27));
+        final double a3Low = a3 - a3High;
+        final double b3High =
+                Double.longBitsToDouble(Double.doubleToRawLongBits(b3) & ((-1L) << 27));
+        final double b3Low = b3 - b3High;
+
+        // accurate multiplication a3 * b3
+        final double prod3High = a3 * b3;
+        final double prod3Low =
+                a3Low * b3Low - (((prod3High - a3High * b3High) - a3Low * b3High) - a3High * b3Low);
+
+        // accurate addition a1 * b1 + a2 * b2
+        final double s12High = prod1High + prod2High;
+        final double s12Prime = s12High - prod2High;
+        final double s12Low = (prod2High - (s12High - s12Prime)) + (prod1High - s12Prime);
+
+        // accurate addition a1 * b1 + a2 * b2 + a3 * b3
+        final double s123High = s12High + prod3High;
+        final double s123Prime = s123High - prod3High;
+        final double s123Low = (prod3High - (s123High - s123Prime)) + (s12High - s123Prime);
+
+        // final rounding, s123 may have suffered many cancellations, we try
+        // to recover some bits from the extra words we have saved up to now
+        double result = s123High + (prod1Low + prod2Low + prod3Low + s12Low + s123Low);
+
+        if (Double.isNaN(result)) {
+            // either we have split infinite numbers or some coefficients were NaNs,
+            // just rely on the naive implementation and let IEEE754 handle this
+            result = a1 * b1 + a2 * b2 + a3 * b3;
+        }
+
+        return result;
+    }
+
+    /**
+     * Compute a linear combination accurately.
+     *
+     * <p>This method computes a<sub>1</sub>&times;b<sub>1</sub> + a<sub>2</sub>&times;b<sub>2</sub>
+     * + a<sub>3</sub>&times;b<sub>3</sub> + a<sub>4</sub>&times;b<sub>4</sub> to high accuracy. It
+     * does so by using specific multiplication and addition algorithms to preserve accuracy and
+     * reduce cancellation effects. It is based on the 2005 paper <a
+     * href="http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.2.1547">Accurate Sum and Dot
+     * Product</a> by Takeshi Ogita, Siegfried M. Rump, and Shin'ichi Oishi published in SIAM J.
+     * Sci. Comput.
+     *
+     * @param a1 first factor of the first term
+     * @param b1 second factor of the first term
+     * @param a2 first factor of the second term
+     * @param b2 second factor of the second term
+     * @param a3 first factor of the third term
+     * @param b3 second factor of the third term
+     * @param a4 first factor of the third term
+     * @param b4 second factor of the third term
+     * @return a<sub>1</sub>&times;b<sub>1</sub> + a<sub>2</sub>&times;b<sub>2</sub> +
+     *     a<sub>3</sub>&times;b<sub>3</sub> + a<sub>4</sub>&times;b<sub>4</sub>
+     * @see #linearCombination(double, double, double, double)
+     * @see #linearCombination(double, double, double, double, double, double)
+     */
+    public static double linearCombination(
+            final double a1,
+            final double b1,
+            final double a2,
+            final double b2,
+            final double a3,
+            final double b3,
+            final double a4,
+            final double b4) {
+
+        // the code below is split in many additions/subtractions that may
+        // appear redundant. However, they should NOT be simplified, as they
+        // do use IEEE754 floating point arithmetic rounding properties.
+        // The variables naming conventions are that xyzHigh contains the most significant
+        // bits of xyz and xyzLow contains its least significant bits. So theoretically
+        // xyz is the sum xyzHigh + xyzLow, but in many cases below, this sum cannot
+        // be represented in only one double precision number so we preserve two numbers
+        // to hold it as long as we can, combining the high and low order bits together
+        // only at the end, after cancellation may have occurred on high order bits
+
+        // split a1 and b1 as one 26 bits number and one 27 bits number
+        final double a1High =
+                Double.longBitsToDouble(Double.doubleToRawLongBits(a1) & ((-1L) << 27));
+        final double a1Low = a1 - a1High;
+        final double b1High =
+                Double.longBitsToDouble(Double.doubleToRawLongBits(b1) & ((-1L) << 27));
+        final double b1Low = b1 - b1High;
+
+        // accurate multiplication a1 * b1
+        final double prod1High = a1 * b1;
+        final double prod1Low =
+                a1Low * b1Low - (((prod1High - a1High * b1High) - a1Low * b1High) - a1High * b1Low);
+
+        // split a2 and b2 as one 26 bits number and one 27 bits number
+        final double a2High =
+                Double.longBitsToDouble(Double.doubleToRawLongBits(a2) & ((-1L) << 27));
+        final double a2Low = a2 - a2High;
+        final double b2High =
+                Double.longBitsToDouble(Double.doubleToRawLongBits(b2) & ((-1L) << 27));
+        final double b2Low = b2 - b2High;
+
+        // accurate multiplication a2 * b2
+        final double prod2High = a2 * b2;
+        final double prod2Low =
+                a2Low * b2Low - (((prod2High - a2High * b2High) - a2Low * b2High) - a2High * b2Low);
+
+        // split a3 and b3 as one 26 bits number and one 27 bits number
+        final double a3High =
+                Double.longBitsToDouble(Double.doubleToRawLongBits(a3) & ((-1L) << 27));
+        final double a3Low = a3 - a3High;
+        final double b3High =
+                Double.longBitsToDouble(Double.doubleToRawLongBits(b3) & ((-1L) << 27));
+        final double b3Low = b3 - b3High;
+
+        // accurate multiplication a3 * b3
+        final double prod3High = a3 * b3;
+        final double prod3Low =
+                a3Low * b3Low - (((prod3High - a3High * b3High) - a3Low * b3High) - a3High * b3Low);
+
+        // split a4 and b4 as one 26 bits number and one 27 bits number
+        final double a4High =
+                Double.longBitsToDouble(Double.doubleToRawLongBits(a4) & ((-1L) << 27));
+        final double a4Low = a4 - a4High;
+        final double b4High =
+                Double.longBitsToDouble(Double.doubleToRawLongBits(b4) & ((-1L) << 27));
+        final double b4Low = b4 - b4High;
+
+        // accurate multiplication a4 * b4
+        final double prod4High = a4 * b4;
+        final double prod4Low =
+                a4Low * b4Low - (((prod4High - a4High * b4High) - a4Low * b4High) - a4High * b4Low);
+
+        // accurate addition a1 * b1 + a2 * b2
+        final double s12High = prod1High + prod2High;
+        final double s12Prime = s12High - prod2High;
+        final double s12Low = (prod2High - (s12High - s12Prime)) + (prod1High - s12Prime);
+
+        // accurate addition a1 * b1 + a2 * b2 + a3 * b3
+        final double s123High = s12High + prod3High;
+        final double s123Prime = s123High - prod3High;
+        final double s123Low = (prod3High - (s123High - s123Prime)) + (s12High - s123Prime);
+
+        // accurate addition a1 * b1 + a2 * b2 + a3 * b3 + a4 * b4
+        final double s1234High = s123High + prod4High;
+        final double s1234Prime = s1234High - prod4High;
+        final double s1234Low = (prod4High - (s1234High - s1234Prime)) + (s123High - s1234Prime);
+
+        // final rounding, s1234 may have suffered many cancellations, we try
+        // to recover some bits from the extra words we have saved up to now
+        double result =
+                s1234High
+                        + (prod1Low + prod2Low + prod3Low + prod4Low + s12Low + s123Low + s1234Low);
+
+        if (Double.isNaN(result)) {
+            // either we have split infinite numbers or some coefficients were NaNs,
+            // just rely on the naive implementation and let IEEE754 handle this
+            result = a1 * b1 + a2 * b2 + a3 * b3 + a4 * b4;
+        }
+
+        return result;
+    }
+
+    /**
+     * Returns true iff both arguments are null or have same dimensions and all their elements are
+     * equal as defined by {@link Precision#equals(float,float)}.
+     *
+     * @param x first array
+     * @param y second array
+     * @return true if the values are both null or have same dimension and equal elements.
+     */
+    public static boolean equals(float[] x, float[] y) {
+        if ((x == null) || (y == null)) {
+            return !((x == null) ^ (y == null));
+        }
+        if (x.length != y.length) {
+            return false;
+        }
+        for (int i = 0; i < x.length; ++i) {
+            if (!Precision.equals(x[i], y[i])) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Returns true iff both arguments are null or have same dimensions and all their elements are
+     * equal as defined by {@link Precision#equalsIncludingNaN(double,double) this method}.
+     *
+     * @param x first array
+     * @param y second array
+     * @return true if the values are both null or have same dimension and equal elements
+     * @since 2.2
+     */
+    public static boolean equalsIncludingNaN(float[] x, float[] y) {
+        if ((x == null) || (y == null)) {
+            return !((x == null) ^ (y == null));
+        }
+        if (x.length != y.length) {
+            return false;
+        }
+        for (int i = 0; i < x.length; ++i) {
+            if (!Precision.equalsIncludingNaN(x[i], y[i])) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Returns {@code true} iff both arguments are {@code null} or have same dimensions and all
+     * their elements are equal as defined by {@link Precision#equals(double,double)}.
+     *
+     * @param x First array.
+     * @param y Second array.
+     * @return {@code true} if the values are both {@code null} or have same dimension and equal
+     *     elements.
+     */
+    public static boolean equals(double[] x, double[] y) {
+        if ((x == null) || (y == null)) {
+            return !((x == null) ^ (y == null));
+        }
+        if (x.length != y.length) {
+            return false;
+        }
+        for (int i = 0; i < x.length; ++i) {
+            if (!Precision.equals(x[i], y[i])) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Returns {@code true} iff both arguments are {@code null} or have same dimensions and all
+     * their elements are equal as defined by {@link Precision#equalsIncludingNaN(double,double)
+     * this method}.
+     *
+     * @param x First array.
+     * @param y Second array.
+     * @return {@code true} if the values are both {@code null} or have same dimension and equal
+     *     elements.
+     * @since 2.2
+     */
+    public static boolean equalsIncludingNaN(double[] x, double[] y) {
+        if ((x == null) || (y == null)) {
+            return !((x == null) ^ (y == null));
+        }
+        if (x.length != y.length) {
+            return false;
+        }
+        for (int i = 0; i < x.length; ++i) {
+            if (!Precision.equalsIncludingNaN(x[i], y[i])) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Normalizes an array to make it sum to a specified value. Returns the result of the
+     * transformation
+     *
+     * <pre>
+     *    x |-> x * normalizedSum / sum
+     * </pre>
+     *
+     * applied to each non-NaN element x of the input array, where sum is the sum of the non-NaN
+     * entries in the input array.
+     *
+     * <p>Throws IllegalArgumentException if {@code normalizedSum} is infinite or NaN and
+     * ArithmeticException if the input array contains any infinite elements or sums to 0.
+     *
+     * <p>Ignores (i.e., copies unchanged to the output array) NaNs in the input array.
+     *
+     * @param values Input array to be normalized
+     * @param normalizedSum Target sum for the normalized array
+     * @return the normalized array.
+     * @throws MathArithmeticException if the input array contains infinite elements or sums to
+     *     zero.
+     * @throws MathIllegalArgumentException if the target sum is infinite or {@code NaN}.
+     * @since 2.1
+     */
+    public static double[] normalizeArray(double[] values, double normalizedSum)
+            throws MathIllegalArgumentException, MathArithmeticException {
+        if (Double.isInfinite(normalizedSum)) {
+            throw new MathIllegalArgumentException(LocalizedFormats.NORMALIZE_INFINITE);
+        }
+        if (Double.isNaN(normalizedSum)) {
+            throw new MathIllegalArgumentException(LocalizedFormats.NORMALIZE_NAN);
+        }
+        double sum = 0d;
+        final int len = values.length;
+        double[] out = new double[len];
+        for (int i = 0; i < len; i++) {
+            if (Double.isInfinite(values[i])) {
+                throw new MathIllegalArgumentException(
+                        LocalizedFormats.INFINITE_ARRAY_ELEMENT, values[i], i);
+            }
+            if (!Double.isNaN(values[i])) {
+                sum += values[i];
+            }
+        }
+        if (sum == 0) {
+            throw new MathArithmeticException(LocalizedFormats.ARRAY_SUMS_TO_ZERO);
+        }
+        for (int i = 0; i < len; i++) {
+            if (Double.isNaN(values[i])) {
+                out[i] = Double.NaN;
+            } else {
+                out[i] = values[i] * normalizedSum / sum;
+            }
+        }
+        return out;
+    }
+
+    /**
+     * Build an array of elements.
+     *
+     * <p>Arrays are filled with field.getZero()
+     *
+     * @param <T> the type of the field elements
+     * @param field field to which array elements belong
+     * @param length of the array
+     * @return a new array
+     * @since 3.2
+     */
+    public static <T> T[] buildArray(final Field<T> field, final int length) {
+        @SuppressWarnings("unchecked") // OK because field must be correct class
+        T[] array = (T[]) Array.newInstance(field.getRuntimeClass(), length);
+        Arrays.fill(array, field.getZero());
+        return array;
+    }
+
+    /**
+     * Build a double dimension array of elements.
+     *
+     * <p>Arrays are filled with field.getZero()
+     *
+     * @param <T> the type of the field elements
+     * @param field field to which array elements belong
+     * @param rows number of rows in the array
+     * @param columns number of columns (may be negative to build partial arrays in the same way
+     *     <code>new Field[rows][]</code> works)
+     * @return a new array
+     * @since 3.2
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T[][] buildArray(final Field<T> field, final int rows, final int columns) {
+        final T[][] array;
+        if (columns < 0) {
+            T[] dummyRow = buildArray(field, 0);
+            array = (T[][]) Array.newInstance(dummyRow.getClass(), rows);
+        } else {
+            array = (T[][]) Array.newInstance(field.getRuntimeClass(), new int[] {rows, columns});
+            for (int i = 0; i < rows; ++i) {
+                Arrays.fill(array[i], field.getZero());
+            }
+        }
+        return array;
+    }
+
+    /**
+     * Calculates the <a href="http://en.wikipedia.org/wiki/Convolution">convolution</a> between two
+     * sequences.
+     *
+     * <p>The solution is obtained via straightforward computation of the convolution sum (and not
+     * via FFT). Whenever the computation needs an element that would be located at an index outside
+     * the input arrays, the value is assumed to be zero.
+     *
+     * @param x First sequence. Typically, this sequence will represent an input signal to a system.
+     * @param h Second sequence. Typically, this sequence will represent the impulse response of the
+     *     system.
+     * @return the convolution of {@code x} and {@code h}. This array's length will be {@code
+     *     x.length + h.length - 1}.
+     * @throws NullArgumentException if either {@code x} or {@code h} is {@code null}.
+     * @throws NoDataException if either {@code x} or {@code h} is empty.
+     * @since 3.3
+     */
+    public static double[] convolve(double[] x, double[] h)
+            throws NullArgumentException, NoDataException {
+        MathUtils.checkNotNull(x);
+        MathUtils.checkNotNull(h);
+
+        final int xLen = x.length;
+        final int hLen = h.length;
+
+        if (xLen == 0 || hLen == 0) {
+            throw new NoDataException();
+        }
+
+        // initialize the output array
+        final int totalLength = xLen + hLen - 1;
+        final double[] y = new double[totalLength];
+
+        // straightforward implementation of the convolution sum
+        for (int n = 0; n < totalLength; n++) {
+            double yn = 0;
+            int k = FastMath.max(0, n + 1 - xLen);
+            int j = n - k;
+            while (k < hLen && j >= 0) {
+                yn += x[j--] * h[k++];
+            }
+            y[n] = yn;
+        }
+
+        return y;
+    }
+
+    /** Specification for indicating that some operation applies before or after a given index. */
+    public enum Position {
+        /** Designates the beginning of the array (near index 0). */
+        HEAD,
+        /** Designates the end of the array. */
+        TAIL
+    }
+
+    /**
+     * Shuffle the entries of the given array. The {@code start} and {@code pos} parameters select
+     * which portion of the array is randomized and which is left untouched.
+     *
+     * @see #shuffle(int[],int,Position,RandomGenerator)
+     * @param list Array whose entries will be shuffled (in-place).
+     * @param start Index at which shuffling begins.
+     * @param pos Shuffling is performed for index positions between {@code start} and either the
+     *     end (if {@link Position#TAIL}) or the beginning (if {@link Position#HEAD}) of the array.
+     */
+    public static void shuffle(int[] list, int start, Position pos) {
+        shuffle(list, start, pos, new Well19937c());
+    }
+
+    /**
+     * Shuffle the entries of the given array, using the <a
+     * href="http://en.wikipedia.org/wiki/Fisher–Yates_shuffle#The_modern_algorithm">
+     * Fisher–Yates</a> algorithm. The {@code start} and {@code pos} parameters select which portion
+     * of the array is randomized and which is left untouched.
+     *
+     * @param list Array whose entries will be shuffled (in-place).
+     * @param start Index at which shuffling begins.
+     * @param pos Shuffling is performed for index positions between {@code start} and either the
+     *     end (if {@link Position#TAIL}) or the beginning (if {@link Position#HEAD}) of the array.
+     * @param rng Random number generator.
+     */
+    public static void shuffle(int[] list, int start, Position pos, RandomGenerator rng) {
+        switch (pos) {
+            case TAIL:
+                {
+                    for (int i = list.length - 1; i >= start; i--) {
+                        final int target;
+                        if (i == start) {
+                            target = start;
+                        } else {
+                            // NumberIsTooLargeException cannot occur.
+                            target = new UniformIntegerDistribution(rng, start, i).sample();
+                        }
+                        final int temp = list[target];
+                        list[target] = list[i];
+                        list[i] = temp;
+                    }
+                }
+                break;
+            case HEAD:
+                {
+                    for (int i = 0; i <= start; i++) {
+                        final int target;
+                        if (i == start) {
+                            target = start;
+                        } else {
+                            // NumberIsTooLargeException cannot occur.
+                            target = new UniformIntegerDistribution(rng, i, start).sample();
+                        }
+                        final int temp = list[target];
+                        list[target] = list[i];
+                        list[i] = temp;
+                    }
+                }
+                break;
+            default:
+                throw new MathInternalError(); // Should never happen.
+        }
+    }
+
+    /**
+     * Shuffle the entries of the given array.
+     *
+     * @see #shuffle(int[],int,Position,RandomGenerator)
+     * @param list Array whose entries will be shuffled (in-place).
+     * @param rng Random number generator.
+     */
+    public static void shuffle(int[] list, RandomGenerator rng) {
+        shuffle(list, 0, Position.TAIL, rng);
+    }
+
+    /**
+     * Shuffle the entries of the given array.
+     *
+     * @see #shuffle(int[],int,Position,RandomGenerator)
+     * @param list Array whose entries will be shuffled (in-place).
+     */
+    public static void shuffle(int[] list) {
+        shuffle(list, new Well19937c());
+    }
+
+    /**
+     * Returns an array representing the natural number {@code n}.
+     *
+     * @param n Natural number.
+     * @return an array whose entries are the numbers 0, 1, ..., {@code n}-1. If {@code n == 0}, the
+     *     returned array is empty.
+     */
+    public static int[] natural(int n) {
+        return sequence(n, 0, 1);
+    }
+
+    /**
+     * Returns an array of {@code size} integers starting at {@code start}, skipping {@code stride}
+     * numbers.
+     *
+     * @param size Natural number.
+     * @param start Natural number.
+     * @param stride Natural number.
+     * @return an array whose entries are the numbers {@code start, start + stride, ..., start +
+     *     (size - 1) * stride}. If {@code size == 0}, the returned array is empty.
+     * @since 3.4
+     */
+    public static int[] sequence(int size, int start, int stride) {
+        final int[] a = new int[size];
+        for (int i = 0; i < size; i++) {
+            a[i] = start + i * stride;
+        }
+        return a;
+    }
+
+    /**
+     * This method is used
+     * to verify that the input parameters designate a subarray of positive length.
+     * <p>
+     * <ul>
+     * <li>returns <code>true</code> iff the parameters designate a subarray of
+     * positive length</li>
+     * <li>throws <code>MathIllegalArgumentException</code> if the array is null or
+     * or the indices are invalid</li>
+     * <li>returns <code>false</li> if the array is non-null, but
+     * <code>length</code> is 0.
+     * </ul></p>
+     *
+     * @param values the input array
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @return true if the parameters are valid and designate a subarray of positive length
+     * @throws MathIllegalArgumentException if the indices are invalid or the array is null
+     * @since 3.3
+     */
+    public static boolean verifyValues(final double[] values, final int begin, final int length)
+            throws MathIllegalArgumentException {
+        return verifyValues(values, begin, length, false);
+    }
+
+    /**
+     * This method is used
+     * to verify that the input parameters designate a subarray of positive length.
+     * <p>
+     * <ul>
+     * <li>returns <code>true</code> iff the parameters designate a subarray of
+     * non-negative length</li>
+     * <li>throws <code>IllegalArgumentException</code> if the array is null or
+     * or the indices are invalid</li>
+     * <li>returns <code>false</li> if the array is non-null, but
+     * <code>length</code> is 0 unless <code>allowEmpty</code> is <code>true</code>
+     * </ul></p>
+     *
+     * @param values the input array
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @param allowEmpty if <code>true</code> then zero length arrays are allowed
+     * @return true if the parameters are valid
+     * @throws MathIllegalArgumentException if the indices are invalid or the array is null
+     * @since 3.3
+     */
+    public static boolean verifyValues(
+            final double[] values, final int begin, final int length, final boolean allowEmpty)
+            throws MathIllegalArgumentException {
+
+        if (values == null) {
+            throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY);
+        }
+
+        if (begin < 0) {
+            throw new NotPositiveException(LocalizedFormats.START_POSITION, Integer.valueOf(begin));
+        }
+
+        if (length < 0) {
+            throw new NotPositiveException(LocalizedFormats.LENGTH, Integer.valueOf(length));
+        }
+
+        if (begin + length > values.length) {
+            throw new NumberIsTooLargeException(
+                    LocalizedFormats.SUBARRAY_ENDS_AFTER_ARRAY_END,
+                    Integer.valueOf(begin + length),
+                    Integer.valueOf(values.length),
+                    true);
+        }
+
+        if (length == 0 && !allowEmpty) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * This method is used
+     * to verify that the begin and length parameters designate a subarray of positive length
+     * and the weights are all non-negative, non-NaN, finite, and not all zero.
+     * <p>
+     * <ul>
+     * <li>returns <code>true</code> iff the parameters designate a subarray of
+     * positive length and the weights array contains legitimate values.</li>
+     * <li>throws <code>IllegalArgumentException</code> if any of the following are true:
+     * <ul><li>the values array is null</li>
+     *     <li>the weights array is null</li>
+     *     <li>the weights array does not have the same length as the values array</li>
+     *     <li>the weights array contains one or more infinite values</li>
+     *     <li>the weights array contains one or more NaN values</li>
+     *     <li>the weights array contains negative values</li>
+     *     <li>the start and length arguments do not determine a valid array</li></ul>
+     * </li>
+     * <li>returns <code>false</li> if the array is non-null, but
+     * <code>length</code> is 0.
+     * </ul></p>
+     *
+     * @param values the input array
+     * @param weights the weights array
+     * @param begin index of the first array element to include
+     * @param length the number of elements to include
+     * @return true if the parameters are valid and designate a subarray of positive length
+     * @throws MathIllegalArgumentException if the indices are invalid or the array is null
+     * @since 3.3
+     */
+    public static boolean verifyValues(
+            final double[] values, final double[] weights, final int begin, final int length)
+            throws MathIllegalArgumentException {
+        return verifyValues(values, weights, begin, length, false);
+    }
+
+    /**
+     * This method is used
+     * to verify that the begin and length parameters designate a subarray of positive length
+     * and the weights are all non-negative, non-NaN, finite, and not all zero.
+     * <p>
+     * <ul>
+     * <li>returns <code>true</code> iff the parameters designate a subarray of
+     * non-negative length and the weights array contains legitimate values.</li>
+     * <li>throws <code>MathIllegalArgumentException</code> if any of the following are true:
+     * <ul><li>the values array is null</li>
+     *     <li>the weights array is null</li>
+     *     <li>the weights array does not have the same length as the values array</li>
+     *     <li>the weights array contains one or more infinite values</li>
+     *     <li>the weights array contains one or more NaN values</li>
+     *     <li>the weights array contains negative values</li>
+     *     <li>the start and length arguments do not determine a valid array</li></ul>
+     * </li>
+     * <li>returns <code>false</li> if the array is non-null, but
+     * <code>length</code> is 0 unless <code>allowEmpty</code> is <code>true</code>.
+     * </ul></p>
+     *
+     * @param values the input array.
+     * @param weights the weights array.
+     * @param begin index of the first array element to include.
+     * @param length the number of elements to include.
+     * @param allowEmpty if {@code true} than allow zero length arrays to pass.
+     * @return {@code true} if the parameters are valid.
+     * @throws NullArgumentException if either of the arrays are null
+     * @throws MathIllegalArgumentException if the array indices are not valid,
+     * the weights array contains NaN, infinite or negative elements, or there
+     * are no positive weights.
+     * @since 3.3
+     */
+    public static boolean verifyValues(
+            final double[] values,
+            final double[] weights,
+            final int begin,
+            final int length,
+            final boolean allowEmpty)
+            throws MathIllegalArgumentException {
+
+        if (weights == null || values == null) {
+            throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY);
+        }
+
+        checkEqualLength(weights, values);
+
+        boolean containsPositiveWeight = false;
+        for (int i = begin; i < begin + length; i++) {
+            final double weight = weights[i];
+            if (Double.isNaN(weight)) {
+                throw new MathIllegalArgumentException(
+                        LocalizedFormats.NAN_ELEMENT_AT_INDEX, Integer.valueOf(i));
+            }
+            if (Double.isInfinite(weight)) {
+                throw new MathIllegalArgumentException(
+                        LocalizedFormats.INFINITE_ARRAY_ELEMENT,
+                        Double.valueOf(weight),
+                        Integer.valueOf(i));
+            }
+            if (weight < 0) {
+                throw new MathIllegalArgumentException(
+                        LocalizedFormats.NEGATIVE_ELEMENT_AT_INDEX,
+                        Integer.valueOf(i),
+                        Double.valueOf(weight));
+            }
+            if (!containsPositiveWeight && weight > 0.0) {
+                containsPositiveWeight = true;
+            }
+        }
+
+        if (!containsPositiveWeight) {
+            throw new MathIllegalArgumentException(LocalizedFormats.WEIGHT_AT_LEAST_ONE_NON_ZERO);
+        }
+
+        return verifyValues(values, begin, length, allowEmpty);
+    }
+
+    /**
+     * Concatenates a sequence of arrays. The return array consists of the entries of the input
+     * arrays concatenated in the order they appear in the argument list. Null arrays cause
+     * NullPointerExceptions; zero length arrays are allowed (contributing nothing to the output
+     * array).
+     *
+     * @param x list of double[] arrays to concatenate
+     * @return a new array consisting of the entries of the argument arrays
+     * @throws NullPointerException if any of the arrays are null
+     * @since 3.6
+     */
+    public static double[] concatenate(double[]... x) {
+        int combinedLength = 0;
+        for (double[] a : x) {
+            combinedLength += a.length;
+        }
+        int offset = 0;
+        int curLength = 0;
+        final double[] combined = new double[combinedLength];
+        for (int i = 0; i < x.length; i++) {
+            curLength = x[i].length;
+            System.arraycopy(x[i], 0, combined, offset, curLength);
+            offset += curLength;
+        }
+        return combined;
+    }
+
+    /**
+     * Returns an array consisting of the unique values in {@code data}. The return array is sorted
+     * in descending order. Empty arrays are allowed, but null arrays result in
+     * NullPointerException. Infinities are allowed. NaN values are allowed with maximum sort order
+     * - i.e., if there are NaN values in {@code data}, {@code Double.NaN} will be the first element
+     * of the output array, even if the array also contains {@code Double.POSITIVE_INFINITY}.
+     *
+     * @param data array to scan
+     * @return descending list of values included in the input array
+     * @throws NullPointerException if data is null
+     * @since 3.6
+     */
+    public static double[] unique(double[] data) {
+        TreeSet<Double> values = new TreeSet<Double>();
+        for (int i = 0; i < data.length; i++) {
+            values.add(data[i]);
+        }
+        final int count = values.size();
+        final double[] out = new double[count];
+        Iterator<Double> iterator = values.iterator();
+        int i = 0;
+        while (iterator.hasNext()) {
+            out[count - ++i] = iterator.next();
+        }
+        return out;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/util/MathUtils.java b/src/main/java/org/apache/commons/math3/util/MathUtils.java
new file mode 100644
index 0000000..2f376d7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/MathUtils.java
@@ -0,0 +1,289 @@
+/*
+ * 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.math3.util;
+
+import org.apache.commons.math3.RealFieldElement;
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.NotFiniteNumberException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.util.Localizable;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+import java.util.Arrays;
+
+/**
+ * Miscellaneous utility functions.
+ *
+ * @see ArithmeticUtils
+ * @see Precision
+ * @see MathArrays
+ */
+public final class MathUtils {
+    /**
+     * \(2\pi\)
+     *
+     * @since 2.1
+     */
+    public static final double TWO_PI = 2 * FastMath.PI;
+
+    /**
+     * \(\pi^2\)
+     *
+     * @since 3.4
+     */
+    public static final double PI_SQUARED = FastMath.PI * FastMath.PI;
+
+    /** Class contains only static methods. */
+    private MathUtils() {}
+
+    /**
+     * Returns an integer hash code representing the given double value.
+     *
+     * @param value the value to be hashed
+     * @return the hash code
+     */
+    public static int hash(double value) {
+        return new Double(value).hashCode();
+    }
+
+    /**
+     * Returns {@code true} if the values are equal according to semantics of {@link
+     * Double#equals(Object)}.
+     *
+     * @param x Value
+     * @param y Value
+     * @return {@code new Double(x).equals(new Double(y))}
+     */
+    public static boolean equals(double x, double y) {
+        return new Double(x).equals(new Double(y));
+    }
+
+    /**
+     * Returns an integer hash code representing the given double array.
+     *
+     * @param value the value to be hashed (may be null)
+     * @return the hash code
+     * @since 1.2
+     */
+    public static int hash(double[] value) {
+        return Arrays.hashCode(value);
+    }
+
+    /**
+     * Normalize an angle in a 2&pi; wide interval around a center value.
+     *
+     * <p>This method has three main uses:
+     *
+     * <ul>
+     *   <li>normalize an angle between 0 and 2&pi;:<br>
+     *       {@code a = MathUtils.normalizeAngle(a, FastMath.PI);}
+     *   <li>normalize an angle between -&pi; and +&pi;<br>
+     *       {@code a = MathUtils.normalizeAngle(a, 0.0);}
+     *   <li>compute the angle between two defining angular positions:<br>
+     *       {@code angle = MathUtils.normalizeAngle(end, start) - start;}
+     * </ul>
+     *
+     * <p>Note that due to numerical accuracy and since &pi; cannot be represented exactly, the
+     * result interval is <em>closed</em>, it cannot be half-closed as would be more satisfactory in
+     * a purely mathematical view.
+     *
+     * @param a angle to normalize
+     * @param center center of the desired 2&pi; interval for the result
+     * @return a-2k&pi; with integer k and center-&pi; &lt;= a-2k&pi; &lt;= center+&pi;
+     * @since 1.2
+     */
+    public static double normalizeAngle(double a, double center) {
+        return a - TWO_PI * FastMath.floor((a + FastMath.PI - center) / TWO_PI);
+    }
+
+    /**
+     * Find the maximum of two field elements.
+     *
+     * @param <T> the type of the field elements
+     * @param e1 first element
+     * @param e2 second element
+     * @return max(a1, e2)
+     * @since 3.6
+     */
+    public static <T extends RealFieldElement<T>> T max(final T e1, final T e2) {
+        return e1.subtract(e2).getReal() >= 0 ? e1 : e2;
+    }
+
+    /**
+     * Find the minimum of two field elements.
+     *
+     * @param <T> the type of the field elements
+     * @param e1 first element
+     * @param e2 second element
+     * @return min(a1, e2)
+     * @since 3.6
+     */
+    public static <T extends RealFieldElement<T>> T min(final T e1, final T e2) {
+        return e1.subtract(e2).getReal() >= 0 ? e2 : e1;
+    }
+
+    /**
+     * Reduce {@code |a - offset|} to the primary interval {@code [0, |period|)}.
+     *
+     * <p>Specifically, the value returned is <br>
+     * {@code a - |period| * floor((a - offset) / |period|) - offset}.
+     *
+     * <p>If any of the parameters are {@code NaN} or infinite, the result is {@code NaN}.
+     *
+     * @param a Value to reduce.
+     * @param period Period.
+     * @param offset Value that will be mapped to {@code 0}.
+     * @return the value, within the interval {@code [0 |period|)}, that corresponds to {@code a}.
+     */
+    public static double reduce(double a, double period, double offset) {
+        final double p = FastMath.abs(period);
+        return a - p * FastMath.floor((a - offset) / p) - offset;
+    }
+
+    /**
+     * Returns the first argument with the sign of the second argument.
+     *
+     * @param magnitude Magnitude of the returned value.
+     * @param sign Sign of the returned value.
+     * @return a value with magnitude equal to {@code magnitude} and with the same sign as the
+     *     {@code sign} argument.
+     * @throws MathArithmeticException if {@code magnitude == Byte.MIN_VALUE} and {@code sign >= 0}.
+     */
+    public static byte copySign(byte magnitude, byte sign) throws MathArithmeticException {
+        if ((magnitude >= 0 && sign >= 0) || (magnitude < 0 && sign < 0)) { // Sign is OK.
+            return magnitude;
+        } else if (sign >= 0 && magnitude == Byte.MIN_VALUE) {
+            throw new MathArithmeticException(LocalizedFormats.OVERFLOW);
+        } else {
+            return (byte) -magnitude; // Flip sign.
+        }
+    }
+
+    /**
+     * Returns the first argument with the sign of the second argument.
+     *
+     * @param magnitude Magnitude of the returned value.
+     * @param sign Sign of the returned value.
+     * @return a value with magnitude equal to {@code magnitude} and with the same sign as the
+     *     {@code sign} argument.
+     * @throws MathArithmeticException if {@code magnitude == Short.MIN_VALUE} and {@code sign >=
+     *     0}.
+     */
+    public static short copySign(short magnitude, short sign) throws MathArithmeticException {
+        if ((magnitude >= 0 && sign >= 0) || (magnitude < 0 && sign < 0)) { // Sign is OK.
+            return magnitude;
+        } else if (sign >= 0 && magnitude == Short.MIN_VALUE) {
+            throw new MathArithmeticException(LocalizedFormats.OVERFLOW);
+        } else {
+            return (short) -magnitude; // Flip sign.
+        }
+    }
+
+    /**
+     * Returns the first argument with the sign of the second argument.
+     *
+     * @param magnitude Magnitude of the returned value.
+     * @param sign Sign of the returned value.
+     * @return a value with magnitude equal to {@code magnitude} and with the same sign as the
+     *     {@code sign} argument.
+     * @throws MathArithmeticException if {@code magnitude == Integer.MIN_VALUE} and {@code sign >=
+     *     0}.
+     */
+    public static int copySign(int magnitude, int sign) throws MathArithmeticException {
+        if ((magnitude >= 0 && sign >= 0) || (magnitude < 0 && sign < 0)) { // Sign is OK.
+            return magnitude;
+        } else if (sign >= 0 && magnitude == Integer.MIN_VALUE) {
+            throw new MathArithmeticException(LocalizedFormats.OVERFLOW);
+        } else {
+            return -magnitude; // Flip sign.
+        }
+    }
+
+    /**
+     * Returns the first argument with the sign of the second argument.
+     *
+     * @param magnitude Magnitude of the returned value.
+     * @param sign Sign of the returned value.
+     * @return a value with magnitude equal to {@code magnitude} and with the same sign as the
+     *     {@code sign} argument.
+     * @throws MathArithmeticException if {@code magnitude == Long.MIN_VALUE} and {@code sign >= 0}.
+     */
+    public static long copySign(long magnitude, long sign) throws MathArithmeticException {
+        if ((magnitude >= 0 && sign >= 0) || (magnitude < 0 && sign < 0)) { // Sign is OK.
+            return magnitude;
+        } else if (sign >= 0 && magnitude == Long.MIN_VALUE) {
+            throw new MathArithmeticException(LocalizedFormats.OVERFLOW);
+        } else {
+            return -magnitude; // Flip sign.
+        }
+    }
+
+    /**
+     * Check that the argument is a real number.
+     *
+     * @param x Argument.
+     * @throws NotFiniteNumberException if {@code x} is not a finite real number.
+     */
+    public static void checkFinite(final double x) throws NotFiniteNumberException {
+        if (Double.isInfinite(x) || Double.isNaN(x)) {
+            throw new NotFiniteNumberException(x);
+        }
+    }
+
+    /**
+     * Check that all the elements are real numbers.
+     *
+     * @param val Arguments.
+     * @throws NotFiniteNumberException if any values of the array is not a finite real number.
+     */
+    public static void checkFinite(final double[] val) throws NotFiniteNumberException {
+        for (int i = 0; i < val.length; i++) {
+            final double x = val[i];
+            if (Double.isInfinite(x) || Double.isNaN(x)) {
+                throw new NotFiniteNumberException(LocalizedFormats.ARRAY_ELEMENT, x, i);
+            }
+        }
+    }
+
+    /**
+     * Checks that an object is not null.
+     *
+     * @param o Object to be checked.
+     * @param pattern Message pattern.
+     * @param args Arguments to replace the placeholders in {@code pattern}.
+     * @throws NullArgumentException if {@code o} is {@code null}.
+     */
+    public static void checkNotNull(Object o, Localizable pattern, Object... args)
+            throws NullArgumentException {
+        if (o == null) {
+            throw new NullArgumentException(pattern, args);
+        }
+    }
+
+    /**
+     * Checks that an object is not null.
+     *
+     * @param o Object to be checked.
+     * @throws NullArgumentException if {@code o} is {@code null}.
+     */
+    public static void checkNotNull(Object o) throws NullArgumentException {
+        if (o == null) {
+            throw new NullArgumentException();
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/util/MedianOf3PivotingStrategy.java b/src/main/java/org/apache/commons/math3/util/MedianOf3PivotingStrategy.java
new file mode 100644
index 0000000..205f114
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/MedianOf3PivotingStrategy.java
@@ -0,0 +1,63 @@
+/*
+ * 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.math3.util;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+
+import java.io.Serializable;
+
+/**
+ * Classic median of 3 strategy given begin and end indices.
+ *
+ * @since 3.4
+ */
+public class MedianOf3PivotingStrategy implements PivotingStrategyInterface, Serializable {
+
+    /** Serializable UID. */
+    private static final long serialVersionUID = 20140713L;
+
+    /**
+     * {@inheritDoc} This in specific makes use of median of 3 pivoting.
+     *
+     * @return The index corresponding to a pivot chosen between the first, middle and the last
+     *     indices of the array slice
+     * @throws MathIllegalArgumentException when indices exceeds range
+     */
+    public int pivotIndex(final double[] work, final int begin, final int end)
+            throws MathIllegalArgumentException {
+        MathArrays.verifyValues(work, begin, end - begin);
+        final int inclusiveEnd = end - 1;
+        final int middle = begin + (inclusiveEnd - begin) / 2;
+        final double wBegin = work[begin];
+        final double wMiddle = work[middle];
+        final double wEnd = work[inclusiveEnd];
+
+        if (wBegin < wMiddle) {
+            if (wMiddle < wEnd) {
+                return middle;
+            } else {
+                return wBegin < wEnd ? inclusiveEnd : begin;
+            }
+        } else {
+            if (wBegin < wEnd) {
+                return begin;
+            } else {
+                return wMiddle < wEnd ? inclusiveEnd : middle;
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/util/MultidimensionalCounter.java b/src/main/java/org/apache/commons/math3/util/MultidimensionalCounter.java
new file mode 100644
index 0000000..5e239e9
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/MultidimensionalCounter.java
@@ -0,0 +1,283 @@
+/*
+ * 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.math3.util;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.OutOfRangeException;
+
+import java.util.NoSuchElementException;
+
+/**
+ * Converter between unidimensional storage structure and multidimensional conceptual structure.
+ * This utility will convert from indices in a multidimensional structure to the corresponding index
+ * in a one-dimensional array. For example, assuming that the ranges (in 3 dimensions) of indices
+ * are 2, 4 and 3, the following correspondences, between 3-tuples indices and unidimensional
+ * indices, will hold:
+ *
+ * <ul>
+ *   <li>(0, 0, 0) corresponds to 0
+ *   <li>(0, 0, 1) corresponds to 1
+ *   <li>(0, 0, 2) corresponds to 2
+ *   <li>(0, 1, 0) corresponds to 3
+ *   <li>...
+ *   <li>(1, 0, 0) corresponds to 12
+ *   <li>...
+ *   <li>(1, 3, 2) corresponds to 23
+ * </ul>
+ *
+ * @since 2.2
+ */
+public class MultidimensionalCounter implements Iterable<Integer> {
+    /** Number of dimensions. */
+    private final int dimension;
+
+    /** Offset for each dimension. */
+    private final int[] uniCounterOffset;
+
+    /** Counter sizes. */
+    private final int[] size;
+
+    /** Total number of (one-dimensional) slots. */
+    private final int totalSize;
+
+    /** Index of last dimension. */
+    private final int last;
+
+    /** Perform iteration over the multidimensional counter. */
+    public class Iterator implements java.util.Iterator<Integer> {
+        /** Multidimensional counter. */
+        private final int[] counter = new int[dimension];
+
+        /** Unidimensional counter. */
+        private int count = -1;
+
+        /** Maximum value for {@link #count}. */
+        private final int maxCount = totalSize - 1;
+
+        /**
+         * Create an iterator
+         *
+         * @see #iterator()
+         */
+        Iterator() {
+            counter[last] = -1;
+        }
+
+        /** {@inheritDoc} */
+        public boolean hasNext() {
+            return count < maxCount;
+        }
+
+        /**
+         * @return the unidimensional count after the counter has been incremented by {@code 1}.
+         * @throws NoSuchElementException if {@link #hasNext()} would have returned {@code false}.
+         */
+        public Integer next() {
+            if (!hasNext()) {
+                throw new NoSuchElementException();
+            }
+
+            for (int i = last; i >= 0; i--) {
+                if (counter[i] == size[i] - 1) {
+                    counter[i] = 0;
+                } else {
+                    ++counter[i];
+                    break;
+                }
+            }
+
+            return ++count;
+        }
+
+        /**
+         * Get the current unidimensional counter slot.
+         *
+         * @return the index within the unidimensionl counter.
+         */
+        public int getCount() {
+            return count;
+        }
+
+        /**
+         * Get the current multidimensional counter slots.
+         *
+         * @return the indices within the multidimensional counter.
+         */
+        public int[] getCounts() {
+            return MathArrays.copyOf(counter);
+        }
+
+        /**
+         * Get the current count in the selected dimension.
+         *
+         * @param dim Dimension index.
+         * @return the count at the corresponding index for the current state of the iterator.
+         * @throws IndexOutOfBoundsException if {@code index} is not in the correct interval (as
+         *     defined by the length of the argument in the {@link
+         *     MultidimensionalCounter#MultidimensionalCounter(int[]) constructor of the enclosing
+         *     class}).
+         */
+        public int getCount(int dim) {
+            return counter[dim];
+        }
+
+        /**
+         * @throws UnsupportedOperationException
+         */
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    /**
+     * Create a counter.
+     *
+     * @param size Counter sizes (number of slots in each dimension).
+     * @throws NotStrictlyPositiveException if one of the sizes is negative or zero.
+     */
+    public MultidimensionalCounter(int... size) throws NotStrictlyPositiveException {
+        dimension = size.length;
+        this.size = MathArrays.copyOf(size);
+
+        uniCounterOffset = new int[dimension];
+
+        last = dimension - 1;
+        int tS = size[last];
+        for (int i = 0; i < last; i++) {
+            int count = 1;
+            for (int j = i + 1; j < dimension; j++) {
+                count *= size[j];
+            }
+            uniCounterOffset[i] = count;
+            tS *= size[i];
+        }
+        uniCounterOffset[last] = 0;
+
+        if (tS <= 0) {
+            throw new NotStrictlyPositiveException(tS);
+        }
+
+        totalSize = tS;
+    }
+
+    /**
+     * Create an iterator over this counter.
+     *
+     * @return the iterator.
+     */
+    public Iterator iterator() {
+        return new Iterator();
+    }
+
+    /**
+     * Get the number of dimensions of the multidimensional counter.
+     *
+     * @return the number of dimensions.
+     */
+    public int getDimension() {
+        return dimension;
+    }
+
+    /**
+     * Convert to multidimensional counter.
+     *
+     * @param index Index in unidimensional counter.
+     * @return the multidimensional counts.
+     * @throws OutOfRangeException if {@code index} is not between {@code 0} and the value returned
+     *     by {@link #getSize()} (excluded).
+     */
+    public int[] getCounts(int index) throws OutOfRangeException {
+        if (index < 0 || index >= totalSize) {
+            throw new OutOfRangeException(index, 0, totalSize);
+        }
+
+        final int[] indices = new int[dimension];
+
+        int count = 0;
+        for (int i = 0; i < last; i++) {
+            int idx = 0;
+            final int offset = uniCounterOffset[i];
+            while (count <= index) {
+                count += offset;
+                ++idx;
+            }
+            --idx;
+            count -= offset;
+            indices[i] = idx;
+        }
+
+        indices[last] = index - count;
+
+        return indices;
+    }
+
+    /**
+     * Convert to unidimensional counter.
+     *
+     * @param c Indices in multidimensional counter.
+     * @return the index within the unidimensionl counter.
+     * @throws DimensionMismatchException if the size of {@code c} does not match the size of the
+     *     array given in the constructor.
+     * @throws OutOfRangeException if a value of {@code c} is not in the range of the corresponding
+     *     dimension, as defined in the {@link
+     *     MultidimensionalCounter#MultidimensionalCounter(int...) constructor}.
+     */
+    public int getCount(int... c) throws OutOfRangeException, DimensionMismatchException {
+        if (c.length != dimension) {
+            throw new DimensionMismatchException(c.length, dimension);
+        }
+        int count = 0;
+        for (int i = 0; i < dimension; i++) {
+            final int index = c[i];
+            if (index < 0 || index >= size[i]) {
+                throw new OutOfRangeException(index, 0, size[i] - 1);
+            }
+            count += uniCounterOffset[i] * c[i];
+        }
+        return count + c[last];
+    }
+
+    /**
+     * Get the total number of elements.
+     *
+     * @return the total size of the unidimensional counter.
+     */
+    public int getSize() {
+        return totalSize;
+    }
+
+    /**
+     * Get the number of multidimensional counter slots in each dimension.
+     *
+     * @return the sizes of the multidimensional counter in each dimension.
+     */
+    public int[] getSizes() {
+        return MathArrays.copyOf(size);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < dimension; i++) {
+            sb.append("[").append(getCount(i)).append("]");
+        }
+        return sb.toString();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/util/NumberTransformer.java b/src/main/java/org/apache/commons/math3/util/NumberTransformer.java
new file mode 100644
index 0000000..d25ce14
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/NumberTransformer.java
@@ -0,0 +1,36 @@
+/*
+ * 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.math3.util;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+
+/**
+ * Subclasses implementing this interface can transform Objects to doubles.
+ *
+ * <p>No longer extends Serializable since 2.0
+ */
+public interface NumberTransformer {
+
+    /**
+     * Implementing this interface provides a facility to transform from Object to Double.
+     *
+     * @param o the Object to be transformed.
+     * @return the double value of the Object.
+     * @throws MathIllegalArgumentException if the Object can not be transformed into a Double.
+     */
+    double transform(Object o) throws MathIllegalArgumentException;
+}
diff --git a/src/main/java/org/apache/commons/math3/util/OpenIntToDoubleHashMap.java b/src/main/java/org/apache/commons/math3/util/OpenIntToDoubleHashMap.java
new file mode 100644
index 0000000..cb8369d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/OpenIntToDoubleHashMap.java
@@ -0,0 +1,604 @@
+/*
+ * 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.math3.util;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+import java.util.ConcurrentModificationException;
+import java.util.NoSuchElementException;
+
+/**
+ * Open addressed map from int to double.
+ *
+ * <p>This class provides a dedicated map from integers to doubles with a much smaller memory
+ * overhead than standard <code>java.util.Map</code>.
+ *
+ * <p>This class is not synchronized. The specialized iterators returned by {@link #iterator()} are
+ * fail-fast: they throw a <code>ConcurrentModificationException</code> when they detect the map has
+ * been modified during iteration.
+ *
+ * @since 2.0
+ */
+public class OpenIntToDoubleHashMap implements Serializable {
+
+    /** Status indicator for free table entries. */
+    protected static final byte FREE = 0;
+
+    /** Status indicator for full table entries. */
+    protected static final byte FULL = 1;
+
+    /** Status indicator for removed table entries. */
+    protected static final byte REMOVED = 2;
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -3646337053166149105L;
+
+    /** Load factor for the map. */
+    private static final float LOAD_FACTOR = 0.5f;
+
+    /**
+     * Default starting size.
+     *
+     * <p>This must be a power of two for bit mask to work properly.
+     */
+    private static final int DEFAULT_EXPECTED_SIZE = 16;
+
+    /**
+     * Multiplier for size growth when map fills up.
+     *
+     * <p>This must be a power of two for bit mask to work properly.
+     */
+    private static final int RESIZE_MULTIPLIER = 2;
+
+    /** Number of bits to perturb the index when probing for collision resolution. */
+    private static final int PERTURB_SHIFT = 5;
+
+    /** Keys table. */
+    private int[] keys;
+
+    /** Values table. */
+    private double[] values;
+
+    /** States table. */
+    private byte[] states;
+
+    /** Return value for missing entries. */
+    private final double missingEntries;
+
+    /** Current size of the map. */
+    private int size;
+
+    /** Bit mask for hash values. */
+    private int mask;
+
+    /** Modifications count. */
+    private transient int count;
+
+    /** Build an empty map with default size and using NaN for missing entries. */
+    public OpenIntToDoubleHashMap() {
+        this(DEFAULT_EXPECTED_SIZE, Double.NaN);
+    }
+
+    /**
+     * Build an empty map with default size
+     *
+     * @param missingEntries value to return when a missing entry is fetched
+     */
+    public OpenIntToDoubleHashMap(final double missingEntries) {
+        this(DEFAULT_EXPECTED_SIZE, missingEntries);
+    }
+
+    /**
+     * Build an empty map with specified size and using NaN for missing entries.
+     *
+     * @param expectedSize expected number of elements in the map
+     */
+    public OpenIntToDoubleHashMap(final int expectedSize) {
+        this(expectedSize, Double.NaN);
+    }
+
+    /**
+     * Build an empty map with specified size.
+     *
+     * @param expectedSize expected number of elements in the map
+     * @param missingEntries value to return when a missing entry is fetched
+     */
+    public OpenIntToDoubleHashMap(final int expectedSize, final double missingEntries) {
+        final int capacity = computeCapacity(expectedSize);
+        keys = new int[capacity];
+        values = new double[capacity];
+        states = new byte[capacity];
+        this.missingEntries = missingEntries;
+        mask = capacity - 1;
+    }
+
+    /**
+     * Copy constructor.
+     *
+     * @param source map to copy
+     */
+    public OpenIntToDoubleHashMap(final OpenIntToDoubleHashMap source) {
+        final int length = source.keys.length;
+        keys = new int[length];
+        System.arraycopy(source.keys, 0, keys, 0, length);
+        values = new double[length];
+        System.arraycopy(source.values, 0, values, 0, length);
+        states = new byte[length];
+        System.arraycopy(source.states, 0, states, 0, length);
+        missingEntries = source.missingEntries;
+        size = source.size;
+        mask = source.mask;
+        count = source.count;
+    }
+
+    /**
+     * Compute the capacity needed for a given size.
+     *
+     * @param expectedSize expected size of the map
+     * @return capacity to use for the specified size
+     */
+    private static int computeCapacity(final int expectedSize) {
+        if (expectedSize == 0) {
+            return 1;
+        }
+        final int capacity = (int) FastMath.ceil(expectedSize / LOAD_FACTOR);
+        final int powerOfTwo = Integer.highestOneBit(capacity);
+        if (powerOfTwo == capacity) {
+            return capacity;
+        }
+        return nextPowerOfTwo(capacity);
+    }
+
+    /**
+     * Find the smallest power of two greater than the input value
+     *
+     * @param i input value
+     * @return smallest power of two greater than the input value
+     */
+    private static int nextPowerOfTwo(final int i) {
+        return Integer.highestOneBit(i) << 1;
+    }
+
+    /**
+     * Get the stored value associated with the given key
+     *
+     * @param key key associated with the data
+     * @return data associated with the key
+     */
+    public double get(final int key) {
+
+        final int hash = hashOf(key);
+        int index = hash & mask;
+        if (containsKey(key, index)) {
+            return values[index];
+        }
+
+        if (states[index] == FREE) {
+            return missingEntries;
+        }
+
+        int j = index;
+        for (int perturb = perturb(hash); states[index] != FREE; perturb >>= PERTURB_SHIFT) {
+            j = probe(perturb, j);
+            index = j & mask;
+            if (containsKey(key, index)) {
+                return values[index];
+            }
+        }
+
+        return missingEntries;
+    }
+
+    /**
+     * Check if a value is associated with a key.
+     *
+     * @param key key to check
+     * @return true if a value is associated with key
+     */
+    public boolean containsKey(final int key) {
+
+        final int hash = hashOf(key);
+        int index = hash & mask;
+        if (containsKey(key, index)) {
+            return true;
+        }
+
+        if (states[index] == FREE) {
+            return false;
+        }
+
+        int j = index;
+        for (int perturb = perturb(hash); states[index] != FREE; perturb >>= PERTURB_SHIFT) {
+            j = probe(perturb, j);
+            index = j & mask;
+            if (containsKey(key, index)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Get an iterator over map elements.
+     *
+     * <p>The specialized iterators returned are fail-fast: they throw a <code>
+     * ConcurrentModificationException</code> when they detect the map has been modified during
+     * iteration.
+     *
+     * @return iterator over the map elements
+     */
+    public Iterator iterator() {
+        return new Iterator();
+    }
+
+    /**
+     * Perturb the hash for starting probing.
+     *
+     * @param hash initial hash
+     * @return perturbed hash
+     */
+    private static int perturb(final int hash) {
+        return hash & 0x7fffffff;
+    }
+
+    /**
+     * Find the index at which a key should be inserted
+     *
+     * @param key key to lookup
+     * @return index at which key should be inserted
+     */
+    private int findInsertionIndex(final int key) {
+        return findInsertionIndex(keys, states, key, mask);
+    }
+
+    /**
+     * Find the index at which a key should be inserted
+     *
+     * @param keys keys table
+     * @param states states table
+     * @param key key to lookup
+     * @param mask bit mask for hash values
+     * @return index at which key should be inserted
+     */
+    private static int findInsertionIndex(
+            final int[] keys, final byte[] states, final int key, final int mask) {
+        final int hash = hashOf(key);
+        int index = hash & mask;
+        if (states[index] == FREE) {
+            return index;
+        } else if (states[index] == FULL && keys[index] == key) {
+            return changeIndexSign(index);
+        }
+
+        int perturb = perturb(hash);
+        int j = index;
+        if (states[index] == FULL) {
+            while (true) {
+                j = probe(perturb, j);
+                index = j & mask;
+                perturb >>= PERTURB_SHIFT;
+
+                if (states[index] != FULL || keys[index] == key) {
+                    break;
+                }
+            }
+        }
+
+        if (states[index] == FREE) {
+            return index;
+        } else if (states[index] == FULL) {
+            // due to the loop exit condition,
+            // if (states[index] == FULL) then keys[index] == key
+            return changeIndexSign(index);
+        }
+
+        final int firstRemoved = index;
+        while (true) {
+            j = probe(perturb, j);
+            index = j & mask;
+
+            if (states[index] == FREE) {
+                return firstRemoved;
+            } else if (states[index] == FULL && keys[index] == key) {
+                return changeIndexSign(index);
+            }
+
+            perturb >>= PERTURB_SHIFT;
+        }
+    }
+
+    /**
+     * Compute next probe for collision resolution
+     *
+     * @param perturb perturbed hash
+     * @param j previous probe
+     * @return next probe
+     */
+    private static int probe(final int perturb, final int j) {
+        return (j << 2) + j + perturb + 1;
+    }
+
+    /**
+     * Change the index sign
+     *
+     * @param index initial index
+     * @return changed index
+     */
+    private static int changeIndexSign(final int index) {
+        return -index - 1;
+    }
+
+    /**
+     * Get the number of elements stored in the map.
+     *
+     * @return number of elements stored in the map
+     */
+    public int size() {
+        return size;
+    }
+
+    /**
+     * Remove the value associated with a key.
+     *
+     * @param key key to which the value is associated
+     * @return removed value
+     */
+    public double remove(final int key) {
+
+        final int hash = hashOf(key);
+        int index = hash & mask;
+        if (containsKey(key, index)) {
+            return doRemove(index);
+        }
+
+        if (states[index] == FREE) {
+            return missingEntries;
+        }
+
+        int j = index;
+        for (int perturb = perturb(hash); states[index] != FREE; perturb >>= PERTURB_SHIFT) {
+            j = probe(perturb, j);
+            index = j & mask;
+            if (containsKey(key, index)) {
+                return doRemove(index);
+            }
+        }
+
+        return missingEntries;
+    }
+
+    /**
+     * Check if the tables contain an element associated with specified key at specified index.
+     *
+     * @param key key to check
+     * @param index index to check
+     * @return true if an element is associated with key at index
+     */
+    private boolean containsKey(final int key, final int index) {
+        return (key != 0 || states[index] == FULL) && keys[index] == key;
+    }
+
+    /**
+     * Remove an element at specified index.
+     *
+     * @param index index of the element to remove
+     * @return removed value
+     */
+    private double doRemove(int index) {
+        keys[index] = 0;
+        states[index] = REMOVED;
+        final double previous = values[index];
+        values[index] = missingEntries;
+        --size;
+        ++count;
+        return previous;
+    }
+
+    /**
+     * Put a value associated with a key in the map.
+     *
+     * @param key key to which value is associated
+     * @param value value to put in the map
+     * @return previous value associated with the key
+     */
+    public double put(final int key, final double value) {
+        int index = findInsertionIndex(key);
+        double previous = missingEntries;
+        boolean newMapping = true;
+        if (index < 0) {
+            index = changeIndexSign(index);
+            previous = values[index];
+            newMapping = false;
+        }
+        keys[index] = key;
+        states[index] = FULL;
+        values[index] = value;
+        if (newMapping) {
+            ++size;
+            if (shouldGrowTable()) {
+                growTable();
+            }
+            ++count;
+        }
+        return previous;
+    }
+
+    /** Grow the tables. */
+    private void growTable() {
+
+        final int oldLength = states.length;
+        final int[] oldKeys = keys;
+        final double[] oldValues = values;
+        final byte[] oldStates = states;
+
+        final int newLength = RESIZE_MULTIPLIER * oldLength;
+        final int[] newKeys = new int[newLength];
+        final double[] newValues = new double[newLength];
+        final byte[] newStates = new byte[newLength];
+        final int newMask = newLength - 1;
+        for (int i = 0; i < oldLength; ++i) {
+            if (oldStates[i] == FULL) {
+                final int key = oldKeys[i];
+                final int index = findInsertionIndex(newKeys, newStates, key, newMask);
+                newKeys[index] = key;
+                newValues[index] = oldValues[i];
+                newStates[index] = FULL;
+            }
+        }
+
+        mask = newMask;
+        keys = newKeys;
+        values = newValues;
+        states = newStates;
+    }
+
+    /**
+     * Check if tables should grow due to increased size.
+     *
+     * @return true if tables should grow
+     */
+    private boolean shouldGrowTable() {
+        return size > (mask + 1) * LOAD_FACTOR;
+    }
+
+    /**
+     * Compute the hash value of a key
+     *
+     * @param key key to hash
+     * @return hash value of the key
+     */
+    private static int hashOf(final int key) {
+        final int h = key ^ ((key >>> 20) ^ (key >>> 12));
+        return h ^ (h >>> 7) ^ (h >>> 4);
+    }
+
+    /** Iterator class for the map. */
+    public class Iterator {
+
+        /** Reference modification count. */
+        private final int referenceCount;
+
+        /** Index of current element. */
+        private int current;
+
+        /** Index of next element. */
+        private int next;
+
+        /** Simple constructor. */
+        private Iterator() {
+
+            // preserve the modification count of the map to detect concurrent modifications later
+            referenceCount = count;
+
+            // initialize current index
+            next = -1;
+            try {
+                advance();
+            } catch (NoSuchElementException nsee) { // NOPMD
+                // ignored
+            }
+        }
+
+        /**
+         * Check if there is a next element in the map.
+         *
+         * @return true if there is a next element
+         */
+        public boolean hasNext() {
+            return next >= 0;
+        }
+
+        /**
+         * Get the key of current entry.
+         *
+         * @return key of current entry
+         * @exception ConcurrentModificationException if the map is modified during iteration
+         * @exception NoSuchElementException if there is no element left in the map
+         */
+        public int key() throws ConcurrentModificationException, NoSuchElementException {
+            if (referenceCount != count) {
+                throw new ConcurrentModificationException();
+            }
+            if (current < 0) {
+                throw new NoSuchElementException();
+            }
+            return keys[current];
+        }
+
+        /**
+         * Get the value of current entry.
+         *
+         * @return value of current entry
+         * @exception ConcurrentModificationException if the map is modified during iteration
+         * @exception NoSuchElementException if there is no element left in the map
+         */
+        public double value() throws ConcurrentModificationException, NoSuchElementException {
+            if (referenceCount != count) {
+                throw new ConcurrentModificationException();
+            }
+            if (current < 0) {
+                throw new NoSuchElementException();
+            }
+            return values[current];
+        }
+
+        /**
+         * Advance iterator one step further.
+         *
+         * @exception ConcurrentModificationException if the map is modified during iteration
+         * @exception NoSuchElementException if there is no element left in the map
+         */
+        public void advance() throws ConcurrentModificationException, NoSuchElementException {
+
+            if (referenceCount != count) {
+                throw new ConcurrentModificationException();
+            }
+
+            // advance on step
+            current = next;
+
+            // prepare next step
+            try {
+                while (states[++next] != FULL) { // NOPMD
+                    // nothing to do
+                }
+            } catch (ArrayIndexOutOfBoundsException e) {
+                next = -2;
+                if (current < 0) {
+                    throw new NoSuchElementException();
+                }
+            }
+        }
+    }
+
+    /**
+     * Read a serialized object.
+     *
+     * @param stream input stream
+     * @throws IOException if object cannot be read
+     * @throws ClassNotFoundException if the class corresponding to the serialized object cannot be
+     *     found
+     */
+    private void readObject(final ObjectInputStream stream)
+            throws IOException, ClassNotFoundException {
+        stream.defaultReadObject();
+        count = 0;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/util/OpenIntToFieldHashMap.java b/src/main/java/org/apache/commons/math3/util/OpenIntToFieldHashMap.java
new file mode 100644
index 0000000..0f4212f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/OpenIntToFieldHashMap.java
@@ -0,0 +1,632 @@
+/*
+ * 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.math3.util;
+
+import org.apache.commons.math3.Field;
+import org.apache.commons.math3.FieldElement;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.util.ConcurrentModificationException;
+import java.util.NoSuchElementException;
+
+/**
+ * Open addressed map from int to FieldElement.
+ *
+ * <p>This class provides a dedicated map from integers to FieldElements with a much smaller memory
+ * overhead than standard <code>java.util.Map</code>.
+ *
+ * <p>This class is not synchronized. The specialized iterators returned by {@link #iterator()} are
+ * fail-fast: they throw a <code>ConcurrentModificationException</code> when they detect the map has
+ * been modified during iteration.
+ *
+ * @param <T> the type of the field elements
+ * @since 2.0
+ */
+public class OpenIntToFieldHashMap<T extends FieldElement<T>> implements Serializable {
+
+    /** Status indicator for free table entries. */
+    protected static final byte FREE = 0;
+
+    /** Status indicator for full table entries. */
+    protected static final byte FULL = 1;
+
+    /** Status indicator for removed table entries. */
+    protected static final byte REMOVED = 2;
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -9179080286849120720L;
+
+    /** Load factor for the map. */
+    private static final float LOAD_FACTOR = 0.5f;
+
+    /**
+     * Default starting size.
+     *
+     * <p>This must be a power of two for bit mask to work properly.
+     */
+    private static final int DEFAULT_EXPECTED_SIZE = 16;
+
+    /**
+     * Multiplier for size growth when map fills up.
+     *
+     * <p>This must be a power of two for bit mask to work properly.
+     */
+    private static final int RESIZE_MULTIPLIER = 2;
+
+    /** Number of bits to perturb the index when probing for collision resolution. */
+    private static final int PERTURB_SHIFT = 5;
+
+    /** Field to which the elements belong. */
+    private final Field<T> field;
+
+    /** Keys table. */
+    private int[] keys;
+
+    /** Values table. */
+    private T[] values;
+
+    /** States table. */
+    private byte[] states;
+
+    /** Return value for missing entries. */
+    private final T missingEntries;
+
+    /** Current size of the map. */
+    private int size;
+
+    /** Bit mask for hash values. */
+    private int mask;
+
+    /** Modifications count. */
+    private transient int count;
+
+    /**
+     * Build an empty map with default size and using zero for missing entries.
+     *
+     * @param field field to which the elements belong
+     */
+    public OpenIntToFieldHashMap(final Field<T> field) {
+        this(field, DEFAULT_EXPECTED_SIZE, field.getZero());
+    }
+
+    /**
+     * Build an empty map with default size
+     *
+     * @param field field to which the elements belong
+     * @param missingEntries value to return when a missing entry is fetched
+     */
+    public OpenIntToFieldHashMap(final Field<T> field, final T missingEntries) {
+        this(field, DEFAULT_EXPECTED_SIZE, missingEntries);
+    }
+
+    /**
+     * Build an empty map with specified size and using zero for missing entries.
+     *
+     * @param field field to which the elements belong
+     * @param expectedSize expected number of elements in the map
+     */
+    public OpenIntToFieldHashMap(final Field<T> field, final int expectedSize) {
+        this(field, expectedSize, field.getZero());
+    }
+
+    /**
+     * Build an empty map with specified size.
+     *
+     * @param field field to which the elements belong
+     * @param expectedSize expected number of elements in the map
+     * @param missingEntries value to return when a missing entry is fetched
+     */
+    public OpenIntToFieldHashMap(
+            final Field<T> field, final int expectedSize, final T missingEntries) {
+        this.field = field;
+        final int capacity = computeCapacity(expectedSize);
+        keys = new int[capacity];
+        values = buildArray(capacity);
+        states = new byte[capacity];
+        this.missingEntries = missingEntries;
+        mask = capacity - 1;
+    }
+
+    /**
+     * Copy constructor.
+     *
+     * @param source map to copy
+     */
+    public OpenIntToFieldHashMap(final OpenIntToFieldHashMap<T> source) {
+        field = source.field;
+        final int length = source.keys.length;
+        keys = new int[length];
+        System.arraycopy(source.keys, 0, keys, 0, length);
+        values = buildArray(length);
+        System.arraycopy(source.values, 0, values, 0, length);
+        states = new byte[length];
+        System.arraycopy(source.states, 0, states, 0, length);
+        missingEntries = source.missingEntries;
+        size = source.size;
+        mask = source.mask;
+        count = source.count;
+    }
+
+    /**
+     * Compute the capacity needed for a given size.
+     *
+     * @param expectedSize expected size of the map
+     * @return capacity to use for the specified size
+     */
+    private static int computeCapacity(final int expectedSize) {
+        if (expectedSize == 0) {
+            return 1;
+        }
+        final int capacity = (int) FastMath.ceil(expectedSize / LOAD_FACTOR);
+        final int powerOfTwo = Integer.highestOneBit(capacity);
+        if (powerOfTwo == capacity) {
+            return capacity;
+        }
+        return nextPowerOfTwo(capacity);
+    }
+
+    /**
+     * Find the smallest power of two greater than the input value
+     *
+     * @param i input value
+     * @return smallest power of two greater than the input value
+     */
+    private static int nextPowerOfTwo(final int i) {
+        return Integer.highestOneBit(i) << 1;
+    }
+
+    /**
+     * Get the stored value associated with the given key
+     *
+     * @param key key associated with the data
+     * @return data associated with the key
+     */
+    public T get(final int key) {
+
+        final int hash = hashOf(key);
+        int index = hash & mask;
+        if (containsKey(key, index)) {
+            return values[index];
+        }
+
+        if (states[index] == FREE) {
+            return missingEntries;
+        }
+
+        int j = index;
+        for (int perturb = perturb(hash); states[index] != FREE; perturb >>= PERTURB_SHIFT) {
+            j = probe(perturb, j);
+            index = j & mask;
+            if (containsKey(key, index)) {
+                return values[index];
+            }
+        }
+
+        return missingEntries;
+    }
+
+    /**
+     * Check if a value is associated with a key.
+     *
+     * @param key key to check
+     * @return true if a value is associated with key
+     */
+    public boolean containsKey(final int key) {
+
+        final int hash = hashOf(key);
+        int index = hash & mask;
+        if (containsKey(key, index)) {
+            return true;
+        }
+
+        if (states[index] == FREE) {
+            return false;
+        }
+
+        int j = index;
+        for (int perturb = perturb(hash); states[index] != FREE; perturb >>= PERTURB_SHIFT) {
+            j = probe(perturb, j);
+            index = j & mask;
+            if (containsKey(key, index)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Get an iterator over map elements.
+     *
+     * <p>The specialized iterators returned are fail-fast: they throw a <code>
+     * ConcurrentModificationException</code> when they detect the map has been modified during
+     * iteration.
+     *
+     * @return iterator over the map elements
+     */
+    public Iterator iterator() {
+        return new Iterator();
+    }
+
+    /**
+     * Perturb the hash for starting probing.
+     *
+     * @param hash initial hash
+     * @return perturbed hash
+     */
+    private static int perturb(final int hash) {
+        return hash & 0x7fffffff;
+    }
+
+    /**
+     * Find the index at which a key should be inserted
+     *
+     * @param key key to lookup
+     * @return index at which key should be inserted
+     */
+    private int findInsertionIndex(final int key) {
+        return findInsertionIndex(keys, states, key, mask);
+    }
+
+    /**
+     * Find the index at which a key should be inserted
+     *
+     * @param keys keys table
+     * @param states states table
+     * @param key key to lookup
+     * @param mask bit mask for hash values
+     * @return index at which key should be inserted
+     */
+    private static int findInsertionIndex(
+            final int[] keys, final byte[] states, final int key, final int mask) {
+        final int hash = hashOf(key);
+        int index = hash & mask;
+        if (states[index] == FREE) {
+            return index;
+        } else if (states[index] == FULL && keys[index] == key) {
+            return changeIndexSign(index);
+        }
+
+        int perturb = perturb(hash);
+        int j = index;
+        if (states[index] == FULL) {
+            while (true) {
+                j = probe(perturb, j);
+                index = j & mask;
+                perturb >>= PERTURB_SHIFT;
+
+                if (states[index] != FULL || keys[index] == key) {
+                    break;
+                }
+            }
+        }
+
+        if (states[index] == FREE) {
+            return index;
+        } else if (states[index] == FULL) {
+            // due to the loop exit condition,
+            // if (states[index] == FULL) then keys[index] == key
+            return changeIndexSign(index);
+        }
+
+        final int firstRemoved = index;
+        while (true) {
+            j = probe(perturb, j);
+            index = j & mask;
+
+            if (states[index] == FREE) {
+                return firstRemoved;
+            } else if (states[index] == FULL && keys[index] == key) {
+                return changeIndexSign(index);
+            }
+
+            perturb >>= PERTURB_SHIFT;
+        }
+    }
+
+    /**
+     * Compute next probe for collision resolution
+     *
+     * @param perturb perturbed hash
+     * @param j previous probe
+     * @return next probe
+     */
+    private static int probe(final int perturb, final int j) {
+        return (j << 2) + j + perturb + 1;
+    }
+
+    /**
+     * Change the index sign
+     *
+     * @param index initial index
+     * @return changed index
+     */
+    private static int changeIndexSign(final int index) {
+        return -index - 1;
+    }
+
+    /**
+     * Get the number of elements stored in the map.
+     *
+     * @return number of elements stored in the map
+     */
+    public int size() {
+        return size;
+    }
+
+    /**
+     * Remove the value associated with a key.
+     *
+     * @param key key to which the value is associated
+     * @return removed value
+     */
+    public T remove(final int key) {
+
+        final int hash = hashOf(key);
+        int index = hash & mask;
+        if (containsKey(key, index)) {
+            return doRemove(index);
+        }
+
+        if (states[index] == FREE) {
+            return missingEntries;
+        }
+
+        int j = index;
+        for (int perturb = perturb(hash); states[index] != FREE; perturb >>= PERTURB_SHIFT) {
+            j = probe(perturb, j);
+            index = j & mask;
+            if (containsKey(key, index)) {
+                return doRemove(index);
+            }
+        }
+
+        return missingEntries;
+    }
+
+    /**
+     * Check if the tables contain an element associated with specified key at specified index.
+     *
+     * @param key key to check
+     * @param index index to check
+     * @return true if an element is associated with key at index
+     */
+    private boolean containsKey(final int key, final int index) {
+        return (key != 0 || states[index] == FULL) && keys[index] == key;
+    }
+
+    /**
+     * Remove an element at specified index.
+     *
+     * @param index index of the element to remove
+     * @return removed value
+     */
+    private T doRemove(int index) {
+        keys[index] = 0;
+        states[index] = REMOVED;
+        final T previous = values[index];
+        values[index] = missingEntries;
+        --size;
+        ++count;
+        return previous;
+    }
+
+    /**
+     * Put a value associated with a key in the map.
+     *
+     * @param key key to which value is associated
+     * @param value value to put in the map
+     * @return previous value associated with the key
+     */
+    public T put(final int key, final T value) {
+        int index = findInsertionIndex(key);
+        T previous = missingEntries;
+        boolean newMapping = true;
+        if (index < 0) {
+            index = changeIndexSign(index);
+            previous = values[index];
+            newMapping = false;
+        }
+        keys[index] = key;
+        states[index] = FULL;
+        values[index] = value;
+        if (newMapping) {
+            ++size;
+            if (shouldGrowTable()) {
+                growTable();
+            }
+            ++count;
+        }
+        return previous;
+    }
+
+    /** Grow the tables. */
+    private void growTable() {
+
+        final int oldLength = states.length;
+        final int[] oldKeys = keys;
+        final T[] oldValues = values;
+        final byte[] oldStates = states;
+
+        final int newLength = RESIZE_MULTIPLIER * oldLength;
+        final int[] newKeys = new int[newLength];
+        final T[] newValues = buildArray(newLength);
+        final byte[] newStates = new byte[newLength];
+        final int newMask = newLength - 1;
+        for (int i = 0; i < oldLength; ++i) {
+            if (oldStates[i] == FULL) {
+                final int key = oldKeys[i];
+                final int index = findInsertionIndex(newKeys, newStates, key, newMask);
+                newKeys[index] = key;
+                newValues[index] = oldValues[i];
+                newStates[index] = FULL;
+            }
+        }
+
+        mask = newMask;
+        keys = newKeys;
+        values = newValues;
+        states = newStates;
+    }
+
+    /**
+     * Check if tables should grow due to increased size.
+     *
+     * @return true if tables should grow
+     */
+    private boolean shouldGrowTable() {
+        return size > (mask + 1) * LOAD_FACTOR;
+    }
+
+    /**
+     * Compute the hash value of a key
+     *
+     * @param key key to hash
+     * @return hash value of the key
+     */
+    private static int hashOf(final int key) {
+        final int h = key ^ ((key >>> 20) ^ (key >>> 12));
+        return h ^ (h >>> 7) ^ (h >>> 4);
+    }
+
+    /** Iterator class for the map. */
+    public class Iterator {
+
+        /** Reference modification count. */
+        private final int referenceCount;
+
+        /** Index of current element. */
+        private int current;
+
+        /** Index of next element. */
+        private int next;
+
+        /** Simple constructor. */
+        private Iterator() {
+
+            // preserve the modification count of the map to detect concurrent modifications later
+            referenceCount = count;
+
+            // initialize current index
+            next = -1;
+            try {
+                advance();
+            } catch (NoSuchElementException nsee) { // NOPMD
+                // ignored
+            }
+        }
+
+        /**
+         * Check if there is a next element in the map.
+         *
+         * @return true if there is a next element
+         */
+        public boolean hasNext() {
+            return next >= 0;
+        }
+
+        /**
+         * Get the key of current entry.
+         *
+         * @return key of current entry
+         * @exception ConcurrentModificationException if the map is modified during iteration
+         * @exception NoSuchElementException if there is no element left in the map
+         */
+        public int key() throws ConcurrentModificationException, NoSuchElementException {
+            if (referenceCount != count) {
+                throw new ConcurrentModificationException();
+            }
+            if (current < 0) {
+                throw new NoSuchElementException();
+            }
+            return keys[current];
+        }
+
+        /**
+         * Get the value of current entry.
+         *
+         * @return value of current entry
+         * @exception ConcurrentModificationException if the map is modified during iteration
+         * @exception NoSuchElementException if there is no element left in the map
+         */
+        public T value() throws ConcurrentModificationException, NoSuchElementException {
+            if (referenceCount != count) {
+                throw new ConcurrentModificationException();
+            }
+            if (current < 0) {
+                throw new NoSuchElementException();
+            }
+            return values[current];
+        }
+
+        /**
+         * Advance iterator one step further.
+         *
+         * @exception ConcurrentModificationException if the map is modified during iteration
+         * @exception NoSuchElementException if there is no element left in the map
+         */
+        public void advance() throws ConcurrentModificationException, NoSuchElementException {
+
+            if (referenceCount != count) {
+                throw new ConcurrentModificationException();
+            }
+
+            // advance on step
+            current = next;
+
+            // prepare next step
+            try {
+                while (states[++next] != FULL) { // NOPMD
+                    // nothing to do
+                }
+            } catch (ArrayIndexOutOfBoundsException e) {
+                next = -2;
+                if (current < 0) {
+                    throw new NoSuchElementException();
+                }
+            }
+        }
+    }
+
+    /**
+     * Read a serialized object.
+     *
+     * @param stream input stream
+     * @throws IOException if object cannot be read
+     * @throws ClassNotFoundException if the class corresponding to the serialized object cannot be
+     *     found
+     */
+    private void readObject(final ObjectInputStream stream)
+            throws IOException, ClassNotFoundException {
+        stream.defaultReadObject();
+        count = 0;
+    }
+
+    /**
+     * Build an array of elements.
+     *
+     * @param length size of the array to build
+     * @return a new array
+     */
+    @SuppressWarnings("unchecked") // field is of type T
+    private T[] buildArray(final int length) {
+        return (T[]) Array.newInstance(field.getRuntimeClass(), length);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/util/Pair.java b/src/main/java/org/apache/commons/math3/util/Pair.java
new file mode 100644
index 0000000..95fbb2b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/Pair.java
@@ -0,0 +1,148 @@
+/*
+ * 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.math3.util;
+
+/**
+ * Generic pair. <br>
+ * Although the instances of this class are immutable, it is impossible to ensure that the
+ * references passed to the constructor will not be modified by the caller.
+ *
+ * @param <K> Key type.
+ * @param <V> Value type.
+ * @since 3.0
+ */
+public class Pair<K, V> {
+    /** Key. */
+    private final K key;
+
+    /** Value. */
+    private final V value;
+
+    /**
+     * Create an entry representing a mapping from the specified key to the specified value.
+     *
+     * @param k Key (first element of the pair).
+     * @param v Value (second element of the pair).
+     */
+    public Pair(K k, V v) {
+        key = k;
+        value = v;
+    }
+
+    /**
+     * Create an entry representing the same mapping as the specified entry.
+     *
+     * @param entry Entry to copy.
+     */
+    public Pair(Pair<? extends K, ? extends V> entry) {
+        this(entry.getKey(), entry.getValue());
+    }
+
+    /**
+     * Get the key.
+     *
+     * @return the key (first element of the pair).
+     */
+    public K getKey() {
+        return key;
+    }
+
+    /**
+     * Get the value.
+     *
+     * @return the value (second element of the pair).
+     */
+    public V getValue() {
+        return value;
+    }
+
+    /**
+     * Get the first element of the pair.
+     *
+     * @return the first element of the pair.
+     * @since 3.1
+     */
+    public K getFirst() {
+        return key;
+    }
+
+    /**
+     * Get the second element of the pair.
+     *
+     * @return the second element of the pair.
+     * @since 3.1
+     */
+    public V getSecond() {
+        return value;
+    }
+
+    /**
+     * Compare the specified object with this entry for equality.
+     *
+     * @param o Object.
+     * @return {@code true} if the given object is also a map entry and the two entries represent
+     *     the same mapping.
+     */
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof Pair)) {
+            return false;
+        } else {
+            Pair<?, ?> oP = (Pair<?, ?>) o;
+            return (key == null ? oP.key == null : key.equals(oP.key))
+                    && (value == null ? oP.value == null : value.equals(oP.value));
+        }
+    }
+
+    /**
+     * Compute a hash code.
+     *
+     * @return the hash code value.
+     */
+    @Override
+    public int hashCode() {
+        int result = key == null ? 0 : key.hashCode();
+
+        final int h = value == null ? 0 : value.hashCode();
+        result = 37 * result + h ^ (h >>> 16);
+
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return "[" + getKey() + ", " + getValue() + "]";
+    }
+
+    /**
+     * Convenience factory method that calls the {@link #Pair(Object, Object) constructor}.
+     *
+     * @param <K> the key type
+     * @param <V> the value type
+     * @param k First element of the pair.
+     * @param v Second element of the pair.
+     * @return a new {@code Pair} containing {@code k} and {@code v}.
+     * @since 3.3
+     */
+    public static <K, V> Pair<K, V> create(K k, V v) {
+        return new Pair<K, V>(k, v);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/util/PivotingStrategyInterface.java b/src/main/java/org/apache/commons/math3/util/PivotingStrategyInterface.java
new file mode 100644
index 0000000..0e2b175
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/PivotingStrategyInterface.java
@@ -0,0 +1,43 @@
+/*
+ * 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.math3.util;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+
+/**
+ * A strategy to pick a pivoting index of an array for doing partitioning.
+ *
+ * @see MedianOf3PivotingStrategy
+ * @see RandomPivotingStrategy
+ * @see CentralPivotingStrategy
+ * @since 3.4
+ */
+public interface PivotingStrategyInterface {
+
+    /**
+     * Find pivot index of the array so that partition and K<sup>th</sup> element selection can be
+     * made
+     *
+     * @param work data array
+     * @param begin index of the first element of the slice
+     * @param end index after the last element of the slice
+     * @return the index of the pivot element chosen between the first and the last element of the
+     *     array slice
+     * @throws MathIllegalArgumentException when indices exceeds range
+     */
+    int pivotIndex(double[] work, int begin, int end) throws MathIllegalArgumentException;
+}
diff --git a/src/main/java/org/apache/commons/math3/util/Precision.java b/src/main/java/org/apache/commons/math3/util/Precision.java
new file mode 100644
index 0000000..fe93003
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/Precision.java
@@ -0,0 +1,613 @@
+/*
+ * 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.math3.util;
+
+import org.apache.commons.math3.exception.MathArithmeticException;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+import java.math.BigDecimal;
+
+/**
+ * Utilities for comparing numbers.
+ *
+ * @since 3.0
+ */
+public class Precision {
+    /**
+     * Largest double-precision floating-point number such that {@code 1 + EPSILON} is numerically
+     * equal to 1. This value is an upper bound on the relative error due to rounding real numbers
+     * to double precision floating-point numbers.
+     *
+     * <p>In IEEE 754 arithmetic, this is 2<sup>-53</sup>.
+     *
+     * @see <a href="http://en.wikipedia.org/wiki/Machine_epsilon">Machine epsilon</a>
+     */
+    public static final double EPSILON;
+
+    /**
+     * Safe minimum, such that {@code 1 / SAFE_MIN} does not overflow. <br>
+     * In IEEE 754 arithmetic, this is also the smallest normalized number 2<sup>-1022</sup>.
+     */
+    public static final double SAFE_MIN;
+
+    /** Exponent offset in IEEE754 representation. */
+    private static final long EXPONENT_OFFSET = 1023l;
+
+    /** Offset to order signed double numbers lexicographically. */
+    private static final long SGN_MASK = 0x8000000000000000L;
+
+    /** Offset to order signed double numbers lexicographically. */
+    private static final int SGN_MASK_FLOAT = 0x80000000;
+
+    /** Positive zero. */
+    private static final double POSITIVE_ZERO = 0d;
+
+    /** Positive zero bits. */
+    private static final long POSITIVE_ZERO_DOUBLE_BITS = Double.doubleToRawLongBits(+0.0);
+
+    /** Negative zero bits. */
+    private static final long NEGATIVE_ZERO_DOUBLE_BITS = Double.doubleToRawLongBits(-0.0);
+
+    /** Positive zero bits. */
+    private static final int POSITIVE_ZERO_FLOAT_BITS = Float.floatToRawIntBits(+0.0f);
+
+    /** Negative zero bits. */
+    private static final int NEGATIVE_ZERO_FLOAT_BITS = Float.floatToRawIntBits(-0.0f);
+
+    static {
+        /*
+         *  This was previously expressed as = 0x1.0p-53;
+         *  However, OpenJDK (Sparc Solaris) cannot handle such small
+         *  constants: MATH-721
+         */
+        EPSILON = Double.longBitsToDouble((EXPONENT_OFFSET - 53l) << 52);
+
+        /*
+         * This was previously expressed as = 0x1.0p-1022;
+         * However, OpenJDK (Sparc Solaris) cannot handle such small
+         * constants: MATH-721
+         */
+        SAFE_MIN = Double.longBitsToDouble((EXPONENT_OFFSET - 1022l) << 52);
+    }
+
+    /** Private constructor. */
+    private Precision() {}
+
+    /**
+     * Compares two numbers given some amount of allowed error.
+     *
+     * @param x the first number
+     * @param y the second number
+     * @param eps the amount of error to allow when checking for equality
+     * @return
+     *     <ul>
+     *       <li>0 if {@link #equals(double, double, double) equals(x, y, eps)}
+     *       <li>&lt; 0 if !{@link #equals(double, double, double) equals(x, y, eps)} &amp;&amp; x
+     *           &lt; y
+     *       <li>> 0 if !{@link #equals(double, double, double) equals(x, y, eps)} &amp;&amp; x > y
+     *           or either argument is NaN
+     *     </ul>
+     */
+    public static int compareTo(double x, double y, double eps) {
+        if (equals(x, y, eps)) {
+            return 0;
+        } else if (x < y) {
+            return -1;
+        }
+        return 1;
+    }
+
+    /**
+     * Compares two numbers given some amount of allowed error. Two float numbers are considered
+     * equal if there are {@code (maxUlps - 1)} (or fewer) floating point numbers between them, i.e.
+     * two adjacent floating point numbers are considered equal. Adapted from <a
+     * href="http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/">
+     * Bruce Dawson</a>. Returns {@code false} if either of the arguments is NaN.
+     *
+     * @param x first value
+     * @param y second value
+     * @param maxUlps {@code (maxUlps - 1)} is the number of floating point values between {@code x}
+     *     and {@code y}.
+     * @return
+     *     <ul>
+     *       <li>0 if {@link #equals(double, double, int) equals(x, y, maxUlps)}
+     *       <li>&lt; 0 if !{@link #equals(double, double, int) equals(x, y, maxUlps)} &amp;&amp; x
+     *           &lt; y
+     *       <li>&gt; 0 if !{@link #equals(double, double, int) equals(x, y, maxUlps)} &amp;&amp; x
+     *           > y or either argument is NaN
+     *     </ul>
+     */
+    public static int compareTo(final double x, final double y, final int maxUlps) {
+        if (equals(x, y, maxUlps)) {
+            return 0;
+        } else if (x < y) {
+            return -1;
+        }
+        return 1;
+    }
+
+    /**
+     * Returns true iff they are equal as defined by {@link #equals(float,float,int) equals(x, y,
+     * 1)}.
+     *
+     * @param x first value
+     * @param y second value
+     * @return {@code true} if the values are equal.
+     */
+    public static boolean equals(float x, float y) {
+        return equals(x, y, 1);
+    }
+
+    /**
+     * Returns true if both arguments are NaN or they are equal as defined by {@link
+     * #equals(float,float) equals(x, y, 1)}.
+     *
+     * @param x first value
+     * @param y second value
+     * @return {@code true} if the values are equal or both are NaN.
+     * @since 2.2
+     */
+    public static boolean equalsIncludingNaN(float x, float y) {
+        return (x != x || y != y) ? !(x != x ^ y != y) : equals(x, y, 1);
+    }
+
+    /**
+     * Returns true if the arguments are equal or within the range of allowed error (inclusive).
+     * Returns {@code false} if either of the arguments is NaN.
+     *
+     * @param x first value
+     * @param y second value
+     * @param eps the amount of absolute error to allow.
+     * @return {@code true} if the values are equal or within range of each other.
+     * @since 2.2
+     */
+    public static boolean equals(float x, float y, float eps) {
+        return equals(x, y, 1) || FastMath.abs(y - x) <= eps;
+    }
+
+    /**
+     * Returns true if the arguments are both NaN, are equal, or are within the range of allowed
+     * error (inclusive).
+     *
+     * @param x first value
+     * @param y second value
+     * @param eps the amount of absolute error to allow.
+     * @return {@code true} if the values are equal or within range of each other, or both are NaN.
+     * @since 2.2
+     */
+    public static boolean equalsIncludingNaN(float x, float y, float eps) {
+        return equalsIncludingNaN(x, y) || (FastMath.abs(y - x) <= eps);
+    }
+
+    /**
+     * Returns true if the arguments are equal or within the range of allowed error (inclusive). Two
+     * float numbers are considered equal if there are {@code (maxUlps - 1)} (or fewer) floating
+     * point numbers between them, i.e. two adjacent floating point numbers are considered equal.
+     * Adapted from <a
+     * href="http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/">
+     * Bruce Dawson</a>. Returns {@code false} if either of the arguments is NaN.
+     *
+     * @param x first value
+     * @param y second value
+     * @param maxUlps {@code (maxUlps - 1)} is the number of floating point values between {@code x}
+     *     and {@code y}.
+     * @return {@code true} if there are fewer than {@code maxUlps} floating point values between
+     *     {@code x} and {@code y}.
+     * @since 2.2
+     */
+    public static boolean equals(final float x, final float y, final int maxUlps) {
+
+        final int xInt = Float.floatToRawIntBits(x);
+        final int yInt = Float.floatToRawIntBits(y);
+
+        final boolean isEqual;
+        if (((xInt ^ yInt) & SGN_MASK_FLOAT) == 0) {
+            // number have same sign, there is no risk of overflow
+            isEqual = FastMath.abs(xInt - yInt) <= maxUlps;
+        } else {
+            // number have opposite signs, take care of overflow
+            final int deltaPlus;
+            final int deltaMinus;
+            if (xInt < yInt) {
+                deltaPlus = yInt - POSITIVE_ZERO_FLOAT_BITS;
+                deltaMinus = xInt - NEGATIVE_ZERO_FLOAT_BITS;
+            } else {
+                deltaPlus = xInt - POSITIVE_ZERO_FLOAT_BITS;
+                deltaMinus = yInt - NEGATIVE_ZERO_FLOAT_BITS;
+            }
+
+            if (deltaPlus > maxUlps) {
+                isEqual = false;
+            } else {
+                isEqual = deltaMinus <= (maxUlps - deltaPlus);
+            }
+        }
+
+        return isEqual && !Float.isNaN(x) && !Float.isNaN(y);
+    }
+
+    /**
+     * Returns true if the arguments are both NaN or if they are equal as defined by {@link
+     * #equals(float,float,int) equals(x, y, maxUlps)}.
+     *
+     * @param x first value
+     * @param y second value
+     * @param maxUlps {@code (maxUlps - 1)} is the number of floating point values between {@code x}
+     *     and {@code y}.
+     * @return {@code true} if both arguments are NaN or if there are less than {@code maxUlps}
+     *     floating point values between {@code x} and {@code y}.
+     * @since 2.2
+     */
+    public static boolean equalsIncludingNaN(float x, float y, int maxUlps) {
+        return (x != x || y != y) ? !(x != x ^ y != y) : equals(x, y, maxUlps);
+    }
+
+    /**
+     * Returns true iff they are equal as defined by {@link #equals(double,double,int) equals(x, y,
+     * 1)}.
+     *
+     * @param x first value
+     * @param y second value
+     * @return {@code true} if the values are equal.
+     */
+    public static boolean equals(double x, double y) {
+        return equals(x, y, 1);
+    }
+
+    /**
+     * Returns true if the arguments are both NaN or they are equal as defined by {@link
+     * #equals(double,double) equals(x, y, 1)}.
+     *
+     * @param x first value
+     * @param y second value
+     * @return {@code true} if the values are equal or both are NaN.
+     * @since 2.2
+     */
+    public static boolean equalsIncludingNaN(double x, double y) {
+        return (x != x || y != y) ? !(x != x ^ y != y) : equals(x, y, 1);
+    }
+
+    /**
+     * Returns {@code true} if there is no double value strictly between the arguments or the
+     * difference between them is within the range of allowed error (inclusive). Returns {@code
+     * false} if either of the arguments is NaN.
+     *
+     * @param x First value.
+     * @param y Second value.
+     * @param eps Amount of allowed absolute error.
+     * @return {@code true} if the values are two adjacent floating point numbers or they are within
+     *     range of each other.
+     */
+    public static boolean equals(double x, double y, double eps) {
+        return equals(x, y, 1) || FastMath.abs(y - x) <= eps;
+    }
+
+    /**
+     * Returns {@code true} if there is no double value strictly between the arguments or the
+     * relative difference between them is less than or equal to the given tolerance. Returns {@code
+     * false} if either of the arguments is NaN.
+     *
+     * @param x First value.
+     * @param y Second value.
+     * @param eps Amount of allowed relative error.
+     * @return {@code true} if the values are two adjacent floating point numbers or they are within
+     *     range of each other.
+     * @since 3.1
+     */
+    public static boolean equalsWithRelativeTolerance(double x, double y, double eps) {
+        if (equals(x, y, 1)) {
+            return true;
+        }
+
+        final double absoluteMax = FastMath.max(FastMath.abs(x), FastMath.abs(y));
+        final double relativeDifference = FastMath.abs((x - y) / absoluteMax);
+
+        return relativeDifference <= eps;
+    }
+
+    /**
+     * Returns true if the arguments are both NaN, are equal or are within the range of allowed
+     * error (inclusive).
+     *
+     * @param x first value
+     * @param y second value
+     * @param eps the amount of absolute error to allow.
+     * @return {@code true} if the values are equal or within range of each other, or both are NaN.
+     * @since 2.2
+     */
+    public static boolean equalsIncludingNaN(double x, double y, double eps) {
+        return equalsIncludingNaN(x, y) || (FastMath.abs(y - x) <= eps);
+    }
+
+    /**
+     * Returns true if the arguments are equal or within the range of allowed error (inclusive).
+     *
+     * <p>Two float numbers are considered equal if there are {@code (maxUlps - 1)} (or fewer)
+     * floating point numbers between them, i.e. two adjacent floating point numbers are considered
+     * equal.
+     *
+     * <p>Adapted from <a
+     * href="http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/">
+     * Bruce Dawson</a>. Returns {@code false} if either of the arguments is NaN.
+     *
+     * @param x first value
+     * @param y second value
+     * @param maxUlps {@code (maxUlps - 1)} is the number of floating point values between {@code x}
+     *     and {@code y}.
+     * @return {@code true} if there are fewer than {@code maxUlps} floating point values between
+     *     {@code x} and {@code y}.
+     */
+    public static boolean equals(final double x, final double y, final int maxUlps) {
+
+        final long xInt = Double.doubleToRawLongBits(x);
+        final long yInt = Double.doubleToRawLongBits(y);
+
+        final boolean isEqual;
+        if (((xInt ^ yInt) & SGN_MASK) == 0l) {
+            // number have same sign, there is no risk of overflow
+            isEqual = FastMath.abs(xInt - yInt) <= maxUlps;
+        } else {
+            // number have opposite signs, take care of overflow
+            final long deltaPlus;
+            final long deltaMinus;
+            if (xInt < yInt) {
+                deltaPlus = yInt - POSITIVE_ZERO_DOUBLE_BITS;
+                deltaMinus = xInt - NEGATIVE_ZERO_DOUBLE_BITS;
+            } else {
+                deltaPlus = xInt - POSITIVE_ZERO_DOUBLE_BITS;
+                deltaMinus = yInt - NEGATIVE_ZERO_DOUBLE_BITS;
+            }
+
+            if (deltaPlus > maxUlps) {
+                isEqual = false;
+            } else {
+                isEqual = deltaMinus <= (maxUlps - deltaPlus);
+            }
+        }
+
+        return isEqual && !Double.isNaN(x) && !Double.isNaN(y);
+    }
+
+    /**
+     * Returns true if both arguments are NaN or if they are equal as defined by {@link
+     * #equals(double,double,int) equals(x, y, maxUlps)}.
+     *
+     * @param x first value
+     * @param y second value
+     * @param maxUlps {@code (maxUlps - 1)} is the number of floating point values between {@code x}
+     *     and {@code y}.
+     * @return {@code true} if both arguments are NaN or if there are less than {@code maxUlps}
+     *     floating point values between {@code x} and {@code y}.
+     * @since 2.2
+     */
+    public static boolean equalsIncludingNaN(double x, double y, int maxUlps) {
+        return (x != x || y != y) ? !(x != x ^ y != y) : equals(x, y, maxUlps);
+    }
+
+    /**
+     * Rounds the given value to the specified number of decimal places. The value is rounded using
+     * the {@link BigDecimal#ROUND_HALF_UP} method.
+     *
+     * @param x Value to round.
+     * @param scale Number of digits to the right of the decimal point.
+     * @return the rounded value.
+     * @since 1.1 (previously in {@code MathUtils}, moved as of version 3.0)
+     */
+    public static double round(double x, int scale) {
+        return round(x, scale, BigDecimal.ROUND_HALF_UP);
+    }
+
+    /**
+     * Rounds the given value to the specified number of decimal places. The value is rounded using
+     * the given method which is any method defined in {@link BigDecimal}. If {@code x} is infinite
+     * or {@code NaN}, then the value of {@code x} is returned unchanged, regardless of the other
+     * parameters.
+     *
+     * @param x Value to round.
+     * @param scale Number of digits to the right of the decimal point.
+     * @param roundingMethod Rounding method as defined in {@link BigDecimal}.
+     * @return the rounded value.
+     * @throws ArithmeticException if {@code roundingMethod == ROUND_UNNECESSARY} and the specified
+     *     scaling operation would require rounding.
+     * @throws IllegalArgumentException if {@code roundingMethod} does not represent a valid
+     *     rounding mode.
+     * @since 1.1 (previously in {@code MathUtils}, moved as of version 3.0)
+     */
+    public static double round(double x, int scale, int roundingMethod) {
+        try {
+            final double rounded =
+                    (new BigDecimal(Double.toString(x)).setScale(scale, roundingMethod))
+                            .doubleValue();
+            // MATH-1089: negative values rounded to zero should result in negative zero
+            return rounded == POSITIVE_ZERO ? POSITIVE_ZERO * x : rounded;
+        } catch (NumberFormatException ex) {
+            if (Double.isInfinite(x)) {
+                return x;
+            } else {
+                return Double.NaN;
+            }
+        }
+    }
+
+    /**
+     * Rounds the given value to the specified number of decimal places. The value is rounded using
+     * the {@link BigDecimal#ROUND_HALF_UP} method.
+     *
+     * @param x Value to round.
+     * @param scale Number of digits to the right of the decimal point.
+     * @return the rounded value.
+     * @since 1.1 (previously in {@code MathUtils}, moved as of version 3.0)
+     */
+    public static float round(float x, int scale) {
+        return round(x, scale, BigDecimal.ROUND_HALF_UP);
+    }
+
+    /**
+     * Rounds the given value to the specified number of decimal places. The value is rounded using
+     * the given method which is any method defined in {@link BigDecimal}.
+     *
+     * @param x Value to round.
+     * @param scale Number of digits to the right of the decimal point.
+     * @param roundingMethod Rounding method as defined in {@link BigDecimal}.
+     * @return the rounded value.
+     * @since 1.1 (previously in {@code MathUtils}, moved as of version 3.0)
+     * @throws MathArithmeticException if an exact operation is required but result is not exact
+     * @throws MathIllegalArgumentException if {@code roundingMethod} is not a valid rounding
+     *     method.
+     */
+    public static float round(float x, int scale, int roundingMethod)
+            throws MathArithmeticException, MathIllegalArgumentException {
+        final float sign = FastMath.copySign(1f, x);
+        final float factor = (float) FastMath.pow(10.0f, scale) * sign;
+        return (float) roundUnscaled(x * factor, sign, roundingMethod) / factor;
+    }
+
+    /**
+     * Rounds the given non-negative value to the "nearest" integer. Nearest is determined by the
+     * rounding method specified. Rounding methods are defined in {@link BigDecimal}.
+     *
+     * @param unscaled Value to round.
+     * @param sign Sign of the original, scaled value.
+     * @param roundingMethod Rounding method, as defined in {@link BigDecimal}.
+     * @return the rounded value.
+     * @throws MathArithmeticException if an exact operation is required but result is not exact
+     * @throws MathIllegalArgumentException if {@code roundingMethod} is not a valid rounding
+     *     method.
+     * @since 1.1 (previously in {@code MathUtils}, moved as of version 3.0)
+     */
+    private static double roundUnscaled(double unscaled, double sign, int roundingMethod)
+            throws MathArithmeticException, MathIllegalArgumentException {
+        switch (roundingMethod) {
+            case BigDecimal.ROUND_CEILING:
+                if (sign == -1) {
+                    unscaled =
+                            FastMath.floor(FastMath.nextAfter(unscaled, Double.NEGATIVE_INFINITY));
+                } else {
+                    unscaled =
+                            FastMath.ceil(FastMath.nextAfter(unscaled, Double.POSITIVE_INFINITY));
+                }
+                break;
+            case BigDecimal.ROUND_DOWN:
+                unscaled = FastMath.floor(FastMath.nextAfter(unscaled, Double.NEGATIVE_INFINITY));
+                break;
+            case BigDecimal.ROUND_FLOOR:
+                if (sign == -1) {
+                    unscaled =
+                            FastMath.ceil(FastMath.nextAfter(unscaled, Double.POSITIVE_INFINITY));
+                } else {
+                    unscaled =
+                            FastMath.floor(FastMath.nextAfter(unscaled, Double.NEGATIVE_INFINITY));
+                }
+                break;
+            case BigDecimal.ROUND_HALF_DOWN:
+                {
+                    unscaled = FastMath.nextAfter(unscaled, Double.NEGATIVE_INFINITY);
+                    double fraction = unscaled - FastMath.floor(unscaled);
+                    if (fraction > 0.5) {
+                        unscaled = FastMath.ceil(unscaled);
+                    } else {
+                        unscaled = FastMath.floor(unscaled);
+                    }
+                    break;
+                }
+            case BigDecimal.ROUND_HALF_EVEN:
+                {
+                    double fraction = unscaled - FastMath.floor(unscaled);
+                    if (fraction > 0.5) {
+                        unscaled = FastMath.ceil(unscaled);
+                    } else if (fraction < 0.5) {
+                        unscaled = FastMath.floor(unscaled);
+                    } else {
+                        // The following equality test is intentional and needed for rounding
+                        // purposes
+                        if (FastMath.floor(unscaled) / 2.0
+                                == FastMath.floor(FastMath.floor(unscaled) / 2.0)) { // even
+                            unscaled = FastMath.floor(unscaled);
+                        } else { // odd
+                            unscaled = FastMath.ceil(unscaled);
+                        }
+                    }
+                    break;
+                }
+            case BigDecimal.ROUND_HALF_UP:
+                {
+                    unscaled = FastMath.nextAfter(unscaled, Double.POSITIVE_INFINITY);
+                    double fraction = unscaled - FastMath.floor(unscaled);
+                    if (fraction >= 0.5) {
+                        unscaled = FastMath.ceil(unscaled);
+                    } else {
+                        unscaled = FastMath.floor(unscaled);
+                    }
+                    break;
+                }
+            case BigDecimal.ROUND_UNNECESSARY:
+                if (unscaled != FastMath.floor(unscaled)) {
+                    throw new MathArithmeticException();
+                }
+                break;
+            case BigDecimal.ROUND_UP:
+                // do not round if the discarded fraction is equal to zero
+                if (unscaled != FastMath.floor(unscaled)) {
+                    unscaled =
+                            FastMath.ceil(FastMath.nextAfter(unscaled, Double.POSITIVE_INFINITY));
+                }
+                break;
+            default:
+                throw new MathIllegalArgumentException(
+                        LocalizedFormats.INVALID_ROUNDING_METHOD,
+                        roundingMethod,
+                        "ROUND_CEILING",
+                        BigDecimal.ROUND_CEILING,
+                        "ROUND_DOWN",
+                        BigDecimal.ROUND_DOWN,
+                        "ROUND_FLOOR",
+                        BigDecimal.ROUND_FLOOR,
+                        "ROUND_HALF_DOWN",
+                        BigDecimal.ROUND_HALF_DOWN,
+                        "ROUND_HALF_EVEN",
+                        BigDecimal.ROUND_HALF_EVEN,
+                        "ROUND_HALF_UP",
+                        BigDecimal.ROUND_HALF_UP,
+                        "ROUND_UNNECESSARY",
+                        BigDecimal.ROUND_UNNECESSARY,
+                        "ROUND_UP",
+                        BigDecimal.ROUND_UP);
+        }
+        return unscaled;
+    }
+
+    /**
+     * Computes a number {@code delta} close to {@code originalDelta} with the property that
+     *
+     * <pre><code>
+     *   x + delta - x
+     * </code></pre>
+     *
+     * is exactly machine-representable. This is useful when computing numerical derivatives, in
+     * order to reduce roundoff errors.
+     *
+     * @param x Value.
+     * @param originalDelta Offset value.
+     * @return a number {@code delta} so that {@code x + delta} and {@code x} differ by a
+     *     representable floating number.
+     */
+    public static double representableDelta(double x, double originalDelta) {
+        return x + originalDelta - x;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/util/RandomPivotingStrategy.java b/src/main/java/org/apache/commons/math3/util/RandomPivotingStrategy.java
new file mode 100644
index 0000000..f2fc28b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/RandomPivotingStrategy.java
@@ -0,0 +1,58 @@
+/*
+ * 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.math3.util;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.random.RandomGenerator;
+
+import java.io.Serializable;
+
+/**
+ * A strategy of selecting random index between begin and end indices.
+ *
+ * @since 3.4
+ */
+public class RandomPivotingStrategy implements PivotingStrategyInterface, Serializable {
+
+    /** Serializable UID. */
+    private static final long serialVersionUID = 20140713L;
+
+    /** Random generator to use for selecting pivot. */
+    private final RandomGenerator random;
+
+    /**
+     * Simple constructor.
+     *
+     * @param random random generator to use for selecting pivot
+     */
+    public RandomPivotingStrategy(final RandomGenerator random) {
+        this.random = random;
+    }
+
+    /**
+     * {@inheritDoc} A uniform random pivot selection between begin and end indices
+     *
+     * @return The index corresponding to a random uniformly selected value between first and the
+     *     last indices of the array slice
+     * @throws MathIllegalArgumentException when indices exceeds range
+     */
+    public int pivotIndex(final double[] work, final int begin, final int end)
+            throws MathIllegalArgumentException {
+        MathArrays.verifyValues(work, begin, end - begin);
+        return begin + random.nextInt(end - begin - 1);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/util/ResizableDoubleArray.java b/src/main/java/org/apache/commons/math3/util/ResizableDoubleArray.java
new file mode 100644
index 0000000..f0963ef
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/ResizableDoubleArray.java
@@ -0,0 +1,1130 @@
+/*
+ * 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.math3.util;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.exception.MathIllegalStateException;
+import org.apache.commons.math3.exception.MathInternalError;
+import org.apache.commons.math3.exception.NotStrictlyPositiveException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.NumberIsTooSmallException;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+/**
+ * A variable length {@link DoubleArray} implementation that automatically handles expanding and
+ * contracting its internal storage array as elements are added and removed.
+ *
+ * <h3>Important note: Usage should not assume that this class is thread-safe even though some of
+ * the methods are {@code synchronized}. This qualifier will be dropped in the next major release
+ * (4.0).</h3>
+ *
+ * <p>The internal storage array starts with capacity determined by the {@code initialCapacity}
+ * property, which can be set by the constructor. The default initial capacity is 16. Adding
+ * elements using {@link #addElement(double)} appends elements to the end of the array. When there
+ * are no open entries at the end of the internal storage array, the array is expanded. The size of
+ * the expanded array depends on the {@code expansionMode} and {@code expansionFactor} properties.
+ * The {@code expansionMode} determines whether the size of the array is multiplied by the {@code
+ * expansionFactor} ({@link ExpansionMode#MULTIPLICATIVE}) or if the expansion is additive ({@link
+ * ExpansionMode#ADDITIVE} -- {@code expansionFactor} storage locations added). The default {@code
+ * expansionMode} is {@code MULTIPLICATIVE} and the default {@code expansionFactor} is 2.
+ *
+ * <p>The {@link #addElementRolling(double)} method adds a new element to the end of the internal
+ * storage array and adjusts the "usable window" of the internal array forward by one position
+ * (effectively making what was the second element the first, and so on). Repeated activations of
+ * this method (or activation of {@link #discardFrontElements(int)}) will effectively orphan the
+ * storage locations at the beginning of the internal storage array. To reclaim this storage, each
+ * time one of these methods is activated, the size of the internal storage array is compared to the
+ * number of addressable elements (the {@code numElements} property) and if the difference is too
+ * large, the internal array is contracted to size {@code numElements + 1}. The determination of
+ * when the internal storage array is "too large" depends on the {@code expansionMode} and {@code
+ * contractionFactor} properties. If the {@code expansionMode} is {@code MULTIPLICATIVE},
+ * contraction is triggered when the ratio between storage array length and {@code numElements}
+ * exceeds {@code contractionFactor.} If the {@code expansionMode} is {@code ADDITIVE}, the number
+ * of excess storage locations is compared to {@code contractionFactor}.
+ *
+ * <p>To avoid cycles of expansions and contractions, the {@code expansionFactor} must not exceed
+ * the {@code contractionFactor}. Constructors and mutators for both of these properties enforce
+ * this requirement, throwing a {@code MathIllegalArgumentException} if it is violated.
+ */
+public class ResizableDoubleArray implements DoubleArray, Serializable {
+    /**
+     * Additive expansion mode.
+     *
+     * @deprecated As of 3.1. Please use {@link ExpansionMode#ADDITIVE} instead.
+     */
+    @Deprecated public static final int ADDITIVE_MODE = 1;
+
+    /**
+     * Multiplicative expansion mode.
+     *
+     * @deprecated As of 3.1. Please use {@link ExpansionMode#MULTIPLICATIVE} instead.
+     */
+    @Deprecated public static final int MULTIPLICATIVE_MODE = 0;
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -3485529955529426875L;
+
+    /** Default value for initial capacity. */
+    private static final int DEFAULT_INITIAL_CAPACITY = 16;
+
+    /** Default value for array size modifier. */
+    private static final double DEFAULT_EXPANSION_FACTOR = 2.0;
+
+    /**
+     * Default value for the difference between {@link #contractionCriterion} and {@link
+     * #expansionFactor}.
+     */
+    private static final double DEFAULT_CONTRACTION_DELTA = 0.5;
+
+    /**
+     * The contraction criteria determines when the internal array will be contracted to fit the
+     * number of elements contained in the element array + 1.
+     */
+    private double contractionCriterion = 2.5;
+
+    /**
+     * The expansion factor of the array. When the array needs to be expanded, the new array size
+     * will be {@code internalArray.length * expansionFactor} if {@code expansionMode} is set to
+     * MULTIPLICATIVE_MODE, or {@code internalArray.length + expansionFactor} if {@code
+     * expansionMode} is set to ADDITIVE_MODE.
+     */
+    private double expansionFactor = 2.0;
+
+    /**
+     * Determines whether array expansion by {@code expansionFactor} is additive or multiplicative.
+     */
+    private ExpansionMode expansionMode = ExpansionMode.MULTIPLICATIVE;
+
+    /** The internal storage array. */
+    private double[] internalArray;
+
+    /**
+     * The number of addressable elements in the array. Note that this has nothing to do with the
+     * length of the internal storage array.
+     */
+    private int numElements = 0;
+
+    /**
+     * The position of the first addressable element in the internal storage array. The addressable
+     * elements in the array are {@code internalArray[startIndex],...,internalArray[startIndex +
+     * numElements - 1]}.
+     */
+    private int startIndex = 0;
+
+    /**
+     * Specification of expansion algorithm.
+     *
+     * @since 3.1
+     */
+    public enum ExpansionMode {
+        /** Multiplicative expansion mode. */
+        MULTIPLICATIVE,
+        /** Additive expansion mode. */
+        ADDITIVE
+    }
+
+    /**
+     * Creates an instance with default properties.
+     *
+     * <ul>
+     *   <li>{@code initialCapacity = 16}
+     *   <li>{@code expansionMode = MULTIPLICATIVE}
+     *   <li>{@code expansionFactor = 2.0}
+     *   <li>{@code contractionCriterion = 2.5}
+     * </ul>
+     */
+    public ResizableDoubleArray() {
+        this(DEFAULT_INITIAL_CAPACITY);
+    }
+
+    /**
+     * Creates an instance with the specified initial capacity. Other properties take default
+     * values:
+     *
+     * <ul>
+     *   <li>{@code expansionMode = MULTIPLICATIVE}
+     *   <li>{@code expansionFactor = 2.0}
+     *   <li>{@code contractionCriterion = 2.5}
+     * </ul>
+     *
+     * @param initialCapacity Initial size of the internal storage array.
+     * @throws MathIllegalArgumentException if {@code initialCapacity <= 0}.
+     */
+    public ResizableDoubleArray(int initialCapacity) throws MathIllegalArgumentException {
+        this(initialCapacity, DEFAULT_EXPANSION_FACTOR);
+    }
+
+    /**
+     * Creates an instance from an existing {@code double[]} with the initial capacity and
+     * numElements corresponding to the size of the supplied {@code double[]} array. If the supplied
+     * array is null, a new empty array with the default initial capacity will be created. The input
+     * array is copied, not referenced. Other properties take default values:
+     *
+     * <ul>
+     *   <li>{@code initialCapacity = 16}
+     *   <li>{@code expansionMode = MULTIPLICATIVE}
+     *   <li>{@code expansionFactor = 2.0}
+     *   <li>{@code contractionCriterion = 2.5}
+     * </ul>
+     *
+     * @param initialArray initial array
+     * @since 2.2
+     */
+    public ResizableDoubleArray(double[] initialArray) {
+        this(
+                DEFAULT_INITIAL_CAPACITY,
+                DEFAULT_EXPANSION_FACTOR,
+                DEFAULT_CONTRACTION_DELTA + DEFAULT_EXPANSION_FACTOR,
+                ExpansionMode.MULTIPLICATIVE,
+                initialArray);
+    }
+
+    /**
+     * Creates an instance with the specified initial capacity and expansion factor. The remaining
+     * properties take default values:
+     *
+     * <ul>
+     *   <li>{@code expansionMode = MULTIPLICATIVE}
+     *   <li>{@code contractionCriterion = 0.5 + expansionFactor}
+     * </ul>
+     *
+     * <br>
+     * Throws IllegalArgumentException if the following conditions are not met:
+     *
+     * <ul>
+     *   <li>{@code initialCapacity > 0}
+     *   <li>{@code expansionFactor > 1}
+     * </ul>
+     *
+     * @param initialCapacity Initial size of the internal storage array.
+     * @param expansionFactor The array will be expanded based on this parameter.
+     * @throws MathIllegalArgumentException if parameters are not valid.
+     * @deprecated As of 3.1. Please use {@link #ResizableDoubleArray(int,double)} instead.
+     */
+    @Deprecated
+    public ResizableDoubleArray(int initialCapacity, float expansionFactor)
+            throws MathIllegalArgumentException {
+        this(initialCapacity, (double) expansionFactor);
+    }
+
+    /**
+     * Creates an instance with the specified initial capacity and expansion factor. The remaining
+     * properties take default values:
+     *
+     * <ul>
+     *   <li>{@code expansionMode = MULTIPLICATIVE}
+     *   <li>{@code contractionCriterion = 0.5 + expansionFactor}
+     * </ul>
+     *
+     * <br>
+     * Throws IllegalArgumentException if the following conditions are not met:
+     *
+     * <ul>
+     *   <li>{@code initialCapacity > 0}
+     *   <li>{@code expansionFactor > 1}
+     * </ul>
+     *
+     * @param initialCapacity Initial size of the internal storage array.
+     * @param expansionFactor The array will be expanded based on this parameter.
+     * @throws MathIllegalArgumentException if parameters are not valid.
+     * @since 3.1
+     */
+    public ResizableDoubleArray(int initialCapacity, double expansionFactor)
+            throws MathIllegalArgumentException {
+        this(initialCapacity, expansionFactor, DEFAULT_CONTRACTION_DELTA + expansionFactor);
+    }
+
+    /**
+     * Creates an instance with the specified initialCapacity, expansionFactor, and
+     * contractionCriterion. The expansion mode will default to {@code MULTIPLICATIVE}. <br>
+     * Throws IllegalArgumentException if the following conditions are not met:
+     *
+     * <ul>
+     *   <li>{@code initialCapacity > 0}
+     *   <li>{@code expansionFactor > 1}
+     *   <li>{@code contractionCriterion >= expansionFactor}
+     * </ul>
+     *
+     * @param initialCapacity Initial size of the internal storage array..
+     * @param expansionFactor The array will be expanded based on this parameter.
+     * @param contractionCriteria Contraction criteria.
+     * @throws MathIllegalArgumentException if parameters are not valid.
+     * @deprecated As of 3.1. Please use {@link #ResizableDoubleArray(int,double,double)} instead.
+     */
+    @Deprecated
+    public ResizableDoubleArray(
+            int initialCapacity, float expansionFactor, float contractionCriteria)
+            throws MathIllegalArgumentException {
+        this(initialCapacity, (double) expansionFactor, (double) contractionCriteria);
+    }
+
+    /**
+     * Creates an instance with the specified initial capacity, expansion factor, and contraction
+     * criteria. The expansion mode will default to {@code MULTIPLICATIVE}. <br>
+     * Throws IllegalArgumentException if the following conditions are not met:
+     *
+     * <ul>
+     *   <li>{@code initialCapacity > 0}
+     *   <li>{@code expansionFactor > 1}
+     *   <li>{@code contractionCriterion >= expansionFactor}
+     * </ul>
+     *
+     * @param initialCapacity Initial size of the internal storage array..
+     * @param expansionFactor The array will be expanded based on this parameter.
+     * @param contractionCriterion Contraction criterion.
+     * @throws MathIllegalArgumentException if the parameters are not valid.
+     * @since 3.1
+     */
+    public ResizableDoubleArray(
+            int initialCapacity, double expansionFactor, double contractionCriterion)
+            throws MathIllegalArgumentException {
+        this(
+                initialCapacity,
+                expansionFactor,
+                contractionCriterion,
+                ExpansionMode.MULTIPLICATIVE,
+                null);
+    }
+
+    /**
+     * Create a ResizableArray with the specified properties.
+     *
+     * <p>Throws IllegalArgumentException if the following conditions are not met:
+     *
+     * <ul>
+     *   <li><code>initialCapacity > 0</code>
+     *   <li><code>expansionFactor > 1</code>
+     *   <li><code>contractionFactor >= expansionFactor</code>
+     *   <li><code>expansionMode in {MULTIPLICATIVE_MODE, ADDITIVE_MODE}</code>
+     * </ul>
+     *
+     * @param initialCapacity the initial size of the internal storage array
+     * @param expansionFactor the array will be expanded based on this parameter
+     * @param contractionCriteria the contraction Criteria
+     * @param expansionMode the expansion mode
+     * @throws MathIllegalArgumentException if parameters are not valid
+     * @deprecated As of 3.1. Please use {@link
+     *     #ResizableDoubleArray(int,double,double,ExpansionMode,double[])} instead.
+     */
+    @Deprecated
+    public ResizableDoubleArray(
+            int initialCapacity,
+            float expansionFactor,
+            float contractionCriteria,
+            int expansionMode)
+            throws MathIllegalArgumentException {
+        this(
+                initialCapacity,
+                expansionFactor,
+                contractionCriteria,
+                expansionMode == ADDITIVE_MODE
+                        ? ExpansionMode.ADDITIVE
+                        : ExpansionMode.MULTIPLICATIVE,
+                null);
+        // XXX Just ot retain the expected failure in a unit test.
+        // With the new "enum", that test will become obsolete.
+        setExpansionMode(expansionMode);
+    }
+
+    /**
+     * Creates an instance with the specified properties. <br>
+     * Throws MathIllegalArgumentException if the following conditions are not met:
+     *
+     * <ul>
+     *   <li>{@code initialCapacity > 0}
+     *   <li>{@code expansionFactor > 1}
+     *   <li>{@code contractionCriterion >= expansionFactor}
+     * </ul>
+     *
+     * @param initialCapacity Initial size of the internal storage array.
+     * @param expansionFactor The array will be expanded based on this parameter.
+     * @param contractionCriterion Contraction criteria.
+     * @param expansionMode Expansion mode.
+     * @param data Initial contents of the array.
+     * @throws MathIllegalArgumentException if the parameters are not valid.
+     */
+    public ResizableDoubleArray(
+            int initialCapacity,
+            double expansionFactor,
+            double contractionCriterion,
+            ExpansionMode expansionMode,
+            double... data)
+            throws MathIllegalArgumentException {
+        if (initialCapacity <= 0) {
+            throw new NotStrictlyPositiveException(
+                    LocalizedFormats.INITIAL_CAPACITY_NOT_POSITIVE, initialCapacity);
+        }
+        checkContractExpand(contractionCriterion, expansionFactor);
+
+        this.expansionFactor = expansionFactor;
+        this.contractionCriterion = contractionCriterion;
+        this.expansionMode = expansionMode;
+        internalArray = new double[initialCapacity];
+        numElements = 0;
+        startIndex = 0;
+
+        if (data != null && data.length > 0) {
+            addElements(data);
+        }
+    }
+
+    /**
+     * Copy constructor. Creates a new ResizableDoubleArray that is a deep, fresh copy of the
+     * original. Needs to acquire synchronization lock on original. Original may not be null;
+     * otherwise a {@link NullArgumentException} is thrown.
+     *
+     * @param original array to copy
+     * @exception NullArgumentException if original is null
+     * @since 2.0
+     */
+    public ResizableDoubleArray(ResizableDoubleArray original) throws NullArgumentException {
+        MathUtils.checkNotNull(original);
+        copy(original, this);
+    }
+
+    /**
+     * Adds an element to the end of this expandable array.
+     *
+     * @param value Value to be added to end of array.
+     */
+    public synchronized void addElement(double value) {
+        if (internalArray.length <= startIndex + numElements) {
+            expand();
+        }
+        internalArray[startIndex + numElements++] = value;
+    }
+
+    /**
+     * Adds several element to the end of this expandable array.
+     *
+     * @param values Values to be added to end of array.
+     * @since 2.2
+     */
+    public synchronized void addElements(double[] values) {
+        final double[] tempArray = new double[numElements + values.length + 1];
+        System.arraycopy(internalArray, startIndex, tempArray, 0, numElements);
+        System.arraycopy(values, 0, tempArray, numElements, values.length);
+        internalArray = tempArray;
+        startIndex = 0;
+        numElements += values.length;
+    }
+
+    /**
+     * Adds an element to the end of the array and removes the first element in the array. Returns
+     * the discarded first element. The effect is similar to a push operation in a FIFO queue.
+     *
+     * <p>Example: If the array contains the elements 1, 2, 3, 4 (in that order) and
+     * addElementRolling(5) is invoked, the result is an array containing the entries 2, 3, 4, 5 and
+     * the value returned is 1.
+     *
+     * @param value Value to be added to the array.
+     * @return the value which has been discarded or "pushed" out of the array by this rolling
+     *     insert.
+     */
+    public synchronized double addElementRolling(double value) {
+        double discarded = internalArray[startIndex];
+
+        if ((startIndex + (numElements + 1)) > internalArray.length) {
+            expand();
+        }
+        // Increment the start index
+        startIndex += 1;
+
+        // Add the new value
+        internalArray[startIndex + (numElements - 1)] = value;
+
+        // Check the contraction criterion.
+        if (shouldContract()) {
+            contract();
+        }
+        return discarded;
+    }
+
+    /**
+     * Substitutes <code>value</code> for the most recently added value. Returns the value that has
+     * been replaced. If the array is empty (i.e. if {@link #numElements} is zero), an
+     * IllegalStateException is thrown.
+     *
+     * @param value New value to substitute for the most recently added value
+     * @return the value that has been replaced in the array.
+     * @throws MathIllegalStateException if the array is empty
+     * @since 2.0
+     */
+    public synchronized double substituteMostRecentElement(double value)
+            throws MathIllegalStateException {
+        if (numElements < 1) {
+            throw new MathIllegalStateException(
+                    LocalizedFormats.CANNOT_SUBSTITUTE_ELEMENT_FROM_EMPTY_ARRAY);
+        }
+
+        final int substIndex = startIndex + (numElements - 1);
+        final double discarded = internalArray[substIndex];
+
+        internalArray[substIndex] = value;
+
+        return discarded;
+    }
+
+    /**
+     * Checks the expansion factor and the contraction criterion and throws an
+     * IllegalArgumentException if the contractionCriteria is less than the expansionCriteria
+     *
+     * @param expansion factor to be checked
+     * @param contraction criteria to be checked
+     * @throws MathIllegalArgumentException if the contractionCriteria is less than the
+     *     expansionCriteria.
+     * @deprecated As of 3.1. Please use {@link #checkContractExpand(double,double)} instead.
+     */
+    @Deprecated
+    protected void checkContractExpand(float contraction, float expansion)
+            throws MathIllegalArgumentException {
+        checkContractExpand((double) contraction, (double) expansion);
+    }
+
+    /**
+     * Checks the expansion factor and the contraction criterion and raises an exception if the
+     * contraction criterion is smaller than the expansion criterion.
+     *
+     * @param contraction Criterion to be checked.
+     * @param expansion Factor to be checked.
+     * @throws NumberIsTooSmallException if {@code contraction < expansion}.
+     * @throws NumberIsTooSmallException if {@code contraction <= 1}.
+     * @throws NumberIsTooSmallException if {@code expansion <= 1 }.
+     * @since 3.1
+     */
+    protected void checkContractExpand(double contraction, double expansion)
+            throws NumberIsTooSmallException {
+        if (contraction < expansion) {
+            final NumberIsTooSmallException e = new NumberIsTooSmallException(contraction, 1, true);
+            e.getContext()
+                    .addMessage(
+                            LocalizedFormats.CONTRACTION_CRITERIA_SMALLER_THAN_EXPANSION_FACTOR,
+                            contraction,
+                            expansion);
+            throw e;
+        }
+
+        if (contraction <= 1) {
+            final NumberIsTooSmallException e =
+                    new NumberIsTooSmallException(contraction, 1, false);
+            e.getContext()
+                    .addMessage(
+                            LocalizedFormats.CONTRACTION_CRITERIA_SMALLER_THAN_ONE, contraction);
+            throw e;
+        }
+
+        if (expansion <= 1) {
+            final NumberIsTooSmallException e =
+                    new NumberIsTooSmallException(contraction, 1, false);
+            e.getContext()
+                    .addMessage(LocalizedFormats.EXPANSION_FACTOR_SMALLER_THAN_ONE, expansion);
+            throw e;
+        }
+    }
+
+    /** Clear the array contents, resetting the number of elements to zero. */
+    public synchronized void clear() {
+        numElements = 0;
+        startIndex = 0;
+    }
+
+    /**
+     * Contracts the storage array to the (size of the element set) + 1 - to avoid a zero length
+     * array. This function also resets the startIndex to zero.
+     */
+    public synchronized void contract() {
+        final double[] tempArray = new double[numElements + 1];
+
+        // Copy and swap - copy only the element array from the src array.
+        System.arraycopy(internalArray, startIndex, tempArray, 0, numElements);
+        internalArray = tempArray;
+
+        // Reset the start index to zero
+        startIndex = 0;
+    }
+
+    /**
+     * Discards the <code>i</code> initial elements of the array. For example, if the array contains
+     * the elements 1,2,3,4, invoking <code>discardFrontElements(2)</code> will cause the first two
+     * elements to be discarded, leaving 3,4 in the array. Throws illegalArgumentException if i
+     * exceeds numElements.
+     *
+     * @param i the number of elements to discard from the front of the array
+     * @throws MathIllegalArgumentException if i is greater than numElements.
+     * @since 2.0
+     */
+    public synchronized void discardFrontElements(int i) throws MathIllegalArgumentException {
+        discardExtremeElements(i, true);
+    }
+
+    /**
+     * Discards the <code>i</code> last elements of the array. For example, if the array contains
+     * the elements 1,2,3,4, invoking <code>discardMostRecentElements(2)</code> will cause the last
+     * two elements to be discarded, leaving 1,2 in the array. Throws illegalArgumentException if i
+     * exceeds numElements.
+     *
+     * @param i the number of elements to discard from the end of the array
+     * @throws MathIllegalArgumentException if i is greater than numElements.
+     * @since 2.0
+     */
+    public synchronized void discardMostRecentElements(int i) throws MathIllegalArgumentException {
+        discardExtremeElements(i, false);
+    }
+
+    /**
+     * Discards the <code>i</code> first or last elements of the array, depending on the value of
+     * <code>front</code>. For example, if the array contains the elements 1,2,3,4, invoking <code>
+     * discardExtremeElements(2,false)</code> will cause the last two elements to be discarded,
+     * leaving 1,2 in the array. For example, if the array contains the elements 1,2,3,4, invoking
+     * <code>discardExtremeElements(2,true)</code> will cause the first two elements to be
+     * discarded, leaving 3,4 in the array. Throws illegalArgumentException if i exceeds
+     * numElements.
+     *
+     * @param i the number of elements to discard from the front/end of the array
+     * @param front true if elements are to be discarded from the front of the array, false if
+     *     elements are to be discarded from the end of the array
+     * @throws MathIllegalArgumentException if i is greater than numElements.
+     * @since 2.0
+     */
+    private synchronized void discardExtremeElements(int i, boolean front)
+            throws MathIllegalArgumentException {
+        if (i > numElements) {
+            throw new MathIllegalArgumentException(
+                    LocalizedFormats.TOO_MANY_ELEMENTS_TO_DISCARD_FROM_ARRAY, i, numElements);
+        } else if (i < 0) {
+            throw new MathIllegalArgumentException(
+                    LocalizedFormats.CANNOT_DISCARD_NEGATIVE_NUMBER_OF_ELEMENTS, i);
+        } else {
+            // "Subtract" this number of discarded from numElements
+            numElements -= i;
+            if (front) {
+                startIndex += i;
+            }
+        }
+        if (shouldContract()) {
+            contract();
+        }
+    }
+
+    /**
+     * Expands the internal storage array using the expansion factor.
+     *
+     * <p>if <code>expansionMode</code> is set to MULTIPLICATIVE_MODE, the new array size will be
+     * <code>internalArray.length * expansionFactor.</code> If <code>expansionMode</code> is set to
+     * ADDITIVE_MODE, the length after expansion will be <code>
+     * internalArray.length + expansionFactor</code>
+     */
+    protected synchronized void expand() {
+        // notice the use of FastMath.ceil(), this guarantees that we will always
+        // have an array of at least currentSize + 1.   Assume that the
+        // current initial capacity is 1 and the expansion factor
+        // is 1.000000000000000001.  The newly calculated size will be
+        // rounded up to 2 after the multiplication is performed.
+        int newSize = 0;
+        if (expansionMode == ExpansionMode.MULTIPLICATIVE) {
+            newSize = (int) FastMath.ceil(internalArray.length * expansionFactor);
+        } else {
+            newSize = (int) (internalArray.length + FastMath.round(expansionFactor));
+        }
+        final double[] tempArray = new double[newSize];
+
+        // Copy and swap
+        System.arraycopy(internalArray, 0, tempArray, 0, internalArray.length);
+        internalArray = tempArray;
+    }
+
+    /**
+     * Expands the internal storage array to the specified size.
+     *
+     * @param size Size of the new internal storage array.
+     */
+    private synchronized void expandTo(int size) {
+        final double[] tempArray = new double[size];
+        // Copy and swap
+        System.arraycopy(internalArray, 0, tempArray, 0, internalArray.length);
+        internalArray = tempArray;
+    }
+
+    /**
+     * The contraction criteria defines when the internal array will contract to store only the
+     * number of elements in the element array. If the <code>expansionMode</code> is <code>
+     * MULTIPLICATIVE_MODE</code>, contraction is triggered when the ratio between storage array
+     * length and <code>numElements</code> exceeds <code>contractionFactor</code>. If the <code>
+     * expansionMode</code> is <code>ADDITIVE_MODE</code>, the number of excess storage locations is
+     * compared to <code>contractionFactor.</code>
+     *
+     * @return the contraction criteria used to reclaim memory.
+     * @deprecated As of 3.1. Please use {@link #getContractionCriterion()} instead.
+     */
+    @Deprecated
+    public float getContractionCriteria() {
+        return (float) getContractionCriterion();
+    }
+
+    /**
+     * The contraction criterion defines when the internal array will contract to store only the
+     * number of elements in the element array. If the <code>expansionMode</code> is <code>
+     * MULTIPLICATIVE_MODE</code>, contraction is triggered when the ratio between storage array
+     * length and <code>numElements</code> exceeds <code>contractionFactor</code>. If the <code>
+     * expansionMode</code> is <code>ADDITIVE_MODE</code>, the number of excess storage locations is
+     * compared to <code>contractionFactor.</code>
+     *
+     * @return the contraction criterion used to reclaim memory.
+     * @since 3.1
+     */
+    public double getContractionCriterion() {
+        return contractionCriterion;
+    }
+
+    /**
+     * Returns the element at the specified index
+     *
+     * @param index index to fetch a value from
+     * @return value stored at the specified index
+     * @throws ArrayIndexOutOfBoundsException if <code>index</code> is less than zero or is greater
+     *     than <code>getNumElements() - 1</code>.
+     */
+    public synchronized double getElement(int index) {
+        if (index >= numElements) {
+            throw new ArrayIndexOutOfBoundsException(index);
+        } else if (index >= 0) {
+            return internalArray[startIndex + index];
+        } else {
+            throw new ArrayIndexOutOfBoundsException(index);
+        }
+    }
+
+    /**
+     * Returns a double array containing the elements of this <code>ResizableArray</code>. This
+     * method returns a copy, not a reference to the underlying array, so that changes made to the
+     * returned array have no effect on this <code>ResizableArray.</code>
+     *
+     * @return the double array.
+     */
+    public synchronized double[] getElements() {
+        final double[] elementArray = new double[numElements];
+        System.arraycopy(internalArray, startIndex, elementArray, 0, numElements);
+        return elementArray;
+    }
+
+    /**
+     * The expansion factor controls the size of a new array when an array needs to be expanded. The
+     * <code>expansionMode</code> determines whether the size of the array is multiplied by the
+     * <code>expansionFactor</code> (MULTIPLICATIVE_MODE) or if the expansion is additive
+     * (ADDITIVE_MODE -- <code>expansionFactor</code> storage locations added). The default <code>
+     * expansionMode</code> is MULTIPLICATIVE_MODE and the default <code>expansionFactor</code> is
+     * 2.0.
+     *
+     * @return the expansion factor of this expandable double array
+     * @deprecated As of 3.1. Return type will be changed to "double" in 4.0.
+     */
+    @Deprecated
+    public float getExpansionFactor() {
+        return (float) expansionFactor;
+    }
+
+    /**
+     * The expansion mode determines whether the internal storage array grows additively or
+     * multiplicatively when it is expanded.
+     *
+     * @return the expansion mode.
+     * @deprecated As of 3.1. Return value to be changed to {@link ExpansionMode} in 4.0.
+     */
+    @Deprecated
+    public int getExpansionMode() {
+        synchronized (this) {
+            switch (expansionMode) {
+                case MULTIPLICATIVE:
+                    return MULTIPLICATIVE_MODE;
+                case ADDITIVE:
+                    return ADDITIVE_MODE;
+                default:
+                    throw new MathInternalError(); // Should never happen.
+            }
+        }
+    }
+
+    /**
+     * Notice the package scope on this method. This method is simply here for the JUnit test, it
+     * allows us check if the expansion is working properly after a number of expansions. This is
+     * not meant to be a part of the public interface of this class.
+     *
+     * @return the length of the internal storage array.
+     * @deprecated As of 3.1. Please use {@link #getCapacity()} instead.
+     */
+    @Deprecated
+    synchronized int getInternalLength() {
+        return internalArray.length;
+    }
+
+    /**
+     * Gets the currently allocated size of the internal data structure used for storing elements.
+     * This is not to be confused with {@link #getNumElements() the number of elements actually
+     * stored}.
+     *
+     * @return the length of the internal array.
+     * @since 3.1
+     */
+    public int getCapacity() {
+        return internalArray.length;
+    }
+
+    /**
+     * Returns the number of elements currently in the array. Please note that this is different
+     * from the length of the internal storage array.
+     *
+     * @return the number of elements.
+     */
+    public synchronized int getNumElements() {
+        return numElements;
+    }
+
+    /**
+     * Returns the internal storage array. Note that this method returns a reference to the internal
+     * storage array, not a copy, and to correctly address elements of the array, the <code>
+     * startIndex</code> is required (available via the {@link #start} method). This method should
+     * only be used in cases where copying the internal array is not practical. The {@link
+     * #getElements} method should be used in all other cases.
+     *
+     * @return the internal storage array used by this object
+     * @since 2.0
+     * @deprecated As of 3.1.
+     */
+    @Deprecated
+    public synchronized double[] getInternalValues() {
+        return internalArray;
+    }
+
+    /**
+     * Provides <em>direct</em> access to the internal storage array. Please note that this method
+     * returns a reference to this object's storage array, not a copy. <br>
+     * To correctly address elements of the array, the "start index" is required (available via the
+     * {@link #getStartIndex() getStartIndex} method. <br>
+     * This method should only be used to avoid copying the internal array. The returned value
+     * <em>must</em> be used for reading only; other uses could lead to this object becoming
+     * inconsistent. <br>
+     * The {@link #getElements} method has no such limitation since it returns a copy of this
+     * array's addressable elements.
+     *
+     * @return the internal storage array used by this object.
+     * @since 3.1
+     */
+    protected double[] getArrayRef() {
+        return internalArray;
+    }
+
+    /**
+     * Returns the "start index" of the internal array. This index is the position of the first
+     * addressable element in the internal storage array. The addressable elements in the array are
+     * at indices contained in the interval [{@link #getStartIndex()}, {@link #getStartIndex()} +
+     * {@link #getNumElements()} - 1].
+     *
+     * @return the start index.
+     * @since 3.1
+     */
+    protected int getStartIndex() {
+        return startIndex;
+    }
+
+    /**
+     * Sets the contraction criteria.
+     *
+     * @param contractionCriteria contraction criteria
+     * @throws MathIllegalArgumentException if the contractionCriteria is less than the
+     *     expansionCriteria.
+     * @deprecated As of 3.1 (to be removed in 4.0 as field will become "final").
+     */
+    @Deprecated
+    public void setContractionCriteria(float contractionCriteria)
+            throws MathIllegalArgumentException {
+        checkContractExpand(contractionCriteria, getExpansionFactor());
+        synchronized (this) {
+            this.contractionCriterion = contractionCriteria;
+        }
+    }
+
+    /**
+     * Performs an operation on the addressable elements of the array.
+     *
+     * @param f Function to be applied on this array.
+     * @return the result.
+     * @since 3.1
+     */
+    public double compute(MathArrays.Function f) {
+        final double[] array;
+        final int start;
+        final int num;
+        synchronized (this) {
+            array = internalArray;
+            start = startIndex;
+            num = numElements;
+        }
+        return f.evaluate(array, start, num);
+    }
+
+    /**
+     * Sets the element at the specified index. If the specified index is greater than <code>
+     * getNumElements() - 1</code>, the <code>numElements</code> property is increased to <code>
+     * index +1</code> and additional storage is allocated (if necessary) for the new element and
+     * all (uninitialized) elements between the new element and the previous end of the array).
+     *
+     * @param index index to store a value in
+     * @param value value to store at the specified index
+     * @throws ArrayIndexOutOfBoundsException if {@code index < 0}.
+     */
+    public synchronized void setElement(int index, double value) {
+        if (index < 0) {
+            throw new ArrayIndexOutOfBoundsException(index);
+        }
+        if (index + 1 > numElements) {
+            numElements = index + 1;
+        }
+        if ((startIndex + index) >= internalArray.length) {
+            expandTo(startIndex + (index + 1));
+        }
+        internalArray[startIndex + index] = value;
+    }
+
+    /**
+     * Sets the expansionFactor. Throws IllegalArgumentException if the the following conditions are
+     * not met:
+     *
+     * <ul>
+     *   <li><code>expansionFactor > 1</code>
+     *   <li><code>contractionFactor >= expansionFactor</code>
+     * </ul>
+     *
+     * @param expansionFactor the new expansion factor value.
+     * @throws MathIllegalArgumentException if expansionFactor is <= 1 or greater than
+     *     contractionFactor
+     * @deprecated As of 3.1 (to be removed in 4.0 as field will become "final").
+     */
+    @Deprecated
+    public void setExpansionFactor(float expansionFactor) throws MathIllegalArgumentException {
+        checkContractExpand(getContractionCriterion(), expansionFactor);
+        // The check above verifies that the expansion factor is > 1.0;
+        synchronized (this) {
+            this.expansionFactor = expansionFactor;
+        }
+    }
+
+    /**
+     * Sets the <code>expansionMode</code>. The specified value must be one of ADDITIVE_MODE,
+     * MULTIPLICATIVE_MODE.
+     *
+     * @param expansionMode The expansionMode to set.
+     * @throws MathIllegalArgumentException if the specified mode value is not valid.
+     * @deprecated As of 3.1. Please use {@link #setExpansionMode(ExpansionMode)} instead.
+     */
+    @Deprecated
+    public void setExpansionMode(int expansionMode) throws MathIllegalArgumentException {
+        if (expansionMode != MULTIPLICATIVE_MODE && expansionMode != ADDITIVE_MODE) {
+            throw new MathIllegalArgumentException(
+                    LocalizedFormats.UNSUPPORTED_EXPANSION_MODE,
+                    expansionMode,
+                    MULTIPLICATIVE_MODE,
+                    "MULTIPLICATIVE_MODE",
+                    ADDITIVE_MODE,
+                    "ADDITIVE_MODE");
+        }
+        synchronized (this) {
+            if (expansionMode == MULTIPLICATIVE_MODE) {
+                setExpansionMode(ExpansionMode.MULTIPLICATIVE);
+            } else if (expansionMode == ADDITIVE_MODE) {
+                setExpansionMode(ExpansionMode.ADDITIVE);
+            }
+        }
+    }
+
+    /**
+     * Sets the {@link ExpansionMode expansion mode}.
+     *
+     * @param expansionMode Expansion mode to use for resizing the array.
+     * @deprecated As of 3.1 (to be removed in 4.0 as field will become "final").
+     */
+    @Deprecated
+    public void setExpansionMode(ExpansionMode expansionMode) {
+        synchronized (this) {
+            this.expansionMode = expansionMode;
+        }
+    }
+
+    /**
+     * Sets the initial capacity. Should only be invoked by constructors.
+     *
+     * @param initialCapacity of the array
+     * @throws MathIllegalArgumentException if <code>initialCapacity</code> is not positive.
+     * @deprecated As of 3.1, this is a no-op.
+     */
+    @Deprecated
+    protected void setInitialCapacity(int initialCapacity) throws MathIllegalArgumentException {
+        // Body removed in 3.1.
+    }
+
+    /**
+     * This function allows you to control the number of elements contained in this array, and can
+     * be used to "throw out" the last n values in an array. This function will also expand the
+     * internal array as needed.
+     *
+     * @param i a new number of elements
+     * @throws MathIllegalArgumentException if <code>i</code> is negative.
+     */
+    public synchronized void setNumElements(int i) throws MathIllegalArgumentException {
+        // If index is negative thrown an error.
+        if (i < 0) {
+            throw new MathIllegalArgumentException(LocalizedFormats.INDEX_NOT_POSITIVE, i);
+        }
+
+        // Test the new num elements, check to see if the array needs to be
+        // expanded to accommodate this new number of elements.
+        final int newSize = startIndex + i;
+        if (newSize > internalArray.length) {
+            expandTo(newSize);
+        }
+
+        // Set the new number of elements to new value.
+        numElements = i;
+    }
+
+    /**
+     * Returns true if the internal storage array has too many unused storage positions.
+     *
+     * @return true if array satisfies the contraction criteria
+     */
+    private synchronized boolean shouldContract() {
+        if (expansionMode == ExpansionMode.MULTIPLICATIVE) {
+            return (internalArray.length / ((float) numElements)) > contractionCriterion;
+        } else {
+            return (internalArray.length - numElements) > contractionCriterion;
+        }
+    }
+
+    /**
+     * Returns the starting index of the internal array. The starting index is the position of the
+     * first addressable element in the internal storage array. The addressable elements in the
+     * array are <code>
+     * internalArray[startIndex],...,internalArray[startIndex + numElements -1]
+     * </code>
+     *
+     * @return the starting index.
+     * @deprecated As of 3.1.
+     */
+    @Deprecated
+    public synchronized int start() {
+        return startIndex;
+    }
+
+    /**
+     * Copies source to dest, copying the underlying data, so dest is a new, independent copy of
+     * source. Does not contract before the copy.
+     *
+     * <p>Obtains synchronization locks on both source and dest (in that order) before performing
+     * the copy.
+     *
+     * <p>Neither source nor dest may be null; otherwise a {@link NullArgumentException} is thrown
+     *
+     * @param source ResizableDoubleArray to copy
+     * @param dest ResizableArray to replace with a copy of the source array
+     * @exception NullArgumentException if either source or dest is null
+     * @since 2.0
+     */
+    public static void copy(ResizableDoubleArray source, ResizableDoubleArray dest)
+            throws NullArgumentException {
+        MathUtils.checkNotNull(source);
+        MathUtils.checkNotNull(dest);
+        synchronized (source) {
+            synchronized (dest) {
+                dest.contractionCriterion = source.contractionCriterion;
+                dest.expansionFactor = source.expansionFactor;
+                dest.expansionMode = source.expansionMode;
+                dest.internalArray = new double[source.internalArray.length];
+                System.arraycopy(
+                        source.internalArray, 0, dest.internalArray, 0, dest.internalArray.length);
+                dest.numElements = source.numElements;
+                dest.startIndex = source.startIndex;
+            }
+        }
+    }
+
+    /**
+     * Returns a copy of the ResizableDoubleArray. Does not contract before the copy, so the
+     * returned object is an exact copy of this.
+     *
+     * @return a new ResizableDoubleArray with the same data and configuration properties as this
+     * @since 2.0
+     */
+    public synchronized ResizableDoubleArray copy() {
+        final ResizableDoubleArray result = new ResizableDoubleArray();
+        copy(this, result);
+        return result;
+    }
+
+    /**
+     * Returns true iff object is a ResizableDoubleArray with the same properties as this and an
+     * identical internal storage array.
+     *
+     * @param object object to be compared for equality with this
+     * @return true iff object is a ResizableDoubleArray with the same data and properties as this
+     * @since 2.0
+     */
+    @Override
+    public boolean equals(Object object) {
+        if (object == this) {
+            return true;
+        }
+        if (object instanceof ResizableDoubleArray == false) {
+            return false;
+        }
+        synchronized (this) {
+            synchronized (object) {
+                boolean result = true;
+                final ResizableDoubleArray other = (ResizableDoubleArray) object;
+                result = result && (other.contractionCriterion == contractionCriterion);
+                result = result && (other.expansionFactor == expansionFactor);
+                result = result && (other.expansionMode == expansionMode);
+                result = result && (other.numElements == numElements);
+                result = result && (other.startIndex == startIndex);
+                if (!result) {
+                    return false;
+                } else {
+                    return Arrays.equals(internalArray, other.internalArray);
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns a hash code consistent with equals.
+     *
+     * @return the hash code representing this {@code ResizableDoubleArray}.
+     * @since 2.0
+     */
+    @Override
+    public synchronized int hashCode() {
+        final int[] hashData = new int[6];
+        hashData[0] = Double.valueOf(expansionFactor).hashCode();
+        hashData[1] = Double.valueOf(contractionCriterion).hashCode();
+        hashData[2] = expansionMode.hashCode();
+        hashData[3] = Arrays.hashCode(internalArray);
+        hashData[4] = numElements;
+        hashData[5] = startIndex;
+        return Arrays.hashCode(hashData);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/util/TransformerMap.java b/src/main/java/org/apache/commons/math3/util/TransformerMap.java
new file mode 100644
index 0000000..404f7db
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/TransformerMap.java
@@ -0,0 +1,182 @@
+/*
+ * 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.math3.util;
+
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This TansformerMap automates the transformation of mixed object types. It provides a means to set
+ * NumberTransformers that will be selected based on the Class of the object handed to the Maps
+ * <code>double transform(Object o)</code> method.
+ */
+public class TransformerMap implements NumberTransformer, Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 4605318041528645258L;
+
+    /** A default Number Transformer for Numbers and numeric Strings. */
+    private NumberTransformer defaultTransformer = null;
+
+    /** The internal Map. */
+    private Map<Class<?>, NumberTransformer> map = null;
+
+    /** Build a map containing only the default transformer. */
+    public TransformerMap() {
+        map = new HashMap<Class<?>, NumberTransformer>();
+        defaultTransformer = new DefaultTransformer();
+    }
+
+    /**
+     * Tests if a Class is present in the TransformerMap.
+     *
+     * @param key Class to check
+     * @return true|false
+     */
+    public boolean containsClass(Class<?> key) {
+        return map.containsKey(key);
+    }
+
+    /**
+     * Tests if a NumberTransformer is present in the TransformerMap.
+     *
+     * @param value NumberTransformer to check
+     * @return true|false
+     */
+    public boolean containsTransformer(NumberTransformer value) {
+        return map.containsValue(value);
+    }
+
+    /**
+     * Returns the Transformer that is mapped to a class if mapping is not present, this returns
+     * null.
+     *
+     * @param key The Class of the object
+     * @return the mapped NumberTransformer or null.
+     */
+    public NumberTransformer getTransformer(Class<?> key) {
+        return map.get(key);
+    }
+
+    /**
+     * Sets a Class to Transformer Mapping in the Map. If the Class is already present, this
+     * overwrites that mapping.
+     *
+     * @param key The Class
+     * @param transformer The NumberTransformer
+     * @return the replaced transformer if one is present
+     */
+    public NumberTransformer putTransformer(Class<?> key, NumberTransformer transformer) {
+        return map.put(key, transformer);
+    }
+
+    /**
+     * Removes a Class to Transformer Mapping in the Map.
+     *
+     * @param key The Class
+     * @return the removed transformer if one is present or null if none was present.
+     */
+    public NumberTransformer removeTransformer(Class<?> key) {
+        return map.remove(key);
+    }
+
+    /** Clears all the Class to Transformer mappings. */
+    public void clear() {
+        map.clear();
+    }
+
+    /**
+     * Returns the Set of Classes used as keys in the map.
+     *
+     * @return Set of Classes
+     */
+    public Set<Class<?>> classes() {
+        return map.keySet();
+    }
+
+    /**
+     * Returns the Set of NumberTransformers used as values in the map.
+     *
+     * @return Set of NumberTransformers
+     */
+    public Collection<NumberTransformer> transformers() {
+        return map.values();
+    }
+
+    /**
+     * Attempts to transform the Object against the map of NumberTransformers. Otherwise it returns
+     * Double.NaN.
+     *
+     * @param o the Object to be transformed.
+     * @return the double value of the Object.
+     * @throws MathIllegalArgumentException if the Object can not be transformed into a Double.
+     * @see org.apache.commons.math3.util.NumberTransformer#transform(java.lang.Object)
+     */
+    public double transform(Object o) throws MathIllegalArgumentException {
+        double value = Double.NaN;
+
+        if (o instanceof Number || o instanceof String) {
+            value = defaultTransformer.transform(o);
+        } else {
+            NumberTransformer trans = getTransformer(o.getClass());
+            if (trans != null) {
+                value = trans.transform(o);
+            }
+        }
+
+        return value;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+        if (other instanceof TransformerMap) {
+            TransformerMap rhs = (TransformerMap) other;
+            if (!defaultTransformer.equals(rhs.defaultTransformer)) {
+                return false;
+            }
+            if (map.size() != rhs.map.size()) {
+                return false;
+            }
+            for (Map.Entry<Class<?>, NumberTransformer> entry : map.entrySet()) {
+                if (!entry.getValue().equals(rhs.map.get(entry.getKey()))) {
+                    return false;
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        int hash = defaultTransformer.hashCode();
+        for (NumberTransformer t : map.values()) {
+            hash = hash * 31 + t.hashCode();
+        }
+        return hash;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math3/util/package-info.java b/src/main/java/org/apache/commons/math3/util/package-info.java
new file mode 100644
index 0000000..e2e6451
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+/** Convenience routines and common data structures used throughout the commons-math library. */
+package org.apache.commons.math3.util;